From 5c092eb0ef9347e88db59618902781e099880196 Mon Sep 17 00:00:00 2001
From: Jordan <eatsleepgame@gmail.com>
Date: Sun, 19 Apr 2020 10:44:11 -0500
Subject: [PATCH] Add support for migrating from Gitlab (#9084)

* First stab at a Gitlab migrations interface.

* Modify JS to show migration for Gitlab

* Properly strip out #gitlab tag from repo name

* Working Gitlab migrations!
Still need to figure out how to hide tokens/etc from showing up in opts.CloneAddr

* Try #2 at trying to hide credentials.
CloneAddr was being used as OriginalURL.
Now passing OriginalURL through from the form and saving it.

* Add go-gitlab dependency

* Vendor go-gitlab

* Use gitlab.BasicAuthClient
Correct CloneURL.
This should be functioning!
Previous commits fixed "Migrated from"
from including the migration credentials.

* Replaced repoPath with repoID globally.
RepoID is grabbed in NewGitlabDownloader

* Logging touchup

* Properly set private repo status.
Properly set milestone deadline time.
Consistently use Gitlab username for 'Name'.

* Add go-gitlab vendor cache

* Fix PR migrations:
- Count of issues is kept to set a non-conflicting PR.ID
- Bool is used to tell whether to fetch Issue or PR comments

* Ensure merged PRs are closed and set with the proper time

* Remove copyright and some commented code

* Rip out '#gitlab' based self-hosted Gitlab support

* Hide given credentials for migrated repos.

CloneAddr was being saved as OriginalURL.

Now passing OriginalURL through from the form and
saving it in it's place

* Use asset.URL directly, no point in parsing.
Opened PRs should fall through to false.

* Fix importing Milestones.
Allow importing using Personal Tokens or anonymous access.

* Fix Gitlab Milestone migration if DueDate isn't set

* Empty Milestone due dates properly return nil, not zero time

* Add GITLAB_READ_TOKEN to drone unit-test step

* Add working gitlab_test.go.
A Personal Access Token, given in env variable GITLAB_READ_TOKEN
is required to run the test.

* Fix linting issues

* Add modified JS files

* Remove pre-build JS files

* Only merged PRs are marged as merged/closed

* Test topics

* Skip test if gitlab is inaccessible

* Grab personal token from username, not password.
Matches Github migration implementation

* Add SetContext() to GitlabDownloader.

* Checking Updated field in Issues.

* Actually fetch Issue Updated time from Gitlab

* Add Gitlab migration GetReviews() stub

* Fix Patch and Clone URLs

* check Updated too

* fix mod

* make vendor with go1.14

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
---
 .drone.yml                                    |   14 +-
 go.mod                                        |    1 +
 go.sum                                        |    5 +
 modules/migrations/gitlab.go                  |  539 ++++++
 modules/migrations/gitlab_test.go             |  246 +++
 modules/structs/repo.go                       |    1 +
 vendor/github.com/xanzy/go-gitlab/.gitignore  |   28 +
 vendor/github.com/xanzy/go-gitlab/.travis.yml |   28 +
 .../github.com/xanzy/go-gitlab/CHANGELOG.md   |   27 +
 vendor/github.com/xanzy/go-gitlab/LICENSE     |  202 +++
 vendor/github.com/xanzy/go-gitlab/README.md   |  173 ++
 .../xanzy/go-gitlab/access_requests.go        |  236 +++
 .../xanzy/go-gitlab/award_emojis.go           |  467 +++++
 vendor/github.com/xanzy/go-gitlab/boards.go   |  344 ++++
 vendor/github.com/xanzy/go-gitlab/branches.go |  242 +++
 .../xanzy/go-gitlab/broadcast_messages.go     |  172 ++
 .../xanzy/go-gitlab/ci_yml_templates.go       |   69 +
 vendor/github.com/xanzy/go-gitlab/commits.go  |  593 +++++++
 .../xanzy/go-gitlab/custom_attributes.go      |  171 ++
 .../github.com/xanzy/go-gitlab/deploy_keys.go |  200 +++
 .../github.com/xanzy/go-gitlab/deployments.go |  120 ++
 .../github.com/xanzy/go-gitlab/discussions.go | 1112 ++++++++++++
 .../xanzy/go-gitlab/environments.go           |  212 +++
 vendor/github.com/xanzy/go-gitlab/epics.go    |  211 +++
 .../xanzy/go-gitlab/event_parsing.go          |  117 ++
 .../github.com/xanzy/go-gitlab/event_types.go |  815 +++++++++
 vendor/github.com/xanzy/go-gitlab/events.go   |  146 ++
 .../xanzy/go-gitlab/feature_flags.go          |   79 +
 .../xanzy/go-gitlab/gitignore_templates.go    |   84 +
 vendor/github.com/xanzy/go-gitlab/gitlab.go   |  955 +++++++++++
 vendor/github.com/xanzy/go-gitlab/go.mod      |   12 +
 vendor/github.com/xanzy/go-gitlab/go.sum      |   25 +
 .../xanzy/go-gitlab/group_badges.go           |  213 +++
 .../xanzy/go-gitlab/group_boards.go           |  261 +++
 .../xanzy/go-gitlab/group_clusters.go         |  211 +++
 .../xanzy/go-gitlab/group_labels.go           |  196 +++
 .../xanzy/go-gitlab/group_members.go          |  219 +++
 .../xanzy/go-gitlab/group_milestones.go       |  249 +++
 .../xanzy/go-gitlab/group_variables.go        |  196 +++
 vendor/github.com/xanzy/go-gitlab/groups.go   |  335 ++++
 .../github.com/xanzy/go-gitlab/issue_links.go |  127 ++
 vendor/github.com/xanzy/go-gitlab/issues.go   |  568 +++++++
 vendor/github.com/xanzy/go-gitlab/jobs.go     |  408 +++++
 vendor/github.com/xanzy/go-gitlab/keys.go     |   65 +
 vendor/github.com/xanzy/go-gitlab/labels.go   |  250 +++
 vendor/github.com/xanzy/go-gitlab/license.go  |   94 +
 .../xanzy/go-gitlab/license_templates.go      |   92 +
 .../go-gitlab/merge_request_approvals.go      |  191 +++
 .../xanzy/go-gitlab/merge_requests.go         |  836 +++++++++
 .../github.com/xanzy/go-gitlab/milestones.go  |  267 +++
 .../github.com/xanzy/go-gitlab/namespaces.go  |  122 ++
 vendor/github.com/xanzy/go-gitlab/notes.go    |  678 ++++++++
 .../xanzy/go-gitlab/notifications.go          |  213 +++
 .../xanzy/go-gitlab/pages_domains.go          |  193 +++
 .../xanzy/go-gitlab/pipeline_schedules.go     |  327 ++++
 .../xanzy/go-gitlab/pipeline_triggers.go      |  231 +++
 .../github.com/xanzy/go-gitlab/pipelines.go   |  286 ++++
 .../xanzy/go-gitlab/project_badges.go         |  207 +++
 .../xanzy/go-gitlab/project_clusters.go       |  221 +++
 .../xanzy/go-gitlab/project_import_export.go  |  196 +++
 .../xanzy/go-gitlab/project_members.go        |  209 +++
 .../xanzy/go-gitlab/project_snippets.go       |  206 +++
 .../xanzy/go-gitlab/project_variables.go      |  201 +++
 vendor/github.com/xanzy/go-gitlab/projects.go | 1512 +++++++++++++++++
 .../xanzy/go-gitlab/protected_branches.go     |  165 ++
 .../xanzy/go-gitlab/protected_tags.go         |  145 ++
 vendor/github.com/xanzy/go-gitlab/registry.go |  219 +++
 .../xanzy/go-gitlab/releaselinks.go           |  176 ++
 vendor/github.com/xanzy/go-gitlab/releases.go |  212 +++
 .../xanzy/go-gitlab/repositories.go           |  327 ++++
 .../xanzy/go-gitlab/repository_files.go       |  311 ++++
 .../xanzy/go-gitlab/resource_label_events.go  |  219 +++
 vendor/github.com/xanzy/go-gitlab/runners.go  |  415 +++++
 vendor/github.com/xanzy/go-gitlab/search.go   |  354 ++++
 vendor/github.com/xanzy/go-gitlab/services.go |  864 ++++++++++
 vendor/github.com/xanzy/go-gitlab/settings.go |  409 +++++
 .../xanzy/go-gitlab/sidekiq_metrics.go        |  154 ++
 vendor/github.com/xanzy/go-gitlab/snippets.go |  230 +++
 vendor/github.com/xanzy/go-gitlab/strings.go  |   94 +
 .../xanzy/go-gitlab/system_hooks.go           |  143 ++
 vendor/github.com/xanzy/go-gitlab/tags.go     |  243 +++
 .../github.com/xanzy/go-gitlab/time_stats.go  |  162 ++
 vendor/github.com/xanzy/go-gitlab/todos.go    |  176 ++
 vendor/github.com/xanzy/go-gitlab/users.go    |  871 ++++++++++
 vendor/github.com/xanzy/go-gitlab/validate.go |   40 +
 vendor/github.com/xanzy/go-gitlab/version.go  |   56 +
 vendor/github.com/xanzy/go-gitlab/wikis.go    |  204 +++
 vendor/modules.txt                            |    3 +
 web_src/js/index.js                           |    2 +-
 89 files changed, 23418 insertions(+), 2 deletions(-)
 create mode 100644 modules/migrations/gitlab.go
 create mode 100644 modules/migrations/gitlab_test.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/.gitignore
 create mode 100644 vendor/github.com/xanzy/go-gitlab/.travis.yml
 create mode 100644 vendor/github.com/xanzy/go-gitlab/CHANGELOG.md
 create mode 100644 vendor/github.com/xanzy/go-gitlab/LICENSE
 create mode 100644 vendor/github.com/xanzy/go-gitlab/README.md
 create mode 100644 vendor/github.com/xanzy/go-gitlab/access_requests.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/award_emojis.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/boards.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/branches.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/broadcast_messages.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/ci_yml_templates.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/commits.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/custom_attributes.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/deploy_keys.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/deployments.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/discussions.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/environments.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/epics.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/event_parsing.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/event_types.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/events.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/feature_flags.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/gitignore_templates.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/gitlab.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/go.mod
 create mode 100644 vendor/github.com/xanzy/go-gitlab/go.sum
 create mode 100644 vendor/github.com/xanzy/go-gitlab/group_badges.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/group_boards.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/group_clusters.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/group_labels.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/group_members.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/group_milestones.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/group_variables.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/groups.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/issue_links.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/issues.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/jobs.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/keys.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/labels.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/license.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/license_templates.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/merge_requests.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/milestones.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/namespaces.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/notes.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/notifications.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/pages_domains.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/pipeline_triggers.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/pipelines.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/project_badges.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/project_clusters.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/project_import_export.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/project_members.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/project_snippets.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/project_variables.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/projects.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/protected_branches.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/protected_tags.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/registry.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/releaselinks.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/releases.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/repositories.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/repository_files.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/resource_label_events.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/runners.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/search.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/services.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/settings.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/sidekiq_metrics.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/snippets.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/strings.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/system_hooks.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/tags.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/time_stats.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/todos.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/users.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/validate.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/version.go
 create mode 100644 vendor/github.com/xanzy/go-gitlab/wikis.go

diff --git a/.drone.yml b/.drone.yml
index 3fe8dd998..345b9fbc3 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -99,7 +99,19 @@ services:
     image: mysql:5.7
     environment:
       MYSQL_ALLOW_EMPTY_PASSWORD: yes
-      MYSQL_DATABASE: test
+      MYSQL_DATABASE: test      
+      GOPROXY: off
+      TAGS: bindata sqlite sqlite_unlock_notify
+      GITLAB_READ_TOKEN:
+        from_secret: gitlab_read_token
+    depends_on:
+      - build
+    when:
+      branch:
+        - master
+      event:
+        - push
+        - pull_request
 
   - name: mysql8
     pull: default
diff --git a/go.mod b/go.mod
index 404ca6d5c..9faf5158e 100644
--- a/go.mod
+++ b/go.mod
@@ -104,6 +104,7 @@ require (
 	github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6
 	github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141
 	github.com/urfave/cli v1.20.0
+	github.com/xanzy/go-gitlab v0.22.1
 	github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53
 	github.com/yuin/goldmark v1.1.25
 	github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60
diff --git a/go.sum b/go.sum
index d904ed86d..731248200 100644
--- a/go.sum
+++ b/go.sum
@@ -608,6 +608,8 @@ github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
 github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
+github.com/xanzy/go-gitlab v0.22.1 h1:TVxgHmoa35jQL+9FCkG0nwPDxU9dQZXknBTDtGaSFno=
+github.com/xanzy/go-gitlab v0.22.1/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
 github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
 github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
@@ -669,6 +671,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -692,6 +695,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -770,6 +774,7 @@ google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn
 google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
diff --git a/modules/migrations/gitlab.go b/modules/migrations/gitlab.go
new file mode 100644
index 000000000..843e90c17
--- /dev/null
+++ b/modules/migrations/gitlab.go
@@ -0,0 +1,539 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/migrations/base"
+	"code.gitea.io/gitea/modules/structs"
+
+	"github.com/xanzy/go-gitlab"
+)
+
+var (
+	_ base.Downloader        = &GitlabDownloader{}
+	_ base.DownloaderFactory = &GitlabDownloaderFactory{}
+)
+
+func init() {
+	RegisterDownloaderFactory(&GitlabDownloaderFactory{})
+}
+
+// GitlabDownloaderFactory defines a gitlab downloader factory
+type GitlabDownloaderFactory struct {
+}
+
+// Match returns ture if the migration remote URL matched this downloader factory
+func (f *GitlabDownloaderFactory) Match(opts base.MigrateOptions) (bool, error) {
+	var matched bool
+
+	u, err := url.Parse(opts.CloneAddr)
+	if err != nil {
+		return false, err
+	}
+	if strings.EqualFold(u.Host, "gitlab.com") && opts.AuthUsername != "" {
+		matched = true
+	}
+
+	return matched, nil
+}
+
+// New returns a Downloader related to this factory according MigrateOptions
+func (f *GitlabDownloaderFactory) New(opts base.MigrateOptions) (base.Downloader, error) {
+	u, err := url.Parse(opts.CloneAddr)
+	if err != nil {
+		return nil, err
+	}
+
+	baseURL := u.Scheme + "://" + u.Host
+	repoNameSpace := strings.TrimPrefix(u.Path, "/")
+
+	log.Trace("Create gitlab downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
+
+	return NewGitlabDownloader(baseURL, repoNameSpace, opts.AuthUsername, opts.AuthPassword), nil
+}
+
+// GitServiceType returns the type of git service
+func (f *GitlabDownloaderFactory) GitServiceType() structs.GitServiceType {
+	return structs.GitlabService
+}
+
+// GitlabDownloader implements a Downloader interface to get repository informations
+// from gitlab via go-gitlab
+// - issueCount is incremented in GetIssues() to ensure PR and Issue numbers do not overlap,
+// because Gitlab has individual Issue and Pull Request numbers.
+// - issueSeen, working alongside issueCount, is checked in GetComments() to see whether we
+// need to fetch the Issue or PR comments, as Gitlab stores them separately.
+type GitlabDownloader struct {
+	ctx             context.Context
+	client          *gitlab.Client
+	repoID          int
+	repoName        string
+	issueCount      int64
+	fetchPRcomments bool
+}
+
+// NewGitlabDownloader creates a gitlab Downloader via gitlab API
+//   Use either a username/password, personal token entered into the username field, or anonymous/public access
+//   Note: Public access only allows very basic access
+func NewGitlabDownloader(baseURL, repoPath, username, password string) *GitlabDownloader {
+	var client *http.Client
+	var gitlabClient *gitlab.Client
+	var err error
+	if username != "" {
+		if password == "" {
+			gitlabClient = gitlab.NewClient(client, username)
+		} else {
+			gitlabClient, err = gitlab.NewBasicAuthClient(client, baseURL, username, password)
+
+		}
+	}
+
+	if err != nil {
+		log.Trace("Error logging into gitlab: %v", err)
+		return nil
+	}
+
+	// Grab and store project/repo ID here, due to issues using the URL escaped path
+	gr, _, err := gitlabClient.Projects.GetProject(repoPath, nil, nil)
+	if err != nil {
+		log.Trace("Error retrieving project: %v", err)
+		return nil
+	}
+
+	if gr == nil {
+		log.Trace("Error getting project, project is nil")
+		return nil
+	}
+
+	return &GitlabDownloader{
+		ctx:      context.Background(),
+		client:   gitlabClient,
+		repoID:   gr.ID,
+		repoName: gr.Name,
+	}
+}
+
+// SetContext set context
+func (g *GitlabDownloader) SetContext(ctx context.Context) {
+	g.ctx = ctx
+}
+
+// GetRepoInfo returns a repository information
+func (g *GitlabDownloader) GetRepoInfo() (*base.Repository, error) {
+	if g == nil {
+		return nil, errors.New("error: GitlabDownloader is nil")
+	}
+
+	gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var private bool
+	switch gr.Visibility {
+	case gitlab.InternalVisibility:
+		private = true
+	case gitlab.PrivateVisibility:
+		private = true
+	}
+
+	var owner string
+	if gr.Owner == nil {
+		log.Trace("gr.Owner is nil, trying to get owner from Namespace")
+		if gr.Namespace != nil && gr.Namespace.Kind == "user" {
+			owner = gr.Namespace.Path
+		}
+	} else {
+		owner = gr.Owner.Username
+	}
+
+	// convert gitlab repo to stand Repo
+	return &base.Repository{
+		Owner:       owner,
+		Name:        gr.Name,
+		IsPrivate:   private,
+		Description: gr.Description,
+		OriginalURL: gr.WebURL,
+		CloneURL:    gr.HTTPURLToRepo,
+	}, nil
+}
+
+// GetTopics return gitlab topics
+func (g *GitlabDownloader) GetTopics() ([]string, error) {
+	if g == nil {
+		return nil, errors.New("error: GitlabDownloader is nil")
+	}
+
+	gr, _, err := g.client.Projects.GetProject(g.repoID, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+	return gr.TagList, err
+}
+
+// GetMilestones returns milestones
+func (g *GitlabDownloader) GetMilestones() ([]*base.Milestone, error) {
+	if g == nil {
+		return nil, errors.New("error: GitlabDownloader is nil")
+	}
+	var perPage = 100
+	var state = "all"
+	var milestones = make([]*base.Milestone, 0, perPage)
+	for i := 1; ; i++ {
+		ms, _, err := g.client.Milestones.ListMilestones(g.repoID, &gitlab.ListMilestonesOptions{
+			State: &state,
+			ListOptions: gitlab.ListOptions{
+				Page:    i,
+				PerPage: perPage,
+			}}, nil)
+		if err != nil {
+			return nil, err
+		}
+
+		for _, m := range ms {
+			var desc string
+			if m.Description != "" {
+				desc = m.Description
+			}
+			var state = "open"
+			var closedAt *time.Time
+			if m.State != "" {
+				state = m.State
+				if state == "closed" {
+					closedAt = m.UpdatedAt
+				}
+			}
+
+			var deadline *time.Time
+			if m.DueDate != nil {
+				deadlineParsed, err := time.Parse("2006-01-02", m.DueDate.String())
+				if err != nil {
+					log.Trace("Error parsing Milestone DueDate time")
+					deadline = nil
+				} else {
+					deadline = &deadlineParsed
+				}
+			}
+
+			milestones = append(milestones, &base.Milestone{
+				Title:       m.Title,
+				Description: desc,
+				Deadline:    deadline,
+				State:       state,
+				Created:     *m.CreatedAt,
+				Updated:     m.UpdatedAt,
+				Closed:      closedAt,
+			})
+		}
+		if len(ms) < perPage {
+			break
+		}
+	}
+	return milestones, nil
+}
+
+// GetLabels returns labels
+func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) {
+	if g == nil {
+		return nil, errors.New("error: GitlabDownloader is nil")
+	}
+	var perPage = 100
+	var labels = make([]*base.Label, 0, perPage)
+	for i := 1; ; i++ {
+		ls, _, err := g.client.Labels.ListLabels(g.repoID, &gitlab.ListLabelsOptions{
+			Page:    i,
+			PerPage: perPage,
+		}, nil)
+		if err != nil {
+			return nil, err
+		}
+		for _, label := range ls {
+			baseLabel := &base.Label{
+				Name:        label.Name,
+				Color:       strings.TrimLeft(label.Color, "#)"),
+				Description: label.Description,
+			}
+			labels = append(labels, baseLabel)
+		}
+		if len(ls) < perPage {
+			break
+		}
+	}
+	return labels, nil
+}
+
+func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Release {
+
+	r := &base.Release{
+		TagName:         rel.TagName,
+		TargetCommitish: rel.Commit.ID,
+		Name:            rel.Name,
+		Body:            rel.Description,
+		Created:         *rel.CreatedAt,
+		PublisherID:     int64(rel.Author.ID),
+		PublisherName:   rel.Author.Username,
+	}
+
+	for k, asset := range rel.Assets.Links {
+		r.Assets = append(r.Assets, base.ReleaseAsset{
+			URL:         asset.URL,
+			Name:        asset.Name,
+			ContentType: &rel.Assets.Sources[k].Format,
+		})
+	}
+	return r
+}
+
+// GetReleases returns releases
+func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) {
+	var perPage = 100
+	var releases = make([]*base.Release, 0, perPage)
+	for i := 1; ; i++ {
+		ls, _, err := g.client.Releases.ListReleases(g.repoID, &gitlab.ListReleasesOptions{
+			Page:    i,
+			PerPage: perPage,
+		}, nil)
+		if err != nil {
+			return nil, err
+		}
+
+		for _, release := range ls {
+			releases = append(releases, g.convertGitlabRelease(release))
+		}
+		if len(ls) < perPage {
+			break
+		}
+	}
+	return releases, nil
+}
+
+// GetIssues returns issues according start and limit
+//   Note: issue label description and colors are not supported by the go-gitlab library at this time
+//   TODO: figure out how to transfer issue reactions
+func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
+	state := "all"
+	sort := "asc"
+
+	opt := &gitlab.ListProjectIssuesOptions{
+		State: &state,
+		Sort:  &sort,
+		ListOptions: gitlab.ListOptions{
+			PerPage: perPage,
+			Page:    page,
+		},
+	}
+
+	var allIssues = make([]*base.Issue, 0, perPage)
+
+	issues, _, err := g.client.Issues.ListProjectIssues(g.repoID, opt, nil)
+	if err != nil {
+		return nil, false, fmt.Errorf("error while listing issues: %v", err)
+	}
+	for _, issue := range issues {
+
+		var labels = make([]*base.Label, 0, len(issue.Labels))
+		for _, l := range issue.Labels {
+			labels = append(labels, &base.Label{
+				Name: l,
+			})
+		}
+
+		var milestone string
+		if issue.Milestone != nil {
+			milestone = issue.Milestone.Title
+		}
+
+		allIssues = append(allIssues, &base.Issue{
+			Title:      issue.Title,
+			Number:     int64(issue.IID),
+			PosterID:   int64(issue.Author.ID),
+			PosterName: issue.Author.Username,
+			Content:    issue.Description,
+			Milestone:  milestone,
+			State:      issue.State,
+			Created:    *issue.CreatedAt,
+			Labels:     labels,
+			Closed:     issue.ClosedAt,
+			IsLocked:   issue.DiscussionLocked,
+			Updated:    *issue.UpdatedAt,
+		})
+
+		// increment issueCount, to be used in GetPullRequests()
+		g.issueCount++
+	}
+
+	return allIssues, len(issues) < perPage, nil
+}
+
+// GetComments returns comments according issueNumber
+func (g *GitlabDownloader) GetComments(issueNumber int64) ([]*base.Comment, error) {
+	var allComments = make([]*base.Comment, 0, 100)
+
+	var page = 1
+	var realIssueNumber int64
+
+	for {
+		var comments []*gitlab.Discussion
+		var resp *gitlab.Response
+		var err error
+		// fetchPRcomments decides whether to fetch Issue or PR comments
+		if !g.fetchPRcomments {
+			realIssueNumber = issueNumber
+			comments, resp, err = g.client.Discussions.ListIssueDiscussions(g.repoID, int(realIssueNumber), &gitlab.ListIssueDiscussionsOptions{
+				Page:    page,
+				PerPage: 100,
+			}, nil)
+		} else {
+			// If this is a PR, we need to figure out the Gitlab/original PR ID to be passed below
+			realIssueNumber = issueNumber - g.issueCount
+			comments, resp, err = g.client.Discussions.ListMergeRequestDiscussions(g.repoID, int(realIssueNumber), &gitlab.ListMergeRequestDiscussionsOptions{
+				Page:    page,
+				PerPage: 100,
+			}, nil)
+		}
+
+		if err != nil {
+			return nil, fmt.Errorf("error while listing comments: %v %v", g.repoID, err)
+		}
+		for _, comment := range comments {
+			// Flatten comment threads
+			if !comment.IndividualNote {
+				for _, note := range comment.Notes {
+					allComments = append(allComments, &base.Comment{
+						IssueIndex:  realIssueNumber,
+						PosterID:    int64(note.Author.ID),
+						PosterName:  note.Author.Username,
+						PosterEmail: note.Author.Email,
+						Content:     note.Body,
+						Created:     *note.CreatedAt,
+					})
+				}
+			} else {
+				c := comment.Notes[0]
+				allComments = append(allComments, &base.Comment{
+					IssueIndex:  realIssueNumber,
+					PosterID:    int64(c.Author.ID),
+					PosterName:  c.Author.Username,
+					PosterEmail: c.Author.Email,
+					Content:     c.Body,
+					Created:     *c.CreatedAt,
+				})
+			}
+
+		}
+		if resp.NextPage == 0 {
+			break
+		}
+		page = resp.NextPage
+	}
+	return allComments, nil
+}
+
+// GetPullRequests returns pull requests according page and perPage
+func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, error) {
+
+	opt := &gitlab.ListProjectMergeRequestsOptions{
+		ListOptions: gitlab.ListOptions{
+			PerPage: perPage,
+			Page:    page,
+		},
+	}
+
+	// Set fetchPRcomments to true here, so PR comments are fetched instead of Issue comments
+	g.fetchPRcomments = true
+
+	var allPRs = make([]*base.PullRequest, 0, perPage)
+
+	prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil)
+	if err != nil {
+		return nil, fmt.Errorf("error while listing merge requests: %v", err)
+	}
+	for _, pr := range prs {
+
+		var labels = make([]*base.Label, 0, len(pr.Labels))
+		for _, l := range pr.Labels {
+			labels = append(labels, &base.Label{
+				Name: l,
+			})
+		}
+
+		var merged bool
+		if pr.State == "merged" {
+			merged = true
+			pr.State = "closed"
+		}
+
+		var mergeTime = pr.MergedAt
+		if merged && pr.MergedAt == nil {
+			mergeTime = pr.UpdatedAt
+		}
+
+		var closeTime = pr.ClosedAt
+		if merged && pr.ClosedAt == nil {
+			closeTime = pr.UpdatedAt
+		}
+
+		var locked bool
+		if pr.State == "locked" {
+			locked = true
+		}
+
+		var milestone string
+		if pr.Milestone != nil {
+			milestone = pr.Milestone.Title
+		}
+
+		// Add the PR ID to the Issue Count because PR and Issues share ID space in Gitea
+		newPRnumber := g.issueCount + int64(pr.IID)
+
+		allPRs = append(allPRs, &base.PullRequest{
+			Title:          pr.Title,
+			Number:         int64(newPRnumber),
+			PosterName:     pr.Author.Username,
+			PosterID:       int64(pr.Author.ID),
+			Content:        pr.Description,
+			Milestone:      milestone,
+			State:          pr.State,
+			Created:        *pr.CreatedAt,
+			Closed:         closeTime,
+			Labels:         labels,
+			Merged:         merged,
+			MergeCommitSHA: pr.MergeCommitSHA,
+			MergedTime:     mergeTime,
+			IsLocked:       locked,
+			Head: base.PullRequestBranch{
+				Ref:       pr.SourceBranch,
+				SHA:       pr.SHA,
+				RepoName:  g.repoName,
+				OwnerName: pr.Author.Username,
+				CloneURL:  pr.WebURL,
+			},
+			Base: base.PullRequestBranch{
+				Ref:       pr.TargetBranch,
+				SHA:       pr.DiffRefs.BaseSha,
+				RepoName:  g.repoName,
+				OwnerName: pr.Author.Username,
+			},
+			PatchURL: pr.WebURL + ".patch",
+		})
+	}
+
+	return allPRs, nil
+}
+
+// GetReviews returns pull requests review
+func (g *GitlabDownloader) GetReviews(pullRequestNumber int64) ([]*base.Review, error) {
+
+	return nil, nil
+}
diff --git a/modules/migrations/gitlab_test.go b/modules/migrations/gitlab_test.go
new file mode 100644
index 000000000..8525d7e9c
--- /dev/null
+++ b/modules/migrations/gitlab_test.go
@@ -0,0 +1,246 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"net/http"
+	"os"
+	"testing"
+	"time"
+
+	"code.gitea.io/gitea/modules/migrations/base"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGitlabDownloadRepo(t *testing.T) {
+	// Skip tests if Gitlab token is not found
+	gitlabPersonalAccessToken := os.Getenv("GITLAB_READ_TOKEN")
+	if gitlabPersonalAccessToken == "" {
+		t.Skip("skipped test because GITLAB_READ_TOKEN was not in the environment")
+	}
+
+	resp, err := http.Get("https://gitlab.com/gitea/test_repo")
+	if err != nil || resp.StatusCode != 200 {
+		t.Skipf("Can't access test repo, skipping %s", t.Name())
+	}
+
+	downloader := NewGitlabDownloader("https://gitlab.com", "gitea/test_repo", gitlabPersonalAccessToken, "")
+	if downloader == nil {
+		t.Fatal("NewGitlabDownloader is nil")
+	}
+	repo, err := downloader.GetRepoInfo()
+	assert.NoError(t, err)
+	// Repo Owner is blank in Gitlab Group repos
+	assert.EqualValues(t, &base.Repository{
+		Name:        "test_repo",
+		Owner:       "",
+		Description: "Test repository for testing migration from gitlab to gitea",
+		CloneURL:    "https://gitlab.com/gitea/test_repo.git",
+		OriginalURL: "https://gitlab.com/gitea/test_repo",
+	}, repo)
+
+	topics, err := downloader.GetTopics()
+	assert.NoError(t, err)
+	assert.True(t, len(topics) == 2)
+	assert.EqualValues(t, []string{"migration", "test"}, topics)
+
+	milestones, err := downloader.GetMilestones()
+	assert.NoError(t, err)
+	assert.True(t, len(milestones) >= 2)
+
+	for _, milestone := range milestones {
+		switch milestone.Title {
+		case "1.0":
+			assertMilestoneEqual(t, "", "1.0",
+				"",
+				"2019-11-28 08:42:30.301 +0000 UTC",
+				"2019-11-28 15:57:52.401 +0000 UTC",
+				"",
+				"closed", milestone)
+		case "1.1.0":
+			assertMilestoneEqual(t, "", "1.1.0",
+				"",
+				"2019-11-28 08:42:44.575 +0000 UTC",
+				"2019-11-28 08:42:44.575 +0000 UTC",
+				"",
+				"active", milestone)
+		}
+	}
+
+	labels, err := downloader.GetLabels()
+	assert.NoError(t, err)
+	assert.True(t, len(labels) >= 9)
+	for _, l := range labels {
+		switch l.Name {
+		case "bug":
+			assertLabelEqual(t, "bug", "d9534f", "", l)
+		case "documentation":
+			assertLabelEqual(t, "documentation", "f0ad4e", "", l)
+		case "confirmed":
+			assertLabelEqual(t, "confirmed", "d9534f", "", l)
+		case "enhancement":
+			assertLabelEqual(t, "enhancement", "5cb85c", "", l)
+		case "critical":
+			assertLabelEqual(t, "critical", "d9534f", "", l)
+		case "discussion":
+			assertLabelEqual(t, "discussion", "428bca", "", l)
+		case "suggestion":
+			assertLabelEqual(t, "suggestion", "428bca", "", l)
+		case "support":
+			assertLabelEqual(t, "support", "f0ad4e", "", l)
+		case "duplicate":
+			assertLabelEqual(t, "duplicate", "7F8C8D", "", l)
+		}
+	}
+
+	releases, err := downloader.GetReleases()
+	assert.NoError(t, err)
+	assert.EqualValues(t, []*base.Release{
+		{
+			TagName:         "v0.9.99",
+			TargetCommitish: "0720a3ec57c1f843568298117b874319e7deee75",
+			Name:            "First Release",
+			Body:            "A test release",
+			Created:         time.Date(2019, 11, 28, 9, 9, 48, 840000000, time.UTC),
+			PublisherID:     1241334,
+			PublisherName:   "lafriks",
+		},
+	}, releases[len(releases)-1:])
+
+	// downloader.GetIssues()
+	issues, isEnd, err := downloader.GetIssues(1, 2)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 2, len(issues))
+	assert.False(t, isEnd)
+
+	var (
+		closed1 = time.Date(2019, 11, 28, 8, 46, 23, 275000000, time.UTC)
+		closed2 = time.Date(2019, 11, 28, 8, 45, 44, 959000000, time.UTC)
+	)
+	assert.EqualValues(t, []*base.Issue{
+		{
+			Number:     1,
+			Title:      "Please add an animated gif icon to the merge button",
+			Content:    "I just want the merge button to hurt my eyes a little. :stuck_out_tongue_closed_eyes:",
+			Milestone:  "1.0.0",
+			PosterID:   1241334,
+			PosterName: "lafriks",
+			State:      "closed",
+			Created:    time.Date(2019, 11, 28, 8, 43, 35, 459000000, time.UTC),
+			Updated:    time.Date(2019, 11, 28, 8, 46, 23, 275000000, time.UTC),
+			Labels: []*base.Label{
+				{
+					Name: "bug",
+				},
+				{
+					Name: "discussion",
+				},
+			},
+			Reactions: nil,
+			Closed:    &closed1,
+		},
+		{
+			Number:     2,
+			Title:      "Test issue",
+			Content:    "This is test issue 2, do not touch!",
+			Milestone:  "1.1.0",
+			PosterID:   1241334,
+			PosterName: "lafriks",
+			State:      "closed",
+			Created:    time.Date(2019, 11, 28, 8, 44, 46, 277000000, time.UTC),
+			Updated:    time.Date(2019, 11, 28, 8, 45, 44, 987000000, time.UTC),
+			Labels: []*base.Label{
+				{
+					Name: "duplicate",
+				},
+			},
+			Reactions: nil,
+			Closed:    &closed2,
+		},
+	}, issues)
+
+	// downloader.GetComments()
+	comments, err := downloader.GetComments(2)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 4, len(comments))
+	assert.EqualValues(t, []*base.Comment{
+		{
+			IssueIndex: 2,
+			PosterID:   1241334,
+			PosterName: "lafriks",
+			Created:    time.Date(2019, 11, 28, 8, 44, 52, 501000000, time.UTC),
+			Updated:    time.Date(2019, 11, 28, 8, 44, 52, 501000000, time.UTC),
+			Content:    "This is a comment",
+			Reactions:  nil,
+		},
+		{
+			IssueIndex: 2,
+			PosterID:   1241334,
+			PosterName: "lafriks",
+			Created:    time.Date(2019, 11, 28, 8, 45, 2, 329000000, time.UTC),
+			Content:    "changed milestone to %2",
+			Reactions:  nil,
+		},
+		{
+			IssueIndex: 2,
+			PosterID:   1241334,
+			PosterName: "lafriks",
+			Created:    time.Date(2019, 11, 28, 8, 45, 45, 7000000, time.UTC),
+			Content:    "closed",
+			Reactions:  nil,
+		},
+		{
+			IssueIndex: 2,
+			PosterID:   1241334,
+			PosterName: "lafriks",
+			Created:    time.Date(2019, 11, 28, 8, 45, 53, 501000000, time.UTC),
+			Content:    "A second comment",
+			Reactions:  nil,
+		},
+	}, comments[:4])
+
+	// downloader.GetPullRequests()
+	prs, err := downloader.GetPullRequests(1, 1)
+	assert.NoError(t, err)
+	assert.EqualValues(t, 1, len(prs))
+
+	assert.EqualValues(t, []*base.PullRequest{
+		{
+			Number:     4,
+			Title:      "Test branch",
+			Content:    "do not merge this PR",
+			Milestone:  "1.0.0",
+			PosterID:   1241334,
+			PosterName: "lafriks",
+			State:      "opened",
+			Created:    time.Date(2019, 11, 28, 15, 56, 54, 104000000, time.UTC),
+			Updated:    time.Date(2019, 11, 28, 15, 56, 54, 104000000, time.UTC),
+			Labels: []*base.Label{
+				{
+					Name: "bug",
+				},
+			},
+			PatchURL: "https://gitlab.com/gitea/test_repo/-/merge_requests/2.patch",
+			Head: base.PullRequestBranch{
+				Ref:       "feat/test",
+				CloneURL:  "https://gitlab.com/gitea/test_repo/-/merge_requests/2",
+				SHA:       "9f733b96b98a4175276edf6a2e1231489c3bdd23",
+				RepoName:  "test_repo",
+				OwnerName: "lafriks",
+			},
+			Base: base.PullRequestBranch{
+				Ref:       "master",
+				SHA:       "",
+				OwnerName: "lafriks",
+				RepoName:  "test_repo",
+			},
+			Closed:         nil,
+			Merged:         false,
+			MergedTime:     nil,
+			MergeCommitSHA: "",
+		},
+	}, prs)
+}
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index cabb5e12f..70de9b746 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -203,6 +203,7 @@ var (
 	// TODO: add to this list after new git service added
 	SupportedFullGitService = []GitServiceType{
 		GithubService,
+		GitlabService,
 	}
 )
 
diff --git a/vendor/github.com/xanzy/go-gitlab/.gitignore b/vendor/github.com/xanzy/go-gitlab/.gitignore
new file mode 100644
index 000000000..19b0dcfbd
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/.gitignore
@@ -0,0 +1,28 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+# IDE specific files and folders
+.idea
+*.iml
diff --git a/vendor/github.com/xanzy/go-gitlab/.travis.yml b/vendor/github.com/xanzy/go-gitlab/.travis.yml
new file mode 100644
index 000000000..ee77c02ba
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/.travis.yml
@@ -0,0 +1,28 @@
+language: go
+
+go:
+  - 1.10.x
+  - 1.11.x
+  - 1.12.x
+  - 1.13.x
+  - master
+
+stages:
+  - lint
+  - test
+
+jobs:
+  include:
+    - stage: lint
+      script:
+        - go get golang.org/x/lint/golint
+        - golint -set_exit_status
+        - go vet -v
+    - stage: test
+      script:
+        - go test -v
+
+matrix:
+  allow_failures:
+    - go: master
+  fast_finish: true
diff --git a/vendor/github.com/xanzy/go-gitlab/CHANGELOG.md b/vendor/github.com/xanzy/go-gitlab/CHANGELOG.md
new file mode 100644
index 000000000..29e93fff7
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/CHANGELOG.md
@@ -0,0 +1,27 @@
+go-github CHANGELOG
+===================
+
+0.6.0
+-----
+- Add support for the V4 Gitlab API. This means the older V3 API is no longer fully supported
+  with this version. If you still need that version, please use the `f-api-v3` branch.
+
+0.4.0
+-----
+- Add support to use [`sudo`](https://docs.gitlab.com/ce/api/README.html#sudo) for all API calls.
+- Add support for the Notification Settings API.
+- Add support for the Time Tracking API.
+- Make sure that the error response correctly outputs any returned errors.
+- And a reasonable number of smaller enhanchements and bugfixes.
+
+0.3.0
+-----
+- Moved the tags related API calls to their own service, following the Gitlab API structure.
+
+0.2.0
+-----
+- Convert all Option structs to use pointers for their fields.
+
+0.1.0
+-----
+- Initial release.
diff --git a/vendor/github.com/xanzy/go-gitlab/LICENSE b/vendor/github.com/xanzy/go-gitlab/LICENSE
new file mode 100644
index 000000000..e06d20818
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/LICENSE
@@ -0,0 +1,202 @@
+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/vendor/github.com/xanzy/go-gitlab/README.md b/vendor/github.com/xanzy/go-gitlab/README.md
new file mode 100644
index 000000000..48fb4882b
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/README.md
@@ -0,0 +1,173 @@
+# go-gitlab
+
+A GitLab API client enabling Go programs to interact with GitLab in a simple and uniform way
+
+[![Build Status](https://travis-ci.org/xanzy/go-gitlab.svg?branch=master)](https://travis-ci.org/xanzy/go-gitlab)
+[![GitHub license](https://img.shields.io/github/license/xanzy/go-gitlab.svg)](https://github.com/xanzy/go-gitlab/blob/master/LICENSE)
+[![Sourcegraph](https://sourcegraph.com/github.com/xanzy/go-gitlab/-/badge.svg)](https://sourcegraph.com/github.com/xanzy/go-gitlab?badge)
+[![GoDoc](https://godoc.org/github.com/xanzy/go-gitlab?status.svg)](https://godoc.org/github.com/xanzy/go-gitlab)
+[![Go Report Card](https://goreportcard.com/badge/github.com/xanzy/go-gitlab)](https://goreportcard.com/report/github.com/xanzy/go-gitlab)
+[![GitHub issues](https://img.shields.io/github/issues/xanzy/go-gitlab.svg)](https://github.com/xanzy/go-gitlab/issues)
+
+## NOTE
+
+Release v0.6.0 (released on 25-08-2017) no longer supports the older V3 Gitlab API. If
+you need V3 support, please use the `f-api-v3` branch. This release contains some backwards
+incompatible changes that were needed to fully support the V4 Gitlab API.
+
+## Coverage
+
+This API client package covers most of the existing Gitlab API calls and is updated regularly
+to add new and/or missing endpoints. Currently the following services are supported:
+
+- [x] Award Emojis
+- [x] Branches
+- [x] Broadcast Messages
+- [x] Commits
+- [x] Container Registry
+- [x] Custom Attributes
+- [x] Deploy Keys
+- [x] Deployments
+- [ ] Discussions (threaded comments)
+- [x] Environments
+- [ ] Epic Issues
+- [ ] Epics
+- [x] Events
+- [x] Feature Flags
+- [ ] Geo Nodes
+- [x] GitLab CI Config Templates
+- [x] Gitignores Templates
+- [x] Group Access Requests
+- [x] Group Issue Boards
+- [x] Group Members
+- [x] Group Milestones
+- [x] Group-Level Variables
+- [x] Groups
+- [x] Issue Boards
+- [x] Issues
+- [x] Jobs
+- [x] Keys
+- [x] Labels
+- [x] License
+- [x] Merge Request Approvals
+- [x] Merge Requests
+- [x] Namespaces
+- [x] Notes (comments)
+- [x] Notification Settings
+- [x] Open Source License Templates
+- [x] Pages Domains
+- [x] Pipeline Schedules
+- [x] Pipeline Triggers
+- [x] Pipelines
+- [x] Project Access Requests
+- [x] Project Badges
+- [x] Project Clusters
+- [x] Project Import/export
+- [x] Project Members
+- [x] Project Milestones
+- [x] Project Snippets
+- [x] Project-Level Variables
+- [x] Projects (including setting Webhooks)
+- [x] Protected Branches
+- [x] Protected Tags
+- [x] Repositories
+- [x] Repository Files
+- [x] Runners
+- [x] Search
+- [x] Services
+- [x] Settings
+- [x] Sidekiq Metrics
+- [x] System Hooks
+- [x] Tags
+- [x] Todos
+- [x] Users
+- [x] Validate CI Configuration
+- [x] Version
+- [x] Wikis
+
+## Usage
+
+```go
+import "github.com/xanzy/go-gitlab"
+```
+
+Construct a new GitLab client, then use the various services on the client to
+access different parts of the GitLab API. For example, to list all
+users:
+
+```go
+git := gitlab.NewClient(nil, "yourtokengoeshere")
+//git.SetBaseURL("https://git.mydomain.com/api/v4")
+users, _, err := git.Users.ListUsers(&gitlab.ListUsersOptions{})
+```
+
+Some API methods have optional parameters that can be passed. For example,
+to list all projects for user "svanharmelen":
+
+```go
+git := gitlab.NewClient(nil)
+opt := &ListProjectsOptions{Search: gitlab.String("svanharmelen")}
+projects, _, err := git.Projects.ListProjects(opt)
+```
+
+### Examples
+
+The [examples](https://github.com/xanzy/go-gitlab/tree/master/examples) directory
+contains a couple for clear examples, of which one is partially listed here as well:
+
+```go
+package main
+
+import (
+	"log"
+
+	"github.com/xanzy/go-gitlab"
+)
+
+func main() {
+	git := gitlab.NewClient(nil, "yourtokengoeshere")
+
+	// Create new project
+	p := &gitlab.CreateProjectOptions{
+		Name:                 gitlab.String("My Project"),
+		Description:          gitlab.String("Just a test project to play with"),
+		MergeRequestsEnabled: gitlab.Bool(true),
+		SnippetsEnabled:      gitlab.Bool(true),
+		Visibility:           gitlab.Visibility(gitlab.PublicVisibility),
+	}
+	project, _, err := git.Projects.CreateProject(p)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Add a new snippet
+	s := &gitlab.CreateProjectSnippetOptions{
+		Title:           gitlab.String("Dummy Snippet"),
+		FileName:        gitlab.String("snippet.go"),
+		Code:            gitlab.String("package main...."),
+		Visibility:      gitlab.Visibility(gitlab.PublicVisibility),
+	}
+	_, _, err = git.ProjectSnippets.CreateSnippet(project.ID, s)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+```
+
+For complete usage of go-gitlab, see the full [package docs](https://godoc.org/github.com/xanzy/go-gitlab).
+
+## ToDo
+
+- The biggest thing this package still needs is tests :disappointed:
+
+## Issues
+
+- If you have an issue: report it on the [issue tracker](https://github.com/xanzy/go-gitlab/issues)
+
+## Author
+
+Sander van Harmelen (<sander@xanzy.io>)
+
+## License
+
+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>
diff --git a/vendor/github.com/xanzy/go-gitlab/access_requests.go b/vendor/github.com/xanzy/go-gitlab/access_requests.go
new file mode 100644
index 000000000..3fa406bca
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/access_requests.go
@@ -0,0 +1,236 @@
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// AccessRequest represents a access request for a group or project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html
+type AccessRequest struct {
+	ID          int              `json:"id"`
+	Username    string           `json:"username"`
+	Name        string           `json:"name"`
+	State       string           `json:"state"`
+	CreatedAt   *time.Time       `json:"created_at"`
+	RequestedAt *time.Time       `json:"requested_at"`
+	AccessLevel AccessLevelValue `json:"access_level"`
+}
+
+// AccessRequestsService handles communication with the project/group
+// access requests related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/access_requests.html
+type AccessRequestsService struct {
+	client *Client
+}
+
+// ListAccessRequestsOptions represents the available
+// ListProjectAccessRequests() or ListGroupAccessRequests() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html#list-access-requests-for-a-group-or-project
+type ListAccessRequestsOptions ListOptions
+
+// ListProjectAccessRequests gets a list of access requests
+// viewable by the authenticated user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html#list-access-requests-for-a-group-or-project
+func (s *AccessRequestsService) ListProjectAccessRequests(pid interface{}, opt *ListAccessRequestsOptions, options ...OptionFunc) ([]*AccessRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/access_requests", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ars []*AccessRequest
+	resp, err := s.client.Do(req, &ars)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ars, resp, err
+}
+
+// ListGroupAccessRequests gets a list of access requests
+// viewable by the authenticated user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html#list-access-requests-for-a-group-or-project
+func (s *AccessRequestsService) ListGroupAccessRequests(gid interface{}, opt *ListAccessRequestsOptions, options ...OptionFunc) ([]*AccessRequest, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/access_requests", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ars []*AccessRequest
+	resp, err := s.client.Do(req, &ars)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ars, resp, err
+}
+
+// RequestProjectAccess requests access for the authenticated user
+// to a group or project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html#request-access-to-a-group-or-project
+func (s *AccessRequestsService) RequestProjectAccess(pid interface{}, options ...OptionFunc) (*AccessRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/access_requests", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ar := new(AccessRequest)
+	resp, err := s.client.Do(req, ar)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ar, resp, err
+}
+
+// RequestGroupAccess requests access for the authenticated user
+// to a group or project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html#request-access-to-a-group-or-project
+func (s *AccessRequestsService) RequestGroupAccess(gid interface{}, options ...OptionFunc) (*AccessRequest, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/access_requests", pathEscape(group))
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ar := new(AccessRequest)
+	resp, err := s.client.Do(req, ar)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ar, resp, err
+}
+
+// ApproveAccessRequestOptions represents the available
+// ApproveProjectAccessRequest() and ApproveGroupAccessRequest() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html#approve-an-access-request
+type ApproveAccessRequestOptions struct {
+	AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+}
+
+// ApproveProjectAccessRequest approves an access request for the given user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html#approve-an-access-request
+func (s *AccessRequestsService) ApproveProjectAccessRequest(pid interface{}, user int, opt *ApproveAccessRequestOptions, options ...OptionFunc) (*AccessRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/access_requests/%d/approve", pathEscape(project), user)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ar := new(AccessRequest)
+	resp, err := s.client.Do(req, ar)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ar, resp, err
+}
+
+// ApproveGroupAccessRequest approves an access request for the given user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html#approve-an-access-request
+func (s *AccessRequestsService) ApproveGroupAccessRequest(gid interface{}, user int, opt *ApproveAccessRequestOptions, options ...OptionFunc) (*AccessRequest, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/access_requests/%d/approve", pathEscape(group), user)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ar := new(AccessRequest)
+	resp, err := s.client.Do(req, ar)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ar, resp, err
+}
+
+// DenyProjectAccessRequest denies an access request for the given user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html#deny-an-access-request
+func (s *AccessRequestsService) DenyProjectAccessRequest(pid interface{}, user int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/access_requests/%d", pathEscape(project), user)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DenyGroupAccessRequest denies an access request for the given user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/access_requests.html#deny-an-access-request
+func (s *AccessRequestsService) DenyGroupAccessRequest(gid interface{}, user int, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/access_requests/%d", pathEscape(group), user)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/award_emojis.go b/vendor/github.com/xanzy/go-gitlab/award_emojis.go
new file mode 100644
index 000000000..4c054f966
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/award_emojis.go
@@ -0,0 +1,467 @@
+//
+// Copyright 2017, Arkbriar
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// AwardEmojiService handles communication with the emoji awards related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/award_emoji.html
+type AwardEmojiService struct {
+	client *Client
+}
+
+// AwardEmoji represents a GitLab Award Emoji.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/award_emoji.html
+type AwardEmoji struct {
+	ID   int    `json:"id"`
+	Name string `json:"name"`
+	User struct {
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		ID        int    `json:"id"`
+		State     string `json:"state"`
+		AvatarURL string `json:"avatar_url"`
+		WebURL    string `json:"web_url"`
+	} `json:"user"`
+	CreatedAt     *time.Time `json:"created_at"`
+	UpdatedAt     *time.Time `json:"updated_at"`
+	AwardableID   int        `json:"awardable_id"`
+	AwardableType string     `json:"awardable_type"`
+}
+
+const (
+	awardMergeRequest = "merge_requests"
+	awardIssue        = "issues"
+	awardSnippets     = "snippets"
+)
+
+// ListAwardEmojiOptions represents the available options for listing emoji
+// for each resources
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html
+type ListAwardEmojiOptions ListOptions
+
+// ListMergeRequestAwardEmoji gets a list of all award emoji on the merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji
+func (s *AwardEmojiService) ListMergeRequestAwardEmoji(pid interface{}, mergeRequestIID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) {
+	return s.listAwardEmoji(pid, awardMergeRequest, mergeRequestIID, opt, options...)
+}
+
+// ListIssueAwardEmoji gets a list of all award emoji on the issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji
+func (s *AwardEmojiService) ListIssueAwardEmoji(pid interface{}, issueIID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) {
+	return s.listAwardEmoji(pid, awardIssue, issueIID, opt, options...)
+}
+
+// ListSnippetAwardEmoji gets a list of all award emoji on the snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji
+func (s *AwardEmojiService) ListSnippetAwardEmoji(pid interface{}, snippetID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) {
+	return s.listAwardEmoji(pid, awardSnippets, snippetID, opt, options...)
+}
+
+func (s *AwardEmojiService) listAwardEmoji(pid interface{}, resource string, resourceID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/award_emoji",
+		pathEscape(project),
+		resource,
+		resourceID,
+	)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var as []*AwardEmoji
+	resp, err := s.client.Do(req, &as)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return as, resp, err
+}
+
+// GetMergeRequestAwardEmoji get an award emoji from merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji
+func (s *AwardEmojiService) GetMergeRequestAwardEmoji(pid interface{}, mergeRequestIID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.getAwardEmoji(pid, awardMergeRequest, mergeRequestIID, awardID, options...)
+}
+
+// GetIssueAwardEmoji get an award emoji from issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji
+func (s *AwardEmojiService) GetIssueAwardEmoji(pid interface{}, issueIID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.getAwardEmoji(pid, awardIssue, issueIID, awardID, options...)
+}
+
+// GetSnippetAwardEmoji get an award emoji from snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#list-an-awardable-39-s-award-emoji
+func (s *AwardEmojiService) GetSnippetAwardEmoji(pid interface{}, snippetID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.getAwardEmoji(pid, awardSnippets, snippetID, awardID, options...)
+}
+
+func (s *AwardEmojiService) getAwardEmoji(pid interface{}, resource string, resourceID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/award_emoji/%d",
+		pathEscape(project),
+		resource,
+		resourceID,
+		awardID,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	a := new(AwardEmoji)
+	resp, err := s.client.Do(req, &a)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return a, resp, err
+}
+
+// CreateAwardEmojiOptions represents the available options for awarding emoji
+// for a resource
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji
+type CreateAwardEmojiOptions struct {
+	Name string `json:"name"`
+}
+
+// CreateMergeRequestAwardEmoji get an award emoji from merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji
+func (s *AwardEmojiService) CreateMergeRequestAwardEmoji(pid interface{}, mergeRequestIID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.createAwardEmoji(pid, awardMergeRequest, mergeRequestIID, opt, options...)
+}
+
+// CreateIssueAwardEmoji get an award emoji from issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji
+func (s *AwardEmojiService) CreateIssueAwardEmoji(pid interface{}, issueIID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.createAwardEmoji(pid, awardIssue, issueIID, opt, options...)
+}
+
+// CreateSnippetAwardEmoji get an award emoji from snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji
+func (s *AwardEmojiService) CreateSnippetAwardEmoji(pid interface{}, snippetID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.createAwardEmoji(pid, awardSnippets, snippetID, opt, options...)
+}
+
+func (s *AwardEmojiService) createAwardEmoji(pid interface{}, resource string, resourceID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/award_emoji",
+		pathEscape(project),
+		resource,
+		resourceID,
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	a := new(AwardEmoji)
+	resp, err := s.client.Do(req, &a)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return a, resp, err
+}
+
+// DeleteIssueAwardEmoji delete award emoji on an issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji-on-a-note
+func (s *AwardEmojiService) DeleteIssueAwardEmoji(pid interface{}, issueIID, awardID int, options ...OptionFunc) (*Response, error) {
+	return s.deleteAwardEmoji(pid, awardMergeRequest, issueIID, awardID, options...)
+}
+
+// DeleteMergeRequestAwardEmoji delete award emoji on a merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji-on-a-note
+func (s *AwardEmojiService) DeleteMergeRequestAwardEmoji(pid interface{}, mergeRequestIID, awardID int, options ...OptionFunc) (*Response, error) {
+	return s.deleteAwardEmoji(pid, awardMergeRequest, mergeRequestIID, awardID, options...)
+}
+
+// DeleteSnippetAwardEmoji delete award emoji on a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji-on-a-note
+func (s *AwardEmojiService) DeleteSnippetAwardEmoji(pid interface{}, snippetID, awardID int, options ...OptionFunc) (*Response, error) {
+	return s.deleteAwardEmoji(pid, awardMergeRequest, snippetID, awardID, options...)
+}
+
+// DeleteAwardEmoji Delete an award emoji on the specified resource.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#delete-an-award-emoji
+func (s *AwardEmojiService) deleteAwardEmoji(pid interface{}, resource string, resourceID, awardID int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/award_emoji/%d", pathEscape(project), resource,
+		resourceID, awardID)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(req, nil)
+}
+
+// ListIssuesAwardEmojiOnNote gets a list of all award emoji on a note from the
+// issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) ListIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) {
+	return s.listAwardEmojiOnNote(pid, awardIssue, issueID, noteID, opt, options...)
+}
+
+// ListMergeRequestAwardEmojiOnNote gets a list of all award emoji on a note
+// from the merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) ListMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) {
+	return s.listAwardEmojiOnNote(pid, awardMergeRequest, mergeRequestIID, noteID, opt, options...)
+}
+
+// ListSnippetAwardEmojiOnNote gets a list of all award emoji on a note from the
+// snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) ListSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) {
+	return s.listAwardEmojiOnNote(pid, awardSnippets, snippetIID, noteID, opt, options...)
+}
+
+func (s *AwardEmojiService) listAwardEmojiOnNote(pid interface{}, resources string, ressourceID, noteID int, opt *ListAwardEmojiOptions, options ...OptionFunc) ([]*AwardEmoji, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/notes/%d/award_emoji", pathEscape(project), resources,
+		ressourceID, noteID)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var as []*AwardEmoji
+	resp, err := s.client.Do(req, &as)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return as, resp, err
+}
+
+// GetIssuesAwardEmojiOnNote gets an award emoji on a note from an issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) GetIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.getSingleNoteAwardEmoji(pid, awardIssue, issueID, noteID, awardID, options...)
+}
+
+// GetMergeRequestAwardEmojiOnNote gets an award emoji on a note from a
+// merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) GetMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.getSingleNoteAwardEmoji(pid, awardMergeRequest, mergeRequestIID, noteID, awardID,
+		options...)
+}
+
+// GetSnippetAwardEmojiOnNote gets an award emoji on a note from a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) GetSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.getSingleNoteAwardEmoji(pid, awardSnippets, snippetIID, noteID, awardID, options...)
+}
+
+func (s *AwardEmojiService) getSingleNoteAwardEmoji(pid interface{}, ressource string, resourceID, noteID, awardID int, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/notes/%d/award_emoji/%d",
+		pathEscape(project),
+		ressource,
+		resourceID,
+		noteID,
+		awardID,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	a := new(AwardEmoji)
+	resp, err := s.client.Do(req, &a)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return a, resp, err
+}
+
+// CreateIssuesAwardEmojiOnNote gets an award emoji on a note from an issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) CreateIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.createAwardEmojiOnNote(pid, awardIssue, issueID, noteID, opt, options...)
+}
+
+// CreateMergeRequestAwardEmojiOnNote gets an award emoji on a note from a
+// merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) CreateMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.createAwardEmojiOnNote(pid, awardMergeRequest, mergeRequestIID, noteID, opt, options...)
+}
+
+// CreateSnippetAwardEmojiOnNote gets an award emoji on a note from a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) CreateSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	return s.createAwardEmojiOnNote(pid, awardSnippets, snippetIID, noteID, opt, options...)
+}
+
+// CreateAwardEmojiOnNote award emoji on a note.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-a-new-emoji-on-a-note
+func (s *AwardEmojiService) createAwardEmojiOnNote(pid interface{}, resource string, resourceID, noteID int, opt *CreateAwardEmojiOptions, options ...OptionFunc) (*AwardEmoji, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/notes/%d/award_emoji",
+		pathEscape(project),
+		resource,
+		resourceID,
+		noteID,
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	a := new(AwardEmoji)
+	resp, err := s.client.Do(req, &a)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return a, resp, err
+}
+
+// DeleteIssuesAwardEmojiOnNote deletes an award emoji on a note from an issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) DeleteIssuesAwardEmojiOnNote(pid interface{}, issueID, noteID, awardID int, options ...OptionFunc) (*Response, error) {
+	return s.deleteAwardEmojiOnNote(pid, awardIssue, issueID, noteID, awardID, options...)
+}
+
+// DeleteMergeRequestAwardEmojiOnNote deletes an award emoji on a note from a
+// merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) DeleteMergeRequestAwardEmojiOnNote(pid interface{}, mergeRequestIID, noteID, awardID int, options ...OptionFunc) (*Response, error) {
+	return s.deleteAwardEmojiOnNote(pid, awardMergeRequest, mergeRequestIID, noteID, awardID,
+		options...)
+}
+
+// DeleteSnippetAwardEmojiOnNote deletes an award emoji on a note from a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/award_emoji.html#award-emoji-on-notes
+func (s *AwardEmojiService) DeleteSnippetAwardEmojiOnNote(pid interface{}, snippetIID, noteID, awardID int, options ...OptionFunc) (*Response, error) {
+	return s.deleteAwardEmojiOnNote(pid, awardSnippets, snippetIID, noteID, awardID, options...)
+}
+
+func (s *AwardEmojiService) deleteAwardEmojiOnNote(pid interface{}, resource string, resourceID, noteID, awardID int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/notes/%d/award_emoji/%d",
+		pathEscape(project),
+		resource,
+		resourceID,
+		noteID,
+		awardID,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/boards.go b/vendor/github.com/xanzy/go-gitlab/boards.go
new file mode 100644
index 000000000..fd5e280f6
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/boards.go
@@ -0,0 +1,344 @@
+//
+// Copyright 2015, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+)
+
+// IssueBoardsService handles communication with the issue board related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html
+type IssueBoardsService struct {
+	client *Client
+}
+
+// IssueBoard represents a GitLab issue board.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html
+type IssueBoard struct {
+	ID        int          `json:"id"`
+	Name      string       `json:"name"`
+	Project   *Project     `json:"project"`
+	Milestone *Milestone   `json:"milestone"`
+	Lists     []*BoardList `json:"lists"`
+}
+
+func (b IssueBoard) String() string {
+	return Stringify(b)
+}
+
+// BoardList represents a GitLab board list.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html
+type BoardList struct {
+	ID       int    `json:"id"`
+	Label    *Label `json:"label"`
+	Position int    `json:"position"`
+}
+
+func (b BoardList) String() string {
+	return Stringify(b)
+}
+
+// CreateIssueBoardOptions represents the available CreateIssueBoard() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/boards.html#create-a-board-starter
+type CreateIssueBoardOptions struct {
+	Name *string `url:"name" json:"name"`
+}
+
+// CreateIssueBoard creates a new issue board.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/boards.html#create-a-board-starter
+func (s *IssueBoardsService) CreateIssueBoard(pid interface{}, opt *CreateIssueBoardOptions, options ...OptionFunc) (*IssueBoard, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/boards", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	board := new(IssueBoard)
+	resp, err := s.client.Do(req, board)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return board, resp, err
+}
+
+// UpdateIssueBoardOptions represents the available UpdateIssueBoard() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/boards.html#update-a-board-starter
+type UpdateIssueBoardOptions struct {
+	Name        *string `url:"name,omitempty" json:"name,omitempty"`
+	AssigneeID  *int    `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+	MilestoneID *int    `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
+	Labels      Labels  `url:"labels,omitempty" json:"labels,omitempty"`
+	Weight      *int    `url:"weight,omitempty" json:"weight,omitempty"`
+}
+
+// UpdateIssueBoard update an issue board.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/boards.html#create-a-board-starter
+func (s *IssueBoardsService) UpdateIssueBoard(pid interface{}, board int, opt *UpdateIssueBoardOptions, options ...OptionFunc) (*IssueBoard, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/boards/%d", pathEscape(project), board)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	is := new(IssueBoard)
+	resp, err := s.client.Do(req, is)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return is, resp, err
+}
+
+// DeleteIssueBoard deletes an issue board.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/boards.html#delete-a-board-starter
+func (s *IssueBoardsService) DeleteIssueBoard(pid interface{}, board int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/boards/%d", pathEscape(project), board)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ListIssueBoardsOptions represents the available ListIssueBoards() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#project-board
+type ListIssueBoardsOptions ListOptions
+
+// ListIssueBoards gets a list of all issue boards in a project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#project-board
+func (s *IssueBoardsService) ListIssueBoards(pid interface{}, opt *ListIssueBoardsOptions, options ...OptionFunc) ([]*IssueBoard, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/boards", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var is []*IssueBoard
+	resp, err := s.client.Do(req, &is)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return is, resp, err
+}
+
+// GetIssueBoard gets a single issue board of a project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#single-board
+func (s *IssueBoardsService) GetIssueBoard(pid interface{}, board int, options ...OptionFunc) (*IssueBoard, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/boards/%d", pathEscape(project), board)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ib := new(IssueBoard)
+	resp, err := s.client.Do(req, ib)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ib, resp, err
+}
+
+// GetIssueBoardListsOptions represents the available GetIssueBoardLists() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#list-board-lists
+type GetIssueBoardListsOptions ListOptions
+
+// GetIssueBoardLists gets a list of the issue board's lists. Does not include
+// backlog and closed lists.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#list-board-lists
+func (s *IssueBoardsService) GetIssueBoardLists(pid interface{}, board int, opt *GetIssueBoardListsOptions, options ...OptionFunc) ([]*BoardList, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/boards/%d/lists", pathEscape(project), board)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var bl []*BoardList
+	resp, err := s.client.Do(req, &bl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return bl, resp, err
+}
+
+// GetIssueBoardList gets a single issue board list.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#single-board-list
+func (s *IssueBoardsService) GetIssueBoardList(pid interface{}, board, list int, options ...OptionFunc) (*BoardList, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/boards/%d/lists/%d",
+		pathEscape(project),
+		board,
+		list,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	bl := new(BoardList)
+	resp, err := s.client.Do(req, bl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return bl, resp, err
+}
+
+// CreateIssueBoardListOptions represents the available CreateIssueBoardList()
+// options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#new-board-list
+type CreateIssueBoardListOptions struct {
+	LabelID *int `url:"label_id" json:"label_id"`
+}
+
+// CreateIssueBoardList creates a new issue board list.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#new-board-list
+func (s *IssueBoardsService) CreateIssueBoardList(pid interface{}, board int, opt *CreateIssueBoardListOptions, options ...OptionFunc) (*BoardList, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/boards/%d/lists", pathEscape(project), board)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	bl := new(BoardList)
+	resp, err := s.client.Do(req, bl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return bl, resp, err
+}
+
+// UpdateIssueBoardListOptions represents the available UpdateIssueBoardList()
+// options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#edit-board-list
+type UpdateIssueBoardListOptions struct {
+	Position *int `url:"position" json:"position"`
+}
+
+// UpdateIssueBoardList updates the position of an existing issue board list.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/boards.html#edit-board-list
+func (s *IssueBoardsService) UpdateIssueBoardList(pid interface{}, board, list int, opt *UpdateIssueBoardListOptions, options ...OptionFunc) (*BoardList, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/boards/%d/lists/%d",
+		pathEscape(project),
+		board,
+		list,
+	)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	bl := new(BoardList)
+	resp, err := s.client.Do(req, bl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return bl, resp, err
+}
+
+// DeleteIssueBoardList soft deletes an issue board list. Only for admins and
+// project owners.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/boards.html#delete-a-board-list
+func (s *IssueBoardsService) DeleteIssueBoardList(pid interface{}, board, list int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/boards/%d/lists/%d",
+		pathEscape(project),
+		board,
+		list,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/branches.go b/vendor/github.com/xanzy/go-gitlab/branches.go
new file mode 100644
index 000000000..e61ddfe74
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/branches.go
@@ -0,0 +1,242 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+)
+
+// BranchesService handles communication with the branch related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/branches.html
+type BranchesService struct {
+	client *Client
+}
+
+// Branch represents a GitLab branch.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/branches.html
+type Branch struct {
+	Commit             *Commit `json:"commit"`
+	Name               string  `json:"name"`
+	Protected          bool    `json:"protected"`
+	Merged             bool    `json:"merged"`
+	Default            bool    `json:"default"`
+	DevelopersCanPush  bool    `json:"developers_can_push"`
+	DevelopersCanMerge bool    `json:"developers_can_merge"`
+}
+
+func (b Branch) String() string {
+	return Stringify(b)
+}
+
+// ListBranchesOptions represents the available ListBranches() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#list-repository-branches
+type ListBranchesOptions struct {
+	ListOptions
+	Search *string `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListBranches gets a list of repository branches from a project, sorted by
+// name alphabetically.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#list-repository-branches
+func (s *BranchesService) ListBranches(pid interface{}, opts *ListBranchesOptions, options ...OptionFunc) ([]*Branch, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/branches", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opts, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var b []*Branch
+	resp, err := s.client.Do(req, &b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b, resp, err
+}
+
+// GetBranch gets a single project repository branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#get-single-repository-branch
+func (s *BranchesService) GetBranch(pid interface{}, branch string, options ...OptionFunc) (*Branch, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/branches/%s", pathEscape(project), url.PathEscape(branch))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	b := new(Branch)
+	resp, err := s.client.Do(req, b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b, resp, err
+}
+
+// ProtectBranchOptions represents the available ProtectBranch() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#protect-repository-branch
+type ProtectBranchOptions struct {
+	DevelopersCanPush  *bool `url:"developers_can_push,omitempty" json:"developers_can_push,omitempty"`
+	DevelopersCanMerge *bool `url:"developers_can_merge,omitempty" json:"developers_can_merge,omitempty"`
+}
+
+// ProtectBranch protects a single project repository branch. This is an
+// idempotent function, protecting an already protected repository branch
+// still returns a 200 OK status code.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#protect-repository-branch
+func (s *BranchesService) ProtectBranch(pid interface{}, branch string, opts *ProtectBranchOptions, options ...OptionFunc) (*Branch, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/branches/%s/protect", pathEscape(project), url.PathEscape(branch))
+
+	req, err := s.client.NewRequest("PUT", u, opts, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	b := new(Branch)
+	resp, err := s.client.Do(req, b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b, resp, err
+}
+
+// UnprotectBranch unprotects a single project repository branch. This is an
+// idempotent function, unprotecting an already unprotected repository branch
+// still returns a 200 OK status code.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#unprotect-repository-branch
+func (s *BranchesService) UnprotectBranch(pid interface{}, branch string, options ...OptionFunc) (*Branch, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/branches/%s/unprotect", pathEscape(project), url.PathEscape(branch))
+
+	req, err := s.client.NewRequest("PUT", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	b := new(Branch)
+	resp, err := s.client.Do(req, b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b, resp, err
+}
+
+// CreateBranchOptions represents the available CreateBranch() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#create-repository-branch
+type CreateBranchOptions struct {
+	Branch *string `url:"branch,omitempty" json:"branch,omitempty"`
+	Ref    *string `url:"ref,omitempty" json:"ref,omitempty"`
+}
+
+// CreateBranch creates branch from commit SHA or existing branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#create-repository-branch
+func (s *BranchesService) CreateBranch(pid interface{}, opt *CreateBranchOptions, options ...OptionFunc) (*Branch, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/branches", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	b := new(Branch)
+	resp, err := s.client.Do(req, b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b, resp, err
+}
+
+// DeleteBranch deletes an existing branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#delete-repository-branch
+func (s *BranchesService) DeleteBranch(pid interface{}, branch string, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/branches/%s", pathEscape(project), url.PathEscape(branch))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteMergedBranches deletes all branches that are merged into the project's default branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/branches.html#delete-merged-branches
+func (s *BranchesService) DeleteMergedBranches(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/merged_branches", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/broadcast_messages.go b/vendor/github.com/xanzy/go-gitlab/broadcast_messages.go
new file mode 100644
index 000000000..aee852d46
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/broadcast_messages.go
@@ -0,0 +1,172 @@
+//
+// Copyright 2018, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// BroadcastMessagesService handles communication with the broadcast
+// messages methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/broadcast_messages.html
+type BroadcastMessagesService struct {
+	client *Client
+}
+
+// BroadcastMessage represents a GitLab issue board.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/broadcast_messages.html#get-all-broadcast-messages
+type BroadcastMessage struct {
+	Message  string     `json:"message"`
+	StartsAt *time.Time `json:"starts_at"`
+	EndsAt   *time.Time `json:"ends_at"`
+	Color    string     `json:"color"`
+	Font     string     `json:"font"`
+	ID       int        `json:"id"`
+	Active   bool       `json:"active"`
+}
+
+// ListBroadcastMessagesOptions represents the available ListBroadcastMessages()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/broadcast_messages.html#get-all-broadcast-messages
+type ListBroadcastMessagesOptions ListOptions
+
+// ListBroadcastMessages gets a list of all broadcasted messages.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/broadcast_messages.html#get-all-broadcast-messages
+func (s *BroadcastMessagesService) ListBroadcastMessages(opt *ListBroadcastMessagesOptions, options ...OptionFunc) ([]*BroadcastMessage, *Response, error) {
+	req, err := s.client.NewRequest("GET", "broadcast_messages", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var bs []*BroadcastMessage
+	resp, err := s.client.Do(req, &bs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return bs, resp, err
+}
+
+// GetBroadcastMessage gets a single broadcast message.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/broadcast_messages.html#get-a-specific-broadcast-message
+func (s *BroadcastMessagesService) GetBroadcastMessage(broadcast int, options ...OptionFunc) (*BroadcastMessage, *Response, error) {
+	u := fmt.Sprintf("broadcast_messages/%d", broadcast)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	b := new(BroadcastMessage)
+	resp, err := s.client.Do(req, &b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b, resp, err
+}
+
+// CreateBroadcastMessageOptions represents the available CreateBroadcastMessage()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/broadcast_messages.html#create-a-broadcast-message
+type CreateBroadcastMessageOptions struct {
+	Message  *string    `url:"message" json:"message"`
+	StartsAt *time.Time `url:"starts_at,omitempty" json:"starts_at,omitempty"`
+	EndsAt   *time.Time `url:"ends_at,omitempty" json:"ends_at,omitempty"`
+	Color    *string    `url:"color,omitempty" json:"color,omitempty"`
+	Font     *string    `url:"font,omitempty" json:"font,omitempty"`
+}
+
+// CreateBroadcastMessage creates a message to broadcast.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/broadcast_messages.html#create-a-broadcast-message
+func (s *BroadcastMessagesService) CreateBroadcastMessage(opt *CreateBroadcastMessageOptions, options ...OptionFunc) (*BroadcastMessage, *Response, error) {
+	req, err := s.client.NewRequest("POST", "broadcast_messages", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	b := new(BroadcastMessage)
+	resp, err := s.client.Do(req, &b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b, resp, err
+}
+
+// UpdateBroadcastMessageOptions represents the available CreateBroadcastMessage()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/broadcast_messages.html#update-a-broadcast-message
+type UpdateBroadcastMessageOptions struct {
+	Message  *string    `url:"message,omitempty" json:"message,omitempty"`
+	StartsAt *time.Time `url:"starts_at,omitempty" json:"starts_at,omitempty"`
+	EndsAt   *time.Time `url:"ends_at,omitempty" json:"ends_at,omitempty"`
+	Color    *string    `url:"color,omitempty" json:"color,omitempty"`
+	Font     *string    `url:"font,omitempty" json:"font,omitempty"`
+}
+
+// UpdateBroadcastMessage update a broadcasted message.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/broadcast_messages.html#update-a-broadcast-message
+func (s *BroadcastMessagesService) UpdateBroadcastMessage(broadcast int, opt *UpdateBroadcastMessageOptions, options ...OptionFunc) (*BroadcastMessage, *Response, error) {
+	u := fmt.Sprintf("broadcast_messages/%d", broadcast)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	b := new(BroadcastMessage)
+	resp, err := s.client.Do(req, &b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b, resp, err
+}
+
+// DeleteBroadcastMessage deletes a broadcasted message.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/broadcast_messages.html#delete-a-broadcast-message
+func (s *BroadcastMessagesService) DeleteBroadcastMessage(broadcast int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("broadcast_messages/%d", broadcast)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/ci_yml_templates.go b/vendor/github.com/xanzy/go-gitlab/ci_yml_templates.go
new file mode 100644
index 000000000..7a185af62
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/ci_yml_templates.go
@@ -0,0 +1,69 @@
+package gitlab
+
+import (
+	"fmt"
+)
+
+// CIYMLTemplatesService handles communication with the gitlab
+// CI YML templates related methods of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/gitlab_ci_ymls.html
+type CIYMLTemplatesService struct {
+	client *Client
+}
+
+// CIYMLTemplate represents a GitLab CI YML template.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/gitlab_ci_ymls.html
+type CIYMLTemplate struct {
+	Name    string `json:"name"`
+	Content string `json:"content"`
+}
+
+// ListCIYMLTemplatesOptions represents the available ListAllTemplates() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/gitignores.html#list-gitignore-templates
+type ListCIYMLTemplatesOptions ListOptions
+
+// ListAllTemplates get all GitLab CI YML templates.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/gitlab_ci_ymls.html#list-gitlab-ci-yml-templates
+func (s *CIYMLTemplatesService) ListAllTemplates(opt *ListCIYMLTemplatesOptions, options ...OptionFunc) ([]*CIYMLTemplate, *Response, error) {
+	req, err := s.client.NewRequest("GET", "templates/gitlab_ci_ymls", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var cts []*CIYMLTemplate
+	resp, err := s.client.Do(req, &cts)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return cts, resp, err
+}
+
+// GetTemplate get a single GitLab CI YML template.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/gitlab_ci_ymls.html#single-gitlab-ci-yml-template
+func (s *CIYMLTemplatesService) GetTemplate(key string, options ...OptionFunc) (*CIYMLTemplate, *Response, error) {
+	u := fmt.Sprintf("templates/gitlab_ci_ymls/%s", pathEscape(key))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ct := new(CIYMLTemplate)
+	resp, err := s.client.Do(req, ct)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ct, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/commits.go b/vendor/github.com/xanzy/go-gitlab/commits.go
new file mode 100644
index 000000000..27dd56459
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/commits.go
@@ -0,0 +1,593 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+	"time"
+)
+
+// CommitsService handles communication with the commit related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
+type CommitsService struct {
+	client *Client
+}
+
+// Commit represents a GitLab commit.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
+type Commit struct {
+	ID             string           `json:"id"`
+	ShortID        string           `json:"short_id"`
+	Title          string           `json:"title"`
+	AuthorName     string           `json:"author_name"`
+	AuthorEmail    string           `json:"author_email"`
+	AuthoredDate   *time.Time       `json:"authored_date"`
+	CommitterName  string           `json:"committer_name"`
+	CommitterEmail string           `json:"committer_email"`
+	CommittedDate  *time.Time       `json:"committed_date"`
+	CreatedAt      *time.Time       `json:"created_at"`
+	Message        string           `json:"message"`
+	ParentIDs      []string         `json:"parent_ids"`
+	Stats          *CommitStats     `json:"stats"`
+	Status         *BuildStateValue `json:"status"`
+	LastPipeline   *PipelineInfo    `json:"last_pipeline"`
+	ProjectID      int              `json:"project_id"`
+}
+
+// CommitStats represents the number of added and deleted files in a commit.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
+type CommitStats struct {
+	Additions int `json:"additions"`
+	Deletions int `json:"deletions"`
+	Total     int `json:"total"`
+}
+
+func (c Commit) String() string {
+	return Stringify(c)
+}
+
+// ListCommitsOptions represents the available ListCommits() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-repository-commits
+type ListCommitsOptions struct {
+	ListOptions
+	RefName   *string    `url:"ref_name,omitempty" json:"ref_name,omitempty"`
+	Since     *time.Time `url:"since,omitempty" json:"since,omitempty"`
+	Until     *time.Time `url:"until,omitempty" json:"until,omitempty"`
+	Path      *string    `url:"path,omitempty" json:"path,omitempty"`
+	All       *bool      `url:"all,omitempty" json:"all,omitempty"`
+	WithStats *bool      `url:"with_stats,omitempty" json:"with_stats,omitempty"`
+}
+
+// ListCommits gets a list of repository commits in a project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#list-commits
+func (s *CommitsService) ListCommits(pid interface{}, opt *ListCommitsOptions, options ...OptionFunc) ([]*Commit, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var c []*Commit
+	resp, err := s.client.Do(req, &c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
+
+// FileAction represents the available actions that can be performed on a file.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
+type FileAction string
+
+// The available file actions.
+const (
+	FileCreate FileAction = "create"
+	FileDelete FileAction = "delete"
+	FileMove   FileAction = "move"
+	FileUpdate FileAction = "update"
+)
+
+// CommitAction represents a single file action within a commit.
+type CommitAction struct {
+	Action       FileAction `url:"action" json:"action"`
+	FilePath     string     `url:"file_path" json:"file_path"`
+	PreviousPath string     `url:"previous_path,omitempty" json:"previous_path,omitempty"`
+	Content      string     `url:"content,omitempty" json:"content,omitempty"`
+	Encoding     string     `url:"encoding,omitempty" json:"encoding,omitempty"`
+}
+
+// CommitRef represents the reference of branches/tags in a commit.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#get-references-a-commit-is-pushed-to
+type CommitRef struct {
+	Type string `json:"type"`
+	Name string `json:"name"`
+}
+
+// GetCommitRefsOptions represents the available GetCommitRefs() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#get-references-a-commit-is-pushed-to
+type GetCommitRefsOptions struct {
+	ListOptions
+	Type *string `url:"type,omitempty" json:"type,omitempty"`
+}
+
+// GetCommitRefs gets all references (from branches or tags) a commit is pushed to
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#get-references-a-commit-is-pushed-to
+func (s *CommitsService) GetCommitRefs(pid interface{}, sha string, opt *GetCommitRefsOptions, options ...OptionFunc) ([]*CommitRef, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/refs", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var cs []*CommitRef
+	resp, err := s.client.Do(req, &cs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return cs, resp, err
+}
+
+// GetCommit gets a specific commit identified by the commit hash or name of a
+// branch or tag.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-a-single-commit
+func (s *CommitsService) GetCommit(pid interface{}, sha string, options ...OptionFunc) (*Commit, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	if sha == "" {
+		return nil, nil, fmt.Errorf("SHA must be a non-empty string")
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	c := new(Commit)
+	resp, err := s.client.Do(req, c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
+
+// CreateCommitOptions represents the available options for a new commit.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
+type CreateCommitOptions struct {
+	Branch        *string         `url:"branch" json:"branch"`
+	CommitMessage *string         `url:"commit_message" json:"commit_message"`
+	StartBranch   *string         `url:"start_branch,omitempty" json:"start_branch,omitempty"`
+	StartSHA      *string         `url:"start_sha,omitempty" json:"start_sha,omitempty"`
+	StartProject  *string         `url:"start_project,omitempty" json:"start_project,omitempty"`
+	Actions       []*CommitAction `url:"actions" json:"actions"`
+	AuthorEmail   *string         `url:"author_email,omitempty" json:"author_email,omitempty"`
+	AuthorName    *string         `url:"author_name,omitempty" json:"author_name,omitempty"`
+	Stats         *bool           `url:"stats,omitempty" json:"stats,omitempty"`
+	Force         *bool           `url:"force,omitempty" json:"force,omitempty"`
+}
+
+// CreateCommit creates a commit with multiple files and actions.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
+func (s *CommitsService) CreateCommit(pid interface{}, opt *CreateCommitOptions, options ...OptionFunc) (*Commit, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	c := new(Commit)
+	resp, err := s.client.Do(req, &c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
+
+// Diff represents a GitLab diff.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
+type Diff struct {
+	Diff        string `json:"diff"`
+	NewPath     string `json:"new_path"`
+	OldPath     string `json:"old_path"`
+	AMode       string `json:"a_mode"`
+	BMode       string `json:"b_mode"`
+	NewFile     bool   `json:"new_file"`
+	RenamedFile bool   `json:"renamed_file"`
+	DeletedFile bool   `json:"deleted_file"`
+}
+
+func (d Diff) String() string {
+	return Stringify(d)
+}
+
+// GetCommitDiffOptions represents the available GetCommitDiff() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#get-the-diff-of-a-commit
+type GetCommitDiffOptions ListOptions
+
+// GetCommitDiff gets the diff of a commit in a project..
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#get-the-diff-of-a-commit
+func (s *CommitsService) GetCommitDiff(pid interface{}, sha string, opt *GetCommitDiffOptions, options ...OptionFunc) ([]*Diff, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/diff", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var d []*Diff
+	resp, err := s.client.Do(req, &d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// CommitComment represents a GitLab commit comment.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html
+type CommitComment struct {
+	Note     string `json:"note"`
+	Path     string `json:"path"`
+	Line     int    `json:"line"`
+	LineType string `json:"line_type"`
+	Author   Author `json:"author"`
+}
+
+// Author represents a GitLab commit author
+type Author struct {
+	ID        int        `json:"id"`
+	Username  string     `json:"username"`
+	Email     string     `json:"email"`
+	Name      string     `json:"name"`
+	State     string     `json:"state"`
+	Blocked   bool       `json:"blocked"`
+	CreatedAt *time.Time `json:"created_at"`
+}
+
+func (c CommitComment) String() string {
+	return Stringify(c)
+}
+
+// GetCommitCommentsOptions represents the available GetCommitComments() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#get-the-comments-of-a-commit
+type GetCommitCommentsOptions ListOptions
+
+// GetCommitComments gets the comments of a commit in a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#get-the-comments-of-a-commit
+func (s *CommitsService) GetCommitComments(pid interface{}, sha string, opt *GetCommitCommentsOptions, options ...OptionFunc) ([]*CommitComment, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var c []*CommitComment
+	resp, err := s.client.Do(req, &c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
+
+// PostCommitCommentOptions represents the available PostCommitComment()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
+type PostCommitCommentOptions struct {
+	Note     *string `url:"note,omitempty" json:"note,omitempty"`
+	Path     *string `url:"path" json:"path"`
+	Line     *int    `url:"line" json:"line"`
+	LineType *string `url:"line_type" json:"line_type"`
+}
+
+// PostCommitComment adds a comment to a commit. Optionally you can post
+// comments on a specific line of a commit. Therefor both path, line_new and
+// line_old are required.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
+func (s *CommitsService) PostCommitComment(pid interface{}, sha string, opt *PostCommitCommentOptions, options ...OptionFunc) (*CommitComment, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/comments", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	c := new(CommitComment)
+	resp, err := s.client.Do(req, c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
+
+// GetCommitStatusesOptions represents the available GetCommitStatuses() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
+type GetCommitStatusesOptions struct {
+	ListOptions
+	Ref   *string `url:"ref,omitempty" json:"ref,omitempty"`
+	Stage *string `url:"stage,omitempty" json:"stage,omitempty"`
+	Name  *string `url:"name,omitempty" json:"name,omitempty"`
+	All   *bool   `url:"all,omitempty" json:"all,omitempty"`
+}
+
+// CommitStatus represents a GitLab commit status.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
+type CommitStatus struct {
+	ID          int        `json:"id"`
+	SHA         string     `json:"sha"`
+	Ref         string     `json:"ref"`
+	Status      string     `json:"status"`
+	Name        string     `json:"name"`
+	TargetURL   string     `json:"target_url"`
+	Description string     `json:"description"`
+	CreatedAt   *time.Time `json:"created_at"`
+	StartedAt   *time.Time `json:"started_at"`
+	FinishedAt  *time.Time `json:"finished_at"`
+	Author      Author     `json:"author"`
+}
+
+// GetCommitStatuses gets the statuses of a commit in a project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#get-the-status-of-a-commit
+func (s *CommitsService) GetCommitStatuses(pid interface{}, sha string, opt *GetCommitStatusesOptions, options ...OptionFunc) ([]*CommitStatus, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/statuses", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var cs []*CommitStatus
+	resp, err := s.client.Do(req, &cs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return cs, resp, err
+}
+
+// SetCommitStatusOptions represents the available SetCommitStatus() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit
+type SetCommitStatusOptions struct {
+	State       BuildStateValue `url:"state" json:"state"`
+	Ref         *string         `url:"ref,omitempty" json:"ref,omitempty"`
+	Name        *string         `url:"name,omitempty" json:"name,omitempty"`
+	Context     *string         `url:"context,omitempty" json:"context,omitempty"`
+	TargetURL   *string         `url:"target_url,omitempty" json:"target_url,omitempty"`
+	Description *string         `url:"description,omitempty" json:"description,omitempty"`
+}
+
+// SetCommitStatus sets the status of a commit in a project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#post-the-status-to-commit
+func (s *CommitsService) SetCommitStatus(pid interface{}, sha string, opt *SetCommitStatusOptions, options ...OptionFunc) (*CommitStatus, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/statuses/%s", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	cs := new(CommitStatus)
+	resp, err := s.client.Do(req, &cs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return cs, resp, err
+}
+
+// GetMergeRequestsByCommit gets merge request associated with a commit.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/commits.html#list-merge-requests-associated-with-a-commit
+func (s *CommitsService) GetMergeRequestsByCommit(pid interface{}, sha string, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/merge_requests", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var mrs []*MergeRequest
+	resp, err := s.client.Do(req, &mrs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return mrs, resp, err
+}
+
+// CherryPickCommitOptions represents the available CherryPickCommit() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#cherry-pick-a-commit
+type CherryPickCommitOptions struct {
+	Branch *string `url:"branch,omitempty" json:"branch,omitempty"`
+}
+
+// CherryPickCommit cherry picks a commit to a given branch.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/commits.html#cherry-pick-a-commit
+func (s *CommitsService) CherryPickCommit(pid interface{}, sha string, opt *CherryPickCommitOptions, options ...OptionFunc) (*Commit, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/cherry_pick", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	c := new(Commit)
+	resp, err := s.client.Do(req, &c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
+
+// RevertCommitOptions represents the available RevertCommit() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/commits.html#revert-a-commit
+type RevertCommitOptions struct {
+	Branch *string `url:"branch,omitempty" json:"branch,omitempty"`
+}
+
+// RevertCommit reverts a commit in a given branch.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/commits.html#revert-a-commit
+func (s *CommitsService) RevertCommit(pid interface{}, sha string, opt *RevertCommitOptions, options ...OptionFunc) (*Commit, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/revert", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	c := new(Commit)
+	resp, err := s.client.Do(req, &c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
+
+// GPGSignature represents a Gitlab commit's GPG Signature.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/commits.html#get-gpg-signature-of-a-commit
+type GPGSignature struct {
+	KeyID              int    `json:"gpg_key_id"`
+	KeyPrimaryKeyID    string `json:"gpg_key_primary_keyid"`
+	KeyUserName        string `json:"gpg_key_user_name"`
+	KeyUserEmail       string `json:"gpg_key_user_email"`
+	VerificationStatus string `json:"verification_status"`
+	KeySubkeyID        int    `json:"gpg_key_subkey_id"`
+}
+
+// GetGPGSiganature gets a GPG signature of a commit.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/commits.html#get-gpg-signature-of-a-commit
+func (s *CommitsService) GetGPGSiganature(pid interface{}, sha string, options ...OptionFunc) (*GPGSignature, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/signature", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	sig := new(GPGSignature)
+	resp, err := s.client.Do(req, &sig)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return sig, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/custom_attributes.go b/vendor/github.com/xanzy/go-gitlab/custom_attributes.go
new file mode 100644
index 000000000..ce165c808
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/custom_attributes.go
@@ -0,0 +1,171 @@
+package gitlab
+
+import (
+	"fmt"
+)
+
+// CustomAttributesService handles communication with the group, project and
+// user custom attributes related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/custom_attributes.html
+type CustomAttributesService struct {
+	client *Client
+}
+
+// CustomAttribute struct is used to unmarshal response to api calls.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/custom_attributes.html
+type CustomAttribute struct {
+	Key   string `json:"key"`
+	Value string `json:"value"`
+}
+
+// ListCustomUserAttributes lists the custom attributes of the specified user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#list-custom-attributes
+func (s *CustomAttributesService) ListCustomUserAttributes(user int, options ...OptionFunc) ([]*CustomAttribute, *Response, error) {
+	return s.listCustomAttributes("users", user, options...)
+}
+
+// ListCustomGroupAttributes lists the custom attributes of the specified group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#list-custom-attributes
+func (s *CustomAttributesService) ListCustomGroupAttributes(group int, options ...OptionFunc) ([]*CustomAttribute, *Response, error) {
+	return s.listCustomAttributes("groups", group, options...)
+}
+
+// ListCustomProjectAttributes lists the custom attributes of the specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#list-custom-attributes
+func (s *CustomAttributesService) ListCustomProjectAttributes(project int, options ...OptionFunc) ([]*CustomAttribute, *Response, error) {
+	return s.listCustomAttributes("projects", project, options...)
+}
+
+func (s *CustomAttributesService) listCustomAttributes(resource string, id int, options ...OptionFunc) ([]*CustomAttribute, *Response, error) {
+	u := fmt.Sprintf("%s/%d/custom_attributes", resource, id)
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var cas []*CustomAttribute
+	resp, err := s.client.Do(req, &cas)
+	if err != nil {
+		return nil, resp, err
+	}
+	return cas, resp, err
+}
+
+// GetCustomUserAttribute returns the user attribute with a speciifc key.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#single-custom-attribute
+func (s *CustomAttributesService) GetCustomUserAttribute(user int, key string, options ...OptionFunc) (*CustomAttribute, *Response, error) {
+	return s.getCustomAttribute("users", user, key, options...)
+}
+
+// GetCustomGroupAttribute returns the group attribute with a speciifc key.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#single-custom-attribute
+func (s *CustomAttributesService) GetCustomGroupAttribute(group int, key string, options ...OptionFunc) (*CustomAttribute, *Response, error) {
+	return s.getCustomAttribute("groups", group, key, options...)
+}
+
+// GetCustomProjectAttribute returns the project attribute with a speciifc key.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#single-custom-attribute
+func (s *CustomAttributesService) GetCustomProjectAttribute(project int, key string, options ...OptionFunc) (*CustomAttribute, *Response, error) {
+	return s.getCustomAttribute("projects", project, key, options...)
+}
+
+func (s *CustomAttributesService) getCustomAttribute(resource string, id int, key string, options ...OptionFunc) (*CustomAttribute, *Response, error) {
+	u := fmt.Sprintf("%s/%d/custom_attributes/%s", resource, id, key)
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ca *CustomAttribute
+	resp, err := s.client.Do(req, &ca)
+	if err != nil {
+		return nil, resp, err
+	}
+	return ca, resp, err
+}
+
+// SetCustomUserAttribute sets the custom attributes of the specified user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#set-custom-attribute
+func (s *CustomAttributesService) SetCustomUserAttribute(user int, c CustomAttribute, options ...OptionFunc) (*CustomAttribute, *Response, error) {
+	return s.setCustomAttribute("users", user, c, options...)
+}
+
+// SetCustomGroupAttribute sets the custom attributes of the specified group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#set-custom-attribute
+func (s *CustomAttributesService) SetCustomGroupAttribute(group int, c CustomAttribute, options ...OptionFunc) (*CustomAttribute, *Response, error) {
+	return s.setCustomAttribute("groups", group, c, options...)
+}
+
+// SetCustomProjectAttribute sets the custom attributes of the specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#set-custom-attribute
+func (s *CustomAttributesService) SetCustomProjectAttribute(project int, c CustomAttribute, options ...OptionFunc) (*CustomAttribute, *Response, error) {
+	return s.setCustomAttribute("projects", project, c, options...)
+}
+
+func (s *CustomAttributesService) setCustomAttribute(resource string, id int, c CustomAttribute, options ...OptionFunc) (*CustomAttribute, *Response, error) {
+	u := fmt.Sprintf("%s/%d/custom_attributes/%s", resource, id, c.Key)
+	req, err := s.client.NewRequest("PUT", u, c, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ca := new(CustomAttribute)
+	resp, err := s.client.Do(req, ca)
+	if err != nil {
+		return nil, resp, err
+	}
+	return ca, resp, err
+}
+
+// DeleteCustomUserAttribute removes the custom attribute of the specified user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#delete-custom-attribute
+func (s *CustomAttributesService) DeleteCustomUserAttribute(user int, key string, options ...OptionFunc) (*Response, error) {
+	return s.deleteCustomAttribute("users", user, key, options...)
+}
+
+// DeleteCustomGroupAttribute removes the custom attribute of the specified group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#delete-custom-attribute
+func (s *CustomAttributesService) DeleteCustomGroupAttribute(group int, key string, options ...OptionFunc) (*Response, error) {
+	return s.deleteCustomAttribute("groups", group, key, options...)
+}
+
+// DeleteCustomProjectAttribute removes the custom attribute of the specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/custom_attributes.html#delete-custom-attribute
+func (s *CustomAttributesService) DeleteCustomProjectAttribute(project int, key string, options ...OptionFunc) (*Response, error) {
+	return s.deleteCustomAttribute("projects", project, key, options...)
+}
+
+func (s *CustomAttributesService) deleteCustomAttribute(resource string, id int, key string, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("%s/%d/custom_attributes/%s", resource, id, key)
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/deploy_keys.go b/vendor/github.com/xanzy/go-gitlab/deploy_keys.go
new file mode 100644
index 000000000..8c184184a
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/deploy_keys.go
@@ -0,0 +1,200 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// DeployKeysService handles communication with the keys related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/deploy_keys.html
+type DeployKeysService struct {
+	client *Client
+}
+
+// DeployKey represents a GitLab deploy key.
+type DeployKey struct {
+	ID        int        `json:"id"`
+	Title     string     `json:"title"`
+	Key       string     `json:"key"`
+	CanPush   *bool      `json:"can_push"`
+	CreatedAt *time.Time `json:"created_at"`
+}
+
+func (k DeployKey) String() string {
+	return Stringify(k)
+}
+
+// ListAllDeployKeys gets a list of all deploy keys
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#list-all-deploy-keys
+func (s *DeployKeysService) ListAllDeployKeys(options ...OptionFunc) ([]*DeployKey, *Response, error) {
+	req, err := s.client.NewRequest("GET", "deploy_keys", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ks []*DeployKey
+	resp, err := s.client.Do(req, &ks)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ks, resp, err
+}
+
+// ListProjectDeployKeysOptions represents the available ListProjectDeployKeys()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#list-project-deploy-keys
+type ListProjectDeployKeysOptions ListOptions
+
+// ListProjectDeployKeys gets a list of a project's deploy keys
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#list-project-deploy-keys
+func (s *DeployKeysService) ListProjectDeployKeys(pid interface{}, opt *ListProjectDeployKeysOptions, options ...OptionFunc) ([]*DeployKey, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/deploy_keys", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ks []*DeployKey
+	resp, err := s.client.Do(req, &ks)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ks, resp, err
+}
+
+// GetDeployKey gets a single deploy key.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#single-deploy-key
+func (s *DeployKeysService) GetDeployKey(pid interface{}, deployKey int, options ...OptionFunc) (*DeployKey, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/deploy_keys/%d", pathEscape(project), deployKey)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	k := new(DeployKey)
+	resp, err := s.client.Do(req, k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, err
+}
+
+// AddDeployKeyOptions represents the available ADDDeployKey() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#add-deploy-key
+type AddDeployKeyOptions struct {
+	Title   *string `url:"title,omitempty" json:"title,omitempty"`
+	Key     *string `url:"key,omitempty" json:"key,omitempty"`
+	CanPush *bool   `url:"can_push,omitempty" json:"can_push,omitempty"`
+}
+
+// AddDeployKey creates a new deploy key for a project. If deploy key already
+// exists in another project - it will be joined to project but only if
+// original one was is accessible by same user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#add-deploy-key
+func (s *DeployKeysService) AddDeployKey(pid interface{}, opt *AddDeployKeyOptions, options ...OptionFunc) (*DeployKey, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/deploy_keys", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	k := new(DeployKey)
+	resp, err := s.client.Do(req, k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, err
+}
+
+// DeleteDeployKey deletes a deploy key from a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#delete-deploy-key
+func (s *DeployKeysService) DeleteDeployKey(pid interface{}, deployKey int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/deploy_keys/%d", pathEscape(project), deployKey)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// EnableDeployKey enables a deploy key.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deploy_keys.html#enable-deploy-key
+func (s *DeployKeysService) EnableDeployKey(pid interface{}, deployKey int, options ...OptionFunc) (*DeployKey, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/deploy_keys/%d/enable", pathEscape(project), deployKey)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	k := new(DeployKey)
+	resp, err := s.client.Do(req, k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/deployments.go b/vendor/github.com/xanzy/go-gitlab/deployments.go
new file mode 100644
index 000000000..8041ce921
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/deployments.go
@@ -0,0 +1,120 @@
+//
+// Copyright 2018, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// DeploymentsService handles communication with the deployment related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/deployments.html
+type DeploymentsService struct {
+	client *Client
+}
+
+// Deployment represents the Gitlab deployment
+type Deployment struct {
+	ID          int          `json:"id"`
+	IID         int          `json:"iid"`
+	Ref         string       `json:"ref"`
+	SHA         string       `json:"sha"`
+	CreatedAt   *time.Time   `json:"created_at"`
+	User        *ProjectUser `json:"user"`
+	Environment *Environment `json:"environment"`
+	Deployable  struct {
+		ID         int        `json:"id"`
+		Status     string     `json:"status"`
+		Stage      string     `json:"stage"`
+		Name       string     `json:"name"`
+		Ref        string     `json:"ref"`
+		Tag        bool       `json:"tag"`
+		Coverage   float64    `json:"coverage"`
+		CreatedAt  *time.Time `json:"created_at"`
+		StartedAt  *time.Time `json:"started_at"`
+		FinishedAt *time.Time `json:"finished_at"`
+		Duration   float64    `json:"duration"`
+		User       *User      `json:"user"`
+		Commit     *Commit    `json:"commit"`
+		Pipeline   struct {
+			ID     int    `json:"id"`
+			SHA    string `json:"sha"`
+			Ref    string `json:"ref"`
+			Status string `json:"status"`
+		} `json:"pipeline"`
+		Runner *Runner `json:"runner"`
+	} `json:"deployable"`
+}
+
+// ListProjectDeploymentsOptions represents the available ListProjectDeployments() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/deployments.html#list-project-deployments
+type ListProjectDeploymentsOptions struct {
+	ListOptions
+	OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort    *string `url:"sort,omitempty" json:"sort,omitempty"`
+}
+
+// ListProjectDeployments gets a list of deployments in a project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/deployments.html#list-project-deployments
+func (s *DeploymentsService) ListProjectDeployments(pid interface{}, opts *ListProjectDeploymentsOptions, options ...OptionFunc) ([]*Deployment, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/deployments", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opts, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ds []*Deployment
+	resp, err := s.client.Do(req, &ds)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ds, resp, err
+}
+
+// GetProjectDeployment get a deployment for a project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/deployments.html#get-a-specific-deployment
+func (s *DeploymentsService) GetProjectDeployment(pid interface{}, deployment int, options ...OptionFunc) (*Deployment, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/deployments/%d", pathEscape(project), deployment)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Deployment)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/discussions.go b/vendor/github.com/xanzy/go-gitlab/discussions.go
new file mode 100644
index 000000000..17ed1c7d5
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/discussions.go
@@ -0,0 +1,1112 @@
+//
+// Copyright 2018, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// DiscussionsService handles communication with the discussions related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/discussions.html
+type DiscussionsService struct {
+	client *Client
+}
+
+// Discussion represents a GitLab discussion.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/discussions.html
+type Discussion struct {
+	ID             string  `json:"id"`
+	IndividualNote bool    `json:"individual_note"`
+	Notes          []*Note `json:"notes"`
+}
+
+func (d Discussion) String() string {
+	return Stringify(d)
+}
+
+// ListIssueDiscussionsOptions represents the available ListIssueDiscussions()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#list-project-issue-discussion-items
+type ListIssueDiscussionsOptions ListOptions
+
+// ListIssueDiscussions gets a list of all discussions for a single
+// issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#list-project-issue-discussion-items
+func (s *DiscussionsService) ListIssueDiscussions(pid interface{}, issue int, opt *ListIssueDiscussionsOptions, options ...OptionFunc) ([]*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/discussions", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ds []*Discussion
+	resp, err := s.client.Do(req, &ds)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ds, resp, err
+}
+
+// GetIssueDiscussion returns a single discussion for a specific project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#get-single-issue-discussion-item
+func (s *DiscussionsService) GetIssueDiscussion(pid interface{}, issue int, discussion string, options ...OptionFunc) (*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/discussions/%s",
+		pathEscape(project),
+		issue,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// CreateIssueDiscussionOptions represents the available CreateIssueDiscussion()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#create-new-issue-thread
+type CreateIssueDiscussionOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// CreateIssueDiscussion creates a new discussion to a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#create-new-issue-thread
+func (s *DiscussionsService) CreateIssueDiscussion(pid interface{}, issue int, opt *CreateIssueDiscussionOptions, options ...OptionFunc) (*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/discussions", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// AddIssueDiscussionNoteOptions represents the available AddIssueDiscussionNote()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-issue-thread
+type AddIssueDiscussionNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// AddIssueDiscussionNote creates a new discussion to a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-issue-thread
+func (s *DiscussionsService) AddIssueDiscussionNote(pid interface{}, issue int, discussion string, opt *AddIssueDiscussionNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/discussions/%s/notes",
+		pathEscape(project),
+		issue,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// UpdateIssueDiscussionNoteOptions represents the available
+// UpdateIssueDiscussion() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#modify-existing-issue-thread-note
+type UpdateIssueDiscussionNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// UpdateIssueDiscussionNote modifies existing discussion of an issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#modify-existing-issue-thread-note
+func (s *DiscussionsService) UpdateIssueDiscussionNote(pid interface{}, issue int, discussion string, note int, opt *UpdateIssueDiscussionNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/discussions/%s/notes/%d",
+		pathEscape(project),
+		issue,
+		discussion,
+		note,
+	)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// DeleteIssueDiscussionNote deletes an existing discussion of an issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#delete-an-issue-thread-note
+func (s *DiscussionsService) DeleteIssueDiscussionNote(pid interface{}, issue int, discussion string, note int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/discussions/%s/notes/%d",
+		pathEscape(project),
+		issue,
+		discussion,
+		note,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ListSnippetDiscussionsOptions represents the available ListSnippetDiscussions()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#list-project-snippet-discussion-items
+type ListSnippetDiscussionsOptions ListOptions
+
+// ListSnippetDiscussions gets a list of all discussions for a single
+// snippet. Snippet discussions are comments users can post to a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#list-project-snippet-discussion-items
+func (s *DiscussionsService) ListSnippetDiscussions(pid interface{}, snippet int, opt *ListSnippetDiscussionsOptions, options ...OptionFunc) ([]*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/discussions", pathEscape(project), snippet)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ds []*Discussion
+	resp, err := s.client.Do(req, &ds)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ds, resp, err
+}
+
+// GetSnippetDiscussion returns a single discussion for a given snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#get-single-snippet-discussion-item
+func (s *DiscussionsService) GetSnippetDiscussion(pid interface{}, snippet int, discussion string, options ...OptionFunc) (*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/discussions/%s",
+		pathEscape(project),
+		snippet,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// CreateSnippetDiscussionOptions represents the available
+// CreateSnippetDiscussion() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#create-new-snippet-thread
+type CreateSnippetDiscussionOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// CreateSnippetDiscussion creates a new discussion for a single snippet.
+// Snippet discussions are comments users can post to a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#create-new-snippet-thread
+func (s *DiscussionsService) CreateSnippetDiscussion(pid interface{}, snippet int, opt *CreateSnippetDiscussionOptions, options ...OptionFunc) (*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/discussions", pathEscape(project), snippet)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// AddSnippetDiscussionNoteOptions represents the available
+// AddSnippetDiscussionNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-snippet-thread
+type AddSnippetDiscussionNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// AddSnippetDiscussionNote creates a new discussion to a single project
+// snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-snippet-thread
+func (s *DiscussionsService) AddSnippetDiscussionNote(pid interface{}, snippet int, discussion string, opt *AddSnippetDiscussionNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/discussions/%s/notes",
+		pathEscape(project),
+		snippet,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// UpdateSnippetDiscussionNoteOptions represents the available
+// UpdateSnippetDiscussion() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#modify-existing-snippet-thread-note
+type UpdateSnippetDiscussionNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// UpdateSnippetDiscussionNote modifies existing discussion of a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#modify-existing-snippet-thread-note
+func (s *DiscussionsService) UpdateSnippetDiscussionNote(pid interface{}, snippet int, discussion string, note int, opt *UpdateSnippetDiscussionNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/discussions/%s/notes/%d",
+		pathEscape(project),
+		snippet,
+		discussion,
+		note,
+	)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// DeleteSnippetDiscussionNote deletes an existing discussion of a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#delete-a-snippet-thread-note
+func (s *DiscussionsService) DeleteSnippetDiscussionNote(pid interface{}, snippet int, discussion string, note int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/discussions/%s/notes/%d",
+		pathEscape(project),
+		snippet,
+		discussion,
+		note,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ListGroupEpicDiscussionsOptions represents the available
+// ListEpicDiscussions() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#list-group-epic-discussion-items
+type ListGroupEpicDiscussionsOptions ListOptions
+
+// ListGroupEpicDiscussions gets a list of all discussions for a single
+// epic. Epic discussions are comments users can post to a epic.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#list-group-epic-discussion-items
+func (s *DiscussionsService) ListGroupEpicDiscussions(gid interface{}, epic int, opt *ListGroupEpicDiscussionsOptions, options ...OptionFunc) ([]*Discussion, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/discussions",
+		pathEscape(group),
+		epic,
+	)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ds []*Discussion
+	resp, err := s.client.Do(req, &ds)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ds, resp, err
+}
+
+// GetEpicDiscussion returns a single discussion for a given epic.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#get-single-epic-discussion-item
+func (s *DiscussionsService) GetEpicDiscussion(gid interface{}, epic int, discussion string, options ...OptionFunc) (*Discussion, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/discussions/%s",
+		pathEscape(group),
+		epic,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// CreateEpicDiscussionOptions represents the available CreateEpicDiscussion()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-epic-thread
+type CreateEpicDiscussionOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// CreateEpicDiscussion creates a new discussion for a single epic. Epic
+// discussions are comments users can post to a epic.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-epic-thread
+func (s *DiscussionsService) CreateEpicDiscussion(gid interface{}, epic int, opt *CreateEpicDiscussionOptions, options ...OptionFunc) (*Discussion, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/discussions",
+		pathEscape(group),
+		epic,
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// AddEpicDiscussionNoteOptions represents the available
+// AddEpicDiscussionNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-epic-thread
+type AddEpicDiscussionNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// AddEpicDiscussionNote creates a new discussion to a single project epic.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-epic-thread
+func (s *DiscussionsService) AddEpicDiscussionNote(gid interface{}, epic int, discussion string, opt *AddEpicDiscussionNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/discussions/%s/notes",
+		pathEscape(group),
+		epic,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// UpdateEpicDiscussionNoteOptions represents the available UpdateEpicDiscussion()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#modify-existing-epic-thread-note
+type UpdateEpicDiscussionNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// UpdateEpicDiscussionNote modifies existing discussion of a epic.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#modify-existing-epic-thread-note
+func (s *DiscussionsService) UpdateEpicDiscussionNote(gid interface{}, epic int, discussion string, note int, opt *UpdateEpicDiscussionNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/discussions/%s/notes/%d",
+		pathEscape(group),
+		epic,
+		discussion,
+		note,
+	)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// DeleteEpicDiscussionNote deletes an existing discussion of a epic.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#delete-an-epic-thread-note
+func (s *DiscussionsService) DeleteEpicDiscussionNote(gid interface{}, epic int, discussion string, note int, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/discussions/%s/notes/%d",
+		pathEscape(group),
+		epic,
+		discussion,
+		note,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ListMergeRequestDiscussionsOptions represents the available
+// ListMergeRequestDiscussions() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#list-project-merge-request-discussion-items
+type ListMergeRequestDiscussionsOptions ListOptions
+
+// ListMergeRequestDiscussions gets a list of all discussions for a single
+// merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#list-project-merge-request-discussion-items
+func (s *DiscussionsService) ListMergeRequestDiscussions(pid interface{}, mergeRequest int, opt *ListMergeRequestDiscussionsOptions, options ...OptionFunc) ([]*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/discussions",
+		pathEscape(project),
+		mergeRequest,
+	)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ds []*Discussion
+	resp, err := s.client.Do(req, &ds)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ds, resp, err
+}
+
+// GetMergeRequestDiscussion returns a single discussion for a given merge
+// request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#get-single-merge-request-discussion-item
+func (s *DiscussionsService) GetMergeRequestDiscussion(pid interface{}, mergeRequest int, discussion string, options ...OptionFunc) (*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/discussions/%s",
+		pathEscape(project),
+		mergeRequest,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// CreateMergeRequestDiscussionOptions represents the available
+// CreateMergeRequestDiscussion() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#create-new-merge-request-thread
+type CreateMergeRequestDiscussionOptions struct {
+	Body      *string       `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time    `url:"created_at,omitempty" json:"created_at,omitempty"`
+	Position  *NotePosition `url:"position,omitempty" json:"position,omitempty"`
+}
+
+// CreateMergeRequestDiscussion creates a new discussion for a single merge
+// request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#create-new-merge-request-thread
+func (s *DiscussionsService) CreateMergeRequestDiscussion(pid interface{}, mergeRequest int, opt *CreateMergeRequestDiscussionOptions, options ...OptionFunc) (*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/discussions",
+		pathEscape(project),
+		mergeRequest,
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// ResolveMergeRequestDiscussionOptions represents the available
+// ResolveMergeRequestDiscussion() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#resolve-a-merge-request-thread
+type ResolveMergeRequestDiscussionOptions struct {
+	Resolved *bool `url:"resolved,omitempty" json:"resolved,omitempty"`
+}
+
+// ResolveMergeRequestDiscussion resolves/unresolves whole discussion of a merge
+// request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#resolve-a-merge-request-thread
+func (s *DiscussionsService) ResolveMergeRequestDiscussion(pid interface{}, mergeRequest int, discussion string, opt *ResolveMergeRequestDiscussionOptions, options ...OptionFunc) (*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/discussions/%s",
+		pathEscape(project),
+		mergeRequest,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// AddMergeRequestDiscussionNoteOptions represents the available
+// AddMergeRequestDiscussionNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-merge-request-discussion
+type AddMergeRequestDiscussionNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// AddMergeRequestDiscussionNote creates a new discussion to a single project
+// merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-merge-request-discussion
+func (s *DiscussionsService) AddMergeRequestDiscussionNote(pid interface{}, mergeRequest int, discussion string, opt *AddMergeRequestDiscussionNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/discussions/%s/notes",
+		pathEscape(project),
+		mergeRequest,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// UpdateMergeRequestDiscussionNoteOptions represents the available
+// UpdateMergeRequestDiscussion() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#modify-existing-merge-request-discussion-note
+type UpdateMergeRequestDiscussionNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+	Resolved  *bool      `url:"resolved,omitempty" json:"resolved,omitempty"`
+}
+
+// UpdateMergeRequestDiscussionNote modifies existing discussion of a merge
+// request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#modify-existing-merge-request-discussion-note
+func (s *DiscussionsService) UpdateMergeRequestDiscussionNote(pid interface{}, mergeRequest int, discussion string, note int, opt *UpdateMergeRequestDiscussionNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/discussions/%s/notes/%d",
+		pathEscape(project),
+		mergeRequest,
+		discussion,
+		note,
+	)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// DeleteMergeRequestDiscussionNote deletes an existing discussion of a merge
+// request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#delete-a-merge-request-discussion-note
+func (s *DiscussionsService) DeleteMergeRequestDiscussionNote(pid interface{}, mergeRequest int, discussion string, note int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/discussions/%s/notes/%d",
+		pathEscape(project),
+		mergeRequest,
+		discussion,
+		note,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ListCommitDiscussionsOptions represents the available
+// ListCommitDiscussions() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#list-project-commit-discussion-items
+type ListCommitDiscussionsOptions ListOptions
+
+// ListCommitDiscussions gets a list of all discussions for a single
+// commit.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#list-project-commit-discussion-items
+func (s *DiscussionsService) ListCommitDiscussions(pid interface{}, commit string, opt *ListCommitDiscussionsOptions, options ...OptionFunc) ([]*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/discussions",
+		pathEscape(project),
+		commit,
+	)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ds []*Discussion
+	resp, err := s.client.Do(req, &ds)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ds, resp, err
+}
+
+// GetCommitDiscussion returns a single discussion for a specific project
+// commit.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#get-single-commit-discussion-item
+func (s *DiscussionsService) GetCommitDiscussion(pid interface{}, commit string, discussion string, options ...OptionFunc) (*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/discussions/%s",
+		pathEscape(project),
+		commit,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// CreateCommitDiscussionOptions represents the available
+// CreateCommitDiscussion() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#create-new-commit-thread
+type CreateCommitDiscussionOptions struct {
+	Body      *string       `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time    `url:"created_at,omitempty" json:"created_at,omitempty"`
+	Position  *NotePosition `url:"position,omitempty" json:"position,omitempty"`
+}
+
+// CreateCommitDiscussion creates a new discussion to a single project commit.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#create-new-commit-thread
+func (s *DiscussionsService) CreateCommitDiscussion(pid interface{}, commit string, opt *CreateCommitDiscussionOptions, options ...OptionFunc) (*Discussion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/discussions",
+		pathEscape(project),
+		commit,
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	d := new(Discussion)
+	resp, err := s.client.Do(req, d)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return d, resp, err
+}
+
+// AddCommitDiscussionNoteOptions represents the available
+// AddCommitDiscussionNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-commit-thread
+type AddCommitDiscussionNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// AddCommitDiscussionNote creates a new discussion to a single project commit.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#add-note-to-existing-commit-thread
+func (s *DiscussionsService) AddCommitDiscussionNote(pid interface{}, commit string, discussion string, opt *AddCommitDiscussionNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/discussions/%s/notes",
+		pathEscape(project),
+		commit,
+		discussion,
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// UpdateCommitDiscussionNoteOptions represents the available
+// UpdateCommitDiscussion() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#modify-an-existing-commit-thread-note
+type UpdateCommitDiscussionNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// UpdateCommitDiscussionNote modifies existing discussion of an commit.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#modify-an-existing-commit-thread-note
+func (s *DiscussionsService) UpdateCommitDiscussionNote(pid interface{}, commit string, discussion string, note int, opt *UpdateCommitDiscussionNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/discussions/%s/notes/%d",
+		pathEscape(project),
+		commit,
+		discussion,
+		note,
+	)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// DeleteCommitDiscussionNote deletes an existing discussion of an commit.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/discussions.html#delete-a-commit-thread-note
+func (s *DiscussionsService) DeleteCommitDiscussionNote(pid interface{}, commit string, discussion string, note int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/commits/%s/discussions/%s/notes/%d",
+		pathEscape(project),
+		commit,
+		discussion,
+		note,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/environments.go b/vendor/github.com/xanzy/go-gitlab/environments.go
new file mode 100644
index 000000000..397c2d280
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/environments.go
@@ -0,0 +1,212 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+)
+
+// EnvironmentsService handles communication with the environment related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/environments.html
+type EnvironmentsService struct {
+	client *Client
+}
+
+// Environment represents a GitLab environment.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/environments.html
+type Environment struct {
+	ID             int         `json:"id"`
+	Name           string      `json:"name"`
+	Slug           string      `json:"slug"`
+	State          string      `json:"state"`
+	ExternalURL    string      `json:"external_url"`
+	Project        *Project    `json:"project"`
+	LastDeployment *Deployment `json:"last_deployment"`
+}
+
+func (env Environment) String() string {
+	return Stringify(env)
+}
+
+// ListEnvironmentsOptions represents the available ListEnvironments() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#list-environments
+type ListEnvironmentsOptions ListOptions
+
+// ListEnvironments gets a list of environments from a project, sorted by name
+// alphabetically.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#list-environments
+func (s *EnvironmentsService) ListEnvironments(pid interface{}, opts *ListEnvironmentsOptions, options ...OptionFunc) ([]*Environment, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/environments", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opts, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var envs []*Environment
+	resp, err := s.client.Do(req, &envs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return envs, resp, err
+}
+
+// GetEnvironment gets a specific environment from a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#get-a-specific-environment
+func (s *EnvironmentsService) GetEnvironment(pid interface{}, environment int, options ...OptionFunc) (*Environment, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/environments/%d", pathEscape(project), environment)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	env := new(Environment)
+	resp, err := s.client.Do(req, env)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return env, resp, err
+}
+
+// CreateEnvironmentOptions represents the available CreateEnvironment() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#create-a-new-environment
+type CreateEnvironmentOptions struct {
+	Name        *string `url:"name,omitempty" json:"name,omitempty"`
+	ExternalURL *string `url:"external_url,omitempty" json:"external_url,omitempty"`
+}
+
+// CreateEnvironment adds an environment to a project. This is an idempotent
+// method and can be called multiple times with the same parameters. Createing
+// an environment that is already a environment does not affect the
+// existing environmentship.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#create-a-new-environment
+func (s *EnvironmentsService) CreateEnvironment(pid interface{}, opt *CreateEnvironmentOptions, options ...OptionFunc) (*Environment, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/environments", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	env := new(Environment)
+	resp, err := s.client.Do(req, env)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return env, resp, err
+}
+
+// EditEnvironmentOptions represents the available EditEnvironment() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#edit-an-existing-environment
+type EditEnvironmentOptions struct {
+	Name        *string `url:"name,omitempty" json:"name,omitempty"`
+	ExternalURL *string `url:"external_url,omitempty" json:"external_url,omitempty"`
+}
+
+// EditEnvironment updates a project team environment to a specified access level..
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/environments.html#edit-an-existing-environment
+func (s *EnvironmentsService) EditEnvironment(pid interface{}, environment int, opt *EditEnvironmentOptions, options ...OptionFunc) (*Environment, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/environments/%d", pathEscape(project), environment)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	env := new(Environment)
+	resp, err := s.client.Do(req, env)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return env, resp, err
+}
+
+// DeleteEnvironment removes an environment from a project team.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/environments.html#remove-a-environment-from-a-group-or-project
+func (s *EnvironmentsService) DeleteEnvironment(pid interface{}, environment int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/environments/%d", pathEscape(project), environment)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// StopEnvironment stop an environment from a project team.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/environments.html#stop-an-environment
+func (s *EnvironmentsService) StopEnvironment(pid interface{}, environmentID int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/environments/%d/stop", pathEscape(project), environmentID)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/epics.go b/vendor/github.com/xanzy/go-gitlab/epics.go
new file mode 100644
index 000000000..1e93c8ec1
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/epics.go
@@ -0,0 +1,211 @@
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// EpicsService handles communication with the epic related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html
+type EpicsService struct {
+	client *Client
+}
+
+// EpicAuthor represents a author of the epic.
+type EpicAuthor struct {
+	ID        int    `json:"id"`
+	State     string `json:"state"`
+	WebURL    string `json:"web_url"`
+	Name      string `json:"name"`
+	AvatarURL string `json:"avatar_url"`
+	Username  string `json:"username"`
+}
+
+// Epic represents a GitLab epic.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html
+type Epic struct {
+	ID                      int         `json:"id"`
+	IID                     int         `json:"iid"`
+	GroupID                 int         `json:"group_id"`
+	Author                  *EpicAuthor `json:"author"`
+	Description             string      `json:"description"`
+	State                   string      `json:"state"`
+	Upvotes                 int         `json:"upvotes"`
+	Downvotes               int         `json:"downvotes"`
+	Labels                  []string    `json:"labels"`
+	Title                   string      `json:"title"`
+	UpdatedAt               *time.Time  `json:"updated_at"`
+	CreatedAt               *time.Time  `json:"created_at"`
+	UserNotesCount          int         `json:"user_notes_count"`
+	StartDate               *ISOTime    `json:"start_date"`
+	StartDateIsFixed        bool        `json:"start_date_is_fixed"`
+	StartDateFixed          *ISOTime    `json:"start_date_fixed"`
+	StartDateFromMilestones *ISOTime    `json:"start_date_from_milestones"`
+	DueDate                 *ISOTime    `json:"due_date"`
+	DueDateIsFixed          bool        `json:"due_date_is_fixed"`
+	DueDateFixed            *ISOTime    `json:"due_date_fixed"`
+	DueDateFromMilestones   *ISOTime    `json:"due_date_from_milestones"`
+}
+
+func (e Epic) String() string {
+	return Stringify(e)
+}
+
+// ListGroupEpicsOptions represents the available ListGroupEpics() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#list-epics-for-a-group
+type ListGroupEpicsOptions struct {
+	ListOptions
+	State    *string `url:"state,omitempty" json:"state,omitempty"`
+	Labels   Labels  `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	AuthorID *int    `url:"author_id,omitempty" json:"author_id,omitempty"`
+	OrderBy  *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort     *string `url:"sort,omitempty" json:"sort,omitempty"`
+	Search   *string `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListGroupEpics gets a list of group epics. This function accepts pagination
+// parameters page and per_page to return the list of group epics.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#list-epics-for-a-group
+func (s *EpicsService) ListGroupEpics(gid interface{}, opt *ListGroupEpicsOptions, options ...OptionFunc) ([]*Epic, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var es []*Epic
+	resp, err := s.client.Do(req, &es)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return es, resp, err
+}
+
+// GetEpic gets a single group epic.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#single-epic
+func (s *EpicsService) GetEpic(gid interface{}, epic int, options ...OptionFunc) (*Epic, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d", pathEscape(group), epic)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	e := new(Epic)
+	resp, err := s.client.Do(req, e)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return e, resp, err
+}
+
+// CreateEpicOptions represents the available CreateEpic() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#new-epic
+type CreateEpicOptions struct {
+	Title            *string  `url:"title,omitempty" json:"title,omitempty"`
+	Description      *string  `url:"description,omitempty" json:"description,omitempty"`
+	Labels           Labels   `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	StartDateIsFixed *bool    `url:"start_date_is_fixed,omitempty" json:"start_date_is_fixed,omitempty"`
+	StartDateFixed   *ISOTime `url:"start_date_fixed,omitempty" json:"start_date_fixed,omitempty"`
+	DueDateIsFixed   *bool    `url:"due_date_is_fixed,omitempty" json:"due_date_is_fixed,omitempty"`
+	DueDateFixed     *ISOTime `url:"due_date_fixed,omitempty" json:"due_date_fixed,omitempty"`
+}
+
+// CreateEpic creates a new group epic.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#new-epic
+func (s *EpicsService) CreateEpic(gid interface{}, opt *CreateEpicOptions, options ...OptionFunc) (*Epic, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics", pathEscape(group))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	e := new(Epic)
+	resp, err := s.client.Do(req, e)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return e, resp, err
+}
+
+// UpdateEpicOptions represents the available UpdateEpic() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#update-epic
+type UpdateEpicOptions struct {
+	Title            *string  `url:"title,omitempty" json:"title,omitempty"`
+	Description      *string  `url:"description,omitempty" json:"description,omitempty"`
+	Labels           Labels   `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	StartDateIsFixed *bool    `url:"start_date_is_fixed,omitempty" json:"start_date_is_fixed,omitempty"`
+	StartDateFixed   *ISOTime `url:"start_date_fixed,omitempty" json:"start_date_fixed,omitempty"`
+	DueDateIsFixed   *bool    `url:"due_date_is_fixed,omitempty" json:"due_date_is_fixed,omitempty"`
+	DueDateFixed     *ISOTime `url:"due_date_fixed,omitempty" json:"due_date_fixed,omitempty"`
+	StateEvent       *string  `url:"state_event,omitempty" json:"state_event,omitempty"`
+}
+
+// UpdateEpic updates an existing group epic. This function is also used
+// to mark an epic as closed.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#update-epic
+func (s *EpicsService) UpdateEpic(gid interface{}, epic int, opt *UpdateEpicOptions, options ...OptionFunc) (*Epic, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d", pathEscape(group), epic)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	e := new(Epic)
+	resp, err := s.client.Do(req, e)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return e, resp, err
+}
+
+// DeleteEpic deletes a single group epic.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/epics.html#delete-epic
+func (s *EpicsService) DeleteEpic(gid interface{}, epic int, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d", pathEscape(group), epic)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/event_parsing.go b/vendor/github.com/xanzy/go-gitlab/event_parsing.go
new file mode 100644
index 000000000..7ac042ae8
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/event_parsing.go
@@ -0,0 +1,117 @@
+package gitlab
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+)
+
+// EventType represents a Gitlab event type.
+type EventType string
+
+// List of available event types.
+const (
+	EventTypeBuild        EventType = "Build Hook"
+	EventTypeIssue        EventType = "Issue Hook"
+	EventTypeJob          EventType = "Job Hook"
+	EventTypeMergeRequest EventType = "Merge Request Hook"
+	EventTypeNote         EventType = "Note Hook"
+	EventTypePipeline     EventType = "Pipeline Hook"
+	EventTypePush         EventType = "Push Hook"
+	EventTypeTagPush      EventType = "Tag Push Hook"
+	EventTypeWikiPage     EventType = "Wiki Page Hook"
+)
+
+const (
+	noteableTypeCommit       = "Commit"
+	noteableTypeMergeRequest = "MergeRequest"
+	noteableTypeIssue        = "Issue"
+	noteableTypeSnippet      = "Snippet"
+)
+
+type noteEvent struct {
+	ObjectKind       string `json:"object_kind"`
+	ObjectAttributes struct {
+		NoteableType string `json:"noteable_type"`
+	} `json:"object_attributes"`
+}
+
+const eventTypeHeader = "X-Gitlab-Event"
+
+// WebhookEventType returns the event type for the given request.
+func WebhookEventType(r *http.Request) EventType {
+	return EventType(r.Header.Get(eventTypeHeader))
+}
+
+// ParseWebhook parses the event payload. For recognized event types, a
+// value of the corresponding struct type will be returned. An error will
+// be returned for unrecognized event types.
+//
+// Example usage:
+//
+// func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+//     payload, err := ioutil.ReadAll(r.Body)
+//     if err != nil { ... }
+//     event, err := gitlab.ParseWebhook(gitlab.WebhookEventType(r), payload)
+//     if err != nil { ... }
+//     switch event := event.(type) {
+//     case *gitlab.PushEvent:
+//         processPushEvent(event)
+//     case *gitlab.MergeEvent:
+//         processMergeEvent(event)
+//     ...
+//     }
+// }
+//
+func ParseWebhook(eventType EventType, payload []byte) (event interface{}, err error) {
+	switch eventType {
+	case EventTypeBuild:
+		event = &BuildEvent{}
+	case EventTypeIssue:
+		event = &IssueEvent{}
+	case EventTypeJob:
+		event = &JobEvent{}
+	case EventTypeMergeRequest:
+		event = &MergeEvent{}
+	case EventTypePipeline:
+		event = &PipelineEvent{}
+	case EventTypePush:
+		event = &PushEvent{}
+	case EventTypeTagPush:
+		event = &TagEvent{}
+	case EventTypeWikiPage:
+		event = &WikiPageEvent{}
+	case EventTypeNote:
+		note := &noteEvent{}
+		err := json.Unmarshal(payload, note)
+		if err != nil {
+			return nil, err
+		}
+
+		if note.ObjectKind != "note" {
+			return nil, fmt.Errorf("unexpected object kind %s", note.ObjectKind)
+		}
+
+		switch note.ObjectAttributes.NoteableType {
+		case noteableTypeCommit:
+			event = &CommitCommentEvent{}
+		case noteableTypeMergeRequest:
+			event = &MergeCommentEvent{}
+		case noteableTypeIssue:
+			event = &IssueCommentEvent{}
+		case noteableTypeSnippet:
+			event = &SnippetCommentEvent{}
+		default:
+			return nil, fmt.Errorf("unexpected noteable type %s", note.ObjectAttributes.NoteableType)
+		}
+
+	default:
+		return nil, fmt.Errorf("unexpected event type: %s", eventType)
+	}
+
+	if err := json.Unmarshal(payload, event); err != nil {
+		return nil, err
+	}
+
+	return event, nil
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/event_types.go b/vendor/github.com/xanzy/go-gitlab/event_types.go
new file mode 100644
index 000000000..ae04a049d
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/event_types.go
@@ -0,0 +1,815 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+	"time"
+)
+
+// PushEvent represents a push event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#push-events
+type PushEvent struct {
+	ObjectKind   string `json:"object_kind"`
+	Before       string `json:"before"`
+	After        string `json:"after"`
+	Ref          string `json:"ref"`
+	CheckoutSHA  string `json:"checkout_sha"`
+	UserID       int    `json:"user_id"`
+	UserName     string `json:"user_name"`
+	UserUsername string `json:"user_username"`
+	UserEmail    string `json:"user_email"`
+	UserAvatar   string `json:"user_avatar"`
+	ProjectID    int    `json:"project_id"`
+	Project      struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Repository *Repository `json:"repository"`
+	Commits    []*struct {
+		ID        string     `json:"id"`
+		Message   string     `json:"message"`
+		Timestamp *time.Time `json:"timestamp"`
+		URL       string     `json:"url"`
+		Author    struct {
+			Name  string `json:"name"`
+			Email string `json:"email"`
+		} `json:"author"`
+		Added    []string `json:"added"`
+		Modified []string `json:"modified"`
+		Removed  []string `json:"removed"`
+	} `json:"commits"`
+	TotalCommitsCount int `json:"total_commits_count"`
+}
+
+// TagEvent represents a tag event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#tag-events
+type TagEvent struct {
+	ObjectKind  string `json:"object_kind"`
+	Before      string `json:"before"`
+	After       string `json:"after"`
+	Ref         string `json:"ref"`
+	CheckoutSHA string `json:"checkout_sha"`
+	UserID      int    `json:"user_id"`
+	UserName    string `json:"user_name"`
+	UserAvatar  string `json:"user_avatar"`
+	ProjectID   int    `json:"project_id"`
+	Message     string `json:"message"`
+	Project     struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Repository *Repository `json:"repository"`
+	Commits    []*struct {
+		ID        string     `json:"id"`
+		Message   string     `json:"message"`
+		Timestamp *time.Time `json:"timestamp"`
+		URL       string     `json:"url"`
+		Author    struct {
+			Name  string `json:"name"`
+			Email string `json:"email"`
+		} `json:"author"`
+		Added    []string `json:"added"`
+		Modified []string `json:"modified"`
+		Removed  []string `json:"removed"`
+	} `json:"commits"`
+	TotalCommitsCount int `json:"total_commits_count"`
+}
+
+// IssueEvent represents a issue event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#issues-events
+type IssueEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	Project    struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Repository       *Repository `json:"repository"`
+	ObjectAttributes struct {
+		ID          int    `json:"id"`
+		Title       string `json:"title"`
+		AssigneeID  int    `json:"assignee_id"`
+		AuthorID    int    `json:"author_id"`
+		ProjectID   int    `json:"project_id"`
+		CreatedAt   string `json:"created_at"` // Should be *time.Time (see Gitlab issue #21468)
+		UpdatedAt   string `json:"updated_at"` // Should be *time.Time (see Gitlab issue #21468)
+		Position    int    `json:"position"`
+		BranchName  string `json:"branch_name"`
+		Description string `json:"description"`
+		MilestoneID int    `json:"milestone_id"`
+		State       string `json:"state"`
+		IID         int    `json:"iid"`
+		URL         string `json:"url"`
+		Action      string `json:"action"`
+	} `json:"object_attributes"`
+	Assignee struct {
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		AvatarURL string `json:"avatar_url"`
+	} `json:"assignee"`
+	Assignees []struct {
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		AvatarURL string `json:"avatar_url"`
+	} `json:"assignees"`
+	Labels  []Label `json:"labels"`
+	Changes struct {
+		Labels struct {
+			Previous []Label `json:"previous"`
+			Current  []Label `json:"current"`
+		} `json:"labels"`
+		UpdatedByID struct {
+			Previous int `json:"previous"`
+			Current  int `json:"current"`
+		} `json:"updated_by_id"`
+	} `json:"changes"`
+}
+
+// JobEvent represents a job event.
+//
+// GitLab API docs:
+// TODO: link to docs instead of src once they are published.
+// https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/data_builder/build.rb
+type JobEvent struct {
+	ObjectKind        string  `json:"object_kind"`
+	Ref               string  `json:"ref"`
+	Tag               bool    `json:"tag"`
+	BeforeSHA         string  `json:"before_sha"`
+	SHA               string  `json:"sha"`
+	BuildID           int     `json:"build_id"`
+	BuildName         string  `json:"build_name"`
+	BuildStage        string  `json:"build_stage"`
+	BuildStatus       string  `json:"build_status"`
+	BuildStartedAt    string  `json:"build_started_at"`
+	BuildFinishedAt   string  `json:"build_finished_at"`
+	BuildDuration     float64 `json:"build_duration"`
+	BuildAllowFailure bool    `json:"build_allow_failure"`
+	ProjectID         int     `json:"project_id"`
+	ProjectName       string  `json:"project_name"`
+	User              struct {
+		ID    int    `json:"id"`
+		Name  string `json:"name"`
+		Email string `json:"email"`
+	} `json:"user"`
+	Commit struct {
+		ID          int    `json:"id"`
+		SHA         string `json:"sha"`
+		Message     string `json:"message"`
+		AuthorName  string `json:"author_name"`
+		AuthorEmail string `json:"author_email"`
+		AuthorURL   string `json:"author_url"`
+		Status      string `json:"status"`
+		Duration    int    `json:"duration"`
+		StartedAt   string `json:"started_at"`
+		FinishedAt  string `json:"finished_at"`
+	} `json:"commit"`
+	Repository *Repository `json:"repository"`
+}
+
+// CommitCommentEvent represents a comment on a commit event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-commit
+type CommitCommentEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	ProjectID  int    `json:"project_id"`
+	Project    struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Repository       *Repository `json:"repository"`
+	ObjectAttributes struct {
+		ID           int    `json:"id"`
+		Note         string `json:"note"`
+		NoteableType string `json:"noteable_type"`
+		AuthorID     int    `json:"author_id"`
+		CreatedAt    string `json:"created_at"`
+		UpdatedAt    string `json:"updated_at"`
+		ProjectID    int    `json:"project_id"`
+		Attachment   string `json:"attachment"`
+		LineCode     string `json:"line_code"`
+		CommitID     string `json:"commit_id"`
+		NoteableID   int    `json:"noteable_id"`
+		System       bool   `json:"system"`
+		StDiff       struct {
+			Diff        string `json:"diff"`
+			NewPath     string `json:"new_path"`
+			OldPath     string `json:"old_path"`
+			AMode       string `json:"a_mode"`
+			BMode       string `json:"b_mode"`
+			NewFile     bool   `json:"new_file"`
+			RenamedFile bool   `json:"renamed_file"`
+			DeletedFile bool   `json:"deleted_file"`
+		} `json:"st_diff"`
+	} `json:"object_attributes"`
+	Commit *struct {
+		ID        string     `json:"id"`
+		Message   string     `json:"message"`
+		Timestamp *time.Time `json:"timestamp"`
+		URL       string     `json:"url"`
+		Author    struct {
+			Name  string `json:"name"`
+			Email string `json:"email"`
+		} `json:"author"`
+	} `json:"commit"`
+}
+
+// MergeCommentEvent represents a comment on a merge event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-merge-request
+type MergeCommentEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	ProjectID  int    `json:"project_id"`
+	Project    struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	ObjectAttributes struct {
+		ID           int    `json:"id"`
+		DiscussionID string `json:"discussion_id"`
+		Note         string `json:"note"`
+		NoteableType string `json:"noteable_type"`
+		AuthorID     int    `json:"author_id"`
+		CreatedAt    string `json:"created_at"`
+		UpdatedAt    string `json:"updated_at"`
+		ProjectID    int    `json:"project_id"`
+		Attachment   string `json:"attachment"`
+		LineCode     string `json:"line_code"`
+		CommitID     string `json:"commit_id"`
+		NoteableID   int    `json:"noteable_id"`
+		System       bool   `json:"system"`
+		StDiff       *Diff  `json:"st_diff"`
+		URL          string `json:"url"`
+	} `json:"object_attributes"`
+	Repository   *Repository `json:"repository"`
+	MergeRequest struct {
+		ID              int    `json:"id"`
+		TargetBranch    string `json:"target_branch"`
+		SourceBranch    string `json:"source_branch"`
+		SourceProjectID int    `json:"source_project_id"`
+		AuthorID        int    `json:"author_id"`
+		AssigneeID      int    `json:"assignee_id"`
+		Title           string `json:"title"`
+		CreatedAt       string `json:"created_at"`
+		UpdatedAt       string `json:"updated_at"`
+		MilestoneID     int    `json:"milestone_id"`
+		State           string `json:"state"`
+		MergeStatus     string `json:"merge_status"`
+		TargetProjectID int    `json:"target_project_id"`
+		IID             int    `json:"iid"`
+		Description     string `json:"description"`
+		Position        int    `json:"position"`
+		LockedAt        string `json:"locked_at"`
+		UpdatedByID     int    `json:"updated_by_id"`
+		MergeError      string `json:"merge_error"`
+		MergeParams     struct {
+			ForceRemoveSourceBranch string `json:"force_remove_source_branch"`
+		} `json:"merge_params"`
+		MergeWhenPipelineSucceeds bool        `json:"merge_when_pipeline_succeeds"`
+		MergeUserID               int         `json:"merge_user_id"`
+		MergeCommitSHA            string      `json:"merge_commit_sha"`
+		DeletedAt                 string      `json:"deleted_at"`
+		InProgressMergeCommitSHA  string      `json:"in_progress_merge_commit_sha"`
+		LockVersion               int         `json:"lock_version"`
+		ApprovalsBeforeMerge      string      `json:"approvals_before_merge"`
+		RebaseCommitSHA           string      `json:"rebase_commit_sha"`
+		TimeEstimate              int         `json:"time_estimate"`
+		Squash                    bool        `json:"squash"`
+		LastEditedAt              string      `json:"last_edited_at"`
+		LastEditedByID            int         `json:"last_edited_by_id"`
+		Source                    *Repository `json:"source"`
+		Target                    *Repository `json:"target"`
+		LastCommit                struct {
+			ID        string     `json:"id"`
+			Message   string     `json:"message"`
+			Timestamp *time.Time `json:"timestamp"`
+			URL       string     `json:"url"`
+			Author    struct {
+				Name  string `json:"name"`
+				Email string `json:"email"`
+			} `json:"author"`
+		} `json:"last_commit"`
+		WorkInProgress bool `json:"work_in_progress"`
+		TotalTimeSpent int  `json:"total_time_spent"`
+		HeadPipelineID int  `json:"head_pipeline_id"`
+	} `json:"merge_request"`
+}
+
+// IssueCommentEvent represents a comment on an issue event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-issue
+type IssueCommentEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	ProjectID  int    `json:"project_id"`
+	Project    struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Repository       *Repository `json:"repository"`
+	ObjectAttributes struct {
+		ID           int     `json:"id"`
+		Note         string  `json:"note"`
+		NoteableType string  `json:"noteable_type"`
+		AuthorID     int     `json:"author_id"`
+		CreatedAt    string  `json:"created_at"`
+		UpdatedAt    string  `json:"updated_at"`
+		ProjectID    int     `json:"project_id"`
+		Attachment   string  `json:"attachment"`
+		LineCode     string  `json:"line_code"`
+		CommitID     string  `json:"commit_id"`
+		NoteableID   int     `json:"noteable_id"`
+		System       bool    `json:"system"`
+		StDiff       []*Diff `json:"st_diff"`
+		URL          string  `json:"url"`
+	} `json:"object_attributes"`
+	Issue struct {
+		ID                  int      `json:"id"`
+		IID                 int      `json:"iid"`
+		ProjectID           int      `json:"project_id"`
+		MilestoneID         int      `json:"milestone_id"`
+		AuthorID            int      `json:"author_id"`
+		Description         string   `json:"description"`
+		State               string   `json:"state"`
+		Title               string   `json:"title"`
+		LastEditedAt        string   `json:"last_edit_at"`
+		LastEditedByID      int      `json:"last_edited_by_id"`
+		UpdatedAt           string   `json:"updated_at"`
+		UpdatedByID         int      `json:"updated_by_id"`
+		CreatedAt           string   `json:"created_at"`
+		ClosedAt            string   `json:"closed_at"`
+		DueDate             *ISOTime `json:"due_date"`
+		URL                 string   `json:"url"`
+		TimeEstimate        int      `json:"time_estimate"`
+		Confidential        bool     `json:"confidential"`
+		TotalTimeSpent      int      `json:"total_time_spent"`
+		HumanTotalTimeSpent int      `json:"human_total_time_spent"`
+		HumanTimeEstimate   int      `json:"human_time_estimate"`
+		AssigneeIDs         []int    `json:"assignee_ids"`
+		AssigneeID          int      `json:"assignee_id"`
+	} `json:"issue"`
+}
+
+// SnippetCommentEvent represents a comment on a snippet event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-code-snippet
+type SnippetCommentEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	ProjectID  int    `json:"project_id"`
+	Project    struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Repository       *Repository `json:"repository"`
+	ObjectAttributes struct {
+		ID           int    `json:"id"`
+		Note         string `json:"note"`
+		NoteableType string `json:"noteable_type"`
+		AuthorID     int    `json:"author_id"`
+		CreatedAt    string `json:"created_at"`
+		UpdatedAt    string `json:"updated_at"`
+		ProjectID    int    `json:"project_id"`
+		Attachment   string `json:"attachment"`
+		LineCode     string `json:"line_code"`
+		CommitID     string `json:"commit_id"`
+		NoteableID   int    `json:"noteable_id"`
+		System       bool   `json:"system"`
+		StDiff       *Diff  `json:"st_diff"`
+		URL          string `json:"url"`
+	} `json:"object_attributes"`
+	Snippet *Snippet `json:"snippet"`
+}
+
+// MergeEvent represents a merge event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#merge-request-events
+type MergeEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	Project    struct {
+		ID                int             `json:"id"`
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	ObjectAttributes struct {
+		ID                       int          `json:"id"`
+		TargetBranch             string       `json:"target_branch"`
+		SourceBranch             string       `json:"source_branch"`
+		SourceProjectID          int          `json:"source_project_id"`
+		AuthorID                 int          `json:"author_id"`
+		AssigneeID               int          `json:"assignee_id"`
+		Title                    string       `json:"title"`
+		CreatedAt                string       `json:"created_at"` // Should be *time.Time (see Gitlab issue #21468)
+		UpdatedAt                string       `json:"updated_at"` // Should be *time.Time (see Gitlab issue #21468)
+		StCommits                []*Commit    `json:"st_commits"`
+		StDiffs                  []*Diff      `json:"st_diffs"`
+		MilestoneID              int          `json:"milestone_id"`
+		State                    string       `json:"state"`
+		MergeStatus              string       `json:"merge_status"`
+		TargetProjectID          int          `json:"target_project_id"`
+		IID                      int          `json:"iid"`
+		Description              string       `json:"description"`
+		Position                 int          `json:"position"`
+		LockedAt                 string       `json:"locked_at"`
+		UpdatedByID              int          `json:"updated_by_id"`
+		MergeError               string       `json:"merge_error"`
+		MergeParams              *MergeParams `json:"merge_params"`
+		MergeWhenBuildSucceeds   bool         `json:"merge_when_build_succeeds"`
+		MergeUserID              int          `json:"merge_user_id"`
+		MergeCommitSHA           string       `json:"merge_commit_sha"`
+		DeletedAt                string       `json:"deleted_at"`
+		ApprovalsBeforeMerge     string       `json:"approvals_before_merge"`
+		RebaseCommitSHA          string       `json:"rebase_commit_sha"`
+		InProgressMergeCommitSHA string       `json:"in_progress_merge_commit_sha"`
+		LockVersion              int          `json:"lock_version"`
+		TimeEstimate             int          `json:"time_estimate"`
+		Source                   *Repository  `json:"source"`
+		Target                   *Repository  `json:"target"`
+		LastCommit               struct {
+			ID        string     `json:"id"`
+			Message   string     `json:"message"`
+			Timestamp *time.Time `json:"timestamp"`
+			URL       string     `json:"url"`
+			Author    struct {
+				Name  string `json:"name"`
+				Email string `json:"email"`
+			} `json:"author"`
+		} `json:"last_commit"`
+		WorkInProgress bool          `json:"work_in_progress"`
+		URL            string        `json:"url"`
+		Action         string        `json:"action"`
+		OldRev         string        `json:"oldrev"`
+		Assignee       MergeAssignee `json:"assignee"`
+	} `json:"object_attributes"`
+	Repository *Repository   `json:"repository"`
+	Assignee   MergeAssignee `json:"assignee"`
+	Labels     []Label       `json:"labels"`
+	Changes    struct {
+		Assignees struct {
+			Previous []MergeAssignee `json:"previous"`
+			Current  []MergeAssignee `json:"current"`
+		} `json:"assignees"`
+		Description struct {
+			Previous string `json:"previous"`
+			Current  string `json:"current"`
+		} `json:"description"`
+		Labels struct {
+			Previous []Label `json:"previous"`
+			Current  []Label `json:"current"`
+		} `json:"labels"`
+		SourceBranch struct {
+			Previous string `json:"previous"`
+			Current  string `json:"current"`
+		} `json:"source_branch"`
+		SourceProjectID struct {
+			Previous int `json:"previous"`
+			Current  int `json:"current"`
+		} `json:"source_project_id"`
+		TargetBranch struct {
+			Previous string `json:"previous"`
+			Current  string `json:"current"`
+		} `json:"target_branch"`
+		TargetProjectID struct {
+			Previous int `json:"previous"`
+			Current  int `json:"current"`
+		} `json:"target_project_id"`
+		Title struct {
+			Previous string `json:"previous"`
+			Current  string `json:"current"`
+		} `json:"title"`
+		UpdatedByID struct {
+			Previous int `json:"previous"`
+			Current  int `json:"current"`
+		} `json:"updated_by_id"`
+	} `json:"changes"`
+}
+
+// MergeAssignee represents a merge assignee.
+type MergeAssignee struct {
+	Name      string `json:"name"`
+	Username  string `json:"username"`
+	AvatarURL string `json:"avatar_url"`
+}
+
+// MergeParams represents the merge params.
+type MergeParams struct {
+	ForceRemoveSourceBranch bool `json:"force_remove_source_branch"`
+}
+
+// UnmarshalJSON decodes the merge parameters
+//
+// This allows support of ForceRemoveSourceBranch for both type bool (>11.9) and string (<11.9)
+func (p *MergeParams) UnmarshalJSON(b []byte) error {
+	type Alias MergeParams
+	raw := struct {
+		*Alias
+		ForceRemoveSourceBranch interface{} `json:"force_remove_source_branch"`
+	}{
+		Alias: (*Alias)(p),
+	}
+
+	err := json.Unmarshal(b, &raw)
+	if err != nil {
+		return err
+	}
+
+	switch v := raw.ForceRemoveSourceBranch.(type) {
+	case nil:
+		// No action needed.
+	case bool:
+		p.ForceRemoveSourceBranch = v
+	case string:
+		p.ForceRemoveSourceBranch, err = strconv.ParseBool(v)
+		if err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("failed to unmarshal ForceRemoveSourceBranch of type: %T", v)
+	}
+
+	return nil
+}
+
+// WikiPageEvent represents a wiki page event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#wiki-page-events
+type WikiPageEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	Project    struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Wiki struct {
+		WebURL            string `json:"web_url"`
+		GitSSHURL         string `json:"git_ssh_url"`
+		GitHTTPURL        string `json:"git_http_url"`
+		PathWithNamespace string `json:"path_with_namespace"`
+		DefaultBranch     string `json:"default_branch"`
+	} `json:"wiki"`
+	ObjectAttributes struct {
+		Title   string `json:"title"`
+		Content string `json:"content"`
+		Format  string `json:"format"`
+		Message string `json:"message"`
+		Slug    string `json:"slug"`
+		URL     string `json:"url"`
+		Action  string `json:"action"`
+	} `json:"object_attributes"`
+}
+
+// PipelineEvent represents a pipeline event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#pipeline-events
+type PipelineEvent struct {
+	ObjectKind       string `json:"object_kind"`
+	ObjectAttributes struct {
+		ID         int      `json:"id"`
+		Ref        string   `json:"ref"`
+		Tag        bool     `json:"tag"`
+		SHA        string   `json:"sha"`
+		BeforeSHA  string   `json:"before_sha"`
+		Status     string   `json:"status"`
+		Stages     []string `json:"stages"`
+		CreatedAt  string   `json:"created_at"`
+		FinishedAt string   `json:"finished_at"`
+		Duration   int      `json:"duration"`
+	} `json:"object_attributes"`
+	User struct {
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		AvatarURL string `json:"avatar_url"`
+	} `json:"user"`
+	Project struct {
+		ID                int             `json:"id"`
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Commit struct {
+		ID        string     `json:"id"`
+		Message   string     `json:"message"`
+		Timestamp *time.Time `json:"timestamp"`
+		URL       string     `json:"url"`
+		Author    struct {
+			Name  string `json:"name"`
+			Email string `json:"email"`
+		} `json:"author"`
+	} `json:"commit"`
+	Builds []struct {
+		ID         int    `json:"id"`
+		Stage      string `json:"stage"`
+		Name       string `json:"name"`
+		Status     string `json:"status"`
+		CreatedAt  string `json:"created_at"`
+		StartedAt  string `json:"started_at"`
+		FinishedAt string `json:"finished_at"`
+		When       string `json:"when"`
+		Manual     bool   `json:"manual"`
+		User       struct {
+			Name      string `json:"name"`
+			Username  string `json:"username"`
+			AvatarURL string `json:"avatar_url"`
+		} `json:"user"`
+		Runner struct {
+			ID          int    `json:"id"`
+			Description string `json:"description"`
+			Active      bool   `json:"active"`
+			IsShared    bool   `json:"is_shared"`
+		} `json:"runner"`
+		ArtifactsFile struct {
+			Filename string `json:"filename"`
+			Size     int    `json:"size"`
+		} `json:"artifacts_file"`
+	} `json:"builds"`
+}
+
+//BuildEvent represents a build event
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#build-events
+type BuildEvent struct {
+	ObjectKind        string  `json:"object_kind"`
+	Ref               string  `json:"ref"`
+	Tag               bool    `json:"tag"`
+	BeforeSHA         string  `json:"before_sha"`
+	SHA               string  `json:"sha"`
+	BuildID           int     `json:"build_id"`
+	BuildName         string  `json:"build_name"`
+	BuildStage        string  `json:"build_stage"`
+	BuildStatus       string  `json:"build_status"`
+	BuildStartedAt    string  `json:"build_started_at"`
+	BuildFinishedAt   string  `json:"build_finished_at"`
+	BuildDuration     float64 `json:"build_duration"`
+	BuildAllowFailure bool    `json:"build_allow_failure"`
+	ProjectID         int     `json:"project_id"`
+	ProjectName       string  `json:"project_name"`
+	User              struct {
+		ID    int    `json:"id"`
+		Name  string `json:"name"`
+		Email string `json:"email"`
+	} `json:"user"`
+	Commit struct {
+		ID          int    `json:"id"`
+		SHA         string `json:"sha"`
+		Message     string `json:"message"`
+		AuthorName  string `json:"author_name"`
+		AuthorEmail string `json:"author_email"`
+		Status      string `json:"status"`
+		Duration    int    `json:"duration"`
+		StartedAt   string `json:"started_at"`
+		FinishedAt  string `json:"finished_at"`
+	} `json:"commit"`
+	Repository *Repository `json:"repository"`
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/events.go b/vendor/github.com/xanzy/go-gitlab/events.go
new file mode 100644
index 000000000..821b19d07
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/events.go
@@ -0,0 +1,146 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// EventsService handles communication with the event related methods of
+// the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/events.html
+type EventsService struct {
+	client *Client
+}
+
+// ContributionEvent represents a user's contribution
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/events.html#get-user-contribution-events
+type ContributionEvent struct {
+	Title       string     `json:"title"`
+	ProjectID   int        `json:"project_id"`
+	ActionName  string     `json:"action_name"`
+	TargetID    int        `json:"target_id"`
+	TargetIID   int        `json:"target_iid"`
+	TargetType  string     `json:"target_type"`
+	AuthorID    int        `json:"author_id"`
+	TargetTitle string     `json:"target_title"`
+	CreatedAt   *time.Time `json:"created_at"`
+	PushData    struct {
+		CommitCount int    `json:"commit_count"`
+		Action      string `json:"action"`
+		RefType     string `json:"ref_type"`
+		CommitFrom  string `json:"commit_from"`
+		CommitTo    string `json:"commit_to"`
+		Ref         string `json:"ref"`
+		CommitTitle string `json:"commit_title"`
+	} `json:"push_data"`
+	Note   *Note `json:"note"`
+	Author struct {
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		ID        int    `json:"id"`
+		State     string `json:"state"`
+		AvatarURL string `json:"avatar_url"`
+		WebURL    string `json:"web_url"`
+	} `json:"author"`
+	AuthorUsername string `json:"author_username"`
+}
+
+// ListContributionEventsOptions represents the options for GetUserContributionEvents
+//
+// GitLap API docs:
+// https://docs.gitlab.com/ce/api/events.html#get-user-contribution-events
+type ListContributionEventsOptions struct {
+	ListOptions
+	Action     *EventTypeValue       `url:"action,omitempty" json:"action,omitempty"`
+	TargetType *EventTargetTypeValue `url:"target_type,omitempty" json:"target_type,omitempty"`
+	Before     *ISOTime              `url:"before,omitempty" json:"before,omitempty"`
+	After      *ISOTime              `url:"after,omitempty" json:"after,omitempty"`
+	Sort       *string               `url:"sort,omitempty" json:"sort,omitempty"`
+}
+
+// ListUserContributionEvents retrieves user contribution events
+// for the specified user, sorted from newest to oldest.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/events.html#get-user-contribution-events
+func (s *UsersService) ListUserContributionEvents(uid interface{}, opt *ListContributionEventsOptions, options ...OptionFunc) ([]*ContributionEvent, *Response, error) {
+	user, err := parseID(uid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("users/%s/events", user)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var cs []*ContributionEvent
+	resp, err := s.client.Do(req, &cs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return cs, resp, err
+}
+
+// ListCurrentUserContributionEvents gets a list currently authenticated user's events
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#list-currently-authenticated-user-39-s-events
+func (s *EventsService) ListCurrentUserContributionEvents(opt *ListContributionEventsOptions, options ...OptionFunc) ([]*ContributionEvent, *Response, error) {
+	req, err := s.client.NewRequest("GET", "events", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var cs []*ContributionEvent
+	resp, err := s.client.Do(req, &cs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return cs, resp, err
+}
+
+// ListProjectVisibleEvents gets a list of visible events for a particular project
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/events.html#list-a-project-s-visible-events
+func (s *EventsService) ListProjectVisibleEvents(pid interface{}, opt *ListContributionEventsOptions, options ...OptionFunc) ([]*ContributionEvent, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/events", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var cs []*ContributionEvent
+	resp, err := s.client.Do(req, &cs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return cs, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/feature_flags.go b/vendor/github.com/xanzy/go-gitlab/feature_flags.go
new file mode 100644
index 000000000..1b5df5fa8
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/feature_flags.go
@@ -0,0 +1,79 @@
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+)
+
+// FeaturesService handles the communication with the application FeaturesService
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/features.html
+type FeaturesService struct {
+	client *Client
+}
+
+// Feature represents a GitLab feature flag.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/features.html
+type Feature struct {
+	Name  string `json:"name"`
+	State string `json:"state"`
+	Gates []Gate
+}
+
+// Gate represents a gate of a GitLab feature flag.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/features.html
+type Gate struct {
+	Key   string      `json:"key"`
+	Value interface{} `json:"value"`
+}
+
+func (f Feature) String() string {
+	return Stringify(f)
+}
+
+// ListFeatures gets a list of feature flags
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/features.html#list-all-features
+func (s *FeaturesService) ListFeatures(options ...OptionFunc) ([]*Feature, *Response, error) {
+	req, err := s.client.NewRequest("GET", "features", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var f []*Feature
+	resp, err := s.client.Do(req, &f)
+	if err != nil {
+		return nil, resp, err
+	}
+	return f, resp, err
+}
+
+// SetFeatureFlag sets or creates a feature flag gate
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/features.html#set-or-create-a-feature
+func (s *FeaturesService) SetFeatureFlag(name string, value interface{}, options ...OptionFunc) (*Feature, *Response, error) {
+	u := fmt.Sprintf("features/%s", url.PathEscape(name))
+
+	opt := struct {
+		Value interface{} `url:"value" json:"value"`
+	}{
+		value,
+	}
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	f := &Feature{}
+	resp, err := s.client.Do(req, f)
+	if err != nil {
+		return nil, resp, err
+	}
+	return f, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/gitignore_templates.go b/vendor/github.com/xanzy/go-gitlab/gitignore_templates.go
new file mode 100644
index 000000000..1f5e83aff
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/gitignore_templates.go
@@ -0,0 +1,84 @@
+//
+// Copyright 2018, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+)
+
+// GitIgnoreTemplatesService handles communication with the gitignore
+// templates related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/templates/gitignores.html
+type GitIgnoreTemplatesService struct {
+	client *Client
+}
+
+// GitIgnoreTemplate represents a GitLab gitignore template.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/templates/gitignores.html
+type GitIgnoreTemplate struct {
+	Name    string `json:"name"`
+	Content string `json:"content"`
+}
+
+// ListTemplatesOptions represents the available ListAllTemplates() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/gitignores.html#list-gitignore-templates
+type ListTemplatesOptions ListOptions
+
+// ListTemplates get a list of available git ignore templates
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/gitignores.html#list-gitignore-templates
+func (s *GitIgnoreTemplatesService) ListTemplates(opt *ListTemplatesOptions, options ...OptionFunc) ([]*GitIgnoreTemplate, *Response, error) {
+	req, err := s.client.NewRequest("GET", "templates/gitignores", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var gs []*GitIgnoreTemplate
+	resp, err := s.client.Do(req, &gs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gs, resp, err
+}
+
+// GetTemplate get a git ignore template
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/gitignores.html#single-gitignore-template
+func (s *GitIgnoreTemplatesService) GetTemplate(key string, options ...OptionFunc) (*GitIgnoreTemplate, *Response, error) {
+	u := fmt.Sprintf("templates/gitignores/%s", url.PathEscape(key))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	g := new(GitIgnoreTemplate)
+	resp, err := s.client.Do(req, g)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return g, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/gitlab.go b/vendor/github.com/xanzy/go-gitlab/gitlab.go
new file mode 100644
index 000000000..b8c951c5d
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/gitlab.go
@@ -0,0 +1,955 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// Package gitlab implements a GitLab API client.
+package gitlab
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/google/go-querystring/query"
+	"golang.org/x/oauth2"
+)
+
+const (
+	defaultBaseURL = "https://gitlab.com/"
+	apiVersionPath = "api/v4/"
+	userAgent      = "go-gitlab"
+)
+
+// authType represents an authentication type within GitLab.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/
+type authType int
+
+// List of available authentication types.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/
+const (
+	basicAuth authType = iota
+	oAuthToken
+	privateToken
+)
+
+// AccessLevelValue represents a permission level within GitLab.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html
+type AccessLevelValue int
+
+// List of available access levels
+//
+// GitLab API docs: https://docs.gitlab.com/ce/permissions/permissions.html
+const (
+	NoPermissions         AccessLevelValue = 0
+	GuestPermissions      AccessLevelValue = 10
+	ReporterPermissions   AccessLevelValue = 20
+	DeveloperPermissions  AccessLevelValue = 30
+	MaintainerPermissions AccessLevelValue = 40
+	OwnerPermissions      AccessLevelValue = 50
+
+	// These are deprecated and should be removed in a future version
+	MasterPermissions AccessLevelValue = 40
+	OwnerPermission   AccessLevelValue = 50
+)
+
+// BuildStateValue represents a GitLab build state.
+type BuildStateValue string
+
+// These constants represent all valid build states.
+const (
+	Pending  BuildStateValue = "pending"
+	Running  BuildStateValue = "running"
+	Success  BuildStateValue = "success"
+	Failed   BuildStateValue = "failed"
+	Canceled BuildStateValue = "canceled"
+	Skipped  BuildStateValue = "skipped"
+	Manual   BuildStateValue = "manual"
+)
+
+// ISOTime represents an ISO 8601 formatted date
+type ISOTime time.Time
+
+// ISO 8601 date format
+const iso8601 = "2006-01-02"
+
+// MarshalJSON implements the json.Marshaler interface
+func (t ISOTime) MarshalJSON() ([]byte, error) {
+	if y := time.Time(t).Year(); y < 0 || y >= 10000 {
+		// ISO 8901 uses 4 digits for the years
+		return nil, errors.New("json: ISOTime year outside of range [0,9999]")
+	}
+
+	b := make([]byte, 0, len(iso8601)+2)
+	b = append(b, '"')
+	b = time.Time(t).AppendFormat(b, iso8601)
+	b = append(b, '"')
+
+	return b, nil
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface
+func (t *ISOTime) UnmarshalJSON(data []byte) error {
+	// Ignore null, like in the main JSON package
+	if string(data) == "null" {
+		return nil
+	}
+
+	isotime, err := time.Parse(`"`+iso8601+`"`, string(data))
+	*t = ISOTime(isotime)
+
+	return err
+}
+
+// EncodeValues implements the query.Encoder interface
+func (t *ISOTime) EncodeValues(key string, v *url.Values) error {
+	if t == nil || (time.Time(*t)).IsZero() {
+		return nil
+	}
+	v.Add(key, t.String())
+	return nil
+}
+
+// String implements the Stringer interface
+func (t ISOTime) String() string {
+	return time.Time(t).Format(iso8601)
+}
+
+// NotificationLevelValue represents a notification level.
+type NotificationLevelValue int
+
+// String implements the fmt.Stringer interface.
+func (l NotificationLevelValue) String() string {
+	return notificationLevelNames[l]
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (l NotificationLevelValue) MarshalJSON() ([]byte, error) {
+	return json.Marshal(l.String())
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (l *NotificationLevelValue) UnmarshalJSON(data []byte) error {
+	var raw interface{}
+	if err := json.Unmarshal(data, &raw); err != nil {
+		return err
+	}
+
+	switch raw := raw.(type) {
+	case float64:
+		*l = NotificationLevelValue(raw)
+	case string:
+		*l = notificationLevelTypes[raw]
+	case nil:
+		// No action needed.
+	default:
+		return fmt.Errorf("json: cannot unmarshal %T into Go value of type %T", raw, *l)
+	}
+
+	return nil
+}
+
+// List of valid notification levels.
+const (
+	DisabledNotificationLevel NotificationLevelValue = iota
+	ParticipatingNotificationLevel
+	WatchNotificationLevel
+	GlobalNotificationLevel
+	MentionNotificationLevel
+	CustomNotificationLevel
+)
+
+var notificationLevelNames = [...]string{
+	"disabled",
+	"participating",
+	"watch",
+	"global",
+	"mention",
+	"custom",
+}
+
+var notificationLevelTypes = map[string]NotificationLevelValue{
+	"disabled":      DisabledNotificationLevel,
+	"participating": ParticipatingNotificationLevel,
+	"watch":         WatchNotificationLevel,
+	"global":        GlobalNotificationLevel,
+	"mention":       MentionNotificationLevel,
+	"custom":        CustomNotificationLevel,
+}
+
+// VisibilityValue represents a visibility level within GitLab.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/
+type VisibilityValue string
+
+// List of available visibility levels.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/
+const (
+	PrivateVisibility  VisibilityValue = "private"
+	InternalVisibility VisibilityValue = "internal"
+	PublicVisibility   VisibilityValue = "public"
+)
+
+// VariableTypeValue represents a variable type within GitLab.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/
+type VariableTypeValue string
+
+// List of available variable types.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/
+const (
+	EnvVariableType  VariableTypeValue = "env_var"
+	FileVariableType VariableTypeValue = "file"
+)
+
+// MergeMethodValue represents a project merge type within GitLab.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method
+type MergeMethodValue string
+
+// List of available merge type
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#project-merge-method
+const (
+	NoFastForwardMerge MergeMethodValue = "merge"
+	FastForwardMerge   MergeMethodValue = "ff"
+	RebaseMerge        MergeMethodValue = "rebase_merge"
+)
+
+// EventTypeValue represents actions type for contribution events
+type EventTypeValue string
+
+// List of available action type
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#action-types
+const (
+	CreatedEventType   EventTypeValue = "created"
+	UpdatedEventType   EventTypeValue = "updated"
+	ClosedEventType    EventTypeValue = "closed"
+	ReopenedEventType  EventTypeValue = "reopened"
+	PushedEventType    EventTypeValue = "pushed"
+	CommentedEventType EventTypeValue = "commented"
+	MergedEventType    EventTypeValue = "merged"
+	JoinedEventType    EventTypeValue = "joined"
+	LeftEventType      EventTypeValue = "left"
+	DestroyedEventType EventTypeValue = "destroyed"
+	ExpiredEventType   EventTypeValue = "expired"
+)
+
+// EventTargetTypeValue represents actions type value for contribution events
+type EventTargetTypeValue string
+
+// List of available action type
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/events.html#target-types
+const (
+	IssueEventTargetType        EventTargetTypeValue = "issue"
+	MilestoneEventTargetType    EventTargetTypeValue = "milestone"
+	MergeRequestEventTargetType EventTargetTypeValue = "merge_request"
+	NoteEventTargetType         EventTargetTypeValue = "note"
+	ProjectEventTargetType      EventTargetTypeValue = "project"
+	SnippetEventTargetType      EventTargetTypeValue = "snippet"
+	UserEventTargetType         EventTargetTypeValue = "user"
+)
+
+// A Client manages communication with the GitLab API.
+type Client struct {
+	// HTTP client used to communicate with the API.
+	client *http.Client
+
+	// Base URL for API requests. Defaults to the public GitLab API, but can be
+	// set to a domain endpoint to use with a self hosted GitLab server. baseURL
+	// should always be specified with a trailing slash.
+	baseURL *url.URL
+
+	// Token type used to make authenticated API calls.
+	authType authType
+
+	// Username and password used for basix authentication.
+	username, password string
+
+	// Token used to make authenticated API calls.
+	token string
+
+	// User agent used when communicating with the GitLab API.
+	UserAgent string
+
+	// Services used for talking to different parts of the GitLab API.
+	AccessRequests        *AccessRequestsService
+	AwardEmoji            *AwardEmojiService
+	Boards                *IssueBoardsService
+	Branches              *BranchesService
+	BroadcastMessage      *BroadcastMessagesService
+	CIYMLTemplate         *CIYMLTemplatesService
+	Commits               *CommitsService
+	ContainerRegistry     *ContainerRegistryService
+	CustomAttribute       *CustomAttributesService
+	DeployKeys            *DeployKeysService
+	Deployments           *DeploymentsService
+	Discussions           *DiscussionsService
+	Environments          *EnvironmentsService
+	Epics                 *EpicsService
+	Events                *EventsService
+	Features              *FeaturesService
+	GitIgnoreTemplates    *GitIgnoreTemplatesService
+	GroupBadges           *GroupBadgesService
+	GroupCluster          *GroupClustersService
+	GroupIssueBoards      *GroupIssueBoardsService
+	GroupLabels           *GroupLabelsService
+	GroupMembers          *GroupMembersService
+	GroupMilestones       *GroupMilestonesService
+	GroupVariables        *GroupVariablesService
+	Groups                *GroupsService
+	IssueLinks            *IssueLinksService
+	Issues                *IssuesService
+	Jobs                  *JobsService
+	Keys                  *KeysService
+	Labels                *LabelsService
+	License               *LicenseService
+	LicenseTemplates      *LicenseTemplatesService
+	MergeRequestApprovals *MergeRequestApprovalsService
+	MergeRequests         *MergeRequestsService
+	Milestones            *MilestonesService
+	Namespaces            *NamespacesService
+	Notes                 *NotesService
+	NotificationSettings  *NotificationSettingsService
+	PagesDomains          *PagesDomainsService
+	PipelineSchedules     *PipelineSchedulesService
+	PipelineTriggers      *PipelineTriggersService
+	Pipelines             *PipelinesService
+	ProjectBadges         *ProjectBadgesService
+	ProjectCluster        *ProjectClustersService
+	ProjectImportExport   *ProjectImportExportService
+	ProjectMembers        *ProjectMembersService
+	ProjectSnippets       *ProjectSnippetsService
+	ProjectVariables      *ProjectVariablesService
+	Projects              *ProjectsService
+	ProtectedBranches     *ProtectedBranchesService
+	ProtectedTags         *ProtectedTagsService
+	ReleaseLinks          *ReleaseLinksService
+	Releases              *ReleasesService
+	Repositories          *RepositoriesService
+	RepositoryFiles       *RepositoryFilesService
+	ResourceLabelEvents   *ResourceLabelEventsService
+	Runners               *RunnersService
+	Search                *SearchService
+	Services              *ServicesService
+	Settings              *SettingsService
+	Sidekiq               *SidekiqService
+	Snippets              *SnippetsService
+	SystemHooks           *SystemHooksService
+	Tags                  *TagsService
+	Todos                 *TodosService
+	Users                 *UsersService
+	Validate              *ValidateService
+	Version               *VersionService
+	Wikis                 *WikisService
+}
+
+// ListOptions specifies the optional parameters to various List methods that
+// support pagination.
+type ListOptions struct {
+	// For paginated result sets, page of results to retrieve.
+	Page int `url:"page,omitempty" json:"page,omitempty"`
+
+	// For paginated result sets, the number of results to include per page.
+	PerPage int `url:"per_page,omitempty" json:"per_page,omitempty"`
+}
+
+// NewClient returns a new GitLab API client. If a nil httpClient is
+// provided, http.DefaultClient will be used. To use API methods which require
+// authentication, provide a valid private or personal token.
+func NewClient(httpClient *http.Client, token string) *Client {
+	client := newClient(httpClient)
+	client.authType = privateToken
+	client.token = token
+	return client
+}
+
+// NewBasicAuthClient returns a new GitLab API client. If a nil httpClient is
+// provided, http.DefaultClient will be used. To use API methods which require
+// authentication, provide a valid username and password.
+func NewBasicAuthClient(httpClient *http.Client, endpoint, username, password string) (*Client, error) {
+	client := newClient(httpClient)
+	client.authType = basicAuth
+	client.username = username
+	client.password = password
+	client.SetBaseURL(endpoint)
+
+	err := client.requestOAuthToken(context.TODO())
+	if err != nil {
+		return nil, err
+	}
+
+	return client, nil
+}
+
+func (c *Client) requestOAuthToken(ctx context.Context) error {
+	config := &oauth2.Config{
+		Endpoint: oauth2.Endpoint{
+			AuthURL:  fmt.Sprintf("%s://%s/oauth/authorize", c.BaseURL().Scheme, c.BaseURL().Host),
+			TokenURL: fmt.Sprintf("%s://%s/oauth/token", c.BaseURL().Scheme, c.BaseURL().Host),
+		},
+	}
+	ctx = context.WithValue(ctx, oauth2.HTTPClient, c.client)
+	t, err := config.PasswordCredentialsToken(ctx, c.username, c.password)
+	if err != nil {
+		return err
+	}
+	c.token = t.AccessToken
+	return nil
+}
+
+// NewOAuthClient returns a new GitLab API client. If a nil httpClient is
+// provided, http.DefaultClient will be used. To use API methods which require
+// authentication, provide a valid oauth token.
+func NewOAuthClient(httpClient *http.Client, token string) *Client {
+	client := newClient(httpClient)
+	client.authType = oAuthToken
+	client.token = token
+	return client
+}
+
+func newClient(httpClient *http.Client) *Client {
+	if httpClient == nil {
+		httpClient = http.DefaultClient
+	}
+
+	c := &Client{client: httpClient, UserAgent: userAgent}
+	if err := c.SetBaseURL(defaultBaseURL); err != nil {
+		// Should never happen since defaultBaseURL is our constant.
+		panic(err)
+	}
+
+	// Create the internal timeStats service.
+	timeStats := &timeStatsService{client: c}
+
+	// Create all the public services.
+	c.AccessRequests = &AccessRequestsService{client: c}
+	c.AwardEmoji = &AwardEmojiService{client: c}
+	c.Boards = &IssueBoardsService{client: c}
+	c.Branches = &BranchesService{client: c}
+	c.BroadcastMessage = &BroadcastMessagesService{client: c}
+	c.CIYMLTemplate = &CIYMLTemplatesService{client: c}
+	c.Commits = &CommitsService{client: c}
+	c.ContainerRegistry = &ContainerRegistryService{client: c}
+	c.CustomAttribute = &CustomAttributesService{client: c}
+	c.DeployKeys = &DeployKeysService{client: c}
+	c.Deployments = &DeploymentsService{client: c}
+	c.Discussions = &DiscussionsService{client: c}
+	c.Environments = &EnvironmentsService{client: c}
+	c.Epics = &EpicsService{client: c}
+	c.Events = &EventsService{client: c}
+	c.Features = &FeaturesService{client: c}
+	c.GitIgnoreTemplates = &GitIgnoreTemplatesService{client: c}
+	c.GroupBadges = &GroupBadgesService{client: c}
+	c.GroupCluster = &GroupClustersService{client: c}
+	c.GroupIssueBoards = &GroupIssueBoardsService{client: c}
+	c.GroupLabels = &GroupLabelsService{client: c}
+	c.GroupMembers = &GroupMembersService{client: c}
+	c.GroupMilestones = &GroupMilestonesService{client: c}
+	c.GroupVariables = &GroupVariablesService{client: c}
+	c.Groups = &GroupsService{client: c}
+	c.IssueLinks = &IssueLinksService{client: c}
+	c.Issues = &IssuesService{client: c, timeStats: timeStats}
+	c.Jobs = &JobsService{client: c}
+	c.Keys = &KeysService{client: c}
+	c.Labels = &LabelsService{client: c}
+	c.License = &LicenseService{client: c}
+	c.LicenseTemplates = &LicenseTemplatesService{client: c}
+	c.MergeRequestApprovals = &MergeRequestApprovalsService{client: c}
+	c.MergeRequests = &MergeRequestsService{client: c, timeStats: timeStats}
+	c.Milestones = &MilestonesService{client: c}
+	c.Namespaces = &NamespacesService{client: c}
+	c.Notes = &NotesService{client: c}
+	c.NotificationSettings = &NotificationSettingsService{client: c}
+	c.PagesDomains = &PagesDomainsService{client: c}
+	c.PipelineSchedules = &PipelineSchedulesService{client: c}
+	c.PipelineTriggers = &PipelineTriggersService{client: c}
+	c.Pipelines = &PipelinesService{client: c}
+	c.ProjectBadges = &ProjectBadgesService{client: c}
+	c.ProjectCluster = &ProjectClustersService{client: c}
+	c.ProjectImportExport = &ProjectImportExportService{client: c}
+	c.ProjectMembers = &ProjectMembersService{client: c}
+	c.ProjectSnippets = &ProjectSnippetsService{client: c}
+	c.ProjectVariables = &ProjectVariablesService{client: c}
+	c.Projects = &ProjectsService{client: c}
+	c.ProtectedBranches = &ProtectedBranchesService{client: c}
+	c.ProtectedTags = &ProtectedTagsService{client: c}
+	c.ReleaseLinks = &ReleaseLinksService{client: c}
+	c.Releases = &ReleasesService{client: c}
+	c.Repositories = &RepositoriesService{client: c}
+	c.RepositoryFiles = &RepositoryFilesService{client: c}
+	c.ResourceLabelEvents = &ResourceLabelEventsService{client: c}
+	c.Runners = &RunnersService{client: c}
+	c.Search = &SearchService{client: c}
+	c.Services = &ServicesService{client: c}
+	c.Settings = &SettingsService{client: c}
+	c.Sidekiq = &SidekiqService{client: c}
+	c.Snippets = &SnippetsService{client: c}
+	c.SystemHooks = &SystemHooksService{client: c}
+	c.Tags = &TagsService{client: c}
+	c.Todos = &TodosService{client: c}
+	c.Users = &UsersService{client: c}
+	c.Validate = &ValidateService{client: c}
+	c.Version = &VersionService{client: c}
+	c.Wikis = &WikisService{client: c}
+
+	return c
+}
+
+// BaseURL return a copy of the baseURL.
+func (c *Client) BaseURL() *url.URL {
+	u := *c.baseURL
+	return &u
+}
+
+// SetBaseURL sets the base URL for API requests to a custom endpoint. urlStr
+// should always be specified with a trailing slash.
+func (c *Client) SetBaseURL(urlStr string) error {
+	// Make sure the given URL end with a slash
+	if !strings.HasSuffix(urlStr, "/") {
+		urlStr += "/"
+	}
+
+	baseURL, err := url.Parse(urlStr)
+	if err != nil {
+		return err
+	}
+
+	if !strings.HasSuffix(baseURL.Path, apiVersionPath) {
+		baseURL.Path += apiVersionPath
+	}
+
+	// Update the base URL of the client.
+	c.baseURL = baseURL
+
+	return nil
+}
+
+// NewRequest creates an API request. A relative URL path can be provided in
+// urlStr, in which case it is resolved relative to the base URL of the Client.
+// Relative URL paths should always be specified without a preceding slash. If
+// specified, the value pointed to by body is JSON encoded and included as the
+// request body.
+func (c *Client) NewRequest(method, path string, opt interface{}, options []OptionFunc) (*http.Request, error) {
+	u := *c.baseURL
+	unescaped, err := url.PathUnescape(path)
+	if err != nil {
+		return nil, err
+	}
+
+	// Set the encoded path data
+	u.RawPath = c.baseURL.Path + path
+	u.Path = c.baseURL.Path + unescaped
+
+	if opt != nil {
+		q, err := query.Values(opt)
+		if err != nil {
+			return nil, err
+		}
+		u.RawQuery = q.Encode()
+	}
+
+	req := &http.Request{
+		Method:     method,
+		URL:        &u,
+		Proto:      "HTTP/1.1",
+		ProtoMajor: 1,
+		ProtoMinor: 1,
+		Header:     make(http.Header),
+		Host:       u.Host,
+	}
+
+	for _, fn := range options {
+		if fn == nil {
+			continue
+		}
+
+		if err := fn(req); err != nil {
+			return nil, err
+		}
+	}
+
+	if method == "POST" || method == "PUT" {
+		bodyBytes, err := json.Marshal(opt)
+		if err != nil {
+			return nil, err
+		}
+		bodyReader := bytes.NewReader(bodyBytes)
+
+		u.RawQuery = ""
+		req.Body = ioutil.NopCloser(bodyReader)
+		req.GetBody = func() (io.ReadCloser, error) {
+			return ioutil.NopCloser(bodyReader), nil
+		}
+		req.ContentLength = int64(bodyReader.Len())
+		req.Header.Set("Content-Type", "application/json")
+	}
+
+	req.Header.Set("Accept", "application/json")
+
+	switch c.authType {
+	case basicAuth, oAuthToken:
+		req.Header.Set("Authorization", "Bearer "+c.token)
+	case privateToken:
+		req.Header.Set("PRIVATE-TOKEN", c.token)
+	}
+
+	if c.UserAgent != "" {
+		req.Header.Set("User-Agent", c.UserAgent)
+	}
+
+	return req, nil
+}
+
+// Response is a GitLab API response. This wraps the standard http.Response
+// returned from GitLab and provides convenient access to things like
+// pagination links.
+type Response struct {
+	*http.Response
+
+	// These fields provide the page values for paginating through a set of
+	// results. Any or all of these may be set to the zero value for
+	// responses that are not part of a paginated set, or for which there
+	// are no additional pages.
+	TotalItems   int
+	TotalPages   int
+	ItemsPerPage int
+	CurrentPage  int
+	NextPage     int
+	PreviousPage int
+}
+
+// newResponse creates a new Response for the provided http.Response.
+func newResponse(r *http.Response) *Response {
+	response := &Response{Response: r}
+	response.populatePageValues()
+	return response
+}
+
+const (
+	xTotal      = "X-Total"
+	xTotalPages = "X-Total-Pages"
+	xPerPage    = "X-Per-Page"
+	xPage       = "X-Page"
+	xNextPage   = "X-Next-Page"
+	xPrevPage   = "X-Prev-Page"
+)
+
+// populatePageValues parses the HTTP Link response headers and populates the
+// various pagination link values in the Response.
+func (r *Response) populatePageValues() {
+	if totalItems := r.Response.Header.Get(xTotal); totalItems != "" {
+		r.TotalItems, _ = strconv.Atoi(totalItems)
+	}
+	if totalPages := r.Response.Header.Get(xTotalPages); totalPages != "" {
+		r.TotalPages, _ = strconv.Atoi(totalPages)
+	}
+	if itemsPerPage := r.Response.Header.Get(xPerPage); itemsPerPage != "" {
+		r.ItemsPerPage, _ = strconv.Atoi(itemsPerPage)
+	}
+	if currentPage := r.Response.Header.Get(xPage); currentPage != "" {
+		r.CurrentPage, _ = strconv.Atoi(currentPage)
+	}
+	if nextPage := r.Response.Header.Get(xNextPage); nextPage != "" {
+		r.NextPage, _ = strconv.Atoi(nextPage)
+	}
+	if previousPage := r.Response.Header.Get(xPrevPage); previousPage != "" {
+		r.PreviousPage, _ = strconv.Atoi(previousPage)
+	}
+}
+
+// Do sends an API request and returns the API response. The API response is
+// JSON decoded and stored in the value pointed to by v, or returned as an
+// error if an API error has occurred. If v implements the io.Writer
+// interface, the raw response body will be written to v, without attempting to
+// first decode it.
+func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
+	resp, err := c.client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode == http.StatusUnauthorized && c.authType == basicAuth {
+		err = c.requestOAuthToken(req.Context())
+		if err != nil {
+			return nil, err
+		}
+		return c.Do(req, v)
+	}
+
+	response := newResponse(resp)
+
+	err = CheckResponse(resp)
+	if err != nil {
+		// even though there was an error, we still return the response
+		// in case the caller wants to inspect it further
+		return response, err
+	}
+
+	if v != nil {
+		if w, ok := v.(io.Writer); ok {
+			_, err = io.Copy(w, resp.Body)
+		} else {
+			err = json.NewDecoder(resp.Body).Decode(v)
+		}
+	}
+
+	return response, err
+}
+
+// Helper function to accept and format both the project ID or name as project
+// identifier for all API calls.
+func parseID(id interface{}) (string, error) {
+	switch v := id.(type) {
+	case int:
+		return strconv.Itoa(v), nil
+	case string:
+		return v, nil
+	default:
+		return "", fmt.Errorf("invalid ID type %#v, the ID must be an int or a string", id)
+	}
+}
+
+// Helper function to escape a project identifier.
+func pathEscape(s string) string {
+	return strings.Replace(url.PathEscape(s), ".", "%2E", -1)
+}
+
+// An ErrorResponse reports one or more errors caused by an API request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/README.html#data-validation-and-error-reporting
+type ErrorResponse struct {
+	Body     []byte
+	Response *http.Response
+	Message  string
+}
+
+func (e *ErrorResponse) Error() string {
+	path, _ := url.QueryUnescape(e.Response.Request.URL.Path)
+	u := fmt.Sprintf("%s://%s%s", e.Response.Request.URL.Scheme, e.Response.Request.URL.Host, path)
+	return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message)
+}
+
+// CheckResponse checks the API response for errors, and returns them if present.
+func CheckResponse(r *http.Response) error {
+	switch r.StatusCode {
+	case 200, 201, 202, 204, 304:
+		return nil
+	}
+
+	errorResponse := &ErrorResponse{Response: r}
+	data, err := ioutil.ReadAll(r.Body)
+	if err == nil && data != nil {
+		errorResponse.Body = data
+
+		var raw interface{}
+		if err := json.Unmarshal(data, &raw); err != nil {
+			errorResponse.Message = "failed to parse unknown error format"
+		} else {
+			errorResponse.Message = parseError(raw)
+		}
+	}
+
+	return errorResponse
+}
+
+// Format:
+// {
+//     "message": {
+//         "<property-name>": [
+//             "<error-message>",
+//             "<error-message>",
+//             ...
+//         ],
+//         "<embed-entity>": {
+//             "<property-name>": [
+//                 "<error-message>",
+//                 "<error-message>",
+//                 ...
+//             ],
+//         }
+//     },
+//     "error": "<error-message>"
+// }
+func parseError(raw interface{}) string {
+	switch raw := raw.(type) {
+	case string:
+		return raw
+
+	case []interface{}:
+		var errs []string
+		for _, v := range raw {
+			errs = append(errs, parseError(v))
+		}
+		return fmt.Sprintf("[%s]", strings.Join(errs, ", "))
+
+	case map[string]interface{}:
+		var errs []string
+		for k, v := range raw {
+			errs = append(errs, fmt.Sprintf("{%s: %s}", k, parseError(v)))
+		}
+		sort.Strings(errs)
+		return strings.Join(errs, ", ")
+
+	default:
+		return fmt.Sprintf("failed to parse unexpected error type: %T", raw)
+	}
+}
+
+// OptionFunc can be passed to all API requests to make the API call as if you were
+// another user, provided your private token is from an administrator account.
+//
+// GitLab docs: https://docs.gitlab.com/ce/api/README.html#sudo
+type OptionFunc func(*http.Request) error
+
+// WithSudo takes either a username or user ID and sets the SUDO request header
+func WithSudo(uid interface{}) OptionFunc {
+	return func(req *http.Request) error {
+		user, err := parseID(uid)
+		if err != nil {
+			return err
+		}
+		req.Header.Set("SUDO", user)
+		return nil
+	}
+}
+
+// WithContext runs the request with the provided context
+func WithContext(ctx context.Context) OptionFunc {
+	return func(req *http.Request) error {
+		*req = *req.WithContext(ctx)
+		return nil
+	}
+}
+
+// Bool is a helper routine that allocates a new bool value
+// to store v and returns a pointer to it.
+func Bool(v bool) *bool {
+	p := new(bool)
+	*p = v
+	return p
+}
+
+// Int is a helper routine that allocates a new int32 value
+// to store v and returns a pointer to it, but unlike Int32
+// its argument value is an int.
+func Int(v int) *int {
+	p := new(int)
+	*p = v
+	return p
+}
+
+// String is a helper routine that allocates a new string value
+// to store v and returns a pointer to it.
+func String(v string) *string {
+	p := new(string)
+	*p = v
+	return p
+}
+
+// Time is a helper routine that allocates a new time.Time value
+// to store v and returns a pointer to it.
+func Time(v time.Time) *time.Time {
+	p := new(time.Time)
+	*p = v
+	return p
+}
+
+// AccessLevel is a helper routine that allocates a new AccessLevelValue
+// to store v and returns a pointer to it.
+func AccessLevel(v AccessLevelValue) *AccessLevelValue {
+	p := new(AccessLevelValue)
+	*p = v
+	return p
+}
+
+// BuildState is a helper routine that allocates a new BuildStateValue
+// to store v and returns a pointer to it.
+func BuildState(v BuildStateValue) *BuildStateValue {
+	p := new(BuildStateValue)
+	*p = v
+	return p
+}
+
+// NotificationLevel is a helper routine that allocates a new NotificationLevelValue
+// to store v and returns a pointer to it.
+func NotificationLevel(v NotificationLevelValue) *NotificationLevelValue {
+	p := new(NotificationLevelValue)
+	*p = v
+	return p
+}
+
+// VariableType is a helper routine that allocates a new VariableTypeValue
+// to store v and returns a pointer to it.
+func VariableType(v VariableTypeValue) *VariableTypeValue {
+	p := new(VariableTypeValue)
+	*p = v
+	return p
+}
+
+// Visibility is a helper routine that allocates a new VisibilityValue
+// to store v and returns a pointer to it.
+func Visibility(v VisibilityValue) *VisibilityValue {
+	p := new(VisibilityValue)
+	*p = v
+	return p
+}
+
+// MergeMethod is a helper routine that allocates a new MergeMethod
+// to sotre v and returns a pointer to it.
+func MergeMethod(v MergeMethodValue) *MergeMethodValue {
+	p := new(MergeMethodValue)
+	*p = v
+	return p
+}
+
+// BoolValue is a boolean value with advanced json unmarshaling features.
+type BoolValue bool
+
+// UnmarshalJSON allows 1 and 0 to be considered as boolean values
+// Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/50122
+func (t *BoolValue) UnmarshalJSON(b []byte) error {
+	switch string(b) {
+	case `"1"`:
+		*t = true
+		return nil
+	case `"0"`:
+		*t = false
+		return nil
+	default:
+		var v bool
+		err := json.Unmarshal(b, &v)
+		*t = BoolValue(v)
+		return err
+	}
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/go.mod b/vendor/github.com/xanzy/go-gitlab/go.mod
new file mode 100644
index 000000000..0632997b6
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/go.mod
@@ -0,0 +1,12 @@
+module github.com/xanzy/go-gitlab
+
+require (
+	github.com/google/go-querystring v1.0.0
+	github.com/stretchr/testify v1.4.0
+	golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect
+	golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288
+	golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
+	google.golang.org/appengine v1.3.0 // indirect
+)
+
+go 1.13
diff --git a/vendor/github.com/xanzy/go-gitlab/go.sum b/vendor/github.com/xanzy/go-gitlab/go.sum
new file mode 100644
index 000000000..d78b9d5f6
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/go.sum
@@ -0,0 +1,25 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg=
+golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 h1:JIqe8uIcRBHXDQVvZtHwp80ai3Lw3IJAeJEs55Dc1W0=
+golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
+google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/vendor/github.com/xanzy/go-gitlab/group_badges.go b/vendor/github.com/xanzy/go-gitlab/group_badges.go
new file mode 100644
index 000000000..9be27bd2a
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/group_badges.go
@@ -0,0 +1,213 @@
+package gitlab
+
+import (
+	"fmt"
+)
+
+// GroupBadgesService handles communication with the group badges
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html
+type GroupBadgesService struct {
+	client *Client
+}
+
+// BadgeKind represents a GitLab Badge Kind
+type BadgeKind string
+
+// all possible values Badge Kind
+const (
+	ProjectBadgeKind BadgeKind = "project"
+	GroupBadgeKind   BadgeKind = "group"
+)
+
+// GroupBadge represents a group badge.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html
+type GroupBadge struct {
+	ID               int       `json:"id"`
+	LinkURL          string    `json:"link_url"`
+	ImageURL         string    `json:"image_url"`
+	RenderedLinkURL  string    `json:"rendered_link_url"`
+	RenderedImageURL string    `json:"rendered_image_url"`
+	Kind             BadgeKind `json:"kind"`
+}
+
+// ListGroupBadgesOptions represents the available ListGroupBadges() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html#list-all-badges-of-a-group
+type ListGroupBadgesOptions ListOptions
+
+// ListGroupBadges gets a list of a group badges.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html#list-all-badges-of-a-group
+func (s *GroupBadgesService) ListGroupBadges(gid interface{}, opt *ListGroupBadgesOptions, options ...OptionFunc) ([]*GroupBadge, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/badges", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var gb []*GroupBadge
+	resp, err := s.client.Do(req, &gb)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gb, resp, err
+}
+
+// GetGroupBadge gets a group badge.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html#get-a-badge-of-a-group
+func (s *GroupBadgesService) GetGroupBadge(gid interface{}, badge int, options ...OptionFunc) (*GroupBadge, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/badges/%d", pathEscape(group), badge)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gb := new(GroupBadge)
+	resp, err := s.client.Do(req, gb)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gb, resp, err
+}
+
+// AddGroupBadgeOptions represents the available AddGroupBadge() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html#add-a-badge-to-a-group
+type AddGroupBadgeOptions struct {
+	LinkURL  *string `url:"link_url,omitempty" json:"link_url,omitempty"`
+	ImageURL *string `url:"image_url,omitempty" json:"image_url,omitempty"`
+}
+
+// AddGroupBadge adds a badge to a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html#add-a-badge-to-a-group
+func (s *GroupBadgesService) AddGroupBadge(gid interface{}, opt *AddGroupBadgeOptions, options ...OptionFunc) (*GroupBadge, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/badges", pathEscape(group))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gb := new(GroupBadge)
+	resp, err := s.client.Do(req, gb)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gb, resp, err
+}
+
+// EditGroupBadgeOptions represents the available EditGroupBadge() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html#edit-a-badge-of-a-group
+type EditGroupBadgeOptions struct {
+	LinkURL  *string `url:"link_url,omitempty" json:"link_url,omitempty"`
+	ImageURL *string `url:"image_url,omitempty" json:"image_url,omitempty"`
+}
+
+// EditGroupBadge updates a badge of a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html#edit-a-badge-of-a-group
+func (s *GroupBadgesService) EditGroupBadge(gid interface{}, badge int, opt *EditGroupBadgeOptions, options ...OptionFunc) (*GroupBadge, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/badges/%d", pathEscape(group), badge)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gb := new(GroupBadge)
+	resp, err := s.client.Do(req, gb)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gb, resp, err
+}
+
+// DeleteGroupBadge removes a badge from a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html#remove-a-badge-from-a-group
+func (s *GroupBadgesService) DeleteGroupBadge(gid interface{}, badge int, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/badges/%d", pathEscape(group), badge)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// GroupBadgePreviewOptions represents the available PreviewGroupBadge() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html#preview-a-badge-from-a-group
+type GroupBadgePreviewOptions struct {
+	LinkURL  *string `url:"link_url,omitempty" json:"link_url,omitempty"`
+	ImageURL *string `url:"image_url,omitempty" json:"image_url,omitempty"`
+}
+
+// PreviewGroupBadge returns how the link_url and image_url final URLs would be after
+// resolving the placeholder interpolation.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_badges.html#preview-a-badge-from-a-group
+func (s *GroupBadgesService) PreviewGroupBadge(gid interface{}, opt *GroupBadgePreviewOptions, options ...OptionFunc) (*GroupBadge, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/badges/render", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gb := new(GroupBadge)
+	resp, err := s.client.Do(req, &gb)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gb, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/group_boards.go b/vendor/github.com/xanzy/go-gitlab/group_boards.go
new file mode 100644
index 000000000..1e814c57f
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/group_boards.go
@@ -0,0 +1,261 @@
+//
+// Copyright 2018, Patrick Webster
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+)
+
+// GroupIssueBoardsService handles communication with the group issue board
+// related methods of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html
+type GroupIssueBoardsService struct {
+	client *Client
+}
+
+// GroupIssueBoard represents a GitLab group issue board.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html
+type GroupIssueBoard struct {
+	ID        int          `json:"id"`
+	Name      string       `json:"name"`
+	Group     *Group       `json:"group"`
+	Milestone *Milestone   `json:"milestone"`
+	Lists     []*BoardList `json:"lists"`
+}
+
+func (b GroupIssueBoard) String() string {
+	return Stringify(b)
+}
+
+// ListGroupIssueBoardsOptions represents the available
+// ListGroupIssueBoards() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html#group-board
+type ListGroupIssueBoardsOptions ListOptions
+
+// ListGroupIssueBoards gets a list of all issue boards in a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html#group-board
+func (s *GroupIssueBoardsService) ListGroupIssueBoards(gid interface{}, opt *ListGroupIssueBoardsOptions, options ...OptionFunc) ([]*GroupIssueBoard, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/boards", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var gs []*GroupIssueBoard
+	resp, err := s.client.Do(req, &gs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gs, resp, err
+}
+
+// GetGroupIssueBoard gets a single issue board of a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html#single-board
+func (s *GroupIssueBoardsService) GetGroupIssueBoard(gid interface{}, board int, options ...OptionFunc) (*GroupIssueBoard, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/boards/%d", pathEscape(group), board)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gib := new(GroupIssueBoard)
+	resp, err := s.client.Do(req, gib)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gib, resp, err
+}
+
+// ListGroupIssueBoardListsOptions represents the available
+// ListGroupIssueBoardLists() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html#list-board-lists
+type ListGroupIssueBoardListsOptions ListOptions
+
+// ListGroupIssueBoardLists gets a list of the issue board's lists. Does not include
+// backlog and closed lists.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/group_boards.html#list-board-lists
+func (s *GroupIssueBoardsService) ListGroupIssueBoardLists(gid interface{}, board int, opt *ListGroupIssueBoardListsOptions, options ...OptionFunc) ([]*BoardList, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/boards/%d/lists", pathEscape(group), board)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var gbl []*BoardList
+	resp, err := s.client.Do(req, &gbl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gbl, resp, err
+}
+
+// GetGroupIssueBoardList gets a single issue board list.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html#single-board-list
+func (s *GroupIssueBoardsService) GetGroupIssueBoardList(gid interface{}, board, list int, options ...OptionFunc) (*BoardList, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/boards/%d/lists/%d",
+		pathEscape(group),
+		board,
+		list,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gbl := new(BoardList)
+	resp, err := s.client.Do(req, gbl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gbl, resp, err
+}
+
+// CreateGroupIssueBoardListOptions represents the available
+// CreateGroupIssueBoardList() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html#new-board-list
+type CreateGroupIssueBoardListOptions struct {
+	LabelID *int `url:"label_id" json:"label_id"`
+}
+
+// CreateGroupIssueBoardList creates a new issue board list.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html#new-board-list
+func (s *GroupIssueBoardsService) CreateGroupIssueBoardList(gid interface{}, board int, opt *CreateGroupIssueBoardListOptions, options ...OptionFunc) (*BoardList, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/boards/%d/lists", pathEscape(group), board)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gbl := new(BoardList)
+	resp, err := s.client.Do(req, gbl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gbl, resp, err
+}
+
+// UpdateGroupIssueBoardListOptions represents the available
+// UpdateGroupIssueBoardList() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html#edit-board-list
+type UpdateGroupIssueBoardListOptions struct {
+	Position *int `url:"position" json:"position"`
+}
+
+// UpdateIssueBoardList updates the position of an existing
+// group issue board list.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html#edit-board-list
+func (s *GroupIssueBoardsService) UpdateIssueBoardList(gid interface{}, board, list int, opt *UpdateGroupIssueBoardListOptions, options ...OptionFunc) ([]*BoardList, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/boards/%d/lists/%d",
+		pathEscape(group),
+		board,
+		list,
+	)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var gbl []*BoardList
+	resp, err := s.client.Do(req, gbl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gbl, resp, err
+}
+
+// DeleteGroupIssueBoardList soft deletes a group issue board list.
+// Only for admins and group owners.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_boards.html#delete-a-board-list
+func (s *GroupIssueBoardsService) DeleteGroupIssueBoardList(gid interface{}, board, list int, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/boards/%d/lists/%d",
+		pathEscape(group),
+		board,
+		list,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/group_clusters.go b/vendor/github.com/xanzy/go-gitlab/group_clusters.go
new file mode 100644
index 000000000..9f38bc0b0
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/group_clusters.go
@@ -0,0 +1,211 @@
+//
+// Copyright 2019, Paul Shoemaker
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// GroupClustersService handles communication with the
+// group clusters related methods of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_clusters.html
+type GroupClustersService struct {
+	client *Client
+}
+
+// GroupCluster represents a GitLab Group Cluster.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/group_clusters.html
+type GroupCluster struct {
+	ID                 int                 `json:"id"`
+	Name               string              `json:"name"`
+	Domain             string              `json:"domain"`
+	CreatedAt          *time.Time          `json:"created_at"`
+	ProviderType       string              `json:"provider_type"`
+	PlatformType       string              `json:"platform_type"`
+	EnvironmentScope   string              `json:"environment_scope"`
+	ClusterType        string              `json:"cluster_type"`
+	User               *User               `json:"user"`
+	PlatformKubernetes *PlatformKubernetes `json:"platform_kubernetes"`
+	Group              *Group              `json:"group"`
+}
+
+func (v GroupCluster) String() string {
+	return Stringify(v)
+}
+
+// ListClusters gets a list of all clusters in a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_clusters.html#list-group-clusters
+func (s *GroupClustersService) ListClusters(pid interface{}, options ...OptionFunc) ([]*GroupCluster, *Response, error) {
+	group, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/clusters", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pcs []*GroupCluster
+	resp, err := s.client.Do(req, &pcs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pcs, resp, err
+}
+
+// GetCluster gets a cluster.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_clusters.html#get-a-single-group-cluster
+func (s *GroupClustersService) GetCluster(pid interface{}, cluster int, options ...OptionFunc) (*GroupCluster, *Response, error) {
+	group, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/clusters/%d", pathEscape(group), cluster)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pc := new(GroupCluster)
+	resp, err := s.client.Do(req, &pc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pc, resp, err
+}
+
+// AddGroupClusterOptions represents the available AddCluster() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_clusters.html#add-existing-cluster-to-group
+type AddGroupClusterOptions struct {
+	Name               *string                            `url:"name,omitempty" json:"name,omitempty"`
+	Domain             *string                            `url:"domain,omitempty" json:"domain,omitempty"`
+	Enabled            *bool                              `url:"enabled,omitempty" json:"enabled,omitempty"`
+	Managed            *bool                              `url:"managed,omitempty" json:"managed,omitempty"`
+	EnvironmentScope   *string                            `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
+	PlatformKubernetes *AddGroupPlatformKubernetesOptions `url:"platform_kubernetes_attributes,omitempty" json:"platform_kubernetes_attributes,omitempty"`
+}
+
+// AddGroupPlatformKubernetesOptions represents the available PlatformKubernetes options for adding.
+type AddGroupPlatformKubernetesOptions struct {
+	APIURL            *string `url:"api_url,omitempty" json:"api_url,omitempty"`
+	Token             *string `url:"token,omitempty" json:"token,omitempty"`
+	CaCert            *string `url:"ca_cert,omitempty" json:"ca_cert,omitempty"`
+	Namespace         *string `url:"namespace,omitempty" json:"namespace,omitempty"`
+	AuthorizationType *string `url:"authorization_type,omitempty" json:"authorization_type,omitempty"`
+}
+
+// AddCluster adds an existing cluster to the group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_clusters.html#add-existing-cluster-to-group
+func (s *GroupClustersService) AddCluster(pid interface{}, opt *AddGroupClusterOptions, options ...OptionFunc) (*GroupCluster, *Response, error) {
+	group, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/clusters/user", pathEscape(group))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pc := new(GroupCluster)
+	resp, err := s.client.Do(req, pc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pc, resp, err
+}
+
+// EditGroupClusterOptions represents the available EditCluster() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_clusters.html#edit-group-cluster
+type EditGroupClusterOptions struct {
+	Name               *string                             `url:"name,omitempty" json:"name,omitempty"`
+	Domain             *string                             `url:"domain,omitempty" json:"domain,omitempty"`
+	EnvironmentScope   *string                             `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
+	PlatformKubernetes *EditGroupPlatformKubernetesOptions `url:"platform_kubernetes_attributes,omitempty" json:"platform_kubernetes_attributes,omitempty"`
+}
+
+// EditGroupPlatformKubernetesOptions represents the available PlatformKubernetes options for editing.
+type EditGroupPlatformKubernetesOptions struct {
+	APIURL *string `url:"api_url,omitempty" json:"api_url,omitempty"`
+	Token  *string `url:"token,omitempty" json:"token,omitempty"`
+	CaCert *string `url:"ca_cert,omitempty" json:"ca_cert,omitempty"`
+}
+
+// EditCluster updates an existing group cluster.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_clusters.html#edit-group-cluster
+func (s *GroupClustersService) EditCluster(pid interface{}, cluster int, opt *EditGroupClusterOptions, options ...OptionFunc) (*GroupCluster, *Response, error) {
+	group, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/clusters/%d", pathEscape(group), cluster)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pc := new(GroupCluster)
+	resp, err := s.client.Do(req, pc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pc, resp, err
+}
+
+// DeleteCluster deletes an existing group cluster.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_clusters.html#delete-group-cluster
+func (s *GroupClustersService) DeleteCluster(pid interface{}, cluster int, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/clusters/%d", pathEscape(group), cluster)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/group_labels.go b/vendor/github.com/xanzy/go-gitlab/group_labels.go
new file mode 100644
index 000000000..4e7332811
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/group_labels.go
@@ -0,0 +1,196 @@
+package gitlab
+
+import (
+	"fmt"
+)
+
+// GroupLabelsService handles communication with the label related methods of the
+// GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/group_labels.html
+type GroupLabelsService struct {
+	client *Client
+}
+
+// GroupLabel represents a GitLab group label.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/group_labels.html
+type GroupLabel Label
+
+func (l GroupLabel) String() string {
+	return Stringify(l)
+}
+
+// ListGroupLabelsOptions represents the available ListGroupLabels() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#list-labels
+type ListGroupLabelsOptions ListOptions
+
+// ListGroupLabels gets all labels for given group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_labels.html#list-group-labels
+func (s *GroupLabelsService) ListGroupLabels(gid interface{}, opt *ListGroupLabelsOptions, options ...OptionFunc) ([]*GroupLabel, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/labels", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var l []*GroupLabel
+	resp, err := s.client.Do(req, &l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// CreateGroupLabelOptions represents the available CreateGroupLabel() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_labels.html#create-a-new-group-label
+type CreateGroupLabelOptions CreateLabelOptions
+
+// CreateGroupLabel creates a new label for given group with given name and
+// color.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_labels.html#create-a-new-group-label
+func (s *GroupLabelsService) CreateGroupLabel(gid interface{}, opt *CreateGroupLabelOptions, options ...OptionFunc) (*GroupLabel, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/labels", pathEscape(group))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(GroupLabel)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// DeleteGroupLabelOptions represents the available DeleteGroupLabel() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_labels.html#delete-a-group-label
+type DeleteGroupLabelOptions DeleteLabelOptions
+
+// DeleteGroupLabel deletes a group label given by its name.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#delete-a-label
+func (s *GroupLabelsService) DeleteGroupLabel(gid interface{}, opt *DeleteGroupLabelOptions, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/labels", pathEscape(group))
+
+	req, err := s.client.NewRequest("DELETE", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// UpdateGroupLabelOptions represents the available UpdateGroupLabel() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_labels.html#update-a-group-label
+type UpdateGroupLabelOptions UpdateLabelOptions
+
+// UpdateGroupLabel updates an existing label with new name or now color. At least
+// one parameter is required, to update the label.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_labels.html#update-a-group-label
+func (s *GroupLabelsService) UpdateGroupLabel(gid interface{}, opt *UpdateGroupLabelOptions, options ...OptionFunc) (*GroupLabel, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/labels", pathEscape(group))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(GroupLabel)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// SubscribeToGroupLabel subscribes the authenticated user to a label to receive
+// notifications. If the user is already subscribed to the label, the status
+// code 304 is returned.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_labels.html#subscribe-to-a-group-label
+func (s *GroupLabelsService) SubscribeToGroupLabel(gid interface{}, labelID interface{}, options ...OptionFunc) (*GroupLabel, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	label, err := parseID(labelID)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/labels/%s/subscribe", pathEscape(group), label)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(GroupLabel)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// UnsubscribeFromGroupLabel unsubscribes the authenticated user from a label to not
+// receive notifications from it. If the user is not subscribed to the label, the
+// status code 304 is returned.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_labels.html#unsubscribe-from-a-group-label
+func (s *GroupLabelsService) UnsubscribeFromGroupLabel(gid interface{}, labelID interface{}, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	label, err := parseID(labelID)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/labels/%s/unsubscribe", pathEscape(group), label)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/group_members.go b/vendor/github.com/xanzy/go-gitlab/group_members.go
new file mode 100644
index 000000000..5d6f5e603
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/group_members.go
@@ -0,0 +1,219 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+)
+
+// GroupMembersService handles communication with the group members
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/members.html
+type GroupMembersService struct {
+	client *Client
+}
+
+// GroupMember represents a GitLab group member.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/members.html
+type GroupMember struct {
+	ID          int              `json:"id"`
+	Username    string           `json:"username"`
+	Name        string           `json:"name"`
+	State       string           `json:"state"`
+	AvatarURL   string           `json:"avatar_url"`
+	WebURL      string           `json:"web_url"`
+	ExpiresAt   *ISOTime         `json:"expires_at"`
+	AccessLevel AccessLevelValue `json:"access_level"`
+}
+
+// ListGroupMembersOptions represents the available ListGroupMembers() and
+// ListAllGroupMembers() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project
+type ListGroupMembersOptions struct {
+	ListOptions
+	Query *string `url:"query,omitempty" json:"query,omitempty"`
+}
+
+// ListGroupMembers get a list of group members viewable by the authenticated
+// user. Inherited members through ancestor groups are not included.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project
+func (s *GroupsService) ListGroupMembers(gid interface{}, opt *ListGroupMembersOptions, options ...OptionFunc) ([]*GroupMember, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/members", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var gm []*GroupMember
+	resp, err := s.client.Do(req, &gm)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gm, resp, err
+}
+
+// ListAllGroupMembers get a list of group members viewable by the authenticated
+// user. Returns a list including inherited members through ancestor groups.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project-including-inherited-members
+func (s *GroupsService) ListAllGroupMembers(gid interface{}, opt *ListGroupMembersOptions, options ...OptionFunc) ([]*GroupMember, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/members/all", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var gm []*GroupMember
+	resp, err := s.client.Do(req, &gm)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gm, resp, err
+}
+
+// AddGroupMemberOptions represents the available AddGroupMember() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project
+type AddGroupMemberOptions struct {
+	UserID      *int              `url:"user_id,omitempty" json:"user_id,omitempty"`
+	AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+	ExpiresAt   *string           `url:"expires_at,omitempty" json:"expires_at"`
+}
+
+// GetGroupMember gets a member of a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#get-a-member-of-a-group-or-project
+func (s *GroupMembersService) GetGroupMember(gid interface{}, user int, options ...OptionFunc) (*GroupMember, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/members/%d", pathEscape(group), user)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gm := new(GroupMember)
+	resp, err := s.client.Do(req, gm)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gm, resp, err
+}
+
+// AddGroupMember adds a user to the list of group members.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project
+func (s *GroupMembersService) AddGroupMember(gid interface{}, opt *AddGroupMemberOptions, options ...OptionFunc) (*GroupMember, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/members", pathEscape(group))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gm := new(GroupMember)
+	resp, err := s.client.Do(req, gm)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gm, resp, err
+}
+
+// EditGroupMemberOptions represents the available EditGroupMember()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project
+type EditGroupMemberOptions struct {
+	AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+	ExpiresAt   *string           `url:"expires_at,omitempty" json:"expires_at"`
+}
+
+// EditGroupMember updates a member of a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project
+func (s *GroupMembersService) EditGroupMember(gid interface{}, user int, opt *EditGroupMemberOptions, options ...OptionFunc) (*GroupMember, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/members/%d", pathEscape(group), user)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	gm := new(GroupMember)
+	resp, err := s.client.Do(req, gm)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return gm, resp, err
+}
+
+// RemoveGroupMember removes user from user team.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#remove-a-member-from-a-group-or-project
+func (s *GroupMembersService) RemoveGroupMember(gid interface{}, user int, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/members/%d", pathEscape(group), user)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/group_milestones.go b/vendor/github.com/xanzy/go-gitlab/group_milestones.go
new file mode 100644
index 000000000..d9436c65b
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/group_milestones.go
@@ -0,0 +1,249 @@
+//
+// Copyright 2018, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// GroupMilestonesService handles communication with the milestone related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/group_milestones.html
+type GroupMilestonesService struct {
+	client *Client
+}
+
+// GroupMilestone represents a GitLab milestone.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/group_milestones.html
+type GroupMilestone struct {
+	ID          int        `json:"id"`
+	IID         int        `json:"iid"`
+	GroupID     int        `json:"group_id"`
+	Title       string     `json:"title"`
+	Description string     `json:"description"`
+	StartDate   *ISOTime   `json:"start_date"`
+	DueDate     *ISOTime   `json:"due_date"`
+	State       string     `json:"state"`
+	UpdatedAt   *time.Time `json:"updated_at"`
+	CreatedAt   *time.Time `json:"created_at"`
+}
+
+func (m GroupMilestone) String() string {
+	return Stringify(m)
+}
+
+// ListGroupMilestonesOptions represents the available
+// ListGroupMilestones() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#list-group-milestones
+type ListGroupMilestonesOptions struct {
+	ListOptions
+	IIDs   []int  `url:"iids,omitempty" json:"iids,omitempty"`
+	State  string `url:"state,omitempty" json:"state,omitempty"`
+	Search string `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListGroupMilestones returns a list of group milestones.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#list-group-milestones
+func (s *GroupMilestonesService) ListGroupMilestones(gid interface{}, opt *ListGroupMilestonesOptions, options ...OptionFunc) ([]*GroupMilestone, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/milestones", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var m []*GroupMilestone
+	resp, err := s.client.Do(req, &m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// GetGroupMilestone gets a single group milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#get-single-milestone
+func (s *GroupMilestonesService) GetGroupMilestone(gid interface{}, milestone int, options ...OptionFunc) (*GroupMilestone, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/milestones/%d", pathEscape(group), milestone)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(GroupMilestone)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// CreateGroupMilestoneOptions represents the available CreateGroupMilestone() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#create-new-milestone
+type CreateGroupMilestoneOptions struct {
+	Title       *string  `url:"title,omitempty" json:"title,omitempty"`
+	Description *string  `url:"description,omitempty" json:"description,omitempty"`
+	StartDate   *ISOTime `url:"start_date,omitempty" json:"start_date,omitempty"`
+	DueDate     *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"`
+}
+
+// CreateGroupMilestone creates a new group milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#create-new-milestone
+func (s *GroupMilestonesService) CreateGroupMilestone(gid interface{}, opt *CreateGroupMilestoneOptions, options ...OptionFunc) (*GroupMilestone, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/milestones", pathEscape(group))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(GroupMilestone)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// UpdateGroupMilestoneOptions represents the available UpdateGroupMilestone() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#edit-milestone
+type UpdateGroupMilestoneOptions struct {
+	Title       *string  `url:"title,omitempty" json:"title,omitempty"`
+	Description *string  `url:"description,omitempty" json:"description,omitempty"`
+	StartDate   *ISOTime `url:"start_date,omitempty" json:"start_date,omitempty"`
+	DueDate     *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"`
+	StateEvent  *string  `url:"state_event,omitempty" json:"state_event,omitempty"`
+}
+
+// UpdateGroupMilestone updates an existing group milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#edit-milestone
+func (s *GroupMilestonesService) UpdateGroupMilestone(gid interface{}, milestone int, opt *UpdateGroupMilestoneOptions, options ...OptionFunc) (*GroupMilestone, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/milestones/%d", pathEscape(group), milestone)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(GroupMilestone)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// GetGroupMilestoneIssuesOptions represents the available GetGroupMilestoneIssues() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#get-all-issues-assigned-to-a-single-milestone
+type GetGroupMilestoneIssuesOptions ListOptions
+
+// GetGroupMilestoneIssues gets all issues assigned to a single group milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#get-all-issues-assigned-to-a-single-milestone
+func (s *GroupMilestonesService) GetGroupMilestoneIssues(gid interface{}, milestone int, opt *GetGroupMilestoneIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/milestones/%d/issues", pathEscape(group), milestone)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var i []*Issue
+	resp, err := s.client.Do(req, &i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// GetGroupMilestoneMergeRequestsOptions represents the available
+// GetGroupMilestoneMergeRequests() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#get-all-merge-requests-assigned-to-a-single-milestone
+type GetGroupMilestoneMergeRequestsOptions ListOptions
+
+// GetGroupMilestoneMergeRequests gets all merge requests assigned to a
+// single group milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/group_milestones.html#get-all-merge-requests-assigned-to-a-single-milestone
+func (s *GroupMilestonesService) GetGroupMilestoneMergeRequests(gid interface{}, milestone int, opt *GetGroupMilestoneMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/milestones/%d/merge_requests", pathEscape(group), milestone)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var mr []*MergeRequest
+	resp, err := s.client.Do(req, &mr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return mr, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/group_variables.go b/vendor/github.com/xanzy/go-gitlab/group_variables.go
new file mode 100644
index 000000000..694b20199
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/group_variables.go
@@ -0,0 +1,196 @@
+//
+// Copyright 2018, Patrick Webster
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+)
+
+// GroupVariablesService handles communication with the
+// group variables related methods of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_level_variables.html
+type GroupVariablesService struct {
+	client *Client
+}
+
+// GroupVariable represents a GitLab group Variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_level_variables.html
+type GroupVariable struct {
+	Key          string            `json:"key"`
+	Value        string            `json:"value"`
+	VariableType VariableTypeValue `json:"variable_type"`
+	Protected    bool              `json:"protected"`
+}
+
+func (v GroupVariable) String() string {
+	return Stringify(v)
+}
+
+// ListGroupVariablesOptions represents the available options for listing variables
+// for a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_level_variables.html#list-group-variables
+type ListGroupVariablesOptions ListOptions
+
+// ListVariables gets a list of all variables for a group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_level_variables.html#list-group-variables
+func (s *GroupVariablesService) ListVariables(gid interface{}, opt *ListGroupVariablesOptions, options ...OptionFunc) ([]*GroupVariable, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/variables", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var vs []*GroupVariable
+	resp, err := s.client.Do(req, &vs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return vs, resp, err
+}
+
+// GetVariable gets a variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_level_variables.html#show-variable-details
+func (s *GroupVariablesService) GetVariable(gid interface{}, key string, options ...OptionFunc) (*GroupVariable, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/variables/%s", pathEscape(group), url.PathEscape(key))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	v := new(GroupVariable)
+	resp, err := s.client.Do(req, v)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return v, resp, err
+}
+
+// CreateGroupVariableOptions represents the available CreateVariable()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_level_variables.html#create-variable
+type CreateGroupVariableOptions struct {
+	Key          *string            `url:"key,omitempty" json:"key,omitempty"`
+	Value        *string            `url:"value,omitempty" json:"value,omitempty"`
+	VariableType *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"`
+	Protected    *bool              `url:"protected,omitempty" json:"protected,omitempty"`
+}
+
+// CreateVariable creates a new group variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_level_variables.html#create-variable
+func (s *GroupVariablesService) CreateVariable(gid interface{}, opt *CreateGroupVariableOptions, options ...OptionFunc) (*GroupVariable, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/variables", pathEscape(group))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	v := new(GroupVariable)
+	resp, err := s.client.Do(req, v)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return v, resp, err
+}
+
+// UpdateGroupVariableOptions represents the available UpdateVariable()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_level_variables.html#update-variable
+type UpdateGroupVariableOptions struct {
+	Value        *string            `url:"value,omitempty" json:"value,omitempty"`
+	VariableType *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"`
+	Protected    *bool              `url:"protected,omitempty" json:"protected,omitempty"`
+}
+
+// UpdateVariable updates the position of an existing
+// group issue board list.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_level_variables.html#update-variable
+func (s *GroupVariablesService) UpdateVariable(gid interface{}, key string, opt *UpdateGroupVariableOptions, options ...OptionFunc) (*GroupVariable, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/variables/%s", pathEscape(group), url.PathEscape(key))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	v := new(GroupVariable)
+	resp, err := s.client.Do(req, v)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return v, resp, err
+}
+
+// RemoveVariable removes a group's variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/group_level_variables.html#remove-variable
+func (s *GroupVariablesService) RemoveVariable(gid interface{}, key string, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/variables/%s", pathEscape(group), url.PathEscape(key))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/groups.go b/vendor/github.com/xanzy/go-gitlab/groups.go
new file mode 100644
index 000000000..b2e7dd326
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/groups.go
@@ -0,0 +1,335 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+)
+
+// GroupsService handles communication with the group related methods of
+// the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html
+type GroupsService struct {
+	client *Client
+}
+
+// Group represents a GitLab group.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html
+type Group struct {
+	ID                             int                `json:"id"`
+	Name                           string             `json:"name"`
+	Path                           string             `json:"path"`
+	Description                    string             `json:"description"`
+	Visibility                     *VisibilityValue   `json:"visibility"`
+	LFSEnabled                     bool               `json:"lfs_enabled"`
+	AvatarURL                      string             `json:"avatar_url"`
+	WebURL                         string             `json:"web_url"`
+	RequestAccessEnabled           bool               `json:"request_access_enabled"`
+	FullName                       string             `json:"full_name"`
+	FullPath                       string             `json:"full_path"`
+	ParentID                       int                `json:"parent_id"`
+	Projects                       []*Project         `json:"projects"`
+	Statistics                     *StorageStatistics `json:"statistics"`
+	CustomAttributes               []*CustomAttribute `json:"custom_attributes"`
+	ShareWithGroupLock             bool               `json:"share_with_group_lock"`
+	RequireTwoFactorAuth           bool               `json:"require_two_factor_authentication"`
+	TwoFactorGracePeriod           int                `json:"two_factor_grace_period"`
+	ProjectCreationLevel           string             `json:"project_creation_level"`
+	AutoDevopsEnabled              bool               `json:"auto_devops_enabled"`
+	SubGroupCreationLevel          string             `json:"subgroup_creation_level"`
+	EmailsDisabled                 bool               `json:"emails_disabled"`
+	RunnersToken                   string             `json:"runners_token"`
+	SharedProjects                 []*Project         `json:"shared_projects"`
+	LDAPCN                         string             `json:"ldap_cn"`
+	LDAPAccess                     bool               `json:"ldap_access"`
+	SharedRunnersMinutesLimit      int                `json:"shared_runners_minutes_limit"`
+	ExtraSharedRunnersMinutesLimit int                `json:"extra_shared_runners_minutes_limit"`
+}
+
+// ListGroupsOptions represents the available ListGroups() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#list-project-groups
+type ListGroupsOptions struct {
+	ListOptions
+	AllAvailable         *bool             `url:"all_available,omitempty" json:"all_available,omitempty"`
+	MinAccessLevel       *AccessLevelValue `url:"min_access_level,omitempty" json:"min_access_level,omitempty"`
+	OrderBy              *string           `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Owned                *bool             `url:"owned,omitempty" json:"owned,omitempty"`
+	Search               *string           `url:"search,omitempty" json:"search,omitempty"`
+	SkipGroups           []int             `url:"skip_groups,omitempty" json:"skip_groups,omitempty"`
+	Sort                 *string           `url:"sort,omitempty" json:"sort,omitempty"`
+	Statistics           *bool             `url:"statistics,omitempty" json:"statistics,omitempty"`
+	WithCustomAttributes *bool             `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"`
+}
+
+// ListGroups gets a list of groups (as user: my groups, as admin: all groups).
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/groups.html#list-project-groups
+func (s *GroupsService) ListGroups(opt *ListGroupsOptions, options ...OptionFunc) ([]*Group, *Response, error) {
+	req, err := s.client.NewRequest("GET", "groups", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var g []*Group
+	resp, err := s.client.Do(req, &g)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return g, resp, err
+}
+
+// GetGroup gets all details of a group.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#details-of-a-group
+func (s *GroupsService) GetGroup(gid interface{}, options ...OptionFunc) (*Group, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	g := new(Group)
+	resp, err := s.client.Do(req, g)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return g, resp, err
+}
+
+// CreateGroupOptions represents the available CreateGroup() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#new-group
+type CreateGroupOptions struct {
+	Name                 *string          `url:"name,omitempty" json:"name,omitempty"`
+	Path                 *string          `url:"path,omitempty" json:"path,omitempty"`
+	Description          *string          `url:"description,omitempty" json:"description,omitempty"`
+	Visibility           *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+	LFSEnabled           *bool            `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"`
+	RequestAccessEnabled *bool            `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"`
+	ParentID             *int             `url:"parent_id,omitempty" json:"parent_id,omitempty"`
+}
+
+// CreateGroup creates a new project group. Available only for users who can
+// create groups.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#new-group
+func (s *GroupsService) CreateGroup(opt *CreateGroupOptions, options ...OptionFunc) (*Group, *Response, error) {
+	req, err := s.client.NewRequest("POST", "groups", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	g := new(Group)
+	resp, err := s.client.Do(req, g)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return g, resp, err
+}
+
+// TransferGroup transfers a project to the Group namespace. Available only
+// for admin.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/groups.html#transfer-project-to-group
+func (s *GroupsService) TransferGroup(gid interface{}, pid interface{}, options ...OptionFunc) (*Group, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/projects/%s", pathEscape(group), pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	g := new(Group)
+	resp, err := s.client.Do(req, g)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return g, resp, err
+}
+
+// UpdateGroupOptions represents the set of available options to update a Group;
+// as of today these are exactly the same available when creating a new Group.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#update-group
+type UpdateGroupOptions CreateGroupOptions
+
+// UpdateGroup updates an existing group; only available to group owners and
+// administrators.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#update-group
+func (s *GroupsService) UpdateGroup(gid interface{}, opt *UpdateGroupOptions, options ...OptionFunc) (*Group, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s", pathEscape(group))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	g := new(Group)
+	resp, err := s.client.Do(req, g)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return g, resp, err
+}
+
+// DeleteGroup removes group with all projects inside.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#remove-group
+func (s *GroupsService) DeleteGroup(gid interface{}, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s", pathEscape(group))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// SearchGroup get all groups that match your string in their name or path.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/groups.html#search-for-group
+func (s *GroupsService) SearchGroup(query string, options ...OptionFunc) ([]*Group, *Response, error) {
+	var q struct {
+		Search string `url:"search,omitempty" json:"search,omitempty"`
+	}
+	q.Search = query
+
+	req, err := s.client.NewRequest("GET", "groups", &q, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var g []*Group
+	resp, err := s.client.Do(req, &g)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return g, resp, err
+}
+
+// ListGroupProjectsOptions represents the available ListGroupProjects()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/groups.html#list-a-group-39-s-projects
+type ListGroupProjectsOptions struct {
+	ListOptions
+	Archived                 *bool            `url:"archived,omitempty" json:"archived,omitempty"`
+	Visibility               *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+	OrderBy                  *string          `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort                     *string          `url:"sort,omitempty" json:"sort,omitempty"`
+	Search                   *string          `url:"search,omitempty" json:"search,omitempty"`
+	Simple                   *bool            `url:"simple,omitempty" json:"simple,omitempty"`
+	Owned                    *bool            `url:"owned,omitempty" json:"owned,omitempty"`
+	Starred                  *bool            `url:"starred,omitempty" json:"starred,omitempty"`
+	WithIssuesEnabled        *bool            `url:"with_issues_enabled,omitempty" json:"with_issues_enabled,omitempty"`
+	WithMergeRequestsEnabled *bool            `url:"with_merge_requests_enabled,omitempty" json:"with_merge_requests_enabled,omitempty"`
+	WithShared               *bool            `url:"with_shared,omitempty" json:"with_shared,omitempty"`
+	IncludeSubgroups         *bool            `url:"include_subgroups,omitempty" json:"include_subgroups,omitempty"`
+	WithCustomAttributes     *bool            `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"`
+}
+
+// ListGroupProjects get a list of group projects
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/groups.html#list-a-group-39-s-projects
+func (s *GroupsService) ListGroupProjects(gid interface{}, opt *ListGroupProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/projects", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var p []*Project
+	resp, err := s.client.Do(req, &p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// ListSubgroupsOptions represents the available ListSubgroupsOptions()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/groups.html#list-a-groups-s-subgroups
+type ListSubgroupsOptions ListGroupsOptions
+
+// ListSubgroups gets a list of subgroups for a given project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/groups.html#list-a-groups-s-subgroups
+func (s *GroupsService) ListSubgroups(gid interface{}, opt *ListSubgroupsOptions, options ...OptionFunc) ([]*Group, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/subgroups", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var g []*Group
+	resp, err := s.client.Do(req, &g)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return g, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/issue_links.go b/vendor/github.com/xanzy/go-gitlab/issue_links.go
new file mode 100644
index 000000000..f310d7915
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/issue_links.go
@@ -0,0 +1,127 @@
+//
+// Copyright 2017, Arkbriar
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+)
+
+// IssueLinksService handles communication with the issue relations related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/issue_links.html
+type IssueLinksService struct {
+	client *Client
+}
+
+// IssueLink represents a two-way relation between two issues.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/issue_links.html
+type IssueLink struct {
+	SourceIssue *Issue `json:"source_issue"`
+	TargetIssue *Issue `json:"target_issue"`
+}
+
+// ListIssueRelations gets a list of related issues of a given issue,
+// sorted by the relationship creation datetime (ascending).
+//
+// Issues will be filtered according to the user authorizations.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/issue_links.html#list-issue-relations
+func (s *IssueLinksService) ListIssueRelations(pid interface{}, issueIID int, options ...OptionFunc) ([]*Issue, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/links", pathEscape(project), issueIID)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var is []*Issue
+	resp, err := s.client.Do(req, &is)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return is, resp, err
+}
+
+// CreateIssueLinkOptions represents the available CreateIssueLink() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/issue_links.html
+type CreateIssueLinkOptions struct {
+	TargetProjectID *string `json:"target_project_id"`
+	TargetIssueIID  *string `json:"target_issue_iid"`
+}
+
+// CreateIssueLink creates a two-way relation between two issues.
+// User must be allowed to update both issues in order to succeed.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/issue_links.html#create-an-issue-link
+func (s *IssueLinksService) CreateIssueLink(pid interface{}, issueIID int, opt *CreateIssueLinkOptions, options ...OptionFunc) (*IssueLink, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/links", pathEscape(project), issueIID)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(IssueLink)
+	resp, err := s.client.Do(req, &i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// DeleteIssueLink deletes an issue link, thus removes the two-way relationship.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/issue_links.html#delete-an-issue-link
+func (s *IssueLinksService) DeleteIssueLink(pid interface{}, issueIID, issueLinkID int, options ...OptionFunc) (*IssueLink, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/links/%d",
+		pathEscape(project),
+		issueIID,
+		issueLinkID)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(IssueLink)
+	resp, err := s.client.Do(req, &i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/issues.go b/vendor/github.com/xanzy/go-gitlab/issues.go
new file mode 100644
index 000000000..70a1255d8
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/issues.go
@@ -0,0 +1,568 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+	"time"
+)
+
+// IssuesService handles communication with the issue related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html
+type IssuesService struct {
+	client    *Client
+	timeStats *timeStatsService
+}
+
+// IssueAuthor represents a author of the issue.
+type IssueAuthor struct {
+	ID        int    `json:"id"`
+	State     string `json:"state"`
+	WebURL    string `json:"web_url"`
+	Name      string `json:"name"`
+	AvatarURL string `json:"avatar_url"`
+	Username  string `json:"username"`
+}
+
+// IssueAssignee represents a assignee of the issue.
+type IssueAssignee struct {
+	ID        int    `json:"id"`
+	State     string `json:"state"`
+	WebURL    string `json:"web_url"`
+	Name      string `json:"name"`
+	AvatarURL string `json:"avatar_url"`
+	Username  string `json:"username"`
+}
+
+// IssueLinks represents links of the issue.
+type IssueLinks struct {
+	Self       string `json:"self"`
+	Notes      string `json:"notes"`
+	AwardEmoji string `json:"award_emoji"`
+	Project    string `json:"project"`
+}
+
+// Issue represents a GitLab issue.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html
+type Issue struct {
+	ID                int              `json:"id"`
+	IID               int              `json:"iid"`
+	ProjectID         int              `json:"project_id"`
+	Milestone         *Milestone       `json:"milestone"`
+	Author            *IssueAuthor     `json:"author"`
+	Description       string           `json:"description"`
+	State             string           `json:"state"`
+	Assignees         []*IssueAssignee `json:"assignees"`
+	Assignee          *IssueAssignee   `json:"assignee"`
+	Upvotes           int              `json:"upvotes"`
+	Downvotes         int              `json:"downvotes"`
+	Labels            Labels           `json:"labels"`
+	Title             string           `json:"title"`
+	UpdatedAt         *time.Time       `json:"updated_at"`
+	CreatedAt         *time.Time       `json:"created_at"`
+	ClosedAt          *time.Time       `json:"closed_at"`
+	Subscribed        bool             `json:"subscribed"`
+	UserNotesCount    int              `json:"user_notes_count"`
+	DueDate           *ISOTime         `json:"due_date"`
+	WebURL            string           `json:"web_url"`
+	TimeStats         *TimeStats       `json:"time_stats"`
+	Confidential      bool             `json:"confidential"`
+	Weight            int              `json:"weight"`
+	DiscussionLocked  bool             `json:"discussion_locked"`
+	Links             *IssueLinks      `json:"_links"`
+	IssueLinkID       int              `json:"issue_link_id"`
+	MergeRequestCount int              `json:"merge_requests_count"`
+}
+
+func (i Issue) String() string {
+	return Stringify(i)
+}
+
+// Labels is a custom type with specific marshaling characteristics.
+type Labels []string
+
+// MarshalJSON implements the json.Marshaler interface.
+func (l *Labels) MarshalJSON() ([]byte, error) {
+	return json.Marshal(strings.Join(*l, ","))
+}
+
+// ListIssuesOptions represents the available ListIssues() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-issues
+type ListIssuesOptions struct {
+	ListOptions
+	State            *string    `url:"state,omitempty" json:"state,omitempty"`
+	Labels           Labels     `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	WithLabelDetails *bool      `url:"with_labels_details,omitempty" json:"with_labels_details,omitempty"`
+	Milestone        *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
+	Scope            *string    `url:"scope,omitempty" json:"scope,omitempty"`
+	AuthorID         *int       `url:"author_id,omitempty" json:"author_id,omitempty"`
+	AssigneeID       *int       `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+	MyReactionEmoji  *string    `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+	IIDs             []int      `url:"iids[],omitempty" json:"iids,omitempty"`
+	OrderBy          *string    `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort             *string    `url:"sort,omitempty" json:"sort,omitempty"`
+	Search           *string    `url:"search,omitempty" json:"search,omitempty"`
+	CreatedAfter     *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
+	CreatedBefore    *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
+	UpdatedAfter     *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
+	UpdatedBefore    *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"`
+	Confidential     *bool      `url:"confidential,omitempty" json:"confidential,omitempty"`
+}
+
+// ListIssues gets all issues created by authenticated user. This function
+// takes pagination parameters page and per_page to restrict the list of issues.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-issues
+func (s *IssuesService) ListIssues(opt *ListIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+	req, err := s.client.NewRequest("GET", "issues", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var i []*Issue
+	resp, err := s.client.Do(req, &i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// ListGroupIssuesOptions represents the available ListGroupIssues() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-group-issues
+type ListGroupIssuesOptions struct {
+	ListOptions
+	State           *string    `url:"state,omitempty" json:"state,omitempty"`
+	Labels          Labels     `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	IIDs            []int      `url:"iids[],omitempty" json:"iids,omitempty"`
+	Milestone       *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
+	Scope           *string    `url:"scope,omitempty" json:"scope,omitempty"`
+	AuthorID        *int       `url:"author_id,omitempty" json:"author_id,omitempty"`
+	AssigneeID      *int       `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+	MyReactionEmoji *string    `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+	OrderBy         *string    `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort            *string    `url:"sort,omitempty" json:"sort,omitempty"`
+	Search          *string    `url:"search,omitempty" json:"search,omitempty"`
+	In              *string    `url:"in,omitempty" json:"in,omitempty"`
+	CreatedAfter    *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
+	CreatedBefore   *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
+	UpdatedAfter    *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
+	UpdatedBefore   *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"`
+}
+
+// ListGroupIssues gets a list of group issues. This function accepts
+// pagination parameters page and per_page to return the list of group issues.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-group-issues
+func (s *IssuesService) ListGroupIssues(pid interface{}, opt *ListGroupIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+	group, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/issues", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var i []*Issue
+	resp, err := s.client.Do(req, &i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// ListProjectIssuesOptions represents the available ListProjectIssues() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-project-issues
+type ListProjectIssuesOptions struct {
+	ListOptions
+	IIDs             []int      `url:"iids[],omitempty" json:"iids,omitempty"`
+	State            *string    `url:"state,omitempty" json:"state,omitempty"`
+	Labels           Labels     `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	WithLabelDetails *bool      `url:"with_labels_details,omitempty" json:"with_labels_details,omitempty"`
+	Milestone        *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
+	Scope            *string    `url:"scope,omitempty" json:"scope,omitempty"`
+	AuthorID         *int       `url:"author_id,omitempty" json:"author_id,omitempty"`
+	AssigneeID       *int       `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+	MyReactionEmoji  *string    `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+	OrderBy          *string    `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort             *string    `url:"sort,omitempty" json:"sort,omitempty"`
+	Search           *string    `url:"search,omitempty" json:"search,omitempty"`
+	In               *string    `url:"in,omitempty" json:"in,omitempty"`
+	CreatedAfter     *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
+	CreatedBefore    *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
+	UpdatedAfter     *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
+	UpdatedBefore    *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"`
+	Confidential     *bool      `url:"confidential,omitempty" json:"confidential,omitempty"`
+}
+
+// ListProjectIssues gets a list of project issues. This function accepts
+// pagination parameters page and per_page to return the list of project issues.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#list-project-issues
+func (s *IssuesService) ListProjectIssues(pid interface{}, opt *ListProjectIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var i []*Issue
+	resp, err := s.client.Do(req, &i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// GetIssue gets a single project issue.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#single-issues
+func (s *IssuesService) GetIssue(pid interface{}, issue int, options ...OptionFunc) (*Issue, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(Issue)
+	resp, err := s.client.Do(req, i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// CreateIssueOptions represents the available CreateIssue() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#new-issues
+type CreateIssueOptions struct {
+	IID                                *int       `url:"iid,omitempty" json:"iid,omitempty"`
+	Title                              *string    `url:"title,omitempty" json:"title,omitempty"`
+	Description                        *string    `url:"description,omitempty" json:"description,omitempty"`
+	Confidential                       *bool      `url:"confidential,omitempty" json:"confidential,omitempty"`
+	AssigneeIDs                        []int      `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"`
+	MilestoneID                        *int       `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
+	Labels                             *Labels    `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	CreatedAt                          *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+	DueDate                            *ISOTime   `url:"due_date,omitempty" json:"due_date,omitempty"`
+	MergeRequestToResolveDiscussionsOf *int       `url:"merge_request_to_resolve_discussions_of,omitempty" json:"merge_request_to_resolve_discussions_of,omitempty"`
+	DiscussionToResolve                *string    `url:"discussion_to_resolve,omitempty" json:"discussion_to_resolve,omitempty"`
+	Weight                             *int       `url:"weight,omitempty" json:"weight,omitempty"`
+}
+
+// CreateIssue creates a new project issue.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#new-issues
+func (s *IssuesService) CreateIssue(pid interface{}, opt *CreateIssueOptions, options ...OptionFunc) (*Issue, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(Issue)
+	resp, err := s.client.Do(req, i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// UpdateIssueOptions represents the available UpdateIssue() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/issues.html#edit-issue
+type UpdateIssueOptions struct {
+	Title            *string    `url:"title,omitempty" json:"title,omitempty"`
+	Description      *string    `url:"description,omitempty" json:"description,omitempty"`
+	Confidential     *bool      `url:"confidential,omitempty" json:"confidential,omitempty"`
+	AssigneeIDs      []int      `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"`
+	MilestoneID      *int       `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
+	Labels           *Labels    `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	StateEvent       *string    `url:"state_event,omitempty" json:"state_event,omitempty"`
+	UpdatedAt        *time.Time `url:"updated_at,omitempty" json:"updated_at,omitempty"`
+	DueDate          *ISOTime   `url:"due_date,omitempty" json:"due_date,omitempty"`
+	Weight           *int       `url:"weight,omitempty" json:"weight,omitempty"`
+	DiscussionLocked *bool      `url:"discussion_locked,omitempty" json:"discussion_locked,omitempty"`
+}
+
+// UpdateIssue updates an existing project issue. This function is also used
+// to mark an issue as closed.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#edit-issues
+func (s *IssuesService) UpdateIssue(pid interface{}, issue int, opt *UpdateIssueOptions, options ...OptionFunc) (*Issue, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(Issue)
+	resp, err := s.client.Do(req, i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// DeleteIssue deletes a single project issue.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/issues.html#delete-an-issue
+func (s *IssuesService) DeleteIssue(pid interface{}, issue int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// MoveIssueOptions represents the available MoveIssue() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/issues.html#move-an-issue
+type MoveIssueOptions struct {
+	ToProjectID *int `url:"to_project_id,omitempty" json:"to_project_id,omitempty"`
+}
+
+// MoveIssue updates an existing project issue. This function is also used
+// to mark an issue as closed.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/issues.html#move-an-issue
+func (s *IssuesService) MoveIssue(pid interface{}, issue int, opt *MoveIssueOptions, options ...OptionFunc) (*Issue, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/move", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(Issue)
+	resp, err := s.client.Do(req, i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// SubscribeToIssue subscribes the authenticated user to the given issue to
+// receive notifications. If the user is already subscribed to the issue, the
+// status code 304 is returned.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#subscribe-to-a-merge-request
+func (s *IssuesService) SubscribeToIssue(pid interface{}, issue int, options ...OptionFunc) (*Issue, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/subscribe", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(Issue)
+	resp, err := s.client.Do(req, i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// UnsubscribeFromIssue unsubscribes the authenticated user from the given
+// issue to not receive notifications from that merge request. If the user
+// is not subscribed to the issue, status code 304 is returned.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#unsubscribe-from-a-merge-request
+func (s *IssuesService) UnsubscribeFromIssue(pid interface{}, issue int, options ...OptionFunc) (*Issue, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/unsubscribe", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	i := new(Issue)
+	resp, err := s.client.Do(req, i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// ListMergeRequestsClosingIssueOptions represents the available
+// ListMergeRequestsClosingIssue() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#list-merge-requests-that-will-close-issue-on-merge
+type ListMergeRequestsClosingIssueOptions ListOptions
+
+// ListMergeRequestsClosingIssue gets all the merge requests that will close
+// issue when merged.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#list-merge-requests-that-will-close-issue-on-merge
+func (s *IssuesService) ListMergeRequestsClosingIssue(pid interface{}, issue int, opt *ListMergeRequestsClosingIssueOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("/projects/%s/issues/%d/closed_by", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var m []*MergeRequest
+	resp, err := s.client.Do(req, &m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// ListMergeRequestsRelatedToIssueOptions represents the available
+// ListMergeRequestsRelatedToIssue() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#list-merge-requests-related-to-issue
+type ListMergeRequestsRelatedToIssueOptions ListOptions
+
+// ListMergeRequestsRelatedToIssue gets all the merge requests that are
+// related to the issue
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#list-merge-requests-related-to-issue
+func (s *IssuesService) ListMergeRequestsRelatedToIssue(pid interface{}, issue int, opt *ListMergeRequestsRelatedToIssueOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("/projects/%s/issues/%d/related_merge_requests",
+		pathEscape(project),
+		issue,
+	)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var m []*MergeRequest
+	resp, err := s.client.Do(req, &m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// SetTimeEstimate sets the time estimate for a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#set-a-time-estimate-for-an-issue
+func (s *IssuesService) SetTimeEstimate(pid interface{}, issue int, opt *SetTimeEstimateOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+	return s.timeStats.setTimeEstimate(pid, "issues", issue, opt, options...)
+}
+
+// ResetTimeEstimate resets the time estimate for a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#reset-the-time-estimate-for-an-issue
+func (s *IssuesService) ResetTimeEstimate(pid interface{}, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+	return s.timeStats.resetTimeEstimate(pid, "issues", issue, options...)
+}
+
+// AddSpentTime adds spent time for a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#add-spent-time-for-an-issue
+func (s *IssuesService) AddSpentTime(pid interface{}, issue int, opt *AddSpentTimeOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+	return s.timeStats.addSpentTime(pid, "issues", issue, opt, options...)
+}
+
+// ResetSpentTime resets the spent time for a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#reset-spent-time-for-an-issue
+func (s *IssuesService) ResetSpentTime(pid interface{}, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+	return s.timeStats.resetSpentTime(pid, "issues", issue, options...)
+}
+
+// GetTimeSpent gets the spent time for a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/issues.html#get-time-tracking-stats
+func (s *IssuesService) GetTimeSpent(pid interface{}, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+	return s.timeStats.getTimeSpent(pid, "issues", issue, options...)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/jobs.go b/vendor/github.com/xanzy/go-gitlab/jobs.go
new file mode 100644
index 000000000..f0c1e2c46
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/jobs.go
@@ -0,0 +1,408 @@
+//
+// Copyright 2017, Arkbriar
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"time"
+)
+
+// JobsService handles communication with the ci builds related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html
+type JobsService struct {
+	client *Client
+}
+
+// Job represents a ci build.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html
+type Job struct {
+	Commit            *Commit    `json:"commit"`
+	Coverage          float64    `json:"coverage"`
+	AllowFailure      bool       `json:"allow_failure"`
+	CreatedAt         *time.Time `json:"created_at"`
+	StartedAt         *time.Time `json:"started_at"`
+	FinishedAt        *time.Time `json:"finished_at"`
+	Duration          float64    `json:"duration"`
+	ArtifactsExpireAt *time.Time `json:"artifacts_expire_at"`
+	ID                int        `json:"id"`
+	Name              string     `json:"name"`
+	Pipeline          struct {
+		ID     int    `json:"id"`
+		Ref    string `json:"ref"`
+		Sha    string `json:"sha"`
+		Status string `json:"status"`
+	} `json:"pipeline"`
+	Ref       string `json:"ref"`
+	Artifacts []struct {
+		FileType   string `json:"file_type"`
+		Filename   string `json:"filename"`
+		Size       int    `json:"size"`
+		FileFormat string `json:"file_format"`
+	} `json:"artifacts"`
+	ArtifactsFile struct {
+		Filename string `json:"filename"`
+		Size     int    `json:"size"`
+	} `json:"artifacts_file"`
+	Runner struct {
+		ID          int    `json:"id"`
+		Description string `json:"description"`
+		Active      bool   `json:"active"`
+		IsShared    bool   `json:"is_shared"`
+		Name        string `json:"name"`
+	} `json:"runner"`
+	Stage  string `json:"stage"`
+	Status string `json:"status"`
+	Tag    bool   `json:"tag"`
+	WebURL string `json:"web_url"`
+	User   *User  `json:"user"`
+}
+
+// ListJobsOptions are options for two list apis
+type ListJobsOptions struct {
+	ListOptions
+	Scope []BuildStateValue `url:"scope[],omitempty" json:"scope,omitempty"`
+}
+
+// ListProjectJobs gets a list of jobs in a project.
+//
+// The scope of jobs to show, one or array of: created, pending, running,
+// failed, success, canceled, skipped; showing all jobs if none provided
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#list-project-jobs
+func (s *JobsService) ListProjectJobs(pid interface{}, opts *ListJobsOptions, options ...OptionFunc) ([]Job, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/jobs", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opts, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var jobs []Job
+	resp, err := s.client.Do(req, &jobs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return jobs, resp, err
+}
+
+// ListPipelineJobs gets a list of jobs for specific pipeline in a
+// project. If the pipeline ID is not found, it will respond with 404.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#list-pipeline-jobs
+func (s *JobsService) ListPipelineJobs(pid interface{}, pipelineID int, opts *ListJobsOptions, options ...OptionFunc) ([]*Job, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipelines/%d/jobs", pathEscape(project), pipelineID)
+
+	req, err := s.client.NewRequest("GET", u, opts, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var jobs []*Job
+	resp, err := s.client.Do(req, &jobs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return jobs, resp, err
+}
+
+// GetJob gets a single job of a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#get-a-single-job
+func (s *JobsService) GetJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/jobs/%d", pathEscape(project), jobID)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	job := new(Job)
+	resp, err := s.client.Do(req, job)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return job, resp, err
+}
+
+// GetJobArtifacts get jobs artifacts of a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#get-job-artifacts
+func (s *JobsService) GetJobArtifacts(pid interface{}, jobID int, options ...OptionFunc) (io.Reader, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/jobs/%d/artifacts", pathEscape(project), jobID)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	artifactsBuf := new(bytes.Buffer)
+	resp, err := s.client.Do(req, artifactsBuf)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return artifactsBuf, resp, err
+}
+
+// DownloadArtifactsFileOptions represents the available DownloadArtifactsFile()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#download-the-artifacts-file
+type DownloadArtifactsFileOptions struct {
+	Job *string `url:"job" json:"job"`
+}
+
+// DownloadArtifactsFile download the artifacts file from the given
+// reference name and job provided the job finished successfully.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#download-the-artifacts-file
+func (s *JobsService) DownloadArtifactsFile(pid interface{}, refName string, opt *DownloadArtifactsFileOptions, options ...OptionFunc) (io.Reader, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/jobs/artifacts/%s/download", pathEscape(project), refName)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	artifactsBuf := new(bytes.Buffer)
+	resp, err := s.client.Do(req, artifactsBuf)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return artifactsBuf, resp, err
+}
+
+// DownloadSingleArtifactsFile download a file from the artifacts from the
+// given reference name and job provided the job finished successfully.
+// Only a single file is going to be extracted from the archive and streamed
+// to a client.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#download-a-single-artifact-file
+func (s *JobsService) DownloadSingleArtifactsFile(pid interface{}, jobID int, artifactPath string, options ...OptionFunc) (io.Reader, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	u := fmt.Sprintf(
+		"projects/%s/jobs/%d/artifacts/%s",
+		pathEscape(project),
+		jobID,
+		artifactPath,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	artifactBuf := new(bytes.Buffer)
+	resp, err := s.client.Do(req, artifactBuf)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return artifactBuf, resp, err
+}
+
+// GetTraceFile gets a trace of a specific job of a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#get-a-trace-file
+func (s *JobsService) GetTraceFile(pid interface{}, jobID int, options ...OptionFunc) (io.Reader, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/jobs/%d/trace", pathEscape(project), jobID)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	traceBuf := new(bytes.Buffer)
+	resp, err := s.client.Do(req, traceBuf)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return traceBuf, resp, err
+}
+
+// CancelJob cancels a single job of a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#cancel-a-job
+func (s *JobsService) CancelJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/jobs/%d/cancel", pathEscape(project), jobID)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	job := new(Job)
+	resp, err := s.client.Do(req, job)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return job, resp, err
+}
+
+// RetryJob retries a single job of a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#retry-a-job
+func (s *JobsService) RetryJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/jobs/%d/retry", pathEscape(project), jobID)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	job := new(Job)
+	resp, err := s.client.Do(req, job)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return job, resp, err
+}
+
+// EraseJob erases a single job of a project, removes a job
+// artifacts and a job trace.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#erase-a-job
+func (s *JobsService) EraseJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/jobs/%d/erase", pathEscape(project), jobID)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	job := new(Job)
+	resp, err := s.client.Do(req, job)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return job, resp, err
+}
+
+// KeepArtifacts prevents artifacts from being deleted when
+// expiration is set.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#keep-artifacts
+func (s *JobsService) KeepArtifacts(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/jobs/%d/artifacts/keep", pathEscape(project), jobID)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	job := new(Job)
+	resp, err := s.client.Do(req, job)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return job, resp, err
+}
+
+// PlayJob triggers a manual action to start a job.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/jobs.html#play-a-job
+func (s *JobsService) PlayJob(pid interface{}, jobID int, options ...OptionFunc) (*Job, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/jobs/%d/play", pathEscape(project), jobID)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	job := new(Job)
+	resp, err := s.client.Do(req, job)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return job, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/keys.go b/vendor/github.com/xanzy/go-gitlab/keys.go
new file mode 100644
index 000000000..9657e862a
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/keys.go
@@ -0,0 +1,65 @@
+//
+// Copyright 2018, Patrick Webster
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// KeysService handles communication with the
+// keys related methods of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/keys.html
+type KeysService struct {
+	client *Client
+}
+
+// Key represents a GitLab user's SSH key.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/keys.html
+type Key struct {
+	ID        int        `json:"id"`
+	Title     string     `json:"title"`
+	Key       string     `json:"key"`
+	CreatedAt *time.Time `json:"created_at"`
+	User      User       `json:"user"`
+}
+
+// GetKeyWithUser gets a single key by id along with the associated
+// user information.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/keys.html#get-ssh-key-with-user-by-id-of-an-ssh-key
+func (s *KeysService) GetKeyWithUser(key int, options ...OptionFunc) (*Key, *Response, error) {
+	u := fmt.Sprintf("keys/%d", key)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	k := new(Key)
+	resp, err := s.client.Do(req, k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/labels.go b/vendor/github.com/xanzy/go-gitlab/labels.go
new file mode 100644
index 000000000..7baf66634
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/labels.go
@@ -0,0 +1,250 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// LabelsService handles communication with the label related methods of the
+// GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html
+type LabelsService struct {
+	client *Client
+}
+
+// Label represents a GitLab label.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html
+type Label struct {
+	ID                     int    `json:"id"`
+	Name                   string `json:"name"`
+	Color                  string `json:"color"`
+	TextColor              string `json:"text_color"`
+	Description            string `json:"description"`
+	OpenIssuesCount        int    `json:"open_issues_count"`
+	ClosedIssuesCount      int    `json:"closed_issues_count"`
+	OpenMergeRequestsCount int    `json:"open_merge_requests_count"`
+	Subscribed             bool   `json:"subscribed"`
+	Priority               int    `json:"priority"`
+	IsProjectLabel         bool   `json:"is_project_label"`
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (l *Label) UnmarshalJSON(data []byte) error {
+	type alias Label
+	if err := json.Unmarshal(data, (*alias)(l)); err != nil {
+		return err
+	}
+
+	if l.Name == "" {
+		var raw map[string]interface{}
+		if err := json.Unmarshal(data, &raw); err != nil {
+			return err
+		}
+		if title, ok := raw["title"].(string); ok {
+			l.Name = title
+		}
+	}
+
+	return nil
+}
+
+func (l Label) String() string {
+	return Stringify(l)
+}
+
+// ListLabelsOptions represents the available ListLabels() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#list-labels
+type ListLabelsOptions ListOptions
+
+// ListLabels gets all labels for given project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#list-labels
+func (s *LabelsService) ListLabels(pid interface{}, opt *ListLabelsOptions, options ...OptionFunc) ([]*Label, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/labels", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var l []*Label
+	resp, err := s.client.Do(req, &l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// CreateLabelOptions represents the available CreateLabel() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#create-a-new-label
+type CreateLabelOptions struct {
+	Name        *string `url:"name,omitempty" json:"name,omitempty"`
+	Color       *string `url:"color,omitempty" json:"color,omitempty"`
+	Description *string `url:"description,omitempty" json:"description,omitempty"`
+}
+
+// CreateLabel creates a new label for given repository with given name and
+// color.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#create-a-new-label
+func (s *LabelsService) CreateLabel(pid interface{}, opt *CreateLabelOptions, options ...OptionFunc) (*Label, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/labels", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(Label)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// DeleteLabelOptions represents the available DeleteLabel() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#delete-a-label
+type DeleteLabelOptions struct {
+	Name *string `url:"name,omitempty" json:"name,omitempty"`
+}
+
+// DeleteLabel deletes a label given by its name.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#delete-a-label
+func (s *LabelsService) DeleteLabel(pid interface{}, opt *DeleteLabelOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/labels", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// UpdateLabelOptions represents the available UpdateLabel() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#delete-a-label
+type UpdateLabelOptions struct {
+	Name        *string `url:"name,omitempty" json:"name,omitempty"`
+	NewName     *string `url:"new_name,omitempty" json:"new_name,omitempty"`
+	Color       *string `url:"color,omitempty" json:"color,omitempty"`
+	Description *string `url:"description,omitempty" json:"description,omitempty"`
+}
+
+// UpdateLabel updates an existing label with new name or now color. At least
+// one parameter is required, to update the label.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/labels.html#edit-an-existing-label
+func (s *LabelsService) UpdateLabel(pid interface{}, opt *UpdateLabelOptions, options ...OptionFunc) (*Label, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/labels", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(Label)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// SubscribeToLabel subscribes the authenticated user to a label to receive
+// notifications. If the user is already subscribed to the label, the status
+// code 304 is returned.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/labels.html#subscribe-to-a-label
+func (s *LabelsService) SubscribeToLabel(pid interface{}, labelID interface{}, options ...OptionFunc) (*Label, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	label, err := parseID(labelID)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/labels/%s/subscribe", pathEscape(project), label)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(Label)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// UnsubscribeFromLabel unsubscribes the authenticated user from a label to not
+// receive notifications from it. If the user is not subscribed to the label, the
+// status code 304 is returned.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/labels.html#unsubscribe-from-a-label
+func (s *LabelsService) UnsubscribeFromLabel(pid interface{}, labelID interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	label, err := parseID(labelID)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/labels/%s/unsubscribe", pathEscape(project), label)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/license.go b/vendor/github.com/xanzy/go-gitlab/license.go
new file mode 100644
index 000000000..746e99ae9
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/license.go
@@ -0,0 +1,94 @@
+//
+// Copyright 2018, Patrick Webster
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+// LicenseService handles communication with the license
+// related methods of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/license.html
+type LicenseService struct {
+	client *Client
+}
+
+// License represents a GitLab license.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/license.html
+type License struct {
+	StartsAt  *ISOTime `json:"starts_at"`
+	ExpiresAt *ISOTime `json:"expires_at"`
+	Licensee  struct {
+		Name    string `json:"Name"`
+		Company string `json:"Company"`
+		Email   string `json:"Email"`
+	} `json:"licensee"`
+	UserLimit   int `json:"user_limit"`
+	ActiveUsers int `json:"active_users"`
+	AddOns      struct {
+		GitLabFileLocks int `json:"GitLabFileLocks"`
+	} `json:"add_ons"`
+}
+
+func (l License) String() string {
+	return Stringify(l)
+}
+
+// GetLicense retrieves information about the current license.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/license.html#retrieve-information-about-the-current-license
+func (s *LicenseService) GetLicense() (*License, *Response, error) {
+	req, err := s.client.NewRequest("GET", "license", nil, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(License)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// AddLicenseOptions represents the available AddLicense() options.
+//
+// https://docs.gitlab.com/ee/api/license.html#add-a-new-license
+type AddLicenseOptions struct {
+	License *string `url:"license" json:"license"`
+}
+
+// AddLicense adds a new license.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/license.html#add-a-new-license
+func (s *LicenseService) AddLicense(opt *AddLicenseOptions, options ...OptionFunc) (*License, *Response, error) {
+	req, err := s.client.NewRequest("POST", "license", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(License)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/license_templates.go b/vendor/github.com/xanzy/go-gitlab/license_templates.go
new file mode 100644
index 000000000..83f8259b7
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/license_templates.go
@@ -0,0 +1,92 @@
+package gitlab
+
+import (
+	"fmt"
+)
+
+// LicenseTemplate represents a license template.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/licenses.html
+type LicenseTemplate struct {
+	Key         string   `json:"key"`
+	Name        string   `json:"name"`
+	Nickname    string   `json:"nickname"`
+	Featured    bool     `json:"featured"`
+	HTMLURL     string   `json:"html_url"`
+	SourceURL   string   `json:"source_url"`
+	Description string   `json:"description"`
+	Conditions  []string `json:"conditions"`
+	Permissions []string `json:"permissions"`
+	Limitations []string `json:"limitations"`
+	Content     string   `json:"content"`
+}
+
+// LicenseTemplatesService handles communication with the license templates
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/templates/licenses.html
+type LicenseTemplatesService struct {
+	client *Client
+}
+
+// ListLicenseTemplatesOptions represents the available
+// ListLicenseTemplates() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/licenses.html#list-license-templates
+type ListLicenseTemplatesOptions struct {
+	ListOptions
+	Popular *bool `url:"popular,omitempty" json:"popular,omitempty"`
+}
+
+// ListLicenseTemplates get all license templates.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/licenses.html#list-license-templates
+func (s *LicenseTemplatesService) ListLicenseTemplates(opt *ListLicenseTemplatesOptions, options ...OptionFunc) ([]*LicenseTemplate, *Response, error) {
+	req, err := s.client.NewRequest("GET", "templates/licenses", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var lts []*LicenseTemplate
+	resp, err := s.client.Do(req, &lts)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return lts, resp, err
+}
+
+// GetLicenseTemplateOptions represents the available
+// GetLicenseTemplate() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/licenses.html#single-license-template
+type GetLicenseTemplateOptions struct {
+	Project  *string `url:"project,omitempty" json:"project,omitempty"`
+	Fullname *string `url:"fullname,omitempty" json:"fullname,omitempty"`
+}
+
+// GetLicenseTemplate get a single license template. You can pass parameters
+// to replace the license placeholder.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/templates/licenses.html#single-license-template
+func (s *LicenseTemplatesService) GetLicenseTemplate(template string, opt *GetLicenseTemplateOptions, options ...OptionFunc) (*LicenseTemplate, *Response, error) {
+	u := fmt.Sprintf("templates/licenses/%s", template)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	lt := new(LicenseTemplate)
+	resp, err := s.client.Do(req, lt)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return lt, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go b/vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go
new file mode 100644
index 000000000..c11fe3573
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/merge_request_approvals.go
@@ -0,0 +1,191 @@
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// MergeRequestApprovalsService handles communication with the merge request
+// approvals related methods of the GitLab API. This includes reading/updating
+// approval settings and approve/unapproving merge requests
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/merge_request_approvals.html
+type MergeRequestApprovalsService struct {
+	client *Client
+}
+
+// MergeRequestApprovals represents GitLab merge request approvals.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals
+type MergeRequestApprovals struct {
+	ID                   int                          `json:"id"`
+	ProjectID            int                          `json:"project_id"`
+	Title                string                       `json:"title"`
+	Description          string                       `json:"description"`
+	State                string                       `json:"state"`
+	CreatedAt            *time.Time                   `json:"created_at"`
+	UpdatedAt            *time.Time                   `json:"updated_at"`
+	MergeStatus          string                       `json:"merge_status"`
+	ApprovalsBeforeMerge int                          `json:"approvals_before_merge"`
+	ApprovalsRequired    int                          `json:"approvals_required"`
+	ApprovalsLeft        int                          `json:"approvals_left"`
+	ApprovedBy           []*MergeRequestApproverUser  `json:"approved_by"`
+	Approvers            []*MergeRequestApproverUser  `json:"approvers"`
+	ApproverGroups       []*MergeRequestApproverGroup `json:"approver_groups"`
+	SuggestedApprovers   []*BasicUser                 `json:"suggested_approvers"`
+}
+
+func (m MergeRequestApprovals) String() string {
+	return Stringify(m)
+}
+
+// MergeRequestApproverGroup  represents GitLab project level merge request approver group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals
+type MergeRequestApproverGroup struct {
+	Group struct {
+		ID                   int    `json:"id"`
+		Name                 string `json:"name"`
+		Path                 string `json:"path"`
+		Description          string `json:"description"`
+		Visibility           string `json:"visibility"`
+		AvatarURL            string `json:"avatar_url"`
+		WebURL               string `json:"web_url"`
+		FullName             string `json:"full_name"`
+		FullPath             string `json:"full_path"`
+		LFSEnabled           bool   `json:"lfs_enabled"`
+		RequestAccessEnabled bool   `json:"request_access_enabled"`
+	}
+}
+
+// MergeRequestApproverUser  represents GitLab project level merge request approver user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals
+type MergeRequestApproverUser struct {
+	User *BasicUser
+}
+
+// ApproveMergeRequestOptions represents the available ApproveMergeRequest() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#approve-merge-request
+type ApproveMergeRequestOptions struct {
+	SHA *string `url:"sha,omitempty" json:"sha,omitempty"`
+}
+
+// ApproveMergeRequest approves a merge request on GitLab. If a non-empty sha
+// is provided then it must match the sha at the HEAD of the MR.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#approve-merge-request
+func (s *MergeRequestApprovalsService) ApproveMergeRequest(pid interface{}, mr int, opt *ApproveMergeRequestOptions, options ...OptionFunc) (*MergeRequestApprovals, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/approve", pathEscape(project), mr)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequestApprovals)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// UnapproveMergeRequest unapproves a previously approved merge request on GitLab.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#unapprove-merge-request
+func (s *MergeRequestApprovalsService) UnapproveMergeRequest(pid interface{}, mr int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/unapprove", pathEscape(project), mr)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ChangeMergeRequestApprovalConfigurationOptions represents the available
+// ChangeMergeRequestApprovalConfiguration() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-approval-configuration
+type ChangeMergeRequestApprovalConfigurationOptions struct {
+	ApprovalsRequired *int `url:"approvals_required,omitempty" json:"approvals_required,omitempty"`
+}
+
+// ChangeApprovalConfiguration updates the approval configuration of a merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-approval-configuration
+func (s *MergeRequestApprovalsService) ChangeApprovalConfiguration(pid interface{}, mergeRequestIID int, opt *ChangeMergeRequestApprovalConfigurationOptions, options ...OptionFunc) (*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/approvals", pathEscape(project), mergeRequestIID)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequest)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// ChangeMergeRequestAllowedApproversOptions represents the available
+// ChangeMergeRequestAllowedApprovers() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers-for-merge-request
+type ChangeMergeRequestAllowedApproversOptions struct {
+	ApproverIDs      []int `url:"approver_ids" json:"approver_ids"`
+	ApproverGroupIDs []int `url:"approver_group_ids" json:"approver_group_ids"`
+}
+
+// ChangeAllowedApprovers updates the approvers for a merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers-for-merge-request
+func (s *MergeRequestApprovalsService) ChangeAllowedApprovers(pid interface{}, mergeRequestIID int, opt *ChangeMergeRequestAllowedApproversOptions, options ...OptionFunc) (*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/approvers", pathEscape(project), mergeRequestIID)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequest)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/merge_requests.go b/vendor/github.com/xanzy/go-gitlab/merge_requests.go
new file mode 100644
index 000000000..4e10cb8ff
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/merge_requests.go
@@ -0,0 +1,836 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// MergeRequestsService handles communication with the merge requests related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/merge_requests.html
+type MergeRequestsService struct {
+	client    *Client
+	timeStats *timeStatsService
+}
+
+// MergeRequest represents a GitLab merge request.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/merge_requests.html
+type MergeRequest struct {
+	ID                        int          `json:"id"`
+	IID                       int          `json:"iid"`
+	TargetBranch              string       `json:"target_branch"`
+	SourceBranch              string       `json:"source_branch"`
+	ProjectID                 int          `json:"project_id"`
+	Title                     string       `json:"title"`
+	State                     string       `json:"state"`
+	CreatedAt                 *time.Time   `json:"created_at"`
+	UpdatedAt                 *time.Time   `json:"updated_at"`
+	Upvotes                   int          `json:"upvotes"`
+	Downvotes                 int          `json:"downvotes"`
+	Author                    *BasicUser   `json:"author"`
+	Assignee                  *BasicUser   `json:"assignee"`
+	Assignees                 []*BasicUser `json:"assignees"`
+	SourceProjectID           int          `json:"source_project_id"`
+	TargetProjectID           int          `json:"target_project_id"`
+	Labels                    Labels       `json:"labels"`
+	Description               string       `json:"description"`
+	WorkInProgress            bool         `json:"work_in_progress"`
+	Milestone                 *Milestone   `json:"milestone"`
+	MergeWhenPipelineSucceeds bool         `json:"merge_when_pipeline_succeeds"`
+	MergeStatus               string       `json:"merge_status"`
+	MergeError                string       `json:"merge_error"`
+	MergedBy                  *BasicUser   `json:"merged_by"`
+	MergedAt                  *time.Time   `json:"merged_at"`
+	ClosedBy                  *BasicUser   `json:"closed_by"`
+	ClosedAt                  *time.Time   `json:"closed_at"`
+	Subscribed                bool         `json:"subscribed"`
+	SHA                       string       `json:"sha"`
+	MergeCommitSHA            string       `json:"merge_commit_sha"`
+	UserNotesCount            int          `json:"user_notes_count"`
+	ChangesCount              string       `json:"changes_count"`
+	ShouldRemoveSourceBranch  bool         `json:"should_remove_source_branch"`
+	ForceRemoveSourceBranch   bool         `json:"force_remove_source_branch"`
+	WebURL                    string       `json:"web_url"`
+	DiscussionLocked          bool         `json:"discussion_locked"`
+	Changes                   []struct {
+		OldPath     string `json:"old_path"`
+		NewPath     string `json:"new_path"`
+		AMode       string `json:"a_mode"`
+		BMode       string `json:"b_mode"`
+		Diff        string `json:"diff"`
+		NewFile     bool   `json:"new_file"`
+		RenamedFile bool   `json:"renamed_file"`
+		DeletedFile bool   `json:"deleted_file"`
+	} `json:"changes"`
+	TimeStats    *TimeStats    `json:"time_stats"`
+	Squash       bool          `json:"squash"`
+	Pipeline     *PipelineInfo `json:"pipeline"`
+	HeadPipeline *Pipeline     `json:"head_pipeline"`
+	DiffRefs     struct {
+		BaseSha  string `json:"base_sha"`
+		HeadSha  string `json:"head_sha"`
+		StartSha string `json:"start_sha"`
+	} `json:"diff_refs"`
+	DivergedCommitsCount int    `json:"diverged_commits_count"`
+	RebaseInProgress     bool   `json:"rebase_in_progress"`
+	ApprovalsBeforeMerge int    `json:"approvals_before_merge"`
+	Reference            string `json:"reference"`
+	TaskCompletionStatus struct {
+		Count          int `json:"count"`
+		CompletedCount int `json:"completed_count"`
+	} `json:"task_completion_status"`
+}
+
+func (m MergeRequest) String() string {
+	return Stringify(m)
+}
+
+// MergeRequestDiffVersion represents Gitlab merge request version.
+//
+// Gitlab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-a-single-mr-diff-version
+type MergeRequestDiffVersion struct {
+	ID             int        `json:"id"`
+	HeadCommitSHA  string     `json:"head_commit_sha,omitempty"`
+	BaseCommitSHA  string     `json:"base_commit_sha,omitempty"`
+	StartCommitSHA string     `json:"start_commit_sha,omitempty"`
+	CreatedAt      *time.Time `json:"created_at,omitempty"`
+	MergeRequestID int        `json:"merge_request_id,omitempty"`
+	State          string     `json:"state,omitempty"`
+	RealSize       string     `json:"real_size,omitempty"`
+	Commits        []*Commit  `json:"commits,omitempty"`
+	Diffs          []*Diff    `json:"diffs,omitempty"`
+}
+
+func (m MergeRequestDiffVersion) String() string {
+	return Stringify(m)
+}
+
+// ListMergeRequestsOptions represents the available ListMergeRequests()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-merge-requests
+type ListMergeRequestsOptions struct {
+	ListOptions
+	State           *string    `url:"state,omitempty" json:"state,omitempty"`
+	OrderBy         *string    `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort            *string    `url:"sort,omitempty" json:"sort,omitempty"`
+	Milestone       *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
+	View            *string    `url:"view,omitempty" json:"view,omitempty"`
+	Labels          Labels     `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	CreatedAfter    *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
+	CreatedBefore   *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
+	UpdatedAfter    *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
+	UpdatedBefore   *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"`
+	Scope           *string    `url:"scope,omitempty" json:"scope,omitempty"`
+	AuthorID        *int       `url:"author_id,omitempty" json:"author_id,omitempty"`
+	AssigneeID      *int       `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+	MyReactionEmoji *string    `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+	SourceBranch    *string    `url:"source_branch,omitempty" json:"source_branch,omitempty"`
+	TargetBranch    *string    `url:"target_branch,omitempty" json:"target_branch,omitempty"`
+	Search          *string    `url:"search,omitempty" json:"search,omitempty"`
+	In              *string    `url:"in,omitempty" json:"in,omitempty"`
+	WIP             *string    `url:"wip,omitempty" json:"wip,omitempty"`
+}
+
+// ListMergeRequests gets all merge requests. The state parameter can be used
+// to get only merge requests with a given state (opened, closed, or merged)
+// or all of them (all). The pagination parameters page and per_page can be
+// used to restrict the list of merge requests.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-merge-requests
+func (s *MergeRequestsService) ListMergeRequests(opt *ListMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	req, err := s.client.NewRequest("GET", "merge_requests", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var m []*MergeRequest
+	resp, err := s.client.Do(req, &m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// ListGroupMergeRequestsOptions represents the available ListGroupMergeRequests()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-group-merge-requests
+type ListGroupMergeRequestsOptions struct {
+	ListOptions
+	State           *string    `url:"state,omitempty" json:"state,omitempty"`
+	OrderBy         *string    `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort            *string    `url:"sort,omitempty" json:"sort,omitempty"`
+	Milestone       *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
+	View            *string    `url:"view,omitempty" json:"view,omitempty"`
+	Labels          *Labels    `url:"labels,omitempty" json:"labels,omitempty"`
+	CreatedAfter    *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
+	CreatedBefore   *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
+	UpdatedAfter    *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
+	UpdatedBefore   *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"`
+	Scope           *string    `url:"scope,omitempty" json:"scope,omitempty"`
+	AuthorID        *int       `url:"author_id,omitempty" json:"author_id,omitempty"`
+	AssigneeID      *int       `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+	MyReactionEmoji *string    `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+	SourceBranch    *string    `url:"source_branch,omitempty" json:"source_branch,omitempty"`
+	TargetBranch    *string    `url:"target_branch,omitempty" json:"target_branch,omitempty"`
+	Search          *string    `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListGroupMergeRequests gets all merge requests for this group.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-group-merge-requests
+func (s *MergeRequestsService) ListGroupMergeRequests(gid interface{}, opt *ListGroupMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/merge_requests", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var m []*MergeRequest
+	resp, err := s.client.Do(req, &m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// ListProjectMergeRequestsOptions represents the available ListMergeRequests()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-project-merge-requests
+type ListProjectMergeRequestsOptions struct {
+	ListOptions
+	IIDs            []int      `url:"iids[],omitempty" json:"iids,omitempty"`
+	State           *string    `url:"state,omitempty" json:"state,omitempty"`
+	OrderBy         *string    `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort            *string    `url:"sort,omitempty" json:"sort,omitempty"`
+	Milestone       *string    `url:"milestone,omitempty" json:"milestone,omitempty"`
+	View            *string    `url:"view,omitempty" json:"view,omitempty"`
+	Labels          *Labels    `url:"labels,omitempty" json:"labels,omitempty"`
+	CreatedAfter    *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
+	CreatedBefore   *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
+	UpdatedAfter    *time.Time `url:"updated_after,omitempty" json:"updated_after,omitempty"`
+	UpdatedBefore   *time.Time `url:"updated_before,omitempty" json:"updated_before,omitempty"`
+	Scope           *string    `url:"scope,omitempty" json:"scope,omitempty"`
+	AuthorID        *int       `url:"author_id,omitempty" json:"author_id,omitempty"`
+	AssigneeID      *int       `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+	MyReactionEmoji *string    `url:"my_reaction_emoji,omitempty" json:"my_reaction_emoji,omitempty"`
+	SourceBranch    *string    `url:"source_branch,omitempty" json:"source_branch,omitempty"`
+	TargetBranch    *string    `url:"target_branch,omitempty" json:"target_branch,omitempty"`
+	Search          *string    `url:"search,omitempty" json:"search,omitempty"`
+	WIP             *string    `url:"wip,omitempty" json:"wip,omitempty"`
+}
+
+// ListProjectMergeRequests gets all merge requests for this project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-project-merge-requests
+func (s *MergeRequestsService) ListProjectMergeRequests(pid interface{}, opt *ListProjectMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var m []*MergeRequest
+	resp, err := s.client.Do(req, &m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// GetMergeRequestsOptions represents the available GetMergeRequests()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr
+type GetMergeRequestsOptions struct {
+	RenderHTML                  *bool `url:"render_html,omitempty" json:"render_html,omitempty"`
+	IncludeDivergedCommitsCount *bool `url:"include_diverged_commits_count,omitempty" json:"include_diverged_commits_count,omitempty"`
+	IncludeRebaseInProgress     *bool `url:"include_rebase_in_progress,omitempty" json:"include_rebase_in_progress,omitempty"`
+}
+
+// GetMergeRequest shows information about a single merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr
+func (s *MergeRequestsService) GetMergeRequest(pid interface{}, mergeRequest int, opt *GetMergeRequestsOptions, options ...OptionFunc) (*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequest)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// GetMergeRequestApprovals gets information about a merge requests approvals
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals
+func (s *MergeRequestsService) GetMergeRequestApprovals(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequestApprovals, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/approvals", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	a := new(MergeRequestApprovals)
+	resp, err := s.client.Do(req, a)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return a, resp, err
+}
+
+// GetMergeRequestCommitsOptions represents the available GetMergeRequestCommits()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr-commits
+type GetMergeRequestCommitsOptions ListOptions
+
+// GetMergeRequestCommits gets a list of merge request commits.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr-commits
+func (s *MergeRequestsService) GetMergeRequestCommits(pid interface{}, mergeRequest int, opt *GetMergeRequestCommitsOptions, options ...OptionFunc) ([]*Commit, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/commits", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var c []*Commit
+	resp, err := s.client.Do(req, &c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
+
+// GetMergeRequestChanges shows information about the merge request including
+// its files and changes.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-single-mr-changes
+func (s *MergeRequestsService) GetMergeRequestChanges(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/changes", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequest)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// ListMergeRequestPipelines gets all pipelines for the provided merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-mr-pipelines
+func (s *MergeRequestsService) ListMergeRequestPipelines(pid interface{}, mergeRequest int, options ...OptionFunc) ([]*PipelineInfo, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/pipelines", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var p []*PipelineInfo
+	resp, err := s.client.Do(req, &p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// GetIssuesClosedOnMergeOptions represents the available GetIssuesClosedOnMerge()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-issues-that-will-close-on-merge
+type GetIssuesClosedOnMergeOptions ListOptions
+
+// GetIssuesClosedOnMerge gets all the issues that would be closed by merging the
+// provided merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#list-issues-that-will-close-on-merge
+func (s *MergeRequestsService) GetIssuesClosedOnMerge(pid interface{}, mergeRequest int, opt *GetIssuesClosedOnMergeOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/closes_issues", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var i []*Issue
+	resp, err := s.client.Do(req, &i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// CreateMergeRequestOptions represents the available CreateMergeRequest()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#create-mr
+type CreateMergeRequestOptions struct {
+	Title              *string `url:"title,omitempty" json:"title,omitempty"`
+	Description        *string `url:"description,omitempty" json:"description,omitempty"`
+	SourceBranch       *string `url:"source_branch,omitempty" json:"source_branch,omitempty"`
+	TargetBranch       *string `url:"target_branch,omitempty" json:"target_branch,omitempty"`
+	Labels             *Labels `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	AssigneeID         *int    `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+	AssigneeIDs        []int   `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"`
+	TargetProjectID    *int    `url:"target_project_id,omitempty" json:"target_project_id,omitempty"`
+	MilestoneID        *int    `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
+	RemoveSourceBranch *bool   `url:"remove_source_branch,omitempty" json:"remove_source_branch,omitempty"`
+	Squash             *bool   `url:"squash,omitempty" json:"squash,omitempty"`
+	AllowCollaboration *bool   `url:"allow_collaboration,omitempty" json:"allow_collaboration,omitempty"`
+}
+
+// CreateMergeRequest creates a new merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#create-mr
+func (s *MergeRequestsService) CreateMergeRequest(pid interface{}, opt *CreateMergeRequestOptions, options ...OptionFunc) (*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequest)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// UpdateMergeRequestOptions represents the available UpdateMergeRequest()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#update-mr
+type UpdateMergeRequestOptions struct {
+	Title              *string `url:"title,omitempty" json:"title,omitempty"`
+	Description        *string `url:"description,omitempty" json:"description,omitempty"`
+	TargetBranch       *string `url:"target_branch,omitempty" json:"target_branch,omitempty"`
+	AssigneeID         *int    `url:"assignee_id,omitempty" json:"assignee_id,omitempty"`
+	AssigneeIDs        []int   `url:"assignee_ids,omitempty" json:"assignee_ids,omitempty"`
+	Labels             *Labels `url:"labels,comma,omitempty" json:"labels,omitempty"`
+	MilestoneID        *int    `url:"milestone_id,omitempty" json:"milestone_id,omitempty"`
+	StateEvent         *string `url:"state_event,omitempty" json:"state_event,omitempty"`
+	RemoveSourceBranch *bool   `url:"remove_source_branch,omitempty" json:"remove_source_branch,omitempty"`
+	Squash             *bool   `url:"squash,omitempty" json:"squash,omitempty"`
+	DiscussionLocked   *bool   `url:"discussion_locked,omitempty" json:"discussion_locked,omitempty"`
+	AllowCollaboration *bool   `url:"allow_collaboration,omitempty" json:"allow_collaboration,omitempty"`
+}
+
+// UpdateMergeRequest updates an existing project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#update-mr
+func (s *MergeRequestsService) UpdateMergeRequest(pid interface{}, mergeRequest int, opt *UpdateMergeRequestOptions, options ...OptionFunc) (*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequest)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// DeleteMergeRequest deletes a merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#delete-a-merge-request
+func (s *MergeRequestsService) DeleteMergeRequest(pid interface{}, mergeRequest int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// AcceptMergeRequestOptions represents the available AcceptMergeRequest()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#accept-mr
+type AcceptMergeRequestOptions struct {
+	MergeCommitMessage        *string `url:"merge_commit_message,omitempty" json:"merge_commit_message,omitempty"`
+	SquashCommitMessage       *string `url:"squash_commit_message,omitempty" json:"squash_commit_message,omitempty"`
+	Squash                    *bool   `url:"squash,omitempty" json:"squash,omitempty"`
+	ShouldRemoveSourceBranch  *bool   `url:"should_remove_source_branch,omitempty" json:"should_remove_source_branch,omitempty"`
+	MergeWhenPipelineSucceeds *bool   `url:"merge_when_pipeline_succeeds,omitempty" json:"merge_when_pipeline_succeeds,omitempty"`
+	SHA                       *string `url:"sha,omitempty" json:"sha,omitempty"`
+}
+
+// AcceptMergeRequest merges changes submitted with MR using this API. If merge
+// success you get 200 OK. If it has some conflicts and can not be merged - you
+// get 405 and error message 'Branch cannot be merged'. If merge request is
+// already merged or closed - you get 405 and error message 'Method Not Allowed'
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#accept-mr
+func (s *MergeRequestsService) AcceptMergeRequest(pid interface{}, mergeRequest int, opt *AcceptMergeRequestOptions, options ...OptionFunc) (*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/merge", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequest)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// CancelMergeWhenPipelineSucceeds cancels a merge when pipeline succeeds. If
+// you don't have permissions to accept this merge request - you'll get a 401.
+// If the merge request is already merged or closed - you get 405 and error
+// message 'Method Not Allowed'. In case the merge request is not set to be
+// merged when the pipeline succeeds, you'll also get a 406 error.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#cancel-merge-when-pipeline-succeeds
+func (s *MergeRequestsService) CancelMergeWhenPipelineSucceeds(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/cancel_merge_when_pipeline_succeeds", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("PUT", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequest)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// RebaseMergeRequest automatically rebases the source_branch of the merge
+// request against its target_branch. If you don’t have permissions to push
+// to the merge request’s source branch, you’ll get a 403 Forbidden response.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#rebase-a-merge-request
+func (s *MergeRequestsService) RebaseMergeRequest(pid interface{}, mergeRequest int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/rebase", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("PUT", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// GetMergeRequestDiffVersionsOptions represents the available
+// GetMergeRequestDiffVersions() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-mr-diff-versions
+type GetMergeRequestDiffVersionsOptions ListOptions
+
+// GetMergeRequestDiffVersions get a list of merge request diff versions.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-mr-diff-versions
+func (s *MergeRequestsService) GetMergeRequestDiffVersions(pid interface{}, mergeRequest int, opt *GetMergeRequestDiffVersionsOptions, options ...OptionFunc) ([]*MergeRequestDiffVersion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/versions", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var v []*MergeRequestDiffVersion
+	resp, err := s.client.Do(req, &v)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return v, resp, err
+}
+
+// GetSingleMergeRequestDiffVersion get a single MR diff version
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-a-single-mr-diff-version
+func (s *MergeRequestsService) GetSingleMergeRequestDiffVersion(pid interface{}, mergeRequest, version int, options ...OptionFunc) (*MergeRequestDiffVersion, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/versions/%d", pathEscape(project), mergeRequest, version)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var v = new(MergeRequestDiffVersion)
+	resp, err := s.client.Do(req, v)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return v, resp, err
+}
+
+// SubscribeToMergeRequest subscribes the authenticated user to the given merge
+// request to receive notifications. If the user is already subscribed to the
+// merge request, the status code 304 is returned.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#subscribe-to-a-merge-request
+func (s *MergeRequestsService) SubscribeToMergeRequest(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/subscribe", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequest)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// UnsubscribeFromMergeRequest unsubscribes the authenticated user from the
+// given merge request to not receive notifications from that merge request.
+// If the user is not subscribed to the merge request, status code 304 is
+// returned.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#unsubscribe-from-a-merge-request
+func (s *MergeRequestsService) UnsubscribeFromMergeRequest(pid interface{}, mergeRequest int, options ...OptionFunc) (*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/unsubscribe", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(MergeRequest)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// CreateTodo manually creates a todo for the current user on a merge request.
+// If there already exists a todo for the user on that merge request,
+// status code 304 is returned.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#create-a-todo
+func (s *MergeRequestsService) CreateTodo(pid interface{}, mergeRequest int, options ...OptionFunc) (*Todo, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/todo", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t := new(Todo)
+	resp, err := s.client.Do(req, t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// SetTimeEstimate sets the time estimate for a single project merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#set-a-time-estimate-for-a-merge-request
+func (s *MergeRequestsService) SetTimeEstimate(pid interface{}, mergeRequest int, opt *SetTimeEstimateOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+	return s.timeStats.setTimeEstimate(pid, "merge_requests", mergeRequest, opt, options...)
+}
+
+// ResetTimeEstimate resets the time estimate for a single project merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#reset-the-time-estimate-for-a-merge-request
+func (s *MergeRequestsService) ResetTimeEstimate(pid interface{}, mergeRequest int, options ...OptionFunc) (*TimeStats, *Response, error) {
+	return s.timeStats.resetTimeEstimate(pid, "merge_requests", mergeRequest, options...)
+}
+
+// AddSpentTime adds spent time for a single project merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#add-spent-time-for-a-merge-request
+func (s *MergeRequestsService) AddSpentTime(pid interface{}, mergeRequest int, opt *AddSpentTimeOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+	return s.timeStats.addSpentTime(pid, "merge_requests", mergeRequest, opt, options...)
+}
+
+// ResetSpentTime resets the spent time for a single project merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#reset-spent-time-for-a-merge-request
+func (s *MergeRequestsService) ResetSpentTime(pid interface{}, mergeRequest int, options ...OptionFunc) (*TimeStats, *Response, error) {
+	return s.timeStats.resetSpentTime(pid, "merge_requests", mergeRequest, options...)
+}
+
+// GetTimeSpent gets the spent time for a single project merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/merge_requests.html#get-time-tracking-stats
+func (s *MergeRequestsService) GetTimeSpent(pid interface{}, mergeRequest int, options ...OptionFunc) (*TimeStats, *Response, error) {
+	return s.timeStats.getTimeSpent(pid, "merge_requests", mergeRequest, options...)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/milestones.go b/vendor/github.com/xanzy/go-gitlab/milestones.go
new file mode 100644
index 000000000..f3f7f7c8c
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/milestones.go
@@ -0,0 +1,267 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// MilestonesService handles communication with the milestone related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/milestones.html
+type MilestonesService struct {
+	client *Client
+}
+
+// Milestone represents a GitLab milestone.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/milestones.html
+type Milestone struct {
+	ID          int        `json:"id"`
+	IID         int        `json:"iid"`
+	ProjectID   int        `json:"project_id"`
+	Title       string     `json:"title"`
+	Description string     `json:"description"`
+	StartDate   *ISOTime   `json:"start_date"`
+	DueDate     *ISOTime   `json:"due_date"`
+	State       string     `json:"state"`
+	UpdatedAt   *time.Time `json:"updated_at"`
+	CreatedAt   *time.Time `json:"created_at"`
+}
+
+func (m Milestone) String() string {
+	return Stringify(m)
+}
+
+// ListMilestonesOptions represents the available ListMilestones() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#list-project-milestones
+type ListMilestonesOptions struct {
+	ListOptions
+	IIDs   []int   `url:"iids,omitempty" json:"iids,omitempty"`
+	Title  *string `url:"title,omitempty" json:"title,omitempty"`
+	State  *string `url:"state,omitempty" json:"state,omitempty"`
+	Search *string `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListMilestones returns a list of project milestones.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#list-project-milestones
+func (s *MilestonesService) ListMilestones(pid interface{}, opt *ListMilestonesOptions, options ...OptionFunc) ([]*Milestone, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/milestones", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var m []*Milestone
+	resp, err := s.client.Do(req, &m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// GetMilestone gets a single project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#get-single-milestone
+func (s *MilestonesService) GetMilestone(pid interface{}, milestone int, options ...OptionFunc) (*Milestone, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/milestones/%d", pathEscape(project), milestone)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(Milestone)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// CreateMilestoneOptions represents the available CreateMilestone() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#create-new-milestone
+type CreateMilestoneOptions struct {
+	Title       *string  `url:"title,omitempty" json:"title,omitempty"`
+	Description *string  `url:"description,omitempty" json:"description,omitempty"`
+	StartDate   *ISOTime `url:"start_date,omitempty" json:"start_date,omitempty"`
+	DueDate     *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"`
+}
+
+// CreateMilestone creates a new project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#create-new-milestone
+func (s *MilestonesService) CreateMilestone(pid interface{}, opt *CreateMilestoneOptions, options ...OptionFunc) (*Milestone, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/milestones", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(Milestone)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// UpdateMilestoneOptions represents the available UpdateMilestone() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#edit-milestone
+type UpdateMilestoneOptions struct {
+	Title       *string  `url:"title,omitempty" json:"title,omitempty"`
+	Description *string  `url:"description,omitempty" json:"description,omitempty"`
+	StartDate   *ISOTime `url:"start_date,omitempty" json:"start_date,omitempty"`
+	DueDate     *ISOTime `url:"due_date,omitempty" json:"due_date,omitempty"`
+	StateEvent  *string  `url:"state_event,omitempty" json:"state_event,omitempty"`
+}
+
+// UpdateMilestone updates an existing project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#edit-milestone
+func (s *MilestonesService) UpdateMilestone(pid interface{}, milestone int, opt *UpdateMilestoneOptions, options ...OptionFunc) (*Milestone, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/milestones/%d", pathEscape(project), milestone)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m := new(Milestone)
+	resp, err := s.client.Do(req, m)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return m, resp, err
+}
+
+// DeleteMilestone deletes a specified project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#delete-project-milestone
+func (s *MilestonesService) DeleteMilestone(pid interface{}, milestone int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/milestones/%d", pathEscape(project), milestone)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(req, nil)
+}
+
+// GetMilestoneIssuesOptions represents the available GetMilestoneIssues() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#get-all-issues-assigned-to-a-single-milestone
+type GetMilestoneIssuesOptions ListOptions
+
+// GetMilestoneIssues gets all issues assigned to a single project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#get-all-issues-assigned-to-a-single-milestone
+func (s *MilestonesService) GetMilestoneIssues(pid interface{}, milestone int, opt *GetMilestoneIssuesOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/milestones/%d/issues", pathEscape(project), milestone)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var i []*Issue
+	resp, err := s.client.Do(req, &i)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return i, resp, err
+}
+
+// GetMilestoneMergeRequestsOptions represents the available
+// GetMilestoneMergeRequests() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#get-all-merge-requests-assigned-to-a-single-milestone
+type GetMilestoneMergeRequestsOptions ListOptions
+
+// GetMilestoneMergeRequests gets all merge requests assigned to a single
+// project milestone.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/milestones.html#get-all-merge-requests-assigned-to-a-single-milestone
+func (s *MilestonesService) GetMilestoneMergeRequests(pid interface{}, milestone int, opt *GetMilestoneMergeRequestsOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/milestones/%d/merge_requests", pathEscape(project), milestone)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var mr []*MergeRequest
+	resp, err := s.client.Do(req, &mr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return mr, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/namespaces.go b/vendor/github.com/xanzy/go-gitlab/namespaces.go
new file mode 100644
index 000000000..9add6449b
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/namespaces.go
@@ -0,0 +1,122 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+)
+
+// NamespacesService handles communication with the namespace related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html
+type NamespacesService struct {
+	client *Client
+}
+
+// Namespace represents a GitLab namespace.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html
+type Namespace struct {
+	ID                          int    `json:"id"`
+	Name                        string `json:"name"`
+	Path                        string `json:"path"`
+	Kind                        string `json:"kind"`
+	FullPath                    string `json:"full_path"`
+	ParentID                    int    `json:"parent_id"`
+	MembersCountWithDescendants int    `json:"members_count_with_descendants"`
+}
+
+func (n Namespace) String() string {
+	return Stringify(n)
+}
+
+// ListNamespacesOptions represents the available ListNamespaces() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html#list-namespaces
+type ListNamespacesOptions struct {
+	ListOptions
+	Search *string `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListNamespaces gets a list of projects accessible by the authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/namespaces.html#list-namespaces
+func (s *NamespacesService) ListNamespaces(opt *ListNamespacesOptions, options ...OptionFunc) ([]*Namespace, *Response, error) {
+	req, err := s.client.NewRequest("GET", "namespaces", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var n []*Namespace
+	resp, err := s.client.Do(req, &n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// SearchNamespace gets all namespaces that match your string in their name
+// or path.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/namespaces.html#search-for-namespace
+func (s *NamespacesService) SearchNamespace(query string, options ...OptionFunc) ([]*Namespace, *Response, error) {
+	var q struct {
+		Search string `url:"search,omitempty" json:"search,omitempty"`
+	}
+	q.Search = query
+
+	req, err := s.client.NewRequest("GET", "namespaces", &q, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var n []*Namespace
+	resp, err := s.client.Do(req, &n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// GetNamespace gets a namespace by id.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/namespaces.html#get-namespace-by-id
+func (s *NamespacesService) GetNamespace(id interface{}, options ...OptionFunc) (*Namespace, *Response, error) {
+	namespace, err := parseID(id)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("namespaces/%s", namespace)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Namespace)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/notes.go b/vendor/github.com/xanzy/go-gitlab/notes.go
new file mode 100644
index 000000000..91ecf450e
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/notes.go
@@ -0,0 +1,678 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// NotesService handles communication with the notes related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/notes.html
+type NotesService struct {
+	client *Client
+}
+
+// Note represents a GitLab note.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/notes.html
+type Note struct {
+	ID         int    `json:"id"`
+	Body       string `json:"body"`
+	Attachment string `json:"attachment"`
+	Title      string `json:"title"`
+	FileName   string `json:"file_name"`
+	Author     struct {
+		ID        int    `json:"id"`
+		Username  string `json:"username"`
+		Email     string `json:"email"`
+		Name      string `json:"name"`
+		State     string `json:"state"`
+		AvatarURL string `json:"avatar_url"`
+		WebURL    string `json:"web_url"`
+	} `json:"author"`
+	System       bool          `json:"system"`
+	ExpiresAt    *time.Time    `json:"expires_at"`
+	UpdatedAt    *time.Time    `json:"updated_at"`
+	CreatedAt    *time.Time    `json:"created_at"`
+	NoteableID   int           `json:"noteable_id"`
+	NoteableType string        `json:"noteable_type"`
+	Position     *NotePosition `json:"position"`
+	Resolvable   bool          `json:"resolvable"`
+	Resolved     bool          `json:"resolved"`
+	ResolvedBy   struct {
+		ID        int    `json:"id"`
+		Username  string `json:"username"`
+		Email     string `json:"email"`
+		Name      string `json:"name"`
+		State     string `json:"state"`
+		AvatarURL string `json:"avatar_url"`
+		WebURL    string `json:"web_url"`
+	} `json:"resolved_by"`
+	NoteableIID int `json:"noteable_iid"`
+}
+
+// NotePosition represents the position attributes of a note.
+type NotePosition struct {
+	BaseSHA      string `json:"base_sha"`
+	StartSHA     string `json:"start_sha"`
+	HeadSHA      string `json:"head_sha"`
+	PositionType string `json:"position_type"`
+	NewPath      string `json:"new_path,omitempty"`
+	NewLine      int    `json:"new_line,omitempty"`
+	OldPath      string `json:"old_path,omitempty"`
+	OldLine      int    `json:"old_line,omitempty"`
+	Width        int    `json:"width,omitempty"`
+	Height       int    `json:"height,omitempty"`
+	X            int    `json:"x,omitempty"`
+	Y            int    `json:"y,omitempty"`
+}
+
+func (n Note) String() string {
+	return Stringify(n)
+}
+
+// ListIssueNotesOptions represents the available ListIssueNotes() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#list-project-issue-notes
+type ListIssueNotesOptions struct {
+	ListOptions
+	OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort    *string `url:"sort,omitempty" json:"sort,omitempty"`
+}
+
+// ListIssueNotes gets a list of all notes for a single issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#list-project-issue-notes
+func (s *NotesService) ListIssueNotes(pid interface{}, issue int, opt *ListIssueNotesOptions, options ...OptionFunc) ([]*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/notes", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var n []*Note
+	resp, err := s.client.Do(req, &n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// GetIssueNote returns a single note for a specific project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#get-single-issue-note
+func (s *NotesService) GetIssueNote(pid interface{}, issue, note int, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/notes/%d", pathEscape(project), issue, note)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// CreateIssueNoteOptions represents the available CreateIssueNote()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-issue-note
+type CreateIssueNoteOptions struct {
+	Body      *string    `url:"body,omitempty" json:"body,omitempty"`
+	CreatedAt *time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
+}
+
+// CreateIssueNote creates a new note to a single project issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-issue-note
+func (s *NotesService) CreateIssueNote(pid interface{}, issue int, opt *CreateIssueNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/notes", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// UpdateIssueNoteOptions represents the available UpdateIssueNote()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-issue-note
+type UpdateIssueNoteOptions struct {
+	Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// UpdateIssueNote modifies existing note of an issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-issue-note
+func (s *NotesService) UpdateIssueNote(pid interface{}, issue, note int, opt *UpdateIssueNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/notes/%d", pathEscape(project), issue, note)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// DeleteIssueNote deletes an existing note of an issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#delete-an-issue-note
+func (s *NotesService) DeleteIssueNote(pid interface{}, issue, note int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/notes/%d", pathEscape(project), issue, note)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ListSnippetNotesOptions represents the available ListSnippetNotes() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#list-all-snippet-notes
+type ListSnippetNotesOptions struct {
+	ListOptions
+	OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort    *string `url:"sort,omitempty" json:"sort,omitempty"`
+}
+
+// ListSnippetNotes gets a list of all notes for a single snippet. Snippet
+// notes are comments users can post to a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#list-all-snippet-notes
+func (s *NotesService) ListSnippetNotes(pid interface{}, snippet int, opt *ListSnippetNotesOptions, options ...OptionFunc) ([]*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/notes", pathEscape(project), snippet)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var n []*Note
+	resp, err := s.client.Do(req, &n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// GetSnippetNote returns a single note for a given snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#get-single-snippet-note
+func (s *NotesService) GetSnippetNote(pid interface{}, snippet, note int, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/notes/%d", pathEscape(project), snippet, note)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// CreateSnippetNoteOptions represents the available CreateSnippetNote()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-snippet-note
+type CreateSnippetNoteOptions struct {
+	Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// CreateSnippetNote creates a new note for a single snippet. Snippet notes are
+// comments users can post to a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-snippet-note
+func (s *NotesService) CreateSnippetNote(pid interface{}, snippet int, opt *CreateSnippetNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/notes", pathEscape(project), snippet)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// UpdateSnippetNoteOptions represents the available UpdateSnippetNote()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-snippet-note
+type UpdateSnippetNoteOptions struct {
+	Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// UpdateSnippetNote modifies existing note of a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-snippet-note
+func (s *NotesService) UpdateSnippetNote(pid interface{}, snippet, note int, opt *UpdateSnippetNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/notes/%d", pathEscape(project), snippet, note)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// DeleteSnippetNote deletes an existing note of a snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#delete-a-snippet-note
+func (s *NotesService) DeleteSnippetNote(pid interface{}, snippet, note int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/notes/%d", pathEscape(project), snippet, note)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ListMergeRequestNotesOptions represents the available ListMergeRequestNotes()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#list-all-merge-request-notes
+type ListMergeRequestNotesOptions struct {
+	ListOptions
+	OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort    *string `url:"sort,omitempty" json:"sort,omitempty"`
+}
+
+// ListMergeRequestNotes gets a list of all notes for a single merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#list-all-merge-request-notes
+func (s *NotesService) ListMergeRequestNotes(pid interface{}, mergeRequest int, opt *ListMergeRequestNotesOptions, options ...OptionFunc) ([]*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/notes", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var n []*Note
+	resp, err := s.client.Do(req, &n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// GetMergeRequestNote returns a single note for a given merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#get-single-merge-request-note
+func (s *NotesService) GetMergeRequestNote(pid interface{}, mergeRequest, note int, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/notes/%d", pathEscape(project), mergeRequest, note)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// CreateMergeRequestNoteOptions represents the available
+// CreateMergeRequestNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-merge-request-note
+type CreateMergeRequestNoteOptions struct {
+	Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// CreateMergeRequestNote creates a new note for a single merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#create-new-merge-request-note
+func (s *NotesService) CreateMergeRequestNote(pid interface{}, mergeRequest int, opt *CreateMergeRequestNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/notes", pathEscape(project), mergeRequest)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// UpdateMergeRequestNoteOptions represents the available
+// UpdateMergeRequestNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-merge-request-note
+type UpdateMergeRequestNoteOptions struct {
+	Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// UpdateMergeRequestNote modifies existing note of a merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#modify-existing-merge-request-note
+func (s *NotesService) UpdateMergeRequestNote(pid interface{}, mergeRequest, note int, opt *UpdateMergeRequestNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf(
+		"projects/%s/merge_requests/%d/notes/%d", pathEscape(project), mergeRequest, note)
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// DeleteMergeRequestNote deletes an existing note of a merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notes.html#delete-a-merge-request-note
+func (s *NotesService) DeleteMergeRequestNote(pid interface{}, mergeRequest, note int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf(
+		"projects/%s/merge_requests/%d/notes/%d", pathEscape(project), mergeRequest, note)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ListEpicNotesOptions represents the available ListEpicNotes() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/notes.html#list-all-epic-notes
+type ListEpicNotesOptions struct {
+	ListOptions
+	OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort    *string `url:"sort,omitempty" json:"sort,omitempty"`
+}
+
+// ListEpicNotes gets a list of all notes for a single epic.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/notes.html#list-all-epic-notes
+func (s *NotesService) ListEpicNotes(gid interface{}, epic int, opt *ListEpicNotesOptions, options ...OptionFunc) ([]*Note, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/notes", pathEscape(group), epic)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var n []*Note
+	resp, err := s.client.Do(req, &n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// GetEpicNote returns a single note for an epic.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/notes.html#get-single-epic-note
+func (s *NotesService) GetEpicNote(gid interface{}, epic, note int, options ...OptionFunc) (*Note, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/notes/%d", pathEscape(group), epic, note)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// CreateEpicNoteOptions represents the available CreateEpicNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/notes.html#create-new-epic-note
+type CreateEpicNoteOptions struct {
+	Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// CreateEpicNote creates a new note for a single merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/notes.html#create-new-epic-note
+func (s *NotesService) CreateEpicNote(gid interface{}, epic int, opt *CreateEpicNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/notes", pathEscape(group), epic)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// UpdateEpicNoteOptions represents the available UpdateEpicNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/notes.html#modify-existing-epic-note
+type UpdateEpicNoteOptions struct {
+	Body *string `url:"body,omitempty" json:"body,omitempty"`
+}
+
+// UpdateEpicNote modifies existing note of an epic.
+//
+// https://docs.gitlab.com/ee/api/notes.html#modify-existing-epic-note
+func (s *NotesService) UpdateEpicNote(gid interface{}, epic, note int, opt *UpdateEpicNoteOptions, options ...OptionFunc) (*Note, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/notes/%d", pathEscape(group), epic, note)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	n := new(Note)
+	resp, err := s.client.Do(req, n)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return n, resp, err
+}
+
+// DeleteEpicNote deletes an existing note of a merge request.
+//
+// https://docs.gitlab.com/ee/api/notes.html#delete-an-epic-note
+func (s *NotesService) DeleteEpicNote(gid interface{}, epic, note int, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/notes/%d", pathEscape(group), epic, note)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/notifications.go b/vendor/github.com/xanzy/go-gitlab/notifications.go
new file mode 100644
index 000000000..c044831b9
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/notifications.go
@@ -0,0 +1,213 @@
+package gitlab
+
+import (
+	"errors"
+	"fmt"
+)
+
+// NotificationSettingsService handles communication with the notification settings
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/notification_settings.html
+type NotificationSettingsService struct {
+	client *Client
+}
+
+// NotificationSettings represents the Gitlab notification setting.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#notification-settings
+type NotificationSettings struct {
+	Level             NotificationLevelValue `json:"level"`
+	NotificationEmail string                 `json:"notification_email"`
+	Events            *NotificationEvents    `json:"events"`
+}
+
+// NotificationEvents represents the available notification setting events.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#notification-settings
+type NotificationEvents struct {
+	CloseIssue           bool `json:"close_issue"`
+	CloseMergeRequest    bool `json:"close_merge_request"`
+	FailedPipeline       bool `json:"failed_pipeline"`
+	MergeMergeRequest    bool `json:"merge_merge_request"`
+	NewIssue             bool `json:"new_issue"`
+	NewMergeRequest      bool `json:"new_merge_request"`
+	NewNote              bool `json:"new_note"`
+	ReassignIssue        bool `json:"reassign_issue"`
+	ReassignMergeRequest bool `json:"reassign_merge_request"`
+	ReopenIssue          bool `json:"reopen_issue"`
+	ReopenMergeRequest   bool `json:"reopen_merge_request"`
+	SuccessPipeline      bool `json:"success_pipeline"`
+}
+
+func (ns NotificationSettings) String() string {
+	return Stringify(ns)
+}
+
+// GetGlobalSettings returns current notification settings and email address.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#global-notification-settings
+func (s *NotificationSettingsService) GetGlobalSettings(options ...OptionFunc) (*NotificationSettings, *Response, error) {
+	u := "notification_settings"
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ns := new(NotificationSettings)
+	resp, err := s.client.Do(req, ns)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ns, resp, err
+}
+
+// NotificationSettingsOptions represents the available options that can be passed
+// to the API when updating the notification settings.
+type NotificationSettingsOptions struct {
+	Level                *NotificationLevelValue `url:"level,omitempty" json:"level,omitempty"`
+	NotificationEmail    *string                 `url:"notification_email,omitempty" json:"notification_email,omitempty"`
+	CloseIssue           *bool                   `url:"close_issue,omitempty" json:"close_issue,omitempty"`
+	CloseMergeRequest    *bool                   `url:"close_merge_request,omitempty" json:"close_merge_request,omitempty"`
+	FailedPipeline       *bool                   `url:"failed_pipeline,omitempty" json:"failed_pipeline,omitempty"`
+	MergeMergeRequest    *bool                   `url:"merge_merge_request,omitempty" json:"merge_merge_request,omitempty"`
+	NewIssue             *bool                   `url:"new_issue,omitempty" json:"new_issue,omitempty"`
+	NewMergeRequest      *bool                   `url:"new_merge_request,omitempty" json:"new_merge_request,omitempty"`
+	NewNote              *bool                   `url:"new_note,omitempty" json:"new_note,omitempty"`
+	ReassignIssue        *bool                   `url:"reassign_issue,omitempty" json:"reassign_issue,omitempty"`
+	ReassignMergeRequest *bool                   `url:"reassign_merge_request,omitempty" json:"reassign_merge_request,omitempty"`
+	ReopenIssue          *bool                   `url:"reopen_issue,omitempty" json:"reopen_issue,omitempty"`
+	ReopenMergeRequest   *bool                   `url:"reopen_merge_request,omitempty" json:"reopen_merge_request,omitempty"`
+	SuccessPipeline      *bool                   `url:"success_pipeline,omitempty" json:"success_pipeline,omitempty"`
+}
+
+// UpdateGlobalSettings updates current notification settings and email address.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#update-global-notification-settings
+func (s *NotificationSettingsService) UpdateGlobalSettings(opt *NotificationSettingsOptions, options ...OptionFunc) (*NotificationSettings, *Response, error) {
+	if opt.Level != nil && *opt.Level == GlobalNotificationLevel {
+		return nil, nil, errors.New(
+			"notification level 'global' is not valid for global notification settings")
+	}
+
+	u := "notification_settings"
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ns := new(NotificationSettings)
+	resp, err := s.client.Do(req, ns)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ns, resp, err
+}
+
+// GetSettingsForGroup returns current group notification settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#group-project-level-notification-settings
+func (s *NotificationSettingsService) GetSettingsForGroup(gid interface{}, options ...OptionFunc) (*NotificationSettings, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/notification_settings", pathEscape(group))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ns := new(NotificationSettings)
+	resp, err := s.client.Do(req, ns)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ns, resp, err
+}
+
+// GetSettingsForProject returns current project notification settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#group-project-level-notification-settings
+func (s *NotificationSettingsService) GetSettingsForProject(pid interface{}, options ...OptionFunc) (*NotificationSettings, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/notification_settings", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ns := new(NotificationSettings)
+	resp, err := s.client.Do(req, ns)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ns, resp, err
+}
+
+// UpdateSettingsForGroup updates current group notification settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#update-group-project-level-notification-settings
+func (s *NotificationSettingsService) UpdateSettingsForGroup(gid interface{}, opt *NotificationSettingsOptions, options ...OptionFunc) (*NotificationSettings, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/notification_settings", pathEscape(group))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ns := new(NotificationSettings)
+	resp, err := s.client.Do(req, ns)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ns, resp, err
+}
+
+// UpdateSettingsForProject updates current project notification settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/notification_settings.html#update-group-project-level-notification-settings
+func (s *NotificationSettingsService) UpdateSettingsForProject(pid interface{}, opt *NotificationSettingsOptions, options ...OptionFunc) (*NotificationSettings, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/notification_settings", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ns := new(NotificationSettings)
+	resp, err := s.client.Do(req, ns)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ns, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/pages_domains.go b/vendor/github.com/xanzy/go-gitlab/pages_domains.go
new file mode 100644
index 000000000..da8f51497
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/pages_domains.go
@@ -0,0 +1,193 @@
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// PagesDomainsService handles communication with the pages domains
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pages_domains.html
+type PagesDomainsService struct {
+	client *Client
+}
+
+// PagesDomain represents a pages domain.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pages_domains.html
+type PagesDomain struct {
+	Domain           string     `json:"domain"`
+	URL              string     `json:"url"`
+	ProjectID        int        `json:"project_id"`
+	Verified         bool       `json:"verified"`
+	VerificationCode string     `json:"verification_code"`
+	EnabledUntil     *time.Time `json:"enabled_until"`
+	Certificate      struct {
+		Expired    bool       `json:"expired"`
+		Expiration *time.Time `json:"expiration"`
+	} `json:"certificate"`
+}
+
+// ListPagesDomainsOptions represents the available ListPagesDomains() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pages_domains.html#list-pages-domains
+type ListPagesDomainsOptions ListOptions
+
+// ListPagesDomains gets a list of project pages domains.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pages_domains.html#list-pages-domains
+func (s *PagesDomainsService) ListPagesDomains(pid interface{}, opt *ListPagesDomainsOptions, options ...OptionFunc) ([]*PagesDomain, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pages/domains", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pd []*PagesDomain
+	resp, err := s.client.Do(req, &pd)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pd, resp, err
+}
+
+// ListAllPagesDomains gets a list of all pages domains.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pages_domains.html#list-all-pages-domains
+func (s *PagesDomainsService) ListAllPagesDomains(options ...OptionFunc) ([]*PagesDomain, *Response, error) {
+	req, err := s.client.NewRequest("GET", "pages/domains", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pd []*PagesDomain
+	resp, err := s.client.Do(req, &pd)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pd, resp, err
+}
+
+// GetPagesDomain get a specific pages domain for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pages_domains.html#single-pages-domain
+func (s *PagesDomainsService) GetPagesDomain(pid interface{}, domain string, options ...OptionFunc) (*PagesDomain, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pages/domains/%s", pathEscape(project), domain)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pd := new(PagesDomain)
+	resp, err := s.client.Do(req, pd)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pd, resp, err
+}
+
+// CreatePagesDomainOptions represents the available CreatePagesDomain() options.
+//
+// GitLab API docs:
+// // https://docs.gitlab.com/ce/api/pages_domains.html#create-new-pages-domain
+type CreatePagesDomainOptions struct {
+	Domain      *string `url:"domain,omitempty" json:"domain,omitempty"`
+	Certificate *string `url:"certifiate,omitempty" json:"certifiate,omitempty"`
+	Key         *string `url:"key,omitempty" json:"key,omitempty"`
+}
+
+// CreatePagesDomain creates a new project pages domain.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pages_domains.html#create-new-pages-domain
+func (s *PagesDomainsService) CreatePagesDomain(pid interface{}, opt *CreatePagesDomainOptions, options ...OptionFunc) (*PagesDomain, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pages/domains", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pd := new(PagesDomain)
+	resp, err := s.client.Do(req, pd)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pd, resp, err
+}
+
+// UpdatePagesDomainOptions represents the available UpdatePagesDomain() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pages_domains.html#update-pages-domain
+type UpdatePagesDomainOptions struct {
+	Cerificate *string `url:"certifiate" json:"certifiate"`
+	Key        *string `url:"key" json:"key"`
+}
+
+// UpdatePagesDomain updates an existing project pages domain.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pages_domains.html#update-pages-domain
+func (s *PagesDomainsService) UpdatePagesDomain(pid interface{}, domain string, opt *UpdatePagesDomainOptions, options ...OptionFunc) (*PagesDomain, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pages/domains/%s", pathEscape(project), domain)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pd := new(PagesDomain)
+	resp, err := s.client.Do(req, pd)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pd, resp, err
+}
+
+// DeletePagesDomain deletes an existing prject pages domain.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pages_domains.html#delete-pages-domain
+func (s *PagesDomainsService) DeletePagesDomain(pid interface{}, domain string, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pages/domains/%s", pathEscape(project), domain)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go b/vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go
new file mode 100644
index 000000000..ebcf8297a
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/pipeline_schedules.go
@@ -0,0 +1,327 @@
+//
+// Copyright 2018, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// PipelineSchedulesService handles communication with the pipeline
+// schedules related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipeline_schedules.html
+type PipelineSchedulesService struct {
+	client *Client
+}
+
+// PipelineSchedule represents a pipeline schedule.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html
+type PipelineSchedule struct {
+	ID           int        `json:"id"`
+	Description  string     `json:"description"`
+	Ref          string     `json:"ref"`
+	Cron         string     `json:"cron"`
+	CronTimezone string     `json:"cron_timezone"`
+	NextRunAt    *time.Time `json:"next_run_at"`
+	Active       bool       `json:"active"`
+	CreatedAt    *time.Time `json:"created_at"`
+	UpdatedAt    *time.Time `json:"updated_at"`
+	Owner        *User      `json:"owner"`
+	LastPipeline struct {
+		ID     int    `json:"id"`
+		SHA    string `json:"sha"`
+		Ref    string `json:"ref"`
+		Status string `json:"status"`
+	} `json:"last_pipeline"`
+	Variables []*PipelineVariable `json:"variables"`
+}
+
+// ListPipelineSchedulesOptions represents the available ListPipelineTriggers() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#list-project-triggers
+type ListPipelineSchedulesOptions ListOptions
+
+// ListPipelineSchedules gets a list of project triggers.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html
+func (s *PipelineSchedulesService) ListPipelineSchedules(pid interface{}, opt *ListPipelineSchedulesOptions, options ...OptionFunc) ([]*PipelineSchedule, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipeline_schedules", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ps []*PipelineSchedule
+	resp, err := s.client.Do(req, &ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, err
+}
+
+// GetPipelineSchedule gets a pipeline schedule.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html
+func (s *PipelineSchedulesService) GetPipelineSchedule(pid interface{}, schedule int, options ...OptionFunc) (*PipelineSchedule, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", pathEscape(project), schedule)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(PipelineSchedule)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// CreatePipelineScheduleOptions represents the available
+// CreatePipelineSchedule() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
+type CreatePipelineScheduleOptions struct {
+	Description  *string `url:"description" json:"description"`
+	Ref          *string `url:"ref" json:"ref"`
+	Cron         *string `url:"cron" json:"cron"`
+	CronTimezone *string `url:"cron_timezone,omitempty" json:"cron_timezone,omitempty"`
+	Active       *bool   `url:"active,omitempty" json:"active,omitempty"`
+}
+
+// CreatePipelineSchedule creates a pipeline schedule.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
+func (s *PipelineSchedulesService) CreatePipelineSchedule(pid interface{}, opt *CreatePipelineScheduleOptions, options ...OptionFunc) (*PipelineSchedule, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipeline_schedules", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(PipelineSchedule)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// EditPipelineScheduleOptions represents the available
+// EditPipelineSchedule() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
+type EditPipelineScheduleOptions struct {
+	Description  *string `url:"description,omitempty" json:"description,omitempty"`
+	Ref          *string `url:"ref,omitempty" json:"ref,omitempty"`
+	Cron         *string `url:"cron,omitempty" json:"cron,omitempty"`
+	CronTimezone *string `url:"cron_timezone,omitempty" json:"cron_timezone,omitempty"`
+	Active       *bool   `url:"active,omitempty" json:"active,omitempty"`
+}
+
+// EditPipelineSchedule edits a pipeline schedule.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule
+func (s *PipelineSchedulesService) EditPipelineSchedule(pid interface{}, schedule int, opt *EditPipelineScheduleOptions, options ...OptionFunc) (*PipelineSchedule, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", pathEscape(project), schedule)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(PipelineSchedule)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// TakeOwnershipOfPipelineSchedule sets the owner of the specified
+// pipeline schedule to the user issuing the request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#take-ownership-of-a-pipeline-schedule
+func (s *PipelineSchedulesService) TakeOwnershipOfPipelineSchedule(pid interface{}, schedule int, options ...OptionFunc) (*PipelineSchedule, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/take_ownership", pathEscape(project), schedule)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(PipelineSchedule)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// DeletePipelineSchedule deletes a pipeline schedule.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#delete-a-pipeline-schedule
+func (s *PipelineSchedulesService) DeletePipelineSchedule(pid interface{}, schedule int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d", pathEscape(project), schedule)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// CreatePipelineScheduleVariableOptions represents the available
+// CreatePipelineScheduleVariable() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
+type CreatePipelineScheduleVariableOptions struct {
+	Key          *string `url:"key" json:"key"`
+	Value        *string `url:"value" json:"value"`
+	VariableType *string `url:"variable_type,omitempty" json:"variable_type,omitempty"`
+}
+
+// CreatePipelineScheduleVariable creates a pipeline schedule variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#create-a-new-pipeline-schedule
+func (s *PipelineSchedulesService) CreatePipelineScheduleVariable(pid interface{}, schedule int, opt *CreatePipelineScheduleVariableOptions, options ...OptionFunc) (*PipelineVariable, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables", pathEscape(project), schedule)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(PipelineVariable)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// EditPipelineScheduleVariableOptions represents the available
+// EditPipelineScheduleVariable() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule-variable
+type EditPipelineScheduleVariableOptions struct {
+	Value        *string `url:"value" json:"value"`
+	VariableType *string `url:"variable_type,omitempty" json:"variable_type,omitempty"`
+}
+
+// EditPipelineScheduleVariable creates a pipeline schedule variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#edit-a-pipeline-schedule-variable
+func (s *PipelineSchedulesService) EditPipelineScheduleVariable(pid interface{}, schedule int, key string, opt *EditPipelineScheduleVariableOptions, options ...OptionFunc) (*PipelineVariable, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables/%s", pathEscape(project), schedule, key)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(PipelineVariable)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// DeletePipelineScheduleVariable creates a pipeline schedule variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_schedules.html#delete-a-pipeline-schedule-variable
+func (s *PipelineSchedulesService) DeletePipelineScheduleVariable(pid interface{}, schedule int, key string, options ...OptionFunc) (*PipelineVariable, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipeline_schedules/%d/variables/%s", pathEscape(project), schedule, key)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(PipelineVariable)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/pipeline_triggers.go b/vendor/github.com/xanzy/go-gitlab/pipeline_triggers.go
new file mode 100644
index 000000000..f4f83ca0d
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/pipeline_triggers.go
@@ -0,0 +1,231 @@
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// PipelineTriggersService handles Project pipeline triggers.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html
+type PipelineTriggersService struct {
+	client *Client
+}
+
+// PipelineTrigger represents a project pipeline trigger.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#pipeline-triggers
+type PipelineTrigger struct {
+	ID          int        `json:"id"`
+	Description string     `json:"description"`
+	CreatedAt   *time.Time `json:"created_at"`
+	DeletedAt   *time.Time `json:"deleted_at"`
+	LastUsed    *time.Time `json:"last_used"`
+	Token       string     `json:"token"`
+	UpdatedAt   *time.Time `json:"updated_at"`
+	Owner       *User      `json:"owner"`
+}
+
+// ListPipelineTriggersOptions represents the available ListPipelineTriggers() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#list-project-triggers
+type ListPipelineTriggersOptions ListOptions
+
+// ListPipelineTriggers gets a list of project triggers.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#list-project-triggers
+func (s *PipelineTriggersService) ListPipelineTriggers(pid interface{}, opt *ListPipelineTriggersOptions, options ...OptionFunc) ([]*PipelineTrigger, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/triggers", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pt []*PipelineTrigger
+	resp, err := s.client.Do(req, &pt)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pt, resp, err
+}
+
+// GetPipelineTrigger gets a specific pipeline trigger for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#get-trigger-details
+func (s *PipelineTriggersService) GetPipelineTrigger(pid interface{}, trigger int, options ...OptionFunc) (*PipelineTrigger, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/triggers/%d", pathEscape(project), trigger)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pt := new(PipelineTrigger)
+	resp, err := s.client.Do(req, pt)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pt, resp, err
+}
+
+// AddPipelineTriggerOptions represents the available AddPipelineTrigger() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#create-a-project-trigger
+type AddPipelineTriggerOptions struct {
+	Description *string `url:"description,omitempty" json:"description,omitempty"`
+}
+
+// AddPipelineTrigger adds a pipeline trigger to a specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#create-a-project-trigger
+func (s *PipelineTriggersService) AddPipelineTrigger(pid interface{}, opt *AddPipelineTriggerOptions, options ...OptionFunc) (*PipelineTrigger, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/triggers", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pt := new(PipelineTrigger)
+	resp, err := s.client.Do(req, pt)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pt, resp, err
+}
+
+// EditPipelineTriggerOptions represents the available EditPipelineTrigger() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#update-a-project-trigger
+type EditPipelineTriggerOptions struct {
+	Description *string `url:"description,omitempty" json:"description,omitempty"`
+}
+
+// EditPipelineTrigger edits a trigger for a specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#update-a-project-trigger
+func (s *PipelineTriggersService) EditPipelineTrigger(pid interface{}, trigger int, opt *EditPipelineTriggerOptions, options ...OptionFunc) (*PipelineTrigger, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/triggers/%d", pathEscape(project), trigger)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pt := new(PipelineTrigger)
+	resp, err := s.client.Do(req, pt)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pt, resp, err
+}
+
+// TakeOwnershipOfPipelineTrigger sets the owner of the specified
+// pipeline trigger to the user issuing the request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#take-ownership-of-a-project-trigger
+func (s *PipelineTriggersService) TakeOwnershipOfPipelineTrigger(pid interface{}, trigger int, options ...OptionFunc) (*PipelineTrigger, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/triggers/%d/take_ownership", pathEscape(project), trigger)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pt := new(PipelineTrigger)
+	resp, err := s.client.Do(req, pt)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pt, resp, err
+}
+
+// DeletePipelineTrigger removes a trigger from a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipeline_triggers.html#remove-a-project-trigger
+func (s *PipelineTriggersService) DeletePipelineTrigger(pid interface{}, trigger int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/triggers/%d", pathEscape(project), trigger)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// RunPipelineTriggerOptions represents the available RunPipelineTrigger() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/ci/triggers/README.html#triggering-a-pipeline
+type RunPipelineTriggerOptions struct {
+	Ref       *string           `url:"ref" json:"ref"`
+	Token     *string           `url:"token" json:"token"`
+	Variables map[string]string `url:"variables,omitempty" json:"variables,omitempty"`
+}
+
+// RunPipelineTrigger starts a trigger from a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/ci/triggers/README.html#triggering-a-pipeline
+func (s *PipelineTriggersService) RunPipelineTrigger(pid interface{}, opt *RunPipelineTriggerOptions, options ...OptionFunc) (*Pipeline, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/trigger/pipeline", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pt := new(Pipeline)
+	resp, err := s.client.Do(req, pt)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pt, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/pipelines.go b/vendor/github.com/xanzy/go-gitlab/pipelines.go
new file mode 100644
index 000000000..071e5e502
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/pipelines.go
@@ -0,0 +1,286 @@
+//
+// Copyright 2017, Igor Varavko
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// PipelinesService handles communication with the repositories related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
+type PipelinesService struct {
+	client *Client
+}
+
+// PipelineVariable represents a pipeline variable.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
+type PipelineVariable struct {
+	Key          string `json:"key"`
+	Value        string `json:"value"`
+	VariableType string `json:"variable_type"`
+}
+
+// Pipeline represents a GitLab pipeline.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
+type Pipeline struct {
+	ID             int             `json:"id"`
+	Status         string          `json:"status"`
+	Ref            string          `json:"ref"`
+	SHA            string          `json:"sha"`
+	BeforeSHA      string          `json:"before_sha"`
+	Tag            bool            `json:"tag"`
+	YamlErrors     string          `json:"yaml_errors"`
+	User           *BasicUser      `json:"user"`
+	UpdatedAt      *time.Time      `json:"updated_at"`
+	CreatedAt      *time.Time      `json:"created_at"`
+	StartedAt      *time.Time      `json:"started_at"`
+	FinishedAt     *time.Time      `json:"finished_at"`
+	CommittedAt    *time.Time      `json:"committed_at"`
+	Duration       int             `json:"duration"`
+	Coverage       string          `json:"coverage"`
+	WebURL         string          `json:"web_url"`
+	DetailedStatus *DetailedStatus `json:"detailed_status"`
+}
+
+// DetailedStatus contains detailed information about the status of a pipeline
+type DetailedStatus struct {
+	Icon         string `json:"icon"`
+	Text         string `json:"text"`
+	Label        string `json:"label"`
+	Group        string `json:"group"`
+	Tooltip      string `json:"tooltip"`
+	HasDetails   bool   `json:"has_details"`
+	DetailsPath  string `json:"details_path"`
+	Illustration struct {
+		Image string `json:"image"`
+	} `json:"illustration"`
+	Favicon string `json:"favicon"`
+}
+
+func (p Pipeline) String() string {
+	return Stringify(p)
+}
+
+// PipelineInfo shows the basic entities of a pipeline, mostly used as fields
+// on other assets, like Commit.
+type PipelineInfo struct {
+	ID        int        `json:"id"`
+	Status    string     `json:"status"`
+	Ref       string     `json:"ref"`
+	SHA       string     `json:"sha"`
+	WebURL    string     `json:"web_url"`
+	UpdatedAt *time.Time `json:"updated_at"`
+	CreatedAt *time.Time `json:"created_at"`
+}
+
+func (p PipelineInfo) String() string {
+	return Stringify(p)
+}
+
+// ListProjectPipelinesOptions represents the available ListProjectPipelines() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines
+type ListProjectPipelinesOptions struct {
+	ListOptions
+	Scope      *string          `url:"scope,omitempty" json:"scope,omitempty"`
+	Status     *BuildStateValue `url:"status,omitempty" json:"status,omitempty"`
+	Ref        *string          `url:"ref,omitempty" json:"ref,omitempty"`
+	SHA        *string          `url:"sha,omitempty" json:"sha,omitempty"`
+	YamlErrors *bool            `url:"yaml_errors,omitempty" json:"yaml_errors,omitempty"`
+	Name       *string          `url:"name,omitempty" json:"name,omitempty"`
+	Username   *string          `url:"username,omitempty" json:"username,omitempty"`
+	OrderBy    *string          `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort       *string          `url:"sort,omitempty" json:"sort,omitempty"`
+}
+
+// ListProjectPipelines gets a list of project piplines.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines
+func (s *PipelinesService) ListProjectPipelines(pid interface{}, opt *ListProjectPipelinesOptions, options ...OptionFunc) ([]*PipelineInfo, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipelines", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var p []*PipelineInfo
+	resp, err := s.client.Do(req, &p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// GetPipeline gets a single project pipeline.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#get-a-single-pipeline
+func (s *PipelinesService) GetPipeline(pid interface{}, pipeline int, options ...OptionFunc) (*Pipeline, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipelines/%d", pathEscape(project), pipeline)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Pipeline)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// GetPipelineVariables gets the variables of a single project pipeline.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#get-variables-of-a-pipeline
+func (s *PipelinesService) GetPipelineVariables(pid interface{}, pipeline int, options ...OptionFunc) ([]*PipelineVariable, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipelines/%d/variables", pathEscape(project), pipeline)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var p []*PipelineVariable
+	resp, err := s.client.Do(req, &p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// CreatePipelineOptions represents the available CreatePipeline() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#create-a-new-pipeline
+type CreatePipelineOptions struct {
+	Ref       *string             `url:"ref" json:"ref"`
+	Variables []*PipelineVariable `url:"variables,omitempty" json:"variables,omitempty"`
+}
+
+// CreatePipeline creates a new project pipeline.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#create-a-new-pipeline
+func (s *PipelinesService) CreatePipeline(pid interface{}, opt *CreatePipelineOptions, options ...OptionFunc) (*Pipeline, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipeline", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Pipeline)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// RetryPipelineBuild retries failed builds in a pipeline
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipelines.html#retry-failed-builds-in-a-pipeline
+func (s *PipelinesService) RetryPipelineBuild(pid interface{}, pipeline int, options ...OptionFunc) (*Pipeline, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipelines/%d/retry", pathEscape(project), pipeline)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Pipeline)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// CancelPipelineBuild cancels a pipeline builds
+//
+// GitLab API docs:
+//https://docs.gitlab.com/ce/api/pipelines.html#cancel-a-pipelines-builds
+func (s *PipelinesService) CancelPipelineBuild(pid interface{}, pipeline int, options ...OptionFunc) (*Pipeline, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipelines/%d/cancel", pathEscape(project), pipeline)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Pipeline)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// DeletePipeline deletes an existing pipeline.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/pipelines.html#delete-a-pipeline
+func (s *PipelinesService) DeletePipeline(pid interface{}, pipeline int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/pipelines/%d", pathEscape(project), pipeline)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/project_badges.go b/vendor/github.com/xanzy/go-gitlab/project_badges.go
new file mode 100644
index 000000000..da2dc4d4e
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/project_badges.go
@@ -0,0 +1,207 @@
+package gitlab
+
+import (
+	"fmt"
+)
+
+// ProjectBadge represents a project badge.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#list-all-badges-of-a-project
+type ProjectBadge struct {
+	ID               int    `json:"id"`
+	LinkURL          string `json:"link_url"`
+	ImageURL         string `json:"image_url"`
+	RenderedLinkURL  string `json:"rendered_link_url"`
+	RenderedImageURL string `json:"rendered_image_url"`
+	// Kind represents a project badge kind. Can be empty, when used PreviewProjectBadge().
+	Kind string `json:"kind"`
+}
+
+// ProjectBadgesService handles communication with the project badges
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/project_badges.html
+type ProjectBadgesService struct {
+	client *Client
+}
+
+// ListProjectBadgesOptions represents the available ListProjectBadges()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#list-all-badges-of-a-project
+type ListProjectBadgesOptions ListOptions
+
+// ListProjectBadges gets a list of a project's badges and its group badges.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#list-all-badges-of-a-project
+func (s *ProjectBadgesService) ListProjectBadges(pid interface{}, opt *ListProjectBadgesOptions, options ...OptionFunc) ([]*ProjectBadge, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/badges", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pb []*ProjectBadge
+	resp, err := s.client.Do(req, &pb)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pb, resp, err
+}
+
+// GetProjectBadge gets a project badge.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#get-a-badge-of-a-project
+func (s *ProjectBadgesService) GetProjectBadge(pid interface{}, badge int, options ...OptionFunc) (*ProjectBadge, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/badges/%d", pathEscape(project), badge)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pb := new(ProjectBadge)
+	resp, err := s.client.Do(req, pb)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pb, resp, err
+}
+
+// AddProjectBadgeOptions represents the available AddProjectBadge() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#add-a-badge-to-a-project
+type AddProjectBadgeOptions struct {
+	LinkURL  *string `url:"link_url,omitempty" json:"link_url,omitempty"`
+	ImageURL *string `url:"image_url,omitempty" json:"image_url,omitempty"`
+}
+
+// AddProjectBadge adds a badge to a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#add-a-badge-to-a-project
+func (s *ProjectBadgesService) AddProjectBadge(pid interface{}, opt *AddProjectBadgeOptions, options ...OptionFunc) (*ProjectBadge, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/badges", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pb := new(ProjectBadge)
+	resp, err := s.client.Do(req, pb)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pb, resp, err
+}
+
+// EditProjectBadgeOptions represents the available EditProjectBadge() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#edit-a-badge-of-a-project
+type EditProjectBadgeOptions struct {
+	LinkURL  *string `url:"link_url,omitempty" json:"link_url,omitempty"`
+	ImageURL *string `url:"image_url,omitempty" json:"image_url,omitempty"`
+}
+
+// EditProjectBadge updates a badge of a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#edit-a-badge-of-a-project
+func (s *ProjectBadgesService) EditProjectBadge(pid interface{}, badge int, opt *EditProjectBadgeOptions, options ...OptionFunc) (*ProjectBadge, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/badges/%d", pathEscape(project), badge)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pb := new(ProjectBadge)
+	resp, err := s.client.Do(req, pb)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pb, resp, err
+}
+
+// DeleteProjectBadge removes a badge from a project. Only project's
+// badges will be removed by using this endpoint.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#remove-a-badge-from-a-project
+func (s *ProjectBadgesService) DeleteProjectBadge(pid interface{}, badge int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/badges/%d", pathEscape(project), badge)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ProjectBadgePreviewOptions represents the available PreviewProjectBadge() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#preview-a-badge-from-a-project
+type ProjectBadgePreviewOptions struct {
+	LinkURL  *string `url:"link_url,omitempty" json:"link_url,omitempty"`
+	ImageURL *string `url:"image_url,omitempty" json:"image_url,omitempty"`
+}
+
+// PreviewProjectBadge returns how the link_url and image_url final URLs would be after
+// resolving the placeholder interpolation.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_badges.html#preview-a-badge-from-a-project
+func (s *ProjectBadgesService) PreviewProjectBadge(pid interface{}, opt *ProjectBadgePreviewOptions, options ...OptionFunc) (*ProjectBadge, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/badges/render", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pb := new(ProjectBadge)
+	resp, err := s.client.Do(req, &pb)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pb, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/project_clusters.go b/vendor/github.com/xanzy/go-gitlab/project_clusters.go
new file mode 100644
index 000000000..d2be60e2e
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/project_clusters.go
@@ -0,0 +1,221 @@
+//
+// Copyright 2019, Matej Velikonja
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// ProjectClustersService handles communication with the
+// project clusters related methods of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_clusters.html
+type ProjectClustersService struct {
+	client *Client
+}
+
+// ProjectCluster represents a GitLab Project Cluster.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/project_clusters.html
+type ProjectCluster struct {
+	ID                 int                 `json:"id"`
+	Name               string              `json:"name"`
+	Domain             string              `json:"domain"`
+	CreatedAt          *time.Time          `json:"created_at"`
+	ProviderType       string              `json:"provider_type"`
+	PlatformType       string              `json:"platform_type"`
+	EnvironmentScope   string              `json:"environment_scope"`
+	ClusterType        string              `json:"cluster_type"`
+	User               *User               `json:"user"`
+	PlatformKubernetes *PlatformKubernetes `json:"platform_kubernetes"`
+	Project            *Project            `json:"project"`
+}
+
+func (v ProjectCluster) String() string {
+	return Stringify(v)
+}
+
+// PlatformKubernetes represents a GitLab Project Cluster PlatformKubernetes.
+type PlatformKubernetes struct {
+	APIURL            string `json:"api_url"`
+	Token             string `json:"token"`
+	CaCert            string `json:"ca_cert"`
+	Namespace         string `json:"namespace"`
+	AuthorizationType string `json:"authorization_type"`
+}
+
+// ListClusters gets a list of all clusters in a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_clusters.html#list-project-clusters
+func (s *ProjectClustersService) ListClusters(pid interface{}, options ...OptionFunc) ([]*ProjectCluster, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/clusters", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pcs []*ProjectCluster
+	resp, err := s.client.Do(req, &pcs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pcs, resp, err
+}
+
+// GetCluster gets a cluster.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_clusters.html#get-a-single-project-cluster
+func (s *ProjectClustersService) GetCluster(pid interface{}, cluster int, options ...OptionFunc) (*ProjectCluster, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/clusters/%d", pathEscape(project), cluster)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pc := new(ProjectCluster)
+	resp, err := s.client.Do(req, &pc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pc, resp, err
+}
+
+// AddClusterOptions represents the available AddCluster() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_clusters.html#add-existing-cluster-to-project
+type AddClusterOptions struct {
+	Name               *string                       `url:"name,omitempty" json:"name,omitempty"`
+	Domain             *string                       `url:"domain,omitempty" json:"domain,omitempty"`
+	Enabled            *bool                         `url:"enabled,omitempty" json:"enabled,omitempty"`
+	Managed            *bool                         `url:"managed,omitempty" json:"managed,omitempty"`
+	EnvironmentScope   *string                       `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
+	PlatformKubernetes *AddPlatformKubernetesOptions `url:"platform_kubernetes_attributes,omitempty" json:"platform_kubernetes_attributes,omitempty"`
+}
+
+// AddPlatformKubernetesOptions represents the available PlatformKubernetes options for adding.
+type AddPlatformKubernetesOptions struct {
+	APIURL            *string `url:"api_url,omitempty" json:"api_url,omitempty"`
+	Token             *string `url:"token,omitempty" json:"token,omitempty"`
+	CaCert            *string `url:"ca_cert,omitempty" json:"ca_cert,omitempty"`
+	Namespace         *string `url:"namespace,omitempty" json:"namespace,omitempty"`
+	AuthorizationType *string `url:"authorization_type,omitempty" json:"authorization_type,omitempty"`
+}
+
+// AddCluster adds an existing cluster to the project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_clusters.html#add-existing-cluster-to-project
+func (s *ProjectClustersService) AddCluster(pid interface{}, opt *AddClusterOptions, options ...OptionFunc) (*ProjectCluster, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/clusters/user", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pc := new(ProjectCluster)
+	resp, err := s.client.Do(req, pc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pc, resp, err
+}
+
+// EditClusterOptions represents the available EditCluster() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_clusters.html#edit-project-cluster
+type EditClusterOptions struct {
+	Name               *string                        `url:"name,omitempty" json:"name,omitempty"`
+	Domain             *string                        `url:"domain,omitempty" json:"domain,omitempty"`
+	EnvironmentScope   *string                        `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
+	PlatformKubernetes *EditPlatformKubernetesOptions `url:"platform_kubernetes_attributes,omitempty" json:"platform_kubernetes_attributes,omitempty"`
+}
+
+// EditPlatformKubernetesOptions represents the available PlatformKubernetes options for editing.
+type EditPlatformKubernetesOptions struct {
+	APIURL    *string `url:"api_url,omitempty" json:"api_url,omitempty"`
+	Token     *string `url:"token,omitempty" json:"token,omitempty"`
+	CaCert    *string `url:"ca_cert,omitempty" json:"ca_cert,omitempty"`
+	Namespace *string `url:"namespace,omitempty" json:"namespace,omitempty"`
+}
+
+// EditCluster updates an existing project cluster.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_clusters.html#edit-project-cluster
+func (s *ProjectClustersService) EditCluster(pid interface{}, cluster int, opt *EditClusterOptions, options ...OptionFunc) (*ProjectCluster, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/clusters/%d", pathEscape(project), cluster)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pc := new(ProjectCluster)
+	resp, err := s.client.Do(req, pc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pc, resp, err
+}
+
+// DeleteCluster deletes an existing project cluster.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_clusters.html#delete-project-cluster
+func (s *ProjectClustersService) DeleteCluster(pid interface{}, cluster int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/clusters/%d", pathEscape(project), cluster)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/project_import_export.go b/vendor/github.com/xanzy/go-gitlab/project_import_export.go
new file mode 100644
index 000000000..839b187b5
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/project_import_export.go
@@ -0,0 +1,196 @@
+package gitlab
+
+import (
+	"bytes"
+	"fmt"
+	"time"
+)
+
+// ProjectImportExportService handles communication with the project
+// import/export related methods of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/settings/import_export.html
+type ProjectImportExportService struct {
+	client *Client
+}
+
+// ImportStatus represents a project import status.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_import_export.html#import-status
+type ImportStatus struct {
+	ID                int        `json:"id"`
+	Description       string     `json:"description"`
+	Name              string     `json:"name"`
+	NameWithNamespace string     `json:"name_with_namespace"`
+	Path              string     `json:"path"`
+	PathWithNamespace string     `json:"path_with_namespace"`
+	CreateAt          *time.Time `json:"create_at"`
+	ImportStatus      string     `json:"import_status"`
+}
+
+func (s ImportStatus) String() string {
+	return Stringify(s)
+}
+
+// ExportStatus represents a project export status.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_import_export.html#export-status
+type ExportStatus struct {
+	ID                int        `json:"id"`
+	Description       string     `json:"description"`
+	Name              string     `json:"name"`
+	NameWithNamespace string     `json:"name_with_namespace"`
+	Path              string     `json:"path"`
+	PathWithNamespace string     `json:"path_with_namespace"`
+	CreatedAt         *time.Time `json:"created_at"`
+	ExportStatus      string     `json:"export_status"`
+	Message           string     `json:"message"`
+	Links             struct {
+		APIURL string `json:"api_url"`
+		WebURL string `json:"web_url"`
+	} `json:"_links"`
+}
+
+func (s ExportStatus) String() string {
+	return Stringify(s)
+}
+
+// ScheduleExportOptions represents the available ScheduleExport() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_import_export.html#schedule-an-export
+type ScheduleExportOptions struct {
+	Description *string `url:"description,omitempty" json:"description,omitempty"`
+	Upload      struct {
+		URL        *string `url:"url,omitempty" json:"url,omitempty"`
+		HTTPMethod *string `url:"http_method,omitempty" json:"http_method,omitempty"`
+	} `url:"upload,omitempty" json:"upload,omitempty"`
+}
+
+// ScheduleExport schedules a project export.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_import_export.html#schedule-an-export
+func (s *ProjectImportExportService) ScheduleExport(pid interface{}, opt *ScheduleExportOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/export", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ExportStatus get the status of export.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_import_export.html#export-status
+func (s *ProjectImportExportService) ExportStatus(pid interface{}, options ...OptionFunc) (*ExportStatus, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/export", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	es := new(ExportStatus)
+	resp, err := s.client.Do(req, es)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return es, resp, err
+}
+
+// ExportDownload download the finished export.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_import_export.html#export-download
+func (s *ProjectImportExportService) ExportDownload(pid interface{}, options ...OptionFunc) ([]byte, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/export/download", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var b bytes.Buffer
+	resp, err := s.client.Do(req, &b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b.Bytes(), resp, err
+}
+
+// ImportFileOptions represents the available ImportFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_import_export.html#import-a-file
+type ImportFileOptions struct {
+	Namespace      *string               `url:"namespace,omitempty" json:"namespace,omitempty"`
+	File           *string               `url:"file,omitempty" json:"file,omitempty"`
+	Path           *string               `url:"path,omitempty" json:"path,omitempty"`
+	Overwrite      *bool                 `url:"overwrite,omitempty" json:"overwrite,omitempty"`
+	OverrideParams *CreateProjectOptions `url:"override_params,omitempty" json:"override_params,omitempty"`
+}
+
+// ImportFile import a file.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_import_export.html#import-a-file
+func (s *ProjectImportExportService) ImportFile(opt *ImportFileOptions, options ...OptionFunc) (*ImportStatus, *Response, error) {
+	req, err := s.client.NewRequest("POST", "projects/import", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	is := new(ImportStatus)
+	resp, err := s.client.Do(req, is)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return is, resp, err
+}
+
+// ImportStatus get the status of an import.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_import_export.html#import-status
+func (s *ProjectImportExportService) ImportStatus(pid interface{}, options ...OptionFunc) (*ImportStatus, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/import", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	is := new(ImportStatus)
+	resp, err := s.client.Do(req, is)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return is, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/project_members.go b/vendor/github.com/xanzy/go-gitlab/project_members.go
new file mode 100644
index 000000000..54a25fdcc
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/project_members.go
@@ -0,0 +1,209 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+)
+
+// ProjectMembersService handles communication with the project members
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/members.html
+type ProjectMembersService struct {
+	client *Client
+}
+
+// ListProjectMembersOptions represents the available ListProjectMembers() and
+// ListAllProjectMembers() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project
+type ListProjectMembersOptions struct {
+	ListOptions
+	Query *string `url:"query,omitempty" json:"query,omitempty"`
+}
+
+// ListProjectMembers gets a list of a project's team members viewable by the
+// authenticated user. Returns only direct members and not inherited members
+// through ancestors groups.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project
+func (s *ProjectMembersService) ListProjectMembers(pid interface{}, opt *ListProjectMembersOptions, options ...OptionFunc) ([]*ProjectMember, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/members", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pm []*ProjectMember
+	resp, err := s.client.Do(req, &pm)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pm, resp, err
+}
+
+// ListAllProjectMembers gets a list of a project's team members viewable by the
+// authenticated user. Returns a list including inherited members through
+// ancestor groups.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#list-all-members-of-a-group-or-project-including-inherited-members
+func (s *ProjectMembersService) ListAllProjectMembers(pid interface{}, opt *ListProjectMembersOptions, options ...OptionFunc) ([]*ProjectMember, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/members/all", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pm []*ProjectMember
+	resp, err := s.client.Do(req, &pm)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pm, resp, err
+}
+
+// GetProjectMember gets a project team member.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#get-a-member-of-a-group-or-project
+func (s *ProjectMembersService) GetProjectMember(pid interface{}, user int, options ...OptionFunc) (*ProjectMember, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/members/%d", pathEscape(project), user)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pm := new(ProjectMember)
+	resp, err := s.client.Do(req, pm)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pm, resp, err
+}
+
+// AddProjectMemberOptions represents the available AddProjectMember() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project
+type AddProjectMemberOptions struct {
+	UserID      *int              `url:"user_id,omitempty" json:"user_id,omitempty"`
+	AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+	ExpiresAt   *string           `url:"expires_at,omitempty" json:"expires_at"`
+}
+
+// AddProjectMember adds a user to a project team. This is an idempotent
+// method and can be called multiple times with the same parameters. Adding
+// team membership to a user that is already a member does not affect the
+// existing membership.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#add-a-member-to-a-group-or-project
+func (s *ProjectMembersService) AddProjectMember(pid interface{}, opt *AddProjectMemberOptions, options ...OptionFunc) (*ProjectMember, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/members", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pm := new(ProjectMember)
+	resp, err := s.client.Do(req, pm)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pm, resp, err
+}
+
+// EditProjectMemberOptions represents the available EditProjectMember() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project
+type EditProjectMemberOptions struct {
+	AccessLevel *AccessLevelValue `url:"access_level,omitempty" json:"access_level,omitempty"`
+	ExpiresAt   *string           `url:"expires_at,omitempty" json:"expires_at"`
+}
+
+// EditProjectMember updates a project team member to a specified access level..
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#edit-a-member-of-a-group-or-project
+func (s *ProjectMembersService) EditProjectMember(pid interface{}, user int, opt *EditProjectMemberOptions, options ...OptionFunc) (*ProjectMember, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/members/%d", pathEscape(project), user)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pm := new(ProjectMember)
+	resp, err := s.client.Do(req, pm)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pm, resp, err
+}
+
+// DeleteProjectMember removes a user from a project team.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/members.html#remove-a-member-from-a-group-or-project
+func (s *ProjectMembersService) DeleteProjectMember(pid interface{}, user int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/members/%d", pathEscape(project), user)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/project_snippets.go b/vendor/github.com/xanzy/go-gitlab/project_snippets.go
new file mode 100644
index 000000000..9dc254f03
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/project_snippets.go
@@ -0,0 +1,206 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// ProjectSnippetsService handles communication with the project snippets
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/project_snippets.html
+type ProjectSnippetsService struct {
+	client *Client
+}
+
+// ListProjectSnippetsOptions represents the available ListSnippets() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/project_snippets.html#list-snippets
+type ListProjectSnippetsOptions ListOptions
+
+// ListSnippets gets a list of project snippets.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/project_snippets.html#list-snippets
+func (s *ProjectSnippetsService) ListSnippets(pid interface{}, opt *ListProjectSnippetsOptions, options ...OptionFunc) ([]*Snippet, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ps []*Snippet
+	resp, err := s.client.Do(req, &ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, err
+}
+
+// GetSnippet gets a single project snippet
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#single-snippet
+func (s *ProjectSnippetsService) GetSnippet(pid interface{}, snippet int, options ...OptionFunc) (*Snippet, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d", pathEscape(project), snippet)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ps := new(Snippet)
+	resp, err := s.client.Do(req, ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, err
+}
+
+// CreateProjectSnippetOptions represents the available CreateSnippet() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#create-new-snippet
+type CreateProjectSnippetOptions struct {
+	Title       *string          `url:"title,omitempty" json:"title,omitempty"`
+	FileName    *string          `url:"file_name,omitempty" json:"file_name,omitempty"`
+	Description *string          `url:"description,omitempty" json:"description,omitempty"`
+	Code        *string          `url:"code,omitempty" json:"code,omitempty"`
+	Visibility  *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+}
+
+// CreateSnippet creates a new project snippet. The user must have permission
+// to create new snippets.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#create-new-snippet
+func (s *ProjectSnippetsService) CreateSnippet(pid interface{}, opt *CreateProjectSnippetOptions, options ...OptionFunc) (*Snippet, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ps := new(Snippet)
+	resp, err := s.client.Do(req, ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, err
+}
+
+// UpdateProjectSnippetOptions represents the available UpdateSnippet() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#update-snippet
+type UpdateProjectSnippetOptions struct {
+	Title       *string          `url:"title,omitempty" json:"title,omitempty"`
+	FileName    *string          `url:"file_name,omitempty" json:"file_name,omitempty"`
+	Description *string          `url:"description,omitempty" json:"description,omitempty"`
+	Code        *string          `url:"code,omitempty" json:"code,omitempty"`
+	Visibility  *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+}
+
+// UpdateSnippet updates an existing project snippet. The user must have
+// permission to change an existing snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#update-snippet
+func (s *ProjectSnippetsService) UpdateSnippet(pid interface{}, snippet int, opt *UpdateProjectSnippetOptions, options ...OptionFunc) (*Snippet, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d", pathEscape(project), snippet)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ps := new(Snippet)
+	resp, err := s.client.Do(req, ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, err
+}
+
+// DeleteSnippet deletes an existing project snippet. This is an idempotent
+// function and deleting a non-existent snippet still returns a 200 OK status
+// code.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#delete-snippet
+func (s *ProjectSnippetsService) DeleteSnippet(pid interface{}, snippet int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d", pathEscape(project), snippet)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// SnippetContent returns the raw project snippet as plain text.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/project_snippets.html#snippet-content
+func (s *ProjectSnippetsService) SnippetContent(pid interface{}, snippet int, options ...OptionFunc) ([]byte, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/snippets/%d/raw", pathEscape(project), snippet)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var b bytes.Buffer
+	resp, err := s.client.Do(req, &b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b.Bytes(), resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/project_variables.go b/vendor/github.com/xanzy/go-gitlab/project_variables.go
new file mode 100644
index 000000000..155e3e558
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/project_variables.go
@@ -0,0 +1,201 @@
+//
+// Copyright 2018, Patrick Webster
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+)
+
+// ProjectVariablesService handles communication with the
+// project variables related methods of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_level_variables.html
+type ProjectVariablesService struct {
+	client *Client
+}
+
+// ProjectVariable represents a GitLab Project Variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_level_variables.html
+type ProjectVariable struct {
+	Key              string            `json:"key"`
+	Value            string            `json:"value"`
+	VariableType     VariableTypeValue `json:"variable_type"`
+	Protected        bool              `json:"protected"`
+	Masked           bool              `json:"masked"`
+	EnvironmentScope string            `json:"environment_scope"`
+}
+
+func (v ProjectVariable) String() string {
+	return Stringify(v)
+}
+
+// ListProjectVariablesOptions represents the available options for listing variables
+// in a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_level_variables.html#list-project-variables
+type ListProjectVariablesOptions ListOptions
+
+// ListVariables gets a list of all variables in a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_level_variables.html#list-project-variables
+func (s *ProjectVariablesService) ListVariables(pid interface{}, opt *ListProjectVariablesOptions, options ...OptionFunc) ([]*ProjectVariable, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/variables", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var vs []*ProjectVariable
+	resp, err := s.client.Do(req, &vs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return vs, resp, err
+}
+
+// GetVariable gets a variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_level_variables.html#show-variable-details
+func (s *ProjectVariablesService) GetVariable(pid interface{}, key string, options ...OptionFunc) (*ProjectVariable, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/variables/%s", pathEscape(project), url.PathEscape(key))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	v := new(ProjectVariable)
+	resp, err := s.client.Do(req, v)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return v, resp, err
+}
+
+// CreateProjectVariableOptions represents the available CreateVariable()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_level_variables.html#create-variable
+type CreateProjectVariableOptions struct {
+	Key              *string            `url:"key,omitempty" json:"key,omitempty"`
+	Value            *string            `url:"value,omitempty" json:"value,omitempty"`
+	VariableType     *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"`
+	Protected        *bool              `url:"protected,omitempty" json:"protected,omitempty"`
+	Masked           *bool              `url:"masked,omitempty" json:"masked,omitempty"`
+	EnvironmentScope *string            `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
+}
+
+// CreateVariable creates a new project variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_level_variables.html#create-variable
+func (s *ProjectVariablesService) CreateVariable(pid interface{}, opt *CreateProjectVariableOptions, options ...OptionFunc) (*ProjectVariable, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/variables", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	v := new(ProjectVariable)
+	resp, err := s.client.Do(req, v)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return v, resp, err
+}
+
+// UpdateProjectVariableOptions represents the available UpdateVariable()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_level_variables.html#update-variable
+type UpdateProjectVariableOptions struct {
+	Value            *string            `url:"value,omitempty" json:"value,omitempty"`
+	VariableType     *VariableTypeValue `url:"variable_type,omitempty" json:"variable_type,omitempty"`
+	Protected        *bool              `url:"protected,omitempty" json:"protected,omitempty"`
+	Masked           *bool              `url:"masked,omitempty" json:"masked,omitempty"`
+	EnvironmentScope *string            `url:"environment_scope,omitempty" json:"environment_scope,omitempty"`
+}
+
+// UpdateVariable updates a project's variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_level_variables.html#update-variable
+func (s *ProjectVariablesService) UpdateVariable(pid interface{}, key string, opt *UpdateProjectVariableOptions, options ...OptionFunc) (*ProjectVariable, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/variables/%s", pathEscape(project), url.PathEscape(key))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	v := new(ProjectVariable)
+	resp, err := s.client.Do(req, v)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return v, resp, err
+}
+
+// RemoveVariable removes a project's variable.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/project_level_variables.html#remove-variable
+func (s *ProjectVariablesService) RemoveVariable(pid interface{}, key string, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/variables/%s", pathEscape(project), url.PathEscape(key))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/projects.go b/vendor/github.com/xanzy/go-gitlab/projects.go
new file mode 100644
index 000000000..04c3c8948
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/projects.go
@@ -0,0 +1,1512 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"mime/multipart"
+	"os"
+	"time"
+)
+
+// ProjectsService handles communication with the repositories related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html
+type ProjectsService struct {
+	client *Client
+}
+
+// Project represents a GitLab project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html
+type Project struct {
+	ID                                        int               `json:"id"`
+	Description                               string            `json:"description"`
+	DefaultBranch                             string            `json:"default_branch"`
+	Public                                    bool              `json:"public"`
+	Visibility                                VisibilityValue   `json:"visibility"`
+	SSHURLToRepo                              string            `json:"ssh_url_to_repo"`
+	HTTPURLToRepo                             string            `json:"http_url_to_repo"`
+	WebURL                                    string            `json:"web_url"`
+	ReadmeURL                                 string            `json:"readme_url"`
+	TagList                                   []string          `json:"tag_list"`
+	Owner                                     *User             `json:"owner"`
+	Name                                      string            `json:"name"`
+	NameWithNamespace                         string            `json:"name_with_namespace"`
+	Path                                      string            `json:"path"`
+	PathWithNamespace                         string            `json:"path_with_namespace"`
+	IssuesEnabled                             bool              `json:"issues_enabled"`
+	OpenIssuesCount                           int               `json:"open_issues_count"`
+	MergeRequestsEnabled                      bool              `json:"merge_requests_enabled"`
+	ApprovalsBeforeMerge                      int               `json:"approvals_before_merge"`
+	JobsEnabled                               bool              `json:"jobs_enabled"`
+	WikiEnabled                               bool              `json:"wiki_enabled"`
+	SnippetsEnabled                           bool              `json:"snippets_enabled"`
+	ResolveOutdatedDiffDiscussions            bool              `json:"resolve_outdated_diff_discussions"`
+	ContainerRegistryEnabled                  bool              `json:"container_registry_enabled"`
+	CreatedAt                                 *time.Time        `json:"created_at,omitempty"`
+	LastActivityAt                            *time.Time        `json:"last_activity_at,omitempty"`
+	CreatorID                                 int               `json:"creator_id"`
+	Namespace                                 *ProjectNamespace `json:"namespace"`
+	ImportStatus                              string            `json:"import_status"`
+	ImportError                               string            `json:"import_error"`
+	Permissions                               *Permissions      `json:"permissions"`
+	Archived                                  bool              `json:"archived"`
+	AvatarURL                                 string            `json:"avatar_url"`
+	SharedRunnersEnabled                      bool              `json:"shared_runners_enabled"`
+	ForksCount                                int               `json:"forks_count"`
+	StarCount                                 int               `json:"star_count"`
+	RunnersToken                              string            `json:"runners_token"`
+	PublicBuilds                              bool              `json:"public_builds"`
+	OnlyAllowMergeIfPipelineSucceeds          bool              `json:"only_allow_merge_if_pipeline_succeeds"`
+	OnlyAllowMergeIfAllDiscussionsAreResolved bool              `json:"only_allow_merge_if_all_discussions_are_resolved"`
+	LFSEnabled                                bool              `json:"lfs_enabled"`
+	RequestAccessEnabled                      bool              `json:"request_access_enabled"`
+	MergeMethod                               MergeMethodValue  `json:"merge_method"`
+	ForkedFromProject                         *ForkParent       `json:"forked_from_project"`
+	Mirror                                    bool              `json:"mirror"`
+	MirrorUserID                              int               `json:"mirror_user_id"`
+	MirrorTriggerBuilds                       bool              `json:"mirror_trigger_builds"`
+	OnlyMirrorProtectedBranches               bool              `json:"only_mirror_protected_branches"`
+	MirrorOverwritesDivergedBranches          bool              `json:"mirror_overwrites_diverged_branches"`
+	SharedWithGroups                          []struct {
+		GroupID          int    `json:"group_id"`
+		GroupName        string `json:"group_name"`
+		GroupAccessLevel int    `json:"group_access_level"`
+	} `json:"shared_with_groups"`
+	Statistics       *ProjectStatistics `json:"statistics"`
+	Links            *Links             `json:"_links,omitempty"`
+	CIConfigPath     *string            `json:"ci_config_path"`
+	CustomAttributes []*CustomAttribute `json:"custom_attributes"`
+}
+
+// Repository represents a repository.
+type Repository struct {
+	Name              string          `json:"name"`
+	Description       string          `json:"description"`
+	WebURL            string          `json:"web_url"`
+	AvatarURL         string          `json:"avatar_url"`
+	GitSSHURL         string          `json:"git_ssh_url"`
+	GitHTTPURL        string          `json:"git_http_url"`
+	Namespace         string          `json:"namespace"`
+	Visibility        VisibilityValue `json:"visibility"`
+	PathWithNamespace string          `json:"path_with_namespace"`
+	DefaultBranch     string          `json:"default_branch"`
+	Homepage          string          `json:"homepage"`
+	URL               string          `json:"url"`
+	SSHURL            string          `json:"ssh_url"`
+	HTTPURL           string          `json:"http_url"`
+}
+
+// ProjectNamespace represents a project namespace.
+type ProjectNamespace struct {
+	ID       int    `json:"id"`
+	Name     string `json:"name"`
+	Path     string `json:"path"`
+	Kind     string `json:"kind"`
+	FullPath string `json:"full_path"`
+}
+
+// StorageStatistics represents a statistics record for a group or project.
+type StorageStatistics struct {
+	StorageSize      int64 `json:"storage_size"`
+	RepositorySize   int64 `json:"repository_size"`
+	LfsObjectsSize   int64 `json:"lfs_objects_size"`
+	JobArtifactsSize int64 `json:"job_artifacts_size"`
+}
+
+// ProjectStatistics represents a statistics record for a project.
+type ProjectStatistics struct {
+	StorageStatistics
+	CommitCount int `json:"commit_count"`
+}
+
+// Permissions represents permissions.
+type Permissions struct {
+	ProjectAccess *ProjectAccess `json:"project_access"`
+	GroupAccess   *GroupAccess   `json:"group_access"`
+}
+
+// ProjectAccess represents project access.
+type ProjectAccess struct {
+	AccessLevel       AccessLevelValue       `json:"access_level"`
+	NotificationLevel NotificationLevelValue `json:"notification_level"`
+}
+
+// GroupAccess represents group access.
+type GroupAccess struct {
+	AccessLevel       AccessLevelValue       `json:"access_level"`
+	NotificationLevel NotificationLevelValue `json:"notification_level"`
+}
+
+// ForkParent represents the parent project when this is a fork.
+type ForkParent struct {
+	HTTPURLToRepo     string `json:"http_url_to_repo"`
+	ID                int    `json:"id"`
+	Name              string `json:"name"`
+	NameWithNamespace string `json:"name_with_namespace"`
+	Path              string `json:"path"`
+	PathWithNamespace string `json:"path_with_namespace"`
+	WebURL            string `json:"web_url"`
+}
+
+// Links represents a project web links for self, issues, merge_requests,
+// repo_branches, labels, events, members.
+type Links struct {
+	Self          string `json:"self"`
+	Issues        string `json:"issues"`
+	MergeRequests string `json:"merge_requests"`
+	RepoBranches  string `json:"repo_branches"`
+	Labels        string `json:"labels"`
+	Events        string `json:"events"`
+	Members       string `json:"members"`
+}
+
+func (s Project) String() string {
+	return Stringify(s)
+}
+
+// ProjectApprovalRule represents a GitLab project approval rule.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-project-level-rules
+type ProjectApprovalRule struct {
+	ID                   int          `json:"id"`
+	Name                 string       `json:"name"`
+	RuleType             string       `json:"rule_type"`
+	EligibleApprovers    []*BasicUser `json:"eligible_approvers"`
+	ApprovalsRequired    int          `json:"approvals_required"`
+	Users                []*BasicUser `json:"users"`
+	Groups               []*Group     `json:"groups"`
+	ContainsHiddenGroups bool         `json:"contains_hidden_groups"`
+}
+
+func (s ProjectApprovalRule) String() string {
+	return Stringify(s)
+}
+
+// ListProjectsOptions represents the available ListProjects() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects
+type ListProjectsOptions struct {
+	ListOptions
+	Archived                 *bool             `url:"archived,omitempty" json:"archived,omitempty"`
+	OrderBy                  *string           `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort                     *string           `url:"sort,omitempty" json:"sort,omitempty"`
+	Search                   *string           `url:"search,omitempty" json:"search,omitempty"`
+	Simple                   *bool             `url:"simple,omitempty" json:"simple,omitempty"`
+	Owned                    *bool             `url:"owned,omitempty" json:"owned,omitempty"`
+	Membership               *bool             `url:"membership,omitempty" json:"membership,omitempty"`
+	Starred                  *bool             `url:"starred,omitempty" json:"starred,omitempty"`
+	Statistics               *bool             `url:"statistics,omitempty" json:"statistics,omitempty"`
+	Visibility               *VisibilityValue  `url:"visibility,omitempty" json:"visibility,omitempty"`
+	WithIssuesEnabled        *bool             `url:"with_issues_enabled,omitempty" json:"with_issues_enabled,omitempty"`
+	WithMergeRequestsEnabled *bool             `url:"with_merge_requests_enabled,omitempty" json:"with_merge_requests_enabled,omitempty"`
+	MinAccessLevel           *AccessLevelValue `url:"min_access_level,omitempty" json:"min_access_level,omitempty"`
+	WithCustomAttributes     *bool             `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"`
+	WithProgrammingLanguage  *string           `url:"with_programming_language,omitempty" json:"with_programming_language,omitempty"`
+}
+
+// ListProjects gets a list of projects accessible by the authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects
+func (s *ProjectsService) ListProjects(opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) {
+	req, err := s.client.NewRequest("GET", "projects", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var p []*Project
+	resp, err := s.client.Do(req, &p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// ListUserProjects gets a list of projects for the given user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#list-user-projects
+func (s *ProjectsService) ListUserProjects(uid interface{}, opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) {
+	user, err := parseID(uid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("users/%s/projects", user)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var p []*Project
+	resp, err := s.client.Do(req, &p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// ProjectUser represents a GitLab project user.
+type ProjectUser struct {
+	ID        int    `json:"id"`
+	Name      string `json:"name"`
+	Username  string `json:"username"`
+	State     string `json:"state"`
+	AvatarURL string `json:"avatar_url"`
+	WebURL    string `json:"web_url"`
+}
+
+// ListProjectUserOptions represents the available ListProjectsUsers() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#get-project-users
+type ListProjectUserOptions struct {
+	ListOptions
+	Search *string `url:"search,omitempty" json:"search,omitempty"`
+}
+
+// ListProjectsUsers gets a list of users for the given project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-project-users
+func (s *ProjectsService) ListProjectsUsers(pid interface{}, opt *ListProjectUserOptions, options ...OptionFunc) ([]*ProjectUser, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/users", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var p []*ProjectUser
+	resp, err := s.client.Do(req, &p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// ProjectLanguages is a map of strings because the response is arbitrary
+//
+// Gitlab API docs: https://docs.gitlab.com/ce/api/projects.html#languages
+type ProjectLanguages map[string]float32
+
+// GetProjectLanguages gets a list of languages used by the project
+//
+// GitLab API docs:  https://docs.gitlab.com/ce/api/projects.html#languages
+func (s *ProjectsService) GetProjectLanguages(pid interface{}, options ...OptionFunc) (*ProjectLanguages, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/languages", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(ProjectLanguages)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// GetProjectOptions represents the available GetProject() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/projects.html#get-single-project
+type GetProjectOptions struct {
+	Statistics           *bool `url:"statistics,omitempty" json:"statistics,omitempty"`
+	License              *bool `url:"license,omitempty" json:"license,omitempty"`
+	WithCustomAttributes *bool `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"`
+}
+
+// GetProject gets a specific project, identified by project ID or
+// NAMESPACE/PROJECT_NAME, which is owned by the authenticated user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-single-project
+func (s *ProjectsService) GetProject(pid interface{}, opt *GetProjectOptions, options ...OptionFunc) (*Project, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Project)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// ProjectEvent represents a GitLab project event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-project-events
+type ProjectEvent struct {
+	Title          interface{} `json:"title"`
+	ProjectID      int         `json:"project_id"`
+	ActionName     string      `json:"action_name"`
+	TargetID       interface{} `json:"target_id"`
+	TargetType     interface{} `json:"target_type"`
+	AuthorID       int         `json:"author_id"`
+	AuthorUsername string      `json:"author_username"`
+	Data           struct {
+		Before            string      `json:"before"`
+		After             string      `json:"after"`
+		Ref               string      `json:"ref"`
+		UserID            int         `json:"user_id"`
+		UserName          string      `json:"user_name"`
+		Repository        *Repository `json:"repository"`
+		Commits           []*Commit   `json:"commits"`
+		TotalCommitsCount int         `json:"total_commits_count"`
+	} `json:"data"`
+	TargetTitle interface{} `json:"target_title"`
+}
+
+func (s ProjectEvent) String() string {
+	return Stringify(s)
+}
+
+// GetProjectEventsOptions represents the available GetProjectEvents() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-project-events
+type GetProjectEventsOptions ListOptions
+
+// GetProjectEvents gets the events for the specified project. Sorted from
+// newest to latest.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-project-events
+func (s *ProjectsService) GetProjectEvents(pid interface{}, opt *GetProjectEventsOptions, options ...OptionFunc) ([]*ProjectEvent, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/events", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var p []*ProjectEvent
+	resp, err := s.client.Do(req, &p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// CreateProjectOptions represents the available CreateProject() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/projects.html#create-project
+type CreateProjectOptions struct {
+	Name                                      *string           `url:"name,omitempty" json:"name,omitempty"`
+	Path                                      *string           `url:"path,omitempty" json:"path,omitempty"`
+	DefaultBranch                             *string           `url:"default_branch,omitempty" json:"default_branch,omitempty"`
+	NamespaceID                               *int              `url:"namespace_id,omitempty" json:"namespace_id,omitempty"`
+	Description                               *string           `url:"description,omitempty" json:"description,omitempty"`
+	IssuesEnabled                             *bool             `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"`
+	MergeRequestsEnabled                      *bool             `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"`
+	JobsEnabled                               *bool             `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"`
+	WikiEnabled                               *bool             `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"`
+	SnippetsEnabled                           *bool             `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"`
+	ResolveOutdatedDiffDiscussions            *bool             `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"`
+	ContainerRegistryEnabled                  *bool             `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"`
+	SharedRunnersEnabled                      *bool             `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
+	Visibility                                *VisibilityValue  `url:"visibility,omitempty" json:"visibility,omitempty"`
+	ImportURL                                 *string           `url:"import_url,omitempty" json:"import_url,omitempty"`
+	PublicBuilds                              *bool             `url:"public_builds,omitempty" json:"public_builds,omitempty"`
+	OnlyAllowMergeIfPipelineSucceeds          *bool             `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"`
+	OnlyAllowMergeIfAllDiscussionsAreResolved *bool             `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"`
+	MergeMethod                               *MergeMethodValue `url:"merge_method,omitempty" json:"merge_method,omitempty"`
+	LFSEnabled                                *bool             `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"`
+	RequestAccessEnabled                      *bool             `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"`
+	TagList                                   *[]string         `url:"tag_list,omitempty" json:"tag_list,omitempty"`
+	PrintingMergeRequestLinkEnabled           *bool             `url:"printing_merge_request_link_enabled,omitempty" json:"printing_merge_request_link_enabled,omitempty"`
+	CIConfigPath                              *string           `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"`
+	ApprovalsBeforeMerge                      *int              `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
+	Mirror                                    *bool             `url:"mirror,omitempty" json:"mirror,omitempty"`
+	MirrorTriggerBuilds                       *bool             `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"`
+	InitializeWithReadme                      *bool             `url:"initialize_with_readme,omitempty" json:"initialize_with_readme,omitempty"`
+	TemplateName                              *string           `url:"template_name,omitempty" json:"template_name,omitempty"`
+	UseCustomTemplate                         *bool             `url:"use_custom_template,omitempty" json:"use_custom_template,omitempty"`
+	GroupWithProjectTemplatesID               *int              `url:"group_with_project_templates_id,omitempty" json:"group_with_project_templates_id,omitempty"`
+}
+
+// CreateProject creates a new project owned by the authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#create-project
+func (s *ProjectsService) CreateProject(opt *CreateProjectOptions, options ...OptionFunc) (*Project, *Response, error) {
+	req, err := s.client.NewRequest("POST", "projects", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Project)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// CreateProjectForUserOptions represents the available CreateProjectForUser()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#create-project-for-user
+type CreateProjectForUserOptions CreateProjectOptions
+
+// CreateProjectForUser creates a new project owned by the specified user.
+// Available only for admins.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#create-project-for-user
+func (s *ProjectsService) CreateProjectForUser(user int, opt *CreateProjectForUserOptions, options ...OptionFunc) (*Project, *Response, error) {
+	u := fmt.Sprintf("projects/user/%d", user)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Project)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// EditProjectOptions represents the available EditProject() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project
+type EditProjectOptions struct {
+	Name                                      *string           `url:"name,omitempty" json:"name,omitempty"`
+	Path                                      *string           `url:"path,omitempty" json:"path,omitempty"`
+	DefaultBranch                             *string           `url:"default_branch,omitempty" json:"default_branch,omitempty"`
+	Description                               *string           `url:"description,omitempty" json:"description,omitempty"`
+	IssuesEnabled                             *bool             `url:"issues_enabled,omitempty" json:"issues_enabled,omitempty"`
+	MergeRequestsEnabled                      *bool             `url:"merge_requests_enabled,omitempty" json:"merge_requests_enabled,omitempty"`
+	JobsEnabled                               *bool             `url:"jobs_enabled,omitempty" json:"jobs_enabled,omitempty"`
+	WikiEnabled                               *bool             `url:"wiki_enabled,omitempty" json:"wiki_enabled,omitempty"`
+	SnippetsEnabled                           *bool             `url:"snippets_enabled,omitempty" json:"snippets_enabled,omitempty"`
+	ResolveOutdatedDiffDiscussions            *bool             `url:"resolve_outdated_diff_discussions,omitempty" json:"resolve_outdated_diff_discussions,omitempty"`
+	ContainerRegistryEnabled                  *bool             `url:"container_registry_enabled,omitempty" json:"container_registry_enabled,omitempty"`
+	SharedRunnersEnabled                      *bool             `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
+	Visibility                                *VisibilityValue  `url:"visibility,omitempty" json:"visibility,omitempty"`
+	ImportURL                                 *string           `url:"import_url,omitempty" json:"import_url,omitempty"`
+	PublicBuilds                              *bool             `url:"public_builds,omitempty" json:"public_builds,omitempty"`
+	OnlyAllowMergeIfPipelineSucceeds          *bool             `url:"only_allow_merge_if_pipeline_succeeds,omitempty" json:"only_allow_merge_if_pipeline_succeeds,omitempty"`
+	OnlyAllowMergeIfAllDiscussionsAreResolved *bool             `url:"only_allow_merge_if_all_discussions_are_resolved,omitempty" json:"only_allow_merge_if_all_discussions_are_resolved,omitempty"`
+	MergeMethod                               *MergeMethodValue `url:"merge_method,omitempty" json:"merge_method,omitempty"`
+	LFSEnabled                                *bool             `url:"lfs_enabled,omitempty" json:"lfs_enabled,omitempty"`
+	RequestAccessEnabled                      *bool             `url:"request_access_enabled,omitempty" json:"request_access_enabled,omitempty"`
+	TagList                                   *[]string         `url:"tag_list,omitempty" json:"tag_list,omitempty"`
+	CIConfigPath                              *string           `url:"ci_config_path,omitempty" json:"ci_config_path,omitempty"`
+	ApprovalsBeforeMerge                      *int              `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
+	ExternalAuthorizationClassificationLabel  *string           `url:"external_authorization_classification_label,omitempty" json:"external_authorization_classification_label,omitempty"`
+	Mirror                                    *bool             `url:"mirror,omitempty" json:"mirror,omitempty"`
+	MirrorTriggerBuilds                       *bool             `url:"mirror_trigger_builds,omitempty" json:"mirror_trigger_builds,omitempty"`
+	MirrorUserID                              *int              `url:"mirror_user_id,omitempty" json:"mirror_user_id,omitempty"`
+	OnlyMirrorProtectedBranches               *bool             `url:"only_mirror_protected_branches,omitempty" json:"only_mirror_protected_branches,omitempty"`
+	MirrorOverwritesDivergedBranches          *bool             `url:"mirror_overwrites_diverged_branches,omitempty" json:"mirror_overwrites_diverged_branches,omitempty"`
+	PackagesEnabled                           *bool             `url:"packages_enabled,omitempty" json:"packages_enabled,omitempty"`
+}
+
+// EditProject updates an existing project.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#edit-project
+func (s *ProjectsService) EditProject(pid interface{}, opt *EditProjectOptions, options ...OptionFunc) (*Project, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Project)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// ForkProjectOptions represents the available ForkProject() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#fork-project
+type ForkProjectOptions struct {
+	Namespace *string `url:"namespace,omitempty" json:"namespace,omitempty"`
+	Name      *string `url:"name,omitempty" json:"name,omitempty" `
+	Path      *string `url:"path,omitempty" json:"path,omitempty"`
+}
+
+// ForkProject forks a project into the user namespace of the authenticated
+// user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#fork-project
+func (s *ProjectsService) ForkProject(pid interface{}, opt *ForkProjectOptions, options ...OptionFunc) (*Project, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/fork", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Project)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// StarProject stars a given the project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#star-a-project
+func (s *ProjectsService) StarProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/star", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Project)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// UnstarProject unstars a given project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#unstar-a-project
+func (s *ProjectsService) UnstarProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/unstar", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Project)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// ArchiveProject archives the project if the user is either admin or the
+// project owner of this project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#archive-a-project
+func (s *ProjectsService) ArchiveProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/archive", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Project)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// UnarchiveProject unarchives the project if the user is either admin or
+// the project owner of this project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#unarchive-a-project
+func (s *ProjectsService) UnarchiveProject(pid interface{}, options ...OptionFunc) (*Project, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/unarchive", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(Project)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// DeleteProject removes a project including all associated resources
+// (issues, merge requests etc.)
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#remove-project
+func (s *ProjectsService) DeleteProject(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ShareWithGroupOptions represents options to share project with groups
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#share-project-with-group
+type ShareWithGroupOptions struct {
+	GroupID     *int              `url:"group_id" json:"group_id"`
+	GroupAccess *AccessLevelValue `url:"group_access" json:"group_access"`
+	ExpiresAt   *string           `url:"expires_at" json:"expires_at"`
+}
+
+// ShareProjectWithGroup allows to share a project with a group.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#share-project-with-group
+func (s *ProjectsService) ShareProjectWithGroup(pid interface{}, opt *ShareWithGroupOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/share", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteSharedProjectFromGroup allows to unshare a project from a group.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#delete-a-shared-project-link-within-a-group
+func (s *ProjectsService) DeleteSharedProjectFromGroup(pid interface{}, groupID int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/share/%d", pathEscape(project), groupID)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ProjectMember represents a project member.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#list-project-team-members
+type ProjectMember struct {
+	ID          int              `json:"id"`
+	Username    string           `json:"username"`
+	Email       string           `json:"email"`
+	Name        string           `json:"name"`
+	State       string           `json:"state"`
+	CreatedAt   *time.Time       `json:"created_at"`
+	ExpiresAt   *ISOTime         `json:"expires_at"`
+	AccessLevel AccessLevelValue `json:"access_level"`
+}
+
+// ProjectHook represents a project hook.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
+type ProjectHook struct {
+	ID                       int        `json:"id"`
+	URL                      string     `json:"url"`
+	ProjectID                int        `json:"project_id"`
+	PushEvents               bool       `json:"push_events"`
+	IssuesEvents             bool       `json:"issues_events"`
+	ConfidentialIssuesEvents bool       `json:"confidential_issues_events"`
+	MergeRequestsEvents      bool       `json:"merge_requests_events"`
+	TagPushEvents            bool       `json:"tag_push_events"`
+	NoteEvents               bool       `json:"note_events"`
+	JobEvents                bool       `json:"job_events"`
+	PipelineEvents           bool       `json:"pipeline_events"`
+	WikiPageEvents           bool       `json:"wiki_page_events"`
+	EnableSSLVerification    bool       `json:"enable_ssl_verification"`
+	CreatedAt                *time.Time `json:"created_at"`
+}
+
+// ListProjectHooksOptions represents the available ListProjectHooks() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
+type ListProjectHooksOptions ListOptions
+
+// ListProjectHooks gets a list of project hooks.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#list-project-hooks
+func (s *ProjectsService) ListProjectHooks(pid interface{}, opt *ListProjectHooksOptions, options ...OptionFunc) ([]*ProjectHook, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/hooks", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ph []*ProjectHook
+	resp, err := s.client.Do(req, &ph)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ph, resp, err
+}
+
+// GetProjectHook gets a specific hook for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#get-project-hook
+func (s *ProjectsService) GetProjectHook(pid interface{}, hook int, options ...OptionFunc) (*ProjectHook, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/hooks/%d", pathEscape(project), hook)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ph := new(ProjectHook)
+	resp, err := s.client.Do(req, ph)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ph, resp, err
+}
+
+// AddProjectHookOptions represents the available AddProjectHook() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#add-project-hook
+type AddProjectHookOptions struct {
+	URL                      *string `url:"url,omitempty" json:"url,omitempty"`
+	PushEvents               *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
+	IssuesEvents             *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
+	ConfidentialIssuesEvents *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
+	MergeRequestsEvents      *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
+	TagPushEvents            *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
+	NoteEvents               *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
+	JobEvents                *bool   `url:"job_events,omitempty" json:"job_events,omitempty"`
+	PipelineEvents           *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
+	WikiPageEvents           *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
+	EnableSSLVerification    *bool   `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
+	Token                    *string `url:"token,omitempty" json:"token,omitempty"`
+}
+
+// AddProjectHook adds a hook to a specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#add-project-hook
+func (s *ProjectsService) AddProjectHook(pid interface{}, opt *AddProjectHookOptions, options ...OptionFunc) (*ProjectHook, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/hooks", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ph := new(ProjectHook)
+	resp, err := s.client.Do(req, ph)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ph, resp, err
+}
+
+// EditProjectHookOptions represents the available EditProjectHook() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#edit-project-hook
+type EditProjectHookOptions struct {
+	URL                      *string `url:"url,omitempty" json:"url,omitempty"`
+	PushEvents               *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
+	IssuesEvents             *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
+	ConfidentialIssuesEvents *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
+	MergeRequestsEvents      *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
+	TagPushEvents            *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
+	NoteEvents               *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
+	JobEvents                *bool   `url:"job_events,omitempty" json:"job_events,omitempty"`
+	PipelineEvents           *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
+	WikiPageEvents           *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
+	EnableSSLVerification    *bool   `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
+	Token                    *string `url:"token,omitempty" json:"token,omitempty"`
+}
+
+// EditProjectHook edits a hook for a specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#edit-project-hook
+func (s *ProjectsService) EditProjectHook(pid interface{}, hook int, opt *EditProjectHookOptions, options ...OptionFunc) (*ProjectHook, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/hooks/%d", pathEscape(project), hook)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ph := new(ProjectHook)
+	resp, err := s.client.Do(req, ph)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ph, resp, err
+}
+
+// DeleteProjectHook removes a hook from a project. This is an idempotent
+// method and can be called multiple times. Either the hook is available or not.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#delete-project-hook
+func (s *ProjectsService) DeleteProjectHook(pid interface{}, hook int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/hooks/%d", pathEscape(project), hook)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ProjectForkRelation represents a project fork relationship.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#admin-fork-relation
+type ProjectForkRelation struct {
+	ID                  int        `json:"id"`
+	ForkedToProjectID   int        `json:"forked_to_project_id"`
+	ForkedFromProjectID int        `json:"forked_from_project_id"`
+	CreatedAt           *time.Time `json:"created_at"`
+	UpdatedAt           *time.Time `json:"updated_at"`
+}
+
+// CreateProjectForkRelation creates a forked from/to relation between
+// existing projects.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#create-a-forked-fromto-relation-between-existing-projects.
+func (s *ProjectsService) CreateProjectForkRelation(pid int, fork int, options ...OptionFunc) (*ProjectForkRelation, *Response, error) {
+	u := fmt.Sprintf("projects/%d/fork/%d", pid, fork)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pfr := new(ProjectForkRelation)
+	resp, err := s.client.Do(req, pfr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pfr, resp, err
+}
+
+// DeleteProjectForkRelation deletes an existing forked from relationship.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#delete-an-existing-forked-from-relationship
+func (s *ProjectsService) DeleteProjectForkRelation(pid int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("projects/%d/fork", pid)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ProjectFile represents an uploaded project file
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#upload-a-file
+type ProjectFile struct {
+	Alt      string `json:"alt"`
+	URL      string `json:"url"`
+	Markdown string `json:"markdown"`
+}
+
+// UploadFile upload a file from disk
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#upload-a-file
+func (s *ProjectsService) UploadFile(pid interface{}, file string, options ...OptionFunc) (*ProjectFile, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/uploads", pathEscape(project))
+
+	f, err := os.Open(file)
+	if err != nil {
+		return nil, nil, err
+	}
+	defer f.Close()
+
+	b := &bytes.Buffer{}
+	w := multipart.NewWriter(b)
+
+	fw, err := w.CreateFormFile("file", file)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	_, err = io.Copy(fw, f)
+	if err != nil {
+		return nil, nil, err
+	}
+	w.Close()
+
+	req, err := s.client.NewRequest("", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req.Body = ioutil.NopCloser(b)
+	req.ContentLength = int64(b.Len())
+	req.Header.Set("Content-Type", w.FormDataContentType())
+	req.Method = "POST"
+
+	uf := &ProjectFile{}
+	resp, err := s.client.Do(req, uf)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return uf, resp, nil
+}
+
+// ListProjectForks gets a list of project forks.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/projects.html#list-forks-of-a-project
+func (s *ProjectsService) ListProjectForks(pid interface{}, opt *ListProjectsOptions, options ...OptionFunc) ([]*Project, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/forks", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var forks []*Project
+	resp, err := s.client.Do(req, &forks)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return forks, resp, err
+}
+
+// ProjectPushRules represents a project push rule.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/projects.html#push-rules
+type ProjectPushRules struct {
+	ID                 int        `json:"id"`
+	ProjectID          int        `json:"project_id"`
+	CommitMessageRegex string     `json:"commit_message_regex"`
+	BranchNameRegex    string     `json:"branch_name_regex"`
+	DenyDeleteTag      bool       `json:"deny_delete_tag"`
+	CreatedAt          *time.Time `json:"created_at"`
+	MemberCheck        bool       `json:"member_check"`
+	PreventSecrets     bool       `json:"prevent_secrets"`
+	AuthorEmailRegex   string     `json:"author_email_regex"`
+	FileNameRegex      string     `json:"file_name_regex"`
+	MaxFileSize        int        `json:"max_file_size"`
+}
+
+// GetProjectPushRules gets the push rules of a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/projects.html#get-project-push-rules
+func (s *ProjectsService) GetProjectPushRules(pid interface{}, options ...OptionFunc) (*ProjectPushRules, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ppr := new(ProjectPushRules)
+	resp, err := s.client.Do(req, ppr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ppr, resp, err
+}
+
+// AddProjectPushRuleOptions represents the available AddProjectPushRule()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/projects.html#add-project-push-rule
+type AddProjectPushRuleOptions struct {
+	DenyDeleteTag      *bool   `url:"deny_delete_tag,omitempty" json:"deny_delete_tag,omitempty"`
+	MemberCheck        *bool   `url:"member_check,omitempty" json:"member_check,omitempty"`
+	PreventSecrets     *bool   `url:"prevent_secrets,omitempty" json:"prevent_secrets,omitempty"`
+	CommitMessageRegex *string `url:"commit_message_regex,omitempty" json:"commit_message_regex,omitempty"`
+	BranchNameRegex    *string `url:"branch_name_regex,omitempty" json:"branch_name_regex,omitempty"`
+	AuthorEmailRegex   *string `url:"author_email_regex,omitempty" json:"author_email_regex,omitempty"`
+	FileNameRegex      *string `url:"file_name_regex,omitempty" json:"file_name_regex,omitempty"`
+	MaxFileSize        *int    `url:"max_file_size,omitempty" json:"max_file_size,omitempty"`
+}
+
+// AddProjectPushRule adds a push rule to a specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/projects.html#add-project-push-rule
+func (s *ProjectsService) AddProjectPushRule(pid interface{}, opt *AddProjectPushRuleOptions, options ...OptionFunc) (*ProjectPushRules, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ppr := new(ProjectPushRules)
+	resp, err := s.client.Do(req, ppr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ppr, resp, err
+}
+
+// EditProjectPushRuleOptions represents the available EditProjectPushRule()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/projects.html#edit-project-push-rule
+type EditProjectPushRuleOptions struct {
+	AuthorEmailRegex   *string `url:"author_email_regex,omitempty" json:"author_email_regex,omitempty"`
+	BranchNameRegex    *string `url:"branch_name_regex,omitempty" json:"branch_name_regex,omitempty"`
+	CommitMessageRegex *string `url:"commit_message_regex,omitempty" json:"commit_message_regex,omitempty"`
+	FileNameRegex      *string `url:"file_name_regex,omitempty" json:"file_name_regex,omitempty"`
+	DenyDeleteTag      *bool   `url:"deny_delete_tag,omitempty" json:"deny_delete_tag,omitempty"`
+	MemberCheck        *bool   `url:"member_check,omitempty" json:"member_check,omitempty"`
+	PreventSecrets     *bool   `url:"prevent_secrets,omitempty" json:"prevent_secrets,omitempty"`
+	MaxFileSize        *int    `url:"max_file_size,omitempty" json:"max_file_size,omitempty"`
+}
+
+// EditProjectPushRule edits a push rule for a specified project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/projects.html#edit-project-push-rule
+func (s *ProjectsService) EditProjectPushRule(pid interface{}, opt *EditProjectPushRuleOptions, options ...OptionFunc) (*ProjectPushRules, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ppr := new(ProjectPushRules)
+	resp, err := s.client.Do(req, ppr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ppr, resp, err
+}
+
+// DeleteProjectPushRule removes a push rule from a project. This is an
+// idempotent method and can be called multiple times. Either the push rule is
+// available or not.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/projects.html#delete-project-push-rule
+func (s *ProjectsService) DeleteProjectPushRule(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/push_rule", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ProjectApprovals represents GitLab project level merge request approvals.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#project-level-mr-approvals
+type ProjectApprovals struct {
+	Approvers                                 []*MergeRequestApproverUser  `json:"approvers"`
+	ApproverGroups                            []*MergeRequestApproverGroup `json:"approver_groups"`
+	ApprovalsBeforeMerge                      int                          `json:"approvals_before_merge"`
+	ResetApprovalsOnPush                      bool                         `json:"reset_approvals_on_push"`
+	DisableOverridingApproversPerMergeRequest bool                         `json:"disable_overriding_approvers_per_merge_request"`
+	MergeRequestsAuthorApproval               bool                         `json:"merge_requests_author_approval"`
+	MergeRequestsDisableCommittersApproval    bool                         `json:"merge_requests_disable_committers_approval"`
+}
+
+// GetApprovalConfiguration get the approval configuration for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-configuration
+func (s *ProjectsService) GetApprovalConfiguration(pid interface{}, options ...OptionFunc) (*ProjectApprovals, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approvals", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pa := new(ProjectApprovals)
+	resp, err := s.client.Do(req, pa)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pa, resp, err
+}
+
+// ChangeApprovalConfigurationOptions represents the available
+// ApprovalConfiguration() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-configuration
+type ChangeApprovalConfigurationOptions struct {
+	ApprovalsBeforeMerge                      *int  `url:"approvals_before_merge,omitempty" json:"approvals_before_merge,omitempty"`
+	ResetApprovalsOnPush                      *bool `url:"reset_approvals_on_push,omitempty" json:"reset_approvals_on_push,omitempty"`
+	DisableOverridingApproversPerMergeRequest *bool `url:"disable_overriding_approvers_per_merge_request,omitempty" json:"disable_overriding_approvers_per_merge_request,omitempty"`
+	MergeRequestsAuthorApproval               *bool `url:"merge_requests_author_approval,omitempty" json:"merge_requests_author_approval,omitempty"`
+	MergeRequestsDisableCommittersApproval    *bool `url:"merge_requests_disable_committers_approval,omitempty" json:"merge_requests_disable_committers_approval,omitempty"`
+}
+
+// ChangeApprovalConfiguration updates the approval configuration for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-configuration
+func (s *ProjectsService) ChangeApprovalConfiguration(pid interface{}, opt *ChangeApprovalConfigurationOptions, options ...OptionFunc) (*ProjectApprovals, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approvals", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pa := new(ProjectApprovals)
+	resp, err := s.client.Do(req, pa)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pa, resp, err
+}
+
+// GetProjectApprovalRules looks up the list of project level approvers.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#get-project-level-rules
+func (s *ProjectsService) GetProjectApprovalRules(pid interface{}, options ...OptionFunc) ([]*ProjectApprovalRule, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approval_rules", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var par []*ProjectApprovalRule
+	resp, err := s.client.Do(req, &par)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return par, resp, err
+}
+
+// CreateProjectLevelRuleOptions represents the available CreateProjectApprovalRule()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-project-level-rules
+type CreateProjectLevelRuleOptions struct {
+	Name              *string `url:"name,omitempty" json:"name,omitempty"`
+	ApprovalsRequired *int    `url:"approvals_required,omitempty" json:"approvals_required,omitempty"`
+	UserIDs           []int   `url:"user_ids,omitempty" json:"user_ids,omitempty"`
+	GroupIDs          []int   `url:"group_ids,omitempty" json:"group_ids,omitempty"`
+}
+
+// CreateProjectApprovalRule creates a new project-level approval rule.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-project-level-rules
+func (s *ProjectsService) CreateProjectApprovalRule(pid interface{}, opt *CreateProjectLevelRuleOptions, options ...OptionFunc) (*ProjectApprovalRule, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approval_rules", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	par := new(ProjectApprovalRule)
+	resp, err := s.client.Do(req, &par)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return par, resp, err
+}
+
+// UpdateProjectLevelRuleOptions represents the available UpdateProjectApprovalRule()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-project-level-rules
+type UpdateProjectLevelRuleOptions struct {
+	Name              *string `url:"name,omitempty" json:"name,omitempty"`
+	ApprovalsRequired *int    `url:"approvals_required,omitempty" json:"approvals_required,omitempty"`
+	UserIDs           []int   `url:"user_ids,omitempty" json:"user_ids,omitempty"`
+	GroupIDs          []int   `url:"group_ids,omitempty" json:"group_ids,omitempty"`
+}
+
+// UpdateProjectApprovalRule updates an existing approval rule with new options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-project-level-rules
+func (s *ProjectsService) UpdateProjectApprovalRule(pid interface{}, approvalRule int, opt *UpdateProjectLevelRuleOptions, options ...OptionFunc) (*ProjectApprovalRule, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approval_rules/%d", pathEscape(project), approvalRule)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	par := new(ProjectApprovalRule)
+	resp, err := s.client.Do(req, &par)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return par, resp, err
+}
+
+// DeleteProjectApprovalRule deletes a project-level approval rule.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#delete-project-level-rules
+func (s *ProjectsService) DeleteProjectApprovalRule(pid interface{}, approvalRule int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approval_rules/%d", pathEscape(project), approvalRule)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ChangeAllowedApproversOptions represents the available ChangeAllowedApprovers()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers
+type ChangeAllowedApproversOptions struct {
+	ApproverIDs      []int `url:"approver_ids,omitempty" json:"approver_ids,omitempty"`
+	ApproverGroupIDs []int `url:"approver_group_ids,omitempty" json:"approver_group_ids,omitempty"`
+}
+
+// ChangeAllowedApprovers updates the list of approvers and approver groups.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/merge_request_approvals.html#change-allowed-approvers
+func (s *ProjectsService) ChangeAllowedApprovers(pid interface{}, opt *ChangeAllowedApproversOptions, options ...OptionFunc) (*ProjectApprovals, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/approvers", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pa := new(ProjectApprovals)
+	resp, err := s.client.Do(req, pa)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pa, resp, err
+}
+
+// StartMirroringProject start the pull mirroring process for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter
+func (s *ProjectsService) StartMirroringProject(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/mirror/pull", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := s.client.Do(req, nil)
+	if err != nil {
+		return resp, err
+	}
+
+	return resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/protected_branches.go b/vendor/github.com/xanzy/go-gitlab/protected_branches.go
new file mode 100644
index 000000000..3567f6ebd
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/protected_branches.go
@@ -0,0 +1,165 @@
+//
+// Copyright 2017, Sander van Harmelen, Michael Lihs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+)
+
+// ProtectedBranchesService handles communication with the protected branch
+// related methods of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/protected_branches.html#protected-branches-api
+type ProtectedBranchesService struct {
+	client *Client
+}
+
+// BranchAccessDescription represents the access description for a protected
+// branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/protected_branches.html#protected-branches-api
+type BranchAccessDescription struct {
+	AccessLevel            AccessLevelValue `json:"access_level"`
+	AccessLevelDescription string           `json:"access_level_description"`
+}
+
+// ProtectedBranch represents a protected branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/protected_branches.html#list-protected-branches
+type ProtectedBranch struct {
+	Name              string                     `json:"name"`
+	PushAccessLevels  []*BranchAccessDescription `json:"push_access_levels"`
+	MergeAccessLevels []*BranchAccessDescription `json:"merge_access_levels"`
+}
+
+// ListProtectedBranchesOptions represents the available ListProtectedBranches()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/protected_branches.html#list-protected-branches
+type ListProtectedBranchesOptions ListOptions
+
+// ListProtectedBranches gets a list of protected branches from a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/protected_branches.html#list-protected-branches
+func (s *ProtectedBranchesService) ListProtectedBranches(pid interface{}, opt *ListProtectedBranchesOptions, options ...OptionFunc) ([]*ProtectedBranch, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/protected_branches", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var p []*ProtectedBranch
+	resp, err := s.client.Do(req, &p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// GetProtectedBranch gets a single protected branch or wildcard protected branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/protected_branches.html#get-a-single-protected-branch-or-wildcard-protected-branch
+func (s *ProtectedBranchesService) GetProtectedBranch(pid interface{}, branch string, options ...OptionFunc) (*ProtectedBranch, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/protected_branches/%s", pathEscape(project), url.PathEscape(branch))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(ProtectedBranch)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// ProtectRepositoryBranchesOptions represents the available
+// ProtectRepositoryBranches() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/protected_branches.html#protect-repository-branches
+type ProtectRepositoryBranchesOptions struct {
+	Name             *string           `url:"name,omitempty" json:"name,omitempty"`
+	PushAccessLevel  *AccessLevelValue `url:"push_access_level,omitempty" json:"push_access_level,omitempty"`
+	MergeAccessLevel *AccessLevelValue `url:"merge_access_level,omitempty" json:"merge_access_level,omitempty"`
+}
+
+// ProtectRepositoryBranches protects a single repository branch or several
+// project repository branches using a wildcard protected branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/protected_branches.html#protect-repository-branches
+func (s *ProtectedBranchesService) ProtectRepositoryBranches(pid interface{}, opt *ProtectRepositoryBranchesOptions, options ...OptionFunc) (*ProtectedBranch, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/protected_branches", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(ProtectedBranch)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// UnprotectRepositoryBranches unprotects the given protected branch or wildcard
+// protected branch.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/protected_branches.html#unprotect-repository-branches
+func (s *ProtectedBranchesService) UnprotectRepositoryBranches(pid interface{}, branch string, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/protected_branches/%s", pathEscape(project), url.PathEscape(branch))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/protected_tags.go b/vendor/github.com/xanzy/go-gitlab/protected_tags.go
new file mode 100644
index 000000000..f50014d2f
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/protected_tags.go
@@ -0,0 +1,145 @@
+package gitlab
+
+import (
+	"fmt"
+)
+
+// ProtectedTagsService handles communication with the protected tag methods
+// of the GitLab API.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_tags.html
+type ProtectedTagsService struct {
+	client *Client
+}
+
+// ProtectedTag represents a protected tag.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_tags.html
+type ProtectedTag struct {
+	Name               string                  `json:"name"`
+	CreateAccessLevels []*TagAccessDescription `json:"create_access_levels"`
+}
+
+// TagAccessDescription reperesents the access decription for a protected tag.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_tags.html
+type TagAccessDescription struct {
+	AccessLevel            AccessLevelValue `json:"access_level"`
+	AccessLevelDescription string           `json:"access_level_description"`
+}
+
+// ListProtectedTagsOptions represents the available ListProtectedTags()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_tags.html#list-protected-tags
+type ListProtectedTagsOptions ListOptions
+
+// ListProtectedTags returns a list of protected tags from a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_tags.html#list-protected-tags
+func (s *ProtectedTagsService) ListProtectedTags(pid interface{}, opt *ListProtectedTagsOptions, options ...OptionFunc) ([]*ProtectedTag, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/protected_tags", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pts []*ProtectedTag
+	resp, err := s.client.Do(req, &pts)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pts, resp, err
+}
+
+// GetProtectedTag returns a single protected tag or wildcard protected tag.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_tags.html#get-a-single-protected-tag-or-wildcard-protected-tag
+func (s *ProtectedTagsService) GetProtectedTag(pid interface{}, tag string, options ...OptionFunc) (*ProtectedTag, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/protected_tags/%s", pathEscape(project), pathEscape(tag))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pt := new(ProtectedTag)
+	resp, err := s.client.Do(req, pt)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pt, resp, err
+}
+
+// ProtectRepositoryTagsOptions represents the available ProtectRepositoryTags()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_tags.html#protect-repository-tags
+type ProtectRepositoryTagsOptions struct {
+	Name              *string           `url:"name" json:"name"`
+	CreateAccessLevel *AccessLevelValue `url:"create_access_level,omitempty" json:"create_access_level,omitempty"`
+}
+
+// ProtectRepositoryTags protects a single repository tag or several project
+// repository tags using a wildcard protected tag.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_tags.html#protect-repository-tags
+func (s *ProtectedTagsService) ProtectRepositoryTags(pid interface{}, opt *ProtectRepositoryTagsOptions, options ...OptionFunc) (*ProtectedTag, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/protected_tags", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pt := new(ProtectedTag)
+	resp, err := s.client.Do(req, pt)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return pt, resp, err
+}
+
+// UnprotectRepositoryTags unprotects the given protected tag or wildcard
+// protected tag.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/protected_tags.html#unprotect-repository-tags
+func (s *ProtectedTagsService) UnprotectRepositoryTags(pid interface{}, tag string, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/protected_tags/%s", pathEscape(project), pathEscape(tag))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/registry.go b/vendor/github.com/xanzy/go-gitlab/registry.go
new file mode 100644
index 000000000..41fff709f
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/registry.go
@@ -0,0 +1,219 @@
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// ContainerRegistryService handles communication with the container registry
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/container_registry.html
+type ContainerRegistryService struct {
+	client *Client
+}
+
+// RegistryRepository represents a GitLab content registry repository.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/container_registry.html
+type RegistryRepository struct {
+	ID        int        `json:"id"`
+	Name      string     `json:"name"`
+	Path      string     `json:"path"`
+	Location  string     `json:"location"`
+	CreatedAt *time.Time `json:"created_at"`
+}
+
+func (s RegistryRepository) String() string {
+	return Stringify(s)
+}
+
+// RegistryRepositoryTag represents a GitLab registry image tag.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/container_registry.html
+type RegistryRepositoryTag struct {
+	Name          string     `json:"name"`
+	Path          string     `json:"path"`
+	Location      string     `json:"location"`
+	Revision      string     `json:"revision"`
+	ShortRevision string     `json:"short_revision"`
+	Digest        string     `json:"digest"`
+	CreatedAt     *time.Time `json:"created_at"`
+	TotalSize     int        `json:"total_size"`
+}
+
+func (s RegistryRepositoryTag) String() string {
+	return Stringify(s)
+}
+
+// ListRegistryRepositoriesOptions represents the available
+// ListRegistryRepositories() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/container_registry.html#list-registry-repositories
+type ListRegistryRepositoriesOptions ListOptions
+
+// ListRegistryRepositories gets a list of registry repositories in a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/container_registry.html#list-registry-repositories
+func (s *ContainerRegistryService) ListRegistryRepositories(pid interface{}, opt *ListRegistryRepositoriesOptions, options ...OptionFunc) ([]*RegistryRepository, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/registry/repositories", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var repos []*RegistryRepository
+	resp, err := s.client.Do(req, &repos)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return repos, resp, err
+}
+
+// DeleteRegistryRepository deletes a repository in a registry.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/container_registry.html#delete-registry-repository
+func (s *ContainerRegistryService) DeleteRegistryRepository(pid interface{}, repository int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/registry/repositories/%d", pathEscape(project), repository)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ListRegistryRepositoryTagsOptions represents the available
+// ListRegistryRepositoryTags() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/container_registry.html#list-repository-tags
+type ListRegistryRepositoryTagsOptions ListOptions
+
+// ListRegistryRepositoryTags gets a list of tags for given registry repository.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/container_registry.html#list-repository-tags
+func (s *ContainerRegistryService) ListRegistryRepositoryTags(pid interface{}, repository int, opt *ListRegistryRepositoryTagsOptions, options ...OptionFunc) ([]*RegistryRepositoryTag, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/registry/repositories/%d/tags",
+		pathEscape(project),
+		repository,
+	)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var tags []*RegistryRepositoryTag
+	resp, err := s.client.Do(req, &tags)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return tags, resp, err
+}
+
+// GetRegistryRepositoryTagDetail get details of a registry repository tag
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/container_registry.html#get-details-of-a-repository-tag
+func (s *ContainerRegistryService) GetRegistryRepositoryTagDetail(pid interface{}, repository int, tagName string, options ...OptionFunc) (*RegistryRepositoryTag, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/registry/repositories/%d/tags/%s",
+		pathEscape(project),
+		repository,
+		tagName,
+	)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	tag := new(RegistryRepositoryTag)
+	resp, err := s.client.Do(req, &tag)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return tag, resp, err
+}
+
+// DeleteRegistryRepositoryTag deletes a registry repository tag.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/container_registry.html#delete-a-repository-tag
+func (s *ContainerRegistryService) DeleteRegistryRepositoryTag(pid interface{}, repository int, tagName string, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/registry/repositories/%d/tags/%s",
+		pathEscape(project),
+		repository,
+		tagName,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteRegistryRepositoryTagsOptions represents the available
+// DeleteRegistryRepositoryTags() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/container_registry.html#delete-repository-tags-in-bulk
+type DeleteRegistryRepositoryTagsOptions struct {
+	NameRegexp *string `url:"name_regex,omitempty" json:"name_regex,omitempty"`
+	KeepN      *int    `url:"keep_n,omitempty" json:"keep_n,omitempty"`
+	OlderThan  *string `url:"older_than,omitempty" json:"older_than,omitempty"`
+}
+
+// DeleteRegistryRepositoryTags deletes repository tags in bulk based on
+// given criteria.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/container_registry.html#delete-repository-tags-in-bulk
+func (s *ContainerRegistryService) DeleteRegistryRepositoryTags(pid interface{}, repository int, opt *DeleteRegistryRepositoryTagsOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/registry/repositories/%d/tags",
+		pathEscape(project),
+		repository,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/releaselinks.go b/vendor/github.com/xanzy/go-gitlab/releaselinks.go
new file mode 100644
index 000000000..b14be1f66
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/releaselinks.go
@@ -0,0 +1,176 @@
+package gitlab
+
+import (
+	"fmt"
+)
+
+// ReleaseLinksService handles communication with the release link methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/releases/links.html
+type ReleaseLinksService struct {
+	client *Client
+}
+
+// ReleaseLink represents a release link.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/releases/links.html
+type ReleaseLink struct {
+	ID       int    `json:"id"`
+	Name     string `json:"name"`
+	URL      string `json:"url"`
+	External bool   `json:"external"`
+}
+
+// ListReleaseLinksOptions represents ListReleaseLinks() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/releases/links.html#get-links
+type ListReleaseLinksOptions ListOptions
+
+// ListReleaseLinks gets assets as links from a Release.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/releases/links.html#get-links
+func (s *ReleaseLinksService) ListReleaseLinks(pid interface{}, tagName string, opt *ListReleaseLinksOptions, options ...OptionFunc) ([]*ReleaseLink, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/releases/%s/assets/links", pathEscape(project), tagName)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var rls []*ReleaseLink
+	resp, err := s.client.Do(req, &rls)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rls, resp, err
+}
+
+// GetReleaseLink returns a link from release assets.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/releases/links.html#get-a-link
+func (s *ReleaseLinksService) GetReleaseLink(pid interface{}, tagName string, link int, options ...OptionFunc) (*ReleaseLink, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/releases/%s/assets/links/%d",
+		pathEscape(project),
+		tagName,
+		link)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	rl := new(ReleaseLink)
+	resp, err := s.client.Do(req, rl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rl, resp, err
+}
+
+// CreateReleaseLinkOptions represents CreateReleaseLink() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/releases/links.html#create-a-link
+type CreateReleaseLinkOptions struct {
+	Name *string `url:"name" json:"name"`
+	URL  *string `url:"url" json:"url"`
+}
+
+// CreateReleaseLink creates a link.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/releases/links.html#create-a-link
+func (s *ReleaseLinksService) CreateReleaseLink(pid interface{}, tagName string, opt *CreateReleaseLinkOptions, options ...OptionFunc) (*ReleaseLink, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/releases/%s/assets/links", pathEscape(project), tagName)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	rl := new(ReleaseLink)
+	resp, err := s.client.Do(req, rl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rl, resp, err
+}
+
+// UpdateReleaseLinkOptions represents UpdateReleaseLink() options.
+//
+// You have to specify at least one of Name of URL.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/releases/links.html#update-a-link
+type UpdateReleaseLinkOptions struct {
+	Name *string `url:"name,omitempty" json:"name,omitempty"`
+	URL  *string `url:"url,omitempty" json:"url,omitempty"`
+}
+
+// UpdateReleaseLink updates an asset link.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/releases/links.html#update-a-link
+func (s *ReleaseLinksService) UpdateReleaseLink(pid interface{}, tagName string, link int, opt *UpdateReleaseLinkOptions, options ...OptionFunc) (*ReleaseLink, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/releases/%s/assets/links/%d",
+		pathEscape(project),
+		tagName,
+		link)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	rl := new(ReleaseLink)
+	resp, err := s.client.Do(req, rl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rl, resp, err
+}
+
+// DeleteReleaseLink deletes a link from release.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/releases/links.html#delete-a-link
+func (s *ReleaseLinksService) DeleteReleaseLink(pid interface{}, tagName string, link int, options ...OptionFunc) (*ReleaseLink, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/releases/%s/assets/links/%d",
+		pathEscape(project),
+		tagName,
+		link,
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	rl := new(ReleaseLink)
+	resp, err := s.client.Do(req, rl)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rl, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/releases.go b/vendor/github.com/xanzy/go-gitlab/releases.go
new file mode 100644
index 000000000..8202e9853
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/releases.go
@@ -0,0 +1,212 @@
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// ReleasesService handles communication with the releases methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/releases/index.html
+type ReleasesService struct {
+	client *Client
+}
+
+// Release represents a project release.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#list-releases
+type Release struct {
+	TagName         string     `json:"tag_name"`
+	Name            string     `json:"name"`
+	Description     string     `json:"description,omitempty"`
+	DescriptionHTML string     `json:"description_html,omitempty"`
+	CreatedAt       *time.Time `json:"created_at,omitempty"`
+	Author          struct {
+		ID        int    `json:"id"`
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		State     string `json:"state"`
+		AvatarURL string `json:"avatar_url"`
+		WebURL    string `json:"web_url"`
+	} `json:"author"`
+	Commit Commit `json:"commit"`
+	Assets struct {
+		Count   int `json:"count"`
+		Sources []struct {
+			Format string `json:"format"`
+			URL    string `json:"url"`
+		} `json:"sources"`
+		Links []*ReleaseLink `json:"links"`
+	} `json:"assets"`
+}
+
+// ListReleasesOptions represents ListReleases() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#list-releases
+type ListReleasesOptions ListOptions
+
+// ListReleases gets a pagenated of releases accessible by the authenticated user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#list-releases
+func (s *ReleasesService) ListReleases(pid interface{}, opt *ListReleasesOptions, options ...OptionFunc) ([]*Release, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/releases", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var rs []*Release
+	resp, err := s.client.Do(req, &rs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rs, resp, err
+}
+
+// GetRelease returns a single release, identified by a tag name.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#get-a-release-by-a-tag-name
+func (s *ReleasesService) GetRelease(pid interface{}, tagName string, options ...OptionFunc) (*Release, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/releases/%s", pathEscape(project), tagName)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	r := new(Release)
+	resp, err := s.client.Do(req, r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
+
+// ReleaseAssets represents release assets in CreateRelease() options
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#create-a-release
+type ReleaseAssets struct {
+	Links []*ReleaseAssetLink `url:"links" json:"links"`
+}
+
+// ReleaseAssetLink represents release asset link in CreateRelease() options
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#create-a-release
+type ReleaseAssetLink struct {
+	Name string `url:"name" json:"name"`
+	URL  string `url:"url" json:"url"`
+}
+
+// CreateReleaseOptions represents CreateRelease() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#create-a-release
+type CreateReleaseOptions struct {
+	Name        *string        `url:"name" json:"name"`
+	TagName     *string        `url:"tag_name" json:"tag_name"`
+	Description *string        `url:"description" json:"description"`
+	Ref         *string        `url:"ref,omitempty" json:"ref,omitempty"`
+	Assets      *ReleaseAssets `url:"assets,omitempty" json:"assets,omitempty"`
+}
+
+// CreateRelease creates a release.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#create-a-release
+func (s *ReleasesService) CreateRelease(pid interface{}, opts *CreateReleaseOptions, options ...OptionFunc) (*Release, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/releases", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opts, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	r := new(Release)
+	resp, err := s.client.Do(req, r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
+
+// UpdateReleaseOptions represents UpdateRelease() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#update-a-release
+type UpdateReleaseOptions struct {
+	Name        *string `url:"name" json:"name"`
+	Description *string `url:"description" json:"description"`
+}
+
+// UpdateRelease updates a release.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#update-a-release
+func (s *ReleasesService) UpdateRelease(pid interface{}, tagName string, opts *UpdateReleaseOptions, options ...OptionFunc) (*Release, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/releases/%s", pathEscape(project), tagName)
+
+	req, err := s.client.NewRequest("PUT", u, opts, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	r := new(Release)
+	resp, err := s.client.Do(req, &r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
+
+// DeleteRelease deletes a release.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/releases/index.html#delete-a-release
+func (s *ReleasesService) DeleteRelease(pid interface{}, tagName string, options ...OptionFunc) (*Release, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/releases/%s", pathEscape(project), tagName)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	r := new(Release)
+	resp, err := s.client.Do(req, r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/repositories.go b/vendor/github.com/xanzy/go-gitlab/repositories.go
new file mode 100644
index 000000000..96766aea7
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/repositories.go
@@ -0,0 +1,327 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"net/url"
+)
+
+// RepositoriesService handles communication with the repositories related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html
+type RepositoriesService struct {
+	client *Client
+}
+
+// TreeNode represents a GitLab repository file or directory.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html
+type TreeNode struct {
+	ID   string `json:"id"`
+	Name string `json:"name"`
+	Type string `json:"type"`
+	Path string `json:"path"`
+	Mode string `json:"mode"`
+}
+
+func (t TreeNode) String() string {
+	return Stringify(t)
+}
+
+// ListTreeOptions represents the available ListTree() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#list-repository-tree
+type ListTreeOptions struct {
+	ListOptions
+	Path      *string `url:"path,omitempty" json:"path,omitempty"`
+	Ref       *string `url:"ref,omitempty" json:"ref,omitempty"`
+	Recursive *bool   `url:"recursive,omitempty" json:"recursive,omitempty"`
+}
+
+// ListTree gets a list of repository files and directories in a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#list-repository-tree
+func (s *RepositoriesService) ListTree(pid interface{}, opt *ListTreeOptions, options ...OptionFunc) ([]*TreeNode, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/tree", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var t []*TreeNode
+	resp, err := s.client.Do(req, &t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// Blob gets information about blob in repository like size and content. Note
+// that blob content is Base64 encoded.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#get-a-blob-from-repository
+func (s *RepositoriesService) Blob(pid interface{}, sha string, options ...OptionFunc) ([]byte, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/blobs/%s", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var b bytes.Buffer
+	resp, err := s.client.Do(req, &b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b.Bytes(), resp, err
+}
+
+// RawBlobContent gets the raw file contents for a blob by blob SHA.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#raw-blob-content
+func (s *RepositoriesService) RawBlobContent(pid interface{}, sha string, options ...OptionFunc) ([]byte, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/blobs/%s/raw", pathEscape(project), url.PathEscape(sha))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var b bytes.Buffer
+	resp, err := s.client.Do(req, &b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b.Bytes(), resp, err
+}
+
+// ArchiveOptions represents the available Archive() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#get-file-archive
+type ArchiveOptions struct {
+	Format *string `url:"-" json:"-"`
+	SHA    *string `url:"sha,omitempty" json:"sha,omitempty"`
+}
+
+// Archive gets an archive of the repository.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#get-file-archive
+func (s *RepositoriesService) Archive(pid interface{}, opt *ArchiveOptions, options ...OptionFunc) ([]byte, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/archive", pathEscape(project))
+
+	// Set an optional format for the archive.
+	if opt != nil && opt.Format != nil {
+		u = fmt.Sprintf("%s.%s", u, *opt.Format)
+	}
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var b bytes.Buffer
+	resp, err := s.client.Do(req, &b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b.Bytes(), resp, err
+}
+
+// StreamArchive streams an archive of the repository to the provided
+// io.Writer.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#get-file-archive
+func (s *RepositoriesService) StreamArchive(pid interface{}, w io.Writer, opt *ArchiveOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/archive", pathEscape(project))
+
+	// Set an optional format for the archive.
+	if opt != nil && opt.Format != nil {
+		u = fmt.Sprintf("%s.%s", u, *opt.Format)
+	}
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, w)
+}
+
+// Compare represents the result of a comparison of branches, tags or commits.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#compare-branches-tags-or-commits
+type Compare struct {
+	Commit         *Commit   `json:"commit"`
+	Commits        []*Commit `json:"commits"`
+	Diffs          []*Diff   `json:"diffs"`
+	CompareTimeout bool      `json:"compare_timeout"`
+	CompareSameRef bool      `json:"compare_same_ref"`
+}
+
+func (c Compare) String() string {
+	return Stringify(c)
+}
+
+// CompareOptions represents the available Compare() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#compare-branches-tags-or-commits
+type CompareOptions struct {
+	From     *string `url:"from,omitempty" json:"from,omitempty"`
+	To       *string `url:"to,omitempty" json:"to,omitempty"`
+	Straight *bool   `url:"straight,omitempty" json:"straight,omitempty"`
+}
+
+// Compare compares branches, tags or commits.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#compare-branches-tags-or-commits
+func (s *RepositoriesService) Compare(pid interface{}, opt *CompareOptions, options ...OptionFunc) (*Compare, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/compare", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	c := new(Compare)
+	resp, err := s.client.Do(req, c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
+
+// Contributor represents a GitLap contributor.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html#contributors
+type Contributor struct {
+	Name      string `json:"name"`
+	Email     string `json:"email"`
+	Commits   int    `json:"commits"`
+	Additions int    `json:"additions"`
+	Deletions int    `json:"deletions"`
+}
+
+func (c Contributor) String() string {
+	return Stringify(c)
+}
+
+// ListContributorsOptions represents the available ListContributors() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html#contributors
+type ListContributorsOptions ListOptions
+
+// Contributors gets the repository contributors list.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repositories.html#contributors
+func (s *RepositoriesService) Contributors(pid interface{}, opt *ListContributorsOptions, options ...OptionFunc) ([]*Contributor, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/contributors", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var c []*Contributor
+	resp, err := s.client.Do(req, &c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
+
+// MergeBaseOptions represents the available MergeBase() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#merge-base
+type MergeBaseOptions struct {
+	Ref []string `url:"refs[],omitempty" json:"refs,omitempty"`
+}
+
+// MergeBase gets the common ancestor for 2 refs (commit SHAs, branch
+// names or tags).
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repositories.html#merge-base
+func (s *RepositoriesService) MergeBase(pid interface{}, opt *MergeBaseOptions, options ...OptionFunc) (*Commit, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/merge_base", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	c := new(Commit)
+	resp, err := s.client.Do(req, c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/repository_files.go b/vendor/github.com/xanzy/go-gitlab/repository_files.go
new file mode 100644
index 000000000..261493195
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/repository_files.go
@@ -0,0 +1,311 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"bytes"
+	"fmt"
+	"net/url"
+	"strconv"
+)
+
+// RepositoryFilesService handles communication with the repository files
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
+type RepositoryFilesService struct {
+	client *Client
+}
+
+// File represents a GitLab repository file.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
+type File struct {
+	FileName string `json:"file_name"`
+	FilePath string `json:"file_path"`
+	Size     int    `json:"size"`
+	Encoding string `json:"encoding"`
+	Content  string `json:"content"`
+	Ref      string `json:"ref"`
+	BlobID   string `json:"blob_id"`
+	CommitID string `json:"commit_id"`
+	SHA256   string `json:"content_sha256"`
+}
+
+func (r File) String() string {
+	return Stringify(r)
+}
+
+// GetFileOptions represents the available GetFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
+type GetFileOptions struct {
+	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
+}
+
+// GetFile allows you to receive information about a file in repository like
+// name, size, content. Note that file content is Base64 encoded.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
+func (s *RepositoryFilesService) GetFile(pid interface{}, fileName string, opt *GetFileOptions, options ...OptionFunc) (*File, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf(
+		"projects/%s/repository/files/%s",
+		pathEscape(project),
+		url.PathEscape(fileName),
+	)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	f := new(File)
+	resp, err := s.client.Do(req, f)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return f, resp, err
+}
+
+// GetFileMetaDataOptions represents the available GetFileMetaData() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
+type GetFileMetaDataOptions struct {
+	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
+}
+
+// GetFileMetaData allows you to receive meta information about a file in
+// repository like name, size.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#get-file-from-repository
+func (s *RepositoryFilesService) GetFileMetaData(pid interface{}, fileName string, opt *GetFileMetaDataOptions, options ...OptionFunc) (*File, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf(
+		"projects/%s/repository/files/%s",
+		pathEscape(project),
+		url.PathEscape(fileName),
+	)
+
+	req, err := s.client.NewRequest("HEAD", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	resp, err := s.client.Do(req, nil)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	f := &File{
+		BlobID:   resp.Header.Get("X-Gitlab-Blob-Id"),
+		CommitID: resp.Header.Get("X-Gitlab-Last-Commit-Id"),
+		Encoding: resp.Header.Get("X-Gitlab-Encoding"),
+		FileName: resp.Header.Get("X-Gitlab-File-Name"),
+		FilePath: resp.Header.Get("X-Gitlab-File-Path"),
+		Ref:      resp.Header.Get("X-Gitlab-Ref"),
+		SHA256:   resp.Header.Get("X-Gitlab-Content-Sha256"),
+	}
+
+	if sizeString := resp.Header.Get("X-Gitlab-Size"); sizeString != "" {
+		f.Size, err = strconv.Atoi(sizeString)
+		if err != nil {
+			return nil, resp, err
+		}
+	}
+
+	return f, resp, err
+}
+
+// GetRawFileOptions represents the available GetRawFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
+type GetRawFileOptions struct {
+	Ref *string `url:"ref,omitempty" json:"ref,omitempty"`
+}
+
+// GetRawFile allows you to receive the raw file in repository.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#get-raw-file-from-repository
+func (s *RepositoryFilesService) GetRawFile(pid interface{}, fileName string, opt *GetRawFileOptions, options ...OptionFunc) ([]byte, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf(
+		"projects/%s/repository/files/%s/raw",
+		pathEscape(project),
+		url.PathEscape(fileName),
+	)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var f bytes.Buffer
+	resp, err := s.client.Do(req, &f)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return f.Bytes(), resp, err
+}
+
+// FileInfo represents file details of a GitLab repository file.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/repository_files.html
+type FileInfo struct {
+	FilePath string `json:"file_path"`
+	Branch   string `json:"branch"`
+}
+
+func (r FileInfo) String() string {
+	return Stringify(r)
+}
+
+// CreateFileOptions represents the available CreateFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
+type CreateFileOptions struct {
+	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
+	Encoding      *string `url:"encoding,omitempty" json:"encoding,omitempty"`
+	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
+	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
+	Content       *string `url:"content,omitempty" json:"content,omitempty"`
+	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
+}
+
+// CreateFile creates a new file in a repository.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#create-new-file-in-repository
+func (s *RepositoryFilesService) CreateFile(pid interface{}, fileName string, opt *CreateFileOptions, options ...OptionFunc) (*FileInfo, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf(
+		"projects/%s/repository/files/%s",
+		pathEscape(project),
+		url.PathEscape(fileName),
+	)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	f := new(FileInfo)
+	resp, err := s.client.Do(req, f)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return f, resp, err
+}
+
+// UpdateFileOptions represents the available UpdateFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
+type UpdateFileOptions struct {
+	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
+	Encoding      *string `url:"encoding,omitempty" json:"encoding,omitempty"`
+	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
+	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
+	Content       *string `url:"content,omitempty" json:"content,omitempty"`
+	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
+	LastCommitID  *string `url:"last_commit_id,omitempty" json:"last_commit_id,omitempty"`
+}
+
+// UpdateFile updates an existing file in a repository
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#update-existing-file-in-repository
+func (s *RepositoryFilesService) UpdateFile(pid interface{}, fileName string, opt *UpdateFileOptions, options ...OptionFunc) (*FileInfo, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf(
+		"projects/%s/repository/files/%s",
+		pathEscape(project),
+		url.PathEscape(fileName),
+	)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	f := new(FileInfo)
+	resp, err := s.client.Do(req, f)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return f, resp, err
+}
+
+// DeleteFileOptions represents the available DeleteFile() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
+type DeleteFileOptions struct {
+	Branch        *string `url:"branch,omitempty" json:"branch,omitempty"`
+	AuthorEmail   *string `url:"author_email,omitempty" json:"author_email,omitempty"`
+	AuthorName    *string `url:"author_name,omitempty" json:"author_name,omitempty"`
+	CommitMessage *string `url:"commit_message,omitempty" json:"commit_message,omitempty"`
+}
+
+// DeleteFile deletes an existing file in a repository
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/repository_files.html#delete-existing-file-in-repository
+func (s *RepositoryFilesService) DeleteFile(pid interface{}, fileName string, opt *DeleteFileOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf(
+		"projects/%s/repository/files/%s",
+		pathEscape(project),
+		url.PathEscape(fileName),
+	)
+
+	req, err := s.client.NewRequest("DELETE", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/resource_label_events.go b/vendor/github.com/xanzy/go-gitlab/resource_label_events.go
new file mode 100644
index 000000000..e9ff66a34
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/resource_label_events.go
@@ -0,0 +1,219 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// ResourceLabelEventsService handles communication with the event related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/resource_label_events.html
+type ResourceLabelEventsService struct {
+	client *Client
+}
+
+// LabelEvent represents a resource label event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_label_events.html#get-single-issue-label-event
+type LabelEvent struct {
+	ID           int        `json:"id"`
+	Action       string     `json:"action"`
+	CreatedAt    *time.Time `json:"created_at"`
+	ResourceType string     `json:"resource_type"`
+	ResourceID   int        `json:"resource_id"`
+	User         struct {
+		ID        int    `json:"id"`
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		State     string `json:"state"`
+		AvatarURL string `json:"avatar_url"`
+		WebURL    string `json:"web_url"`
+	} `json:"user"`
+	Label struct {
+		ID          int    `json:"id"`
+		Name        string `json:"name"`
+		Color       string `json:"color"`
+		TextColor   string `json:"text_color"`
+		Description string `json:"description"`
+	} `json:"label"`
+}
+
+// ListLabelEventsOptions represents the options for all resource label events
+// list methods.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_label_events.html#list-project-issue-label-events
+type ListLabelEventsOptions struct {
+	ListOptions
+}
+
+// ListIssueLabelEvents retrieves resource label events for the
+// specified project and issue.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_label_events.html#list-project-issue-label-events
+func (s *ResourceLabelEventsService) ListIssueLabelEvents(pid interface{}, issue int, opt *ListLabelEventsOptions, options ...OptionFunc) ([]*LabelEvent, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/resource_label_events", pathEscape(project), issue)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ls []*LabelEvent
+	resp, err := s.client.Do(req, &ls)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ls, resp, err
+}
+
+// GetIssueLabelEvent gets a single issue-label-event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_label_events.html#get-single-issue-label-event
+func (s *ResourceLabelEventsService) GetIssueLabelEvent(pid interface{}, issue int, event int, options ...OptionFunc) (*LabelEvent, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/issues/%d/resource_label_events/%d", pathEscape(project), issue, event)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(LabelEvent)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// ListGroupEpicLabelEvents retrieves resource label events for the specified
+// group and epic.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_label_events.html#list-group-epic-label-events
+func (s *ResourceLabelEventsService) ListGroupEpicLabelEvents(gid interface{}, epic int, opt *ListLabelEventsOptions, options ...OptionFunc) ([]*LabelEvent, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/resource_label_events", pathEscape(group), epic)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ls []*LabelEvent
+	resp, err := s.client.Do(req, &ls)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ls, resp, err
+}
+
+// GetGroupEpicLabelEvent gets a single group epic label event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_label_events.html#get-single-epic-label-event
+func (s *ResourceLabelEventsService) GetGroupEpicLabelEvent(gid interface{}, epic int, event int, options ...OptionFunc) (*LabelEvent, *Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("groups/%s/epics/%d/resource_label_events/%d", pathEscape(group), epic, event)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(LabelEvent)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
+
+// ListMergeLabelEvents retrieves resource label events for the specified
+// project and merge request.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_label_events.html#list-project-merge-request-label-events
+func (s *ResourceLabelEventsService) ListMergeLabelEvents(pid interface{}, request int, opt *ListLabelEventsOptions, options ...OptionFunc) ([]*LabelEvent, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/resource_label_events", pathEscape(project), request)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ls []*LabelEvent
+	resp, err := s.client.Do(req, &ls)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ls, resp, err
+}
+
+// GetMergeRequestLabelEvent gets a single merge request label event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/resource_label_events.html#get-single-merge-request-label-event
+func (s *ResourceLabelEventsService) GetMergeRequestLabelEvent(pid interface{}, request int, event int, options ...OptionFunc) (*LabelEvent, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/merge_requests/%d/resource_label_events/%d", pathEscape(project), request, event)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(LabelEvent)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/runners.go b/vendor/github.com/xanzy/go-gitlab/runners.go
new file mode 100644
index 000000000..d3330d44f
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/runners.go
@@ -0,0 +1,415 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// RunnersService handles communication with the runner related methods of the
+// GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
+type RunnersService struct {
+	client *Client
+}
+
+// Runner represents a GitLab CI Runner.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
+type Runner struct {
+	ID          int    `json:"id"`
+	Description string `json:"description"`
+	Active      bool   `json:"active"`
+	IsShared    bool   `json:"is_shared"`
+	IPAddress   string `json:"ip_address"`
+	Name        string `json:"name"`
+	Online      bool   `json:"online"`
+	Status      string `json:"status"`
+	Token       string `json:"token"`
+}
+
+// RunnerDetails represents the GitLab CI runner details.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
+type RunnerDetails struct {
+	Active       bool       `json:"active"`
+	Architecture string     `json:"architecture"`
+	Description  string     `json:"description"`
+	ID           int        `json:"id"`
+	IPAddress    string     `json:"ip_address"`
+	IsShared     bool       `json:"is_shared"`
+	ContactedAt  *time.Time `json:"contacted_at"`
+	Name         string     `json:"name"`
+	Online       bool       `json:"online"`
+	Status       string     `json:"status"`
+	Platform     string     `json:"platform"`
+	Projects     []struct {
+		ID                int    `json:"id"`
+		Name              string `json:"name"`
+		NameWithNamespace string `json:"name_with_namespace"`
+		Path              string `json:"path"`
+		PathWithNamespace string `json:"path_with_namespace"`
+	} `json:"projects"`
+	Token          string   `json:"token"`
+	Revision       string   `json:"revision"`
+	TagList        []string `json:"tag_list"`
+	Version        string   `json:"version"`
+	AccessLevel    string   `json:"access_level"`
+	MaximumTimeout int      `json:"maximum_timeout"`
+	Groups         []struct {
+		ID     int    `json:"id"`
+		Name   string `json:"name"`
+		WebURL string `json:"web_url"`
+	} `json:"groups"`
+}
+
+// ListRunnersOptions represents the available ListRunners() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#list-owned-runners
+type ListRunnersOptions struct {
+	ListOptions
+	Scope   *string  `url:"scope,omitempty" json:"scope,omitempty"`
+	Type    *string  `url:"type,omitempty" json:"type,omitempty"`
+	Status  *string  `url:"status,omitempty" json:"status,omitempty"`
+	TagList []string `url:"tag_list,comma,omitempty" json:"tag_list,omitempty"`
+}
+
+// ListRunners gets a list of runners accessible by the authenticated user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#list-owned-runners
+func (s *RunnersService) ListRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
+	req, err := s.client.NewRequest("GET", "runners", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var rs []*Runner
+	resp, err := s.client.Do(req, &rs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rs, resp, err
+}
+
+// ListAllRunners gets a list of all runners in the GitLab instance. Access is
+// restricted to users with admin privileges.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#list-all-runners
+func (s *RunnersService) ListAllRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
+	req, err := s.client.NewRequest("GET", "runners/all", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var rs []*Runner
+	resp, err := s.client.Do(req, &rs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rs, resp, err
+}
+
+// GetRunnerDetails returns details for given runner.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#get-runner-39-s-details
+func (s *RunnersService) GetRunnerDetails(rid interface{}, options ...OptionFunc) (*RunnerDetails, *Response, error) {
+	runner, err := parseID(rid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("runners/%s", runner)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var rs *RunnerDetails
+	resp, err := s.client.Do(req, &rs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rs, resp, err
+}
+
+// UpdateRunnerDetailsOptions represents the available UpdateRunnerDetails() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#update-runner-39-s-details
+type UpdateRunnerDetailsOptions struct {
+	Description    *string  `url:"description,omitempty" json:"description,omitempty"`
+	Active         *bool    `url:"active,omitempty" json:"active,omitempty"`
+	TagList        []string `url:"tag_list[],omitempty" json:"tag_list,omitempty"`
+	RunUntagged    *bool    `url:"run_untagged,omitempty" json:"run_untagged,omitempty"`
+	Locked         *bool    `url:"locked,omitempty" json:"locked,omitempty"`
+	AccessLevel    *string  `url:"access_level,omitempty" json:"access_level,omitempty"`
+	MaximumTimeout *int     `url:"maximum_timeout,omitempty" json:"maximum_timeout,omitempty"`
+}
+
+// UpdateRunnerDetails updates details for a given runner.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#update-runner-39-s-details
+func (s *RunnersService) UpdateRunnerDetails(rid interface{}, opt *UpdateRunnerDetailsOptions, options ...OptionFunc) (*RunnerDetails, *Response, error) {
+	runner, err := parseID(rid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("runners/%s", runner)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var rs *RunnerDetails
+	resp, err := s.client.Do(req, &rs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rs, resp, err
+}
+
+// RemoveRunner removes a runner.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#remove-a-runner
+func (s *RunnersService) RemoveRunner(rid interface{}, options ...OptionFunc) (*Response, error) {
+	runner, err := parseID(rid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("runners/%s", runner)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ListRunnerJobsOptions represents the available ListRunnerJobs()
+// options. Status can be one of: running, success, failed, canceled.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#list-runners-jobs
+type ListRunnerJobsOptions struct {
+	ListOptions
+	Status  *string `url:"status,omitempty" json:"status,omitempty"`
+	OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort    *string `url:"sort,omitempty" json:"sort,omitempty"`
+}
+
+// ListRunnerJobs gets a list of jobs that are being processed or were processed by specified Runner.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#list-runner-39-s-jobs
+func (s *RunnersService) ListRunnerJobs(rid interface{}, opt *ListRunnerJobsOptions, options ...OptionFunc) ([]*Job, *Response, error) {
+	runner, err := parseID(rid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("runners/%s/jobs", runner)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var rs []*Job
+	resp, err := s.client.Do(req, &rs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rs, resp, err
+}
+
+// ListProjectRunnersOptions represents the available ListProjectRunners()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#list-project-s-runners
+type ListProjectRunnersOptions ListRunnersOptions
+
+// ListProjectRunners gets a list of runners accessible by the authenticated user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#list-project-s-runners
+func (s *RunnersService) ListProjectRunners(pid interface{}, opt *ListProjectRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/runners", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var rs []*Runner
+	resp, err := s.client.Do(req, &rs)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return rs, resp, err
+}
+
+// EnableProjectRunnerOptions represents the available EnableProjectRunner()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project
+type EnableProjectRunnerOptions struct {
+	RunnerID int `json:"runner_id"`
+}
+
+// EnableProjectRunner enables an available specific runner in the project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project
+func (s *RunnersService) EnableProjectRunner(pid interface{}, opt *EnableProjectRunnerOptions, options ...OptionFunc) (*Runner, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/runners", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var r *Runner
+	resp, err := s.client.Do(req, &r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
+
+// DisableProjectRunner disables a specific runner from project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#disable-a-runner-from-project
+func (s *RunnersService) DisableProjectRunner(pid interface{}, runner int, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/runners/%d", pathEscape(project), runner)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// RegisterNewRunnerOptions represents the available RegisterNewRunner()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#register-a-new-runner
+type RegisterNewRunnerOptions struct {
+	Token          *string  `url:"token" json:"token"`
+	Description    *string  `url:"description,omitempty" json:"description,omitempty"`
+	Info           *string  `url:"info,omitempty" json:"info,omitempty"`
+	Active         *bool    `url:"active,omitempty" json:"active,omitempty"`
+	Locked         *bool    `url:"locked,omitempty" json:"locked,omitempty"`
+	RunUntagged    *bool    `url:"run_untagged,omitempty" json:"run_untagged,omitempty"`
+	TagList        []string `url:"tag_list[],omitempty" json:"tag_list,omitempty"`
+	MaximumTimeout *int     `url:"maximum_timeout,omitempty" json:"maximum_timeout,omitempty"`
+}
+
+// RegisterNewRunner registers a new Runner for the instance.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#register-a-new-runner
+func (s *RunnersService) RegisterNewRunner(opt *RegisterNewRunnerOptions, options ...OptionFunc) (*Runner, *Response, error) {
+	req, err := s.client.NewRequest("POST", "runners", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var r *Runner
+	resp, err := s.client.Do(req, &r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
+
+// DeleteRegisteredRunnerOptions represents the available
+// DeleteRegisteredRunner() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#delete-a-registered-runner
+type DeleteRegisteredRunnerOptions struct {
+	Token *string `url:"token" json:"token"`
+}
+
+// DeleteRegisteredRunner registers a new Runner for the instance.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#delete-a-registered-runner
+func (s *RunnersService) DeleteRegisteredRunner(opt *DeleteRegisteredRunnerOptions, options ...OptionFunc) (*Response, error) {
+	req, err := s.client.NewRequest("DELETE", "runners", opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// VerifyRegisteredRunnerOptions represents the available
+// VerifyRegisteredRunner() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#verify-authentication-for-a-registered-runner
+type VerifyRegisteredRunnerOptions struct {
+	Token *string `url:"token" json:"token"`
+}
+
+// VerifyRegisteredRunner registers a new Runner for the instance.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/runners.html#verify-authentication-for-a-registered-runner
+func (s *RunnersService) VerifyRegisteredRunner(opt *VerifyRegisteredRunnerOptions, options ...OptionFunc) (*Response, error) {
+	req, err := s.client.NewRequest("POST", "runners/verify", opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/search.go b/vendor/github.com/xanzy/go-gitlab/search.go
new file mode 100644
index 000000000..de3ad3620
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/search.go
@@ -0,0 +1,354 @@
+//
+// Copyright 2018, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+)
+
+// SearchService handles communication with the search related methods of the
+// GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html
+type SearchService struct {
+	client *Client
+}
+
+// SearchOptions represents the available options for all search methods.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html
+type SearchOptions ListOptions
+
+type searchOptions struct {
+	SearchOptions
+	Scope  string `url:"scope" json:"scope"`
+	Search string `url:"search" json:"search"`
+}
+
+// Projects searches the expression within projects
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-projects
+func (s *SearchService) Projects(query string, opt *SearchOptions, options ...OptionFunc) ([]*Project, *Response, error) {
+	var ps []*Project
+	resp, err := s.search("projects", query, &ps, opt, options...)
+	return ps, resp, err
+}
+
+// ProjectsByGroup searches the expression within projects for
+// the specified group
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#group-search-api
+func (s *SearchService) ProjectsByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Project, *Response, error) {
+	var ps []*Project
+	resp, err := s.searchByGroup(gid, "projects", query, &ps, opt, options...)
+	return ps, resp, err
+}
+
+// Issues searches the expression within issues
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-issues
+func (s *SearchService) Issues(query string, opt *SearchOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+	var is []*Issue
+	resp, err := s.search("issues", query, &is, opt, options...)
+	return is, resp, err
+}
+
+// IssuesByGroup searches the expression within issues for
+// the specified group
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-issues
+func (s *SearchService) IssuesByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+	var is []*Issue
+	resp, err := s.searchByGroup(gid, "issues", query, &is, opt, options...)
+	return is, resp, err
+}
+
+// IssuesByProject searches the expression within issues for
+// the specified project
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-issues
+func (s *SearchService) IssuesByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Issue, *Response, error) {
+	var is []*Issue
+	resp, err := s.searchByProject(pid, "issues", query, &is, opt, options...)
+	return is, resp, err
+}
+
+// MergeRequests searches the expression within merge requests
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/search.html#scope-merge_requests
+func (s *SearchService) MergeRequests(query string, opt *SearchOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	var ms []*MergeRequest
+	resp, err := s.search("merge_requests", query, &ms, opt, options...)
+	return ms, resp, err
+}
+
+// MergeRequestsByGroup searches the expression within merge requests for
+// the specified group
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/search.html#scope-merge_requests
+func (s *SearchService) MergeRequestsByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	var ms []*MergeRequest
+	resp, err := s.searchByGroup(gid, "merge_requests", query, &ms, opt, options...)
+	return ms, resp, err
+}
+
+// MergeRequestsByProject searches the expression within merge requests for
+// the specified project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/search.html#scope-merge_requests
+func (s *SearchService) MergeRequestsByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*MergeRequest, *Response, error) {
+	var ms []*MergeRequest
+	resp, err := s.searchByProject(pid, "merge_requests", query, &ms, opt, options...)
+	return ms, resp, err
+}
+
+// Milestones searches the expression within milestones
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-milestones
+func (s *SearchService) Milestones(query string, opt *SearchOptions, options ...OptionFunc) ([]*Milestone, *Response, error) {
+	var ms []*Milestone
+	resp, err := s.search("milestones", query, &ms, opt, options...)
+	return ms, resp, err
+}
+
+// MilestonesByGroup searches the expression within milestones for
+// the specified group
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-milestones
+func (s *SearchService) MilestonesByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Milestone, *Response, error) {
+	var ms []*Milestone
+	resp, err := s.searchByGroup(gid, "milestones", query, &ms, opt, options...)
+	return ms, resp, err
+}
+
+// MilestonesByProject searches the expression within milestones for
+// the specified project
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-milestones
+func (s *SearchService) MilestonesByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Milestone, *Response, error) {
+	var ms []*Milestone
+	resp, err := s.searchByProject(pid, "milestones", query, &ms, opt, options...)
+	return ms, resp, err
+}
+
+// SnippetTitles searches the expression within snippet titles
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/search.html#scope-snippet_titles
+func (s *SearchService) SnippetTitles(query string, opt *SearchOptions, options ...OptionFunc) ([]*Snippet, *Response, error) {
+	var ss []*Snippet
+	resp, err := s.search("snippet_titles", query, &ss, opt, options...)
+	return ss, resp, err
+}
+
+// SnippetBlobs searches the expression within snippet blobs
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/search.html#scope-snippet_blobs
+func (s *SearchService) SnippetBlobs(query string, opt *SearchOptions, options ...OptionFunc) ([]*Snippet, *Response, error) {
+	var ss []*Snippet
+	resp, err := s.search("snippet_blobs", query, &ss, opt, options...)
+	return ss, resp, err
+}
+
+// NotesByProject searches the expression within notes for the specified
+// project
+//
+// GitLab API docs: // https://docs.gitlab.com/ce/api/search.html#scope-notes
+func (s *SearchService) NotesByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Note, *Response, error) {
+	var ns []*Note
+	resp, err := s.searchByProject(pid, "notes", query, &ns, opt, options...)
+	return ns, resp, err
+}
+
+// WikiBlobs searches the expression within all wiki blobs
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/search.html#scope-wiki_blobs
+func (s *SearchService) WikiBlobs(query string, opt *SearchOptions, options ...OptionFunc) ([]*Wiki, *Response, error) {
+	var ws []*Wiki
+	resp, err := s.search("wiki_blobs", query, &ws, opt, options...)
+	return ws, resp, err
+}
+
+// WikiBlobsByGroup searches the expression within wiki blobs for
+// specified group
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/search.html#scope-wiki_blobs
+func (s *SearchService) WikiBlobsByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Wiki, *Response, error) {
+	var ws []*Wiki
+	resp, err := s.searchByGroup(gid, "wiki_blobs", query, &ws, opt, options...)
+	return ws, resp, err
+}
+
+// WikiBlobsByProject searches the expression within wiki blobs for
+// the specified project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/search.html#scope-wiki_blobs
+func (s *SearchService) WikiBlobsByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Wiki, *Response, error) {
+	var ws []*Wiki
+	resp, err := s.searchByProject(pid, "wiki_blobs", query, &ws, opt, options...)
+	return ws, resp, err
+}
+
+// Commits searches the expression within all commits
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-commits
+func (s *SearchService) Commits(query string, opt *SearchOptions, options ...OptionFunc) ([]*Commit, *Response, error) {
+	var cs []*Commit
+	resp, err := s.search("commits", query, &cs, opt, options...)
+	return cs, resp, err
+}
+
+// CommitsByGroup searches the expression within commits for the specified
+// group
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-commits
+func (s *SearchService) CommitsByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Commit, *Response, error) {
+	var cs []*Commit
+	resp, err := s.searchByGroup(gid, "commits", query, &cs, opt, options...)
+	return cs, resp, err
+}
+
+// CommitsByProject searches the expression within commits for the
+// specified project
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-commits
+func (s *SearchService) CommitsByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Commit, *Response, error) {
+	var cs []*Commit
+	resp, err := s.searchByProject(pid, "commits", query, &cs, opt, options...)
+	return cs, resp, err
+}
+
+// Blob represents a single blob.
+type Blob struct {
+	Basename  string `json:"basename"`
+	Data      string `json:"data"`
+	Filename  string `json:"filename"`
+	ID        int    `json:"id"`
+	Ref       string `json:"ref"`
+	Startline int    `json:"startline"`
+	ProjectID int    `json:"project_id"`
+}
+
+// Blobs searches the expression within all blobs
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-blobs
+func (s *SearchService) Blobs(query string, opt *SearchOptions, options ...OptionFunc) ([]*Blob, *Response, error) {
+	var bs []*Blob
+	resp, err := s.search("blobs", query, &bs, opt, options...)
+	return bs, resp, err
+}
+
+// BlobsByGroup searches the expression within blobs for the specified
+// group
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-blobs
+func (s *SearchService) BlobsByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Blob, *Response, error) {
+	var bs []*Blob
+	resp, err := s.searchByGroup(gid, "blobs", query, &bs, opt, options...)
+	return bs, resp, err
+}
+
+// BlobsByProject searches the expression within blobs for the specified
+// project
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/search.html#scope-blobs
+func (s *SearchService) BlobsByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*Blob, *Response, error) {
+	var bs []*Blob
+	resp, err := s.searchByProject(pid, "blobs", query, &bs, opt, options...)
+	return bs, resp, err
+}
+
+// Users searches the expression within all users
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/search.html#scope-users
+func (s *SearchService) Users(query string, opt *SearchOptions, options ...OptionFunc) ([]*User, *Response, error) {
+	var ret []*User
+	resp, err := s.search("users", query, &ret, opt, options...)
+	return ret, resp, err
+}
+
+// UsersByGroup searches the expression within users for the specified
+// group
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/search.html#scope-users-1
+func (s *SearchService) UsersByGroup(gid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*User, *Response, error) {
+	var ret []*User
+	resp, err := s.searchByGroup(gid, "users", query, &ret, opt, options...)
+	return ret, resp, err
+}
+
+// UsersByProject searches the expression within users for the
+// specified project
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/search.html#scope-users-2
+func (s *SearchService) UsersByProject(pid interface{}, query string, opt *SearchOptions, options ...OptionFunc) ([]*User, *Response, error) {
+	var ret []*User
+	resp, err := s.searchByProject(pid, "users", query, &ret, opt, options...)
+	return ret, resp, err
+}
+
+func (s *SearchService) search(scope, query string, result interface{}, opt *SearchOptions, options ...OptionFunc) (*Response, error) {
+	opts := &searchOptions{SearchOptions: *opt, Scope: scope, Search: query}
+
+	req, err := s.client.NewRequest("GET", "search", opts, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, result)
+}
+
+func (s *SearchService) searchByGroup(gid interface{}, scope, query string, result interface{}, opt *SearchOptions, options ...OptionFunc) (*Response, error) {
+	group, err := parseID(gid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("groups/%s/-/search", pathEscape(group))
+
+	opts := &searchOptions{SearchOptions: *opt, Scope: scope, Search: query}
+
+	req, err := s.client.NewRequest("GET", u, opts, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, result)
+}
+
+func (s *SearchService) searchByProject(pid interface{}, scope, query string, result interface{}, opt *SearchOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/-/search", pathEscape(project))
+
+	opts := &searchOptions{SearchOptions: *opt, Scope: scope, Search: query}
+
+	req, err := s.client.NewRequest("GET", u, opts, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, result)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/services.go b/vendor/github.com/xanzy/go-gitlab/services.go
new file mode 100644
index 000000000..8d16cadc2
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/services.go
@@ -0,0 +1,864 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"encoding/json"
+	"fmt"
+	"strconv"
+	"time"
+)
+
+// ServicesService handles communication with the services related methods of
+// the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/services.html
+type ServicesService struct {
+	client *Client
+}
+
+// Service represents a GitLab service.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/services.html
+type Service struct {
+	ID                       int        `json:"id"`
+	Title                    string     `json:"title"`
+	CreatedAt                *time.Time `json:"created_at"`
+	UpdatedAt                *time.Time `json:"updated_at"`
+	Active                   bool       `json:"active"`
+	PushEvents               bool       `json:"push_events"`
+	IssuesEvents             bool       `json:"issues_events"`
+	ConfidentialIssuesEvents bool       `json:"confidential_issues_events"`
+	MergeRequestsEvents      bool       `json:"merge_requests_events"`
+	TagPushEvents            bool       `json:"tag_push_events"`
+	NoteEvents               bool       `json:"note_events"`
+	ConfidentialNoteEvents   bool       `json:"confidential_note_events"`
+	PipelineEvents           bool       `json:"pipeline_events"`
+	JobEvents                bool       `json:"job_events"`
+	WikiPageEvents           bool       `json:"wiki_page_events"`
+}
+
+// SetGitLabCIServiceOptions represents the available SetGitLabCIService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-gitlab-ci-service
+type SetGitLabCIServiceOptions struct {
+	Token      *string `url:"token,omitempty" json:"token,omitempty"`
+	ProjectURL *string `url:"project_url,omitempty" json:"project_url,omitempty"`
+}
+
+// SetGitLabCIService sets GitLab CI service for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-gitlab-ci-service
+func (s *ServicesService) SetGitLabCIService(pid interface{}, opt *SetGitLabCIServiceOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/gitlab-ci", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteGitLabCIService deletes GitLab CI service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-gitlab-ci-service
+func (s *ServicesService) DeleteGitLabCIService(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/gitlab-ci", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// GithubService represents Github service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#github-premium
+type GithubService struct {
+	Service
+	Properties *GithubServiceProperties `json:"properties"`
+}
+
+// GithubServiceProperties represents Github specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#github-premium
+type GithubServiceProperties struct {
+	RepositoryURL string `json:"repository_url,omitempty"`
+	StaticContext string `json:"static_context,omitempty"`
+}
+
+// GetGithubService gets Github service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#get-github-service-settings
+func (s *ServicesService) GetGithubService(pid interface{}, options ...OptionFunc) (*GithubService, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/github", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	svc := new(GithubService)
+	resp, err := s.client.Do(req, svc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return svc, resp, err
+}
+
+// SetGithubServiceOptions represents the available SetGithubService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#createedit-github-service
+type SetGithubServiceOptions struct {
+	Token         *string `url:"token,omitempty" json:"token,omitempty"`
+	RepositoryURL *string `url:"repository_url,omitempty" json:"repository_url,omitempty"`
+	StaticContext *bool   `url:"static_context,omitempty" json:"static_context,omitempty"`
+}
+
+// SetGithubService sets Github service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#createedit-github-service
+func (s *ServicesService) SetGithubService(pid interface{}, opt *SetGithubServiceOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/github", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteGithubService deletes Github service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-github-service
+func (s *ServicesService) DeleteGithubService(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/github", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// SetHipChatServiceOptions represents the available SetHipChatService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-hipchat-service
+type SetHipChatServiceOptions struct {
+	Token *string `url:"token,omitempty" json:"token,omitempty" `
+	Room  *string `url:"room,omitempty" json:"room,omitempty"`
+}
+
+// SetHipChatService sets HipChat service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-hipchat-service
+func (s *ServicesService) SetHipChatService(pid interface{}, opt *SetHipChatServiceOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/hipchat", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteHipChatService deletes HipChat service for project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-hipchat-service
+func (s *ServicesService) DeleteHipChatService(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/hipchat", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DroneCIService represents Drone CI service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#drone-ci
+type DroneCIService struct {
+	Service
+	Properties *DroneCIServiceProperties `json:"properties"`
+}
+
+// DroneCIServiceProperties represents Drone CI specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#drone-ci
+type DroneCIServiceProperties struct {
+	Token                 string `json:"token"`
+	DroneURL              string `json:"drone_url"`
+	EnableSSLVerification bool   `json:"enable_ssl_verification"`
+}
+
+// GetDroneCIService gets Drone CI service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#get-drone-ci-service-settings
+func (s *ServicesService) GetDroneCIService(pid interface{}, options ...OptionFunc) (*DroneCIService, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/drone-ci", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	svc := new(DroneCIService)
+	resp, err := s.client.Do(req, svc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return svc, resp, err
+}
+
+// SetDroneCIServiceOptions represents the available SetDroneCIService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#createedit-drone-ci-service
+type SetDroneCIServiceOptions struct {
+	Token                 *string `url:"token" json:"token" `
+	DroneURL              *string `url:"drone_url" json:"drone_url"`
+	EnableSSLVerification *bool   `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
+}
+
+// SetDroneCIService sets Drone CI service for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#createedit-drone-ci-service
+func (s *ServicesService) SetDroneCIService(pid interface{}, opt *SetDroneCIServiceOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/drone-ci", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteDroneCIService deletes Drone CI service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-drone-ci-service
+func (s *ServicesService) DeleteDroneCIService(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/drone-ci", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// SlackService represents Slack service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#slack
+type SlackService struct {
+	Service
+	Properties *SlackServiceProperties `json:"properties"`
+}
+
+// SlackServiceProperties represents Slack specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#slack
+type SlackServiceProperties struct {
+	WebHook                   string    `json:"webhook,omitempty"`
+	Username                  string    `json:"username,omitempty"`
+	Channel                   string    `json:"channel,omitempty"`
+	NotifyOnlyBrokenPipelines BoolValue `json:"notify_only_broken_pipelines,omitempty"`
+	NotifyOnlyDefaultBranch   BoolValue `json:"notify_only_default_branch,omitempty"`
+	ConfidentialIssueChannel  string    `json:"confidential_issue_channel,omitempty"`
+	ConfidentialNoteChannel   string    `json:"confidential_note_channel,omitempty"`
+	DeploymentChannel         string    `json:"deployment_channel,omitempty"`
+	IssueChannel              string    `json:"issue_channel,omitempty"`
+	MergeRequestChannel       string    `json:"merge_request_channel,omitempty"`
+	NoteChannel               string    `json:"note_channel,omitempty"`
+	TagPushChannel            string    `json:"tag_push_channel,omitempty"`
+	PipelineChannel           string    `json:"pipeline_channel,omitempty"`
+	PushChannel               string    `json:"push_channel,omitempty"`
+	WikiPageChannel           string    `json:"wiki_page_channel,omitempty"`
+}
+
+// GetSlackService gets Slack service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#get-slack-service-settings
+func (s *ServicesService) GetSlackService(pid interface{}, options ...OptionFunc) (*SlackService, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/slack", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	svc := new(SlackService)
+	resp, err := s.client.Do(req, svc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return svc, resp, err
+}
+
+// SetSlackServiceOptions represents the available SetSlackService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-slack-service
+type SetSlackServiceOptions struct {
+	WebHook                   *string `url:"webhook,omitempty" json:"webhook,omitempty"`
+	Username                  *string `url:"username,omitempty" json:"username,omitempty"`
+	Channel                   *string `url:"channel,omitempty" json:"channel,omitempty"`
+	NotifyOnlyBrokenPipelines *bool   `url:"notify_only_broken_pipelines,omitempty" json:"notify_only_broken_pipelines,omitempty"`
+	NotifyOnlyDefaultBranch   *bool   `url:"notify_only_default_branch,omitempty" json:"notify_only_default_branch,omitempty"`
+	ConfidentialIssueChannel  *string `url:"confidential_issue_channel,omitempty" json:"confidential_issue_channel,omitempty"`
+	ConfidentialIssuesEvents  *bool   `url:"confidential_issues_events,omitempty" json:"confidential_issues_events,omitempty"`
+	// TODO: Currently, GitLab ignores this option (not implemented yet?), so
+	// there is no way to set it. Uncomment when this is fixed.
+	// See: https://gitlab.com/gitlab-org/gitlab-ce/issues/49730
+	//ConfidentialNoteChannel   *string `json:"confidential_note_channel,omitempty"`
+	ConfidentialNoteEvents *bool   `url:"confidential_note_events,omitempty" json:"confidential_note_events,omitempty"`
+	DeploymentChannel      *string `url:"deployment_channel,omitempty" json:"deployment_channel,omitempty"`
+	DeploymentEvents       *bool   `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
+	IssueChannel           *string `url:"issue_channel,omitempty" json:"issue_channel,omitempty"`
+	IssuesEvents           *bool   `url:"issues_events,omitempty" json:"issues_events,omitempty"`
+	MergeRequestChannel    *string `url:"merge_request_channel,omitempty" json:"merge_request_channel,omitempty"`
+	MergeRequestsEvents    *bool   `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"`
+	TagPushChannel         *string `url:"tag_push_channel,omitempty" json:"tag_push_channel,omitempty"`
+	TagPushEvents          *bool   `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"`
+	NoteChannel            *string `url:"note_channel,omitempty" json:"note_channel,omitempty"`
+	NoteEvents             *bool   `url:"note_events,omitempty" json:"note_events,omitempty"`
+	PipelineChannel        *string `url:"pipeline_channel,omitempty" json:"pipeline_channel,omitempty"`
+	PipelineEvents         *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
+	PushChannel            *string `url:"push_channel,omitempty" json:"push_channel,omitempty"`
+	PushEvents             *bool   `url:"push_events,omitempty" json:"push_events,omitempty"`
+	WikiPageChannel        *string `url:"wiki_page_channel,omitempty" json:"wiki_page_channel,omitempty"`
+	WikiPageEvents         *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
+}
+
+// SetSlackService sets Slack service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-slack-service
+func (s *ServicesService) SetSlackService(pid interface{}, opt *SetSlackServiceOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/slack", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteSlackService deletes Slack service for project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-slack-service
+func (s *ServicesService) DeleteSlackService(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/slack", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// JiraService represents Jira service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#jira
+type JiraService struct {
+	Service
+	Properties *JiraServiceProperties `json:"properties"`
+}
+
+// JiraServiceProperties represents Jira specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#jira
+type JiraServiceProperties struct {
+	URL                   string `json:"url,omitempty"`
+	APIURL                string `json:"api_url,omitempty"`
+	ProjectKey            string `json:"project_key,omitempty" `
+	Username              string `json:"username,omitempty" `
+	Password              string `json:"password,omitempty" `
+	JiraIssueTransitionID string `json:"jira_issue_transition_id,omitempty"`
+}
+
+// UnmarshalJSON decodes the Jira Service Properties.
+//
+// This allows support of JiraIssueTransitionID for both type string (>11.9) and float64 (<11.9)
+func (p *JiraServiceProperties) UnmarshalJSON(b []byte) error {
+	type Alias JiraServiceProperties
+	raw := struct {
+		*Alias
+		JiraIssueTransitionID interface{} `json:"jira_issue_transition_id"`
+	}{
+		Alias: (*Alias)(p),
+	}
+
+	if err := json.Unmarshal(b, &raw); err != nil {
+		return err
+	}
+
+	switch id := raw.JiraIssueTransitionID.(type) {
+	case nil:
+		// No action needed.
+	case string:
+		p.JiraIssueTransitionID = id
+	case float64:
+		p.JiraIssueTransitionID = strconv.Itoa(int(id))
+	default:
+		return fmt.Errorf("failed to unmarshal JiraTransitionID of type: %T", id)
+	}
+
+	return nil
+}
+
+// GetJiraService gets Jira service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#get-jira-service-settings
+func (s *ServicesService) GetJiraService(pid interface{}, options ...OptionFunc) (*JiraService, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/jira", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	svc := new(JiraService)
+	resp, err := s.client.Do(req, svc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return svc, resp, err
+}
+
+// SetJiraServiceOptions represents the available SetJiraService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-jira-service
+type SetJiraServiceOptions struct {
+	URL                   *string `url:"url,omitempty" json:"url,omitempty"`
+	APIURL                *string `url:"api_url,omitempty" json:"api_url,omitempty"`
+	ProjectKey            *string `url:"project_key,omitempty" json:"project_key,omitempty" `
+	Username              *string `url:"username,omitempty" json:"username,omitempty" `
+	Password              *string `url:"password,omitempty" json:"password,omitempty" `
+	JiraIssueTransitionID *string `url:"jira_issue_transition_id,omitempty" json:"jira_issue_transition_id,omitempty"`
+}
+
+// SetJiraService sets Jira service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#edit-jira-service
+func (s *ServicesService) SetJiraService(pid interface{}, opt *SetJiraServiceOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/jira", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteJiraService deletes Jira service for project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-jira-service
+func (s *ServicesService) DeleteJiraService(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/jira", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// JenkinsCIService represents Jenkins CI service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/services.html#jenkins-ci
+type JenkinsCIService struct {
+	Service
+	Properties *JenkinsCIServiceProperties `json:"properties"`
+}
+
+// JenkinsCIServiceProperties represents Jenkins CI specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/services.html#jenkins-ci
+type JenkinsCIServiceProperties struct {
+	URL         string `json:"jenkins_url,omitempty"`
+	ProjectName string `json:"project_name,omitempty"`
+	Username    string `json:"username,omitempty"`
+}
+
+// GetJenkinsCIService gets Jenkins CI service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/services.html#get-jenkins-ci-service-settings
+func (s *ServicesService) GetJenkinsCIService(pid interface{}, options ...OptionFunc) (*JenkinsCIService, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/jenkins", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	svc := new(JenkinsCIService)
+	resp, err := s.client.Do(req, svc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return svc, resp, err
+}
+
+// SetJenkinsCIServiceOptions represents the available SetJenkinsCIService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/services.html#jenkins-ci
+type SetJenkinsCIServiceOptions struct {
+	URL         *string `url:"jenkins_url,omitempty" json:"jenkins_url,omitempty"`
+	ProjectName *string `url:"project_name,omitempty" json:"project_name,omitempty"`
+	Username    *string `url:"username,omitempty" json:"username,omitempty"`
+	Password    *string `url:"password,omitempty" json:"password,omitempty"`
+}
+
+// SetJenkinsCIService sets Jenkins service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ee/api/services.html#create-edit-jenkins-ci-service
+func (s *ServicesService) SetJenkinsCIService(pid interface{}, opt *SetJenkinsCIServiceOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/jenkins", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteJenkinsCIService deletes Jenkins CI service for project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-jira-service
+func (s *ServicesService) DeleteJenkinsCIService(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/jenkins", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// MicrosoftTeamsService represents Microsoft Teams service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#microsoft-teams
+type MicrosoftTeamsService struct {
+	Service
+	Properties *MicrosoftTeamsServiceProperties `json:"properties"`
+}
+
+// MicrosoftTeamsServiceProperties represents Microsoft Teams specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#microsoft-teams
+type MicrosoftTeamsServiceProperties struct {
+	WebHook string `json:"webhook"`
+}
+
+// GetMicrosoftTeamsService gets MicrosoftTeams service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#get-microsoft-teams-service-settings
+func (s *ServicesService) GetMicrosoftTeamsService(pid interface{}, options ...OptionFunc) (*MicrosoftTeamsService, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/microsoft-teams", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	svc := new(MicrosoftTeamsService)
+	resp, err := s.client.Do(req, svc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return svc, resp, err
+}
+
+// SetMicrosoftTeamsServiceOptions represents the available SetMicrosoftTeamsService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#create-edit-microsoft-teams-service
+type SetMicrosoftTeamsServiceOptions struct {
+	WebHook *string `url:"webhook,omitempty" json:"webhook,omitempty"`
+}
+
+// SetMicrosoftTeamsService sets Microsoft Teams service for a project
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#create-edit-microsoft-teams-service
+func (s *ServicesService) SetMicrosoftTeamsService(pid interface{}, opt *SetMicrosoftTeamsServiceOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/microsoft-teams", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+	return s.client.Do(req, nil)
+}
+
+// DeleteMicrosoftTeamsService deletes Microsoft Teams service for project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-microsoft-teams-service
+func (s *ServicesService) DeleteMicrosoftTeamsService(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/microsoft-teams", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ExternalWikiService represents External Wiki service settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#external-wiki
+type ExternalWikiService struct {
+	Service
+	Properties *ExternalWikiServiceProperties `json:"properties"`
+}
+
+// ExternalWikiServiceProperties represents External Wiki specific properties.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#external-wiki
+type ExternalWikiServiceProperties struct {
+	ExternalWikiURL string `json:"external_wiki_url"`
+}
+
+// GetExternalWikiService gets External Wiki service settings for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#get-external-wiki-service-settings
+func (s *ServicesService) GetExternalWikiService(pid interface{}, options ...OptionFunc) (*ExternalWikiService, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/external-wiki", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	svc := new(ExternalWikiService)
+	resp, err := s.client.Do(req, svc)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return svc, resp, err
+}
+
+// SetExternalWikiServiceOptions represents the available SetExternalWikiService()
+// options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#createedit-external-wiki-service
+type SetExternalWikiServiceOptions struct {
+	ExternalWikiURL *string `url:"external_wiki_url,omitempty" json:"external_wiki_url,omitempty"`
+}
+
+// SetExternalWikiService sets External Wiki service for a project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#createedit-external-wiki-service
+func (s *ServicesService) SetExternalWikiService(pid interface{}, opt *SetExternalWikiServiceOptions, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/external-wiki", pathEscape(project))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteExternalWikiService deletes External Wiki service for project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/services.html#delete-external-wiki-service
+func (s *ServicesService) DeleteExternalWikiService(pid interface{}, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/services/external-wiki", pathEscape(project))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/settings.go b/vendor/github.com/xanzy/go-gitlab/settings.go
new file mode 100644
index 000000000..c25b28aab
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/settings.go
@@ -0,0 +1,409 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import "time"
+
+// SettingsService handles communication with the application SettingsService
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/settings.html
+type SettingsService struct {
+	client *Client
+}
+
+// Settings represents the GitLab application settings.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/settings.html
+type Settings struct {
+	ID                                        int               `json:"id"`
+	CreatedAt                                 *time.Time        `json:"created_at"`
+	UpdatedAt                                 *time.Time        `json:"updated_at"`
+	AdminNotificationEmail                    string            `json:"admin_notification_email"`
+	AfterSignOutPath                          string            `json:"after_sign_out_path"`
+	AfterSignUpText                           string            `json:"after_sign_up_text"`
+	AkismetAPIKey                             string            `json:"akismet_api_key"`
+	AkismetEnabled                            bool              `json:"akismet_enabled"`
+	AllowGroupOwnersToManageLDAP              bool              `json:"allow_group_owners_to_manage_ldap"`
+	AllowLocalRequestsFromHooksAndServices    bool              `json:"allow_local_requests_from_hooks_and_services"`
+	AllowLocalRequestsFromSystemHooks         bool              `json:"allow_local_requests_from_system_hooks"`
+	AllowLocalRequestsFromWebHooksAndServices bool              `json:"allow_local_requests_from_web_hooks_and_services"`
+	ArchiveBuildsInHumanReadable              string            `json:"archive_builds_in_human_readable"`
+	AssetProxyEnabled                         bool              `json:"asset_proxy_enabled"`
+	AssetProxySecretKey                       string            `json:"asset_proxy_secret_key"`
+	AssetProxyURL                             string            `json:"asset_proxy_url"`
+	AssetProxyWhitelist                       []string          `json:"asset_proxy_whitelist"`
+	AuthorizedKeysEnabled                     bool              `json:"authorized_keys_enabled_enabled"`
+	AutoDevOpsDomain                          string            `json:"auto_devops_domain"`
+	AutoDevOpsEnabled                         bool              `json:"auto_devops_enabled"`
+	CheckNamespacePlan                        bool              `json:"check_namespace_plan"`
+	CommitEmailHostname                       string            `json:"commit_email_hostname"`
+	ContainerRegistryTokenExpireDelay         int               `json:"container_registry_token_expire_delay"`
+	DefaultArtifactsExpireIn                  string            `json:"default_artifacts_expire_in"`
+	DefaultBranchProtection                   int               `json:"default_branch_protection"`
+	DefaultGroupVisibility                    *VisibilityValue  `json:"default_group_visibility"`
+	DefaultProjectCreation                    int               `json:"default_project_creation"`
+	DefaultProjectsLimit                      int               `json:"default_projects_limit"`
+	DefaultProjectVisibility                  *VisibilityValue  `json:"default_project_visibility"`
+	DefaultSnippetVisibility                  *VisibilityValue  `json:"default_snippet_visibility"`
+	DiffMaxPatchBytes                         int               `json:"diff_max_patch_bytes"`
+	DisabledOauthSignInSources                []string          `json:"disabled_oauth_sign_in_sources"`
+	DNSRebindingProtectionEnabled             bool              `json:"dns_rebinding_protection_enabled"`
+	DomainBlacklist                           []string          `json:"domain_blacklist"`
+	DomainBlacklistEnabled                    bool              `json:"domain_blacklist_enabled"`
+	DomainWhitelist                           []string          `json:"domain_whitelist"`
+	DSAKeyRestriction                         int               `json:"dsa_key_restriction"`
+	ECDSAKeyRestriction                       int               `json:"ecdsa_key_restriction"`
+	Ed25519KeyRestriction                     int               `json:"ed25519_key_restriction"`
+	ElasticsearchAWSAccessKey                 string            `json:"elasticsearch_aws_access_key"`
+	ElasticsearchAWS                          bool              `json:"elasticsearch_aws"`
+	ElasticsearchAWSRegion                    string            `json:"elasticsearch_aws_region"`
+	ElasticsearchAWSSecretAccessKey           string            `json:"elasticsearch_aws_secret_access_key"`
+	ElasticsearchIndexing                     bool              `json:"elasticsearch_indexing"`
+	ElasticsearchLimitIndexing                bool              `json:"elasticsearch_limit_indexing"`
+	ElasticsearchNamespaceIDs                 []int             `json:"elasticsearch_namespace_ids"`
+	ElasticsearchProjectIDs                   []int             `json:"elasticsearch_project_ids"`
+	ElasticsearchSearch                       bool              `json:"elasticsearch_search"`
+	ElasticsearchURL                          []string          `json:"elasticsearch_url"`
+	EmailAdditionalText                       string            `json:"email_additional_text"`
+	EmailAuthorInBody                         bool              `json:"email_author_in_body"`
+	EnabledGitAccessProtocol                  string            `json:"enabled_git_access_protocol"`
+	EnforceTerms                              bool              `json:"enforce_terms"`
+	ExternalAuthClientCert                    string            `json:"external_auth_client_cert"`
+	ExternalAuthClientKeyPass                 string            `json:"external_auth_client_key_pass"`
+	ExternalAuthClientKey                     string            `json:"external_auth_client_key"`
+	ExternalAuthorizationServiceDefaultLabel  string            `json:"external_authorization_service_default_label"`
+	ExternalAuthorizationServiceEnabled       bool              `json:"external_authorization_service_enabled"`
+	ExternalAuthorizationServiceTimeout       float64           `json:"external_authorization_service_timeout"`
+	ExternalAuthorizationServiceURL           string            `json:"external_authorization_service_url"`
+	FileTemplateProjectID                     int               `json:"file_template_project_id"`
+	FirstDayOfWeek                            int               `json:"first_day_of_week"`
+	GeoNodeAllowedIPs                         string            `json:"geo_node_allowed_ips"`
+	GeoStatusTimeout                          int               `json:"geo_status_timeout"`
+	GitalyTimeoutDefault                      int               `json:"gitaly_timeout_default"`
+	GitalyTimeoutFast                         int               `json:"gitaly_timeout_fast"`
+	GitalyTimeoutMedium                       int               `json:"gitaly_timeout_medium"`
+	GrafanaEnabled                            bool              `json:"grafana_enabled"`
+	GrafanaURL                                string            `json:"grafana_url"`
+	GravatarEnabled                           bool              `json:"gravatar_enabled"`
+	HashedStorageEnabled                      bool              `json:"hashed_storage_enabled"`
+	HelpPageHideCommercialContent             bool              `json:"help_page_hide_commercial_content"`
+	HelpPageSupportURL                        string            `json:"help_page_support_url"`
+	HelpPageText                              string            `json:"help_page_text"`
+	HelpText                                  string            `json:"help_text"`
+	HideThirdPartyOffers                      bool              `json:"hide_third_party_offers"`
+	HomePageURL                               string            `json:"home_page_url"`
+	HousekeepingBitmapsEnabled                bool              `json:"housekeeping_bitmaps_enabled"`
+	HousekeepingEnabled                       bool              `json:"housekeeping_enabled"`
+	HousekeepingFullRepackPeriod              int               `json:"housekeeping_full_repack_period"`
+	HousekeepingGcPeriod                      int               `json:"housekeeping_gc_period"`
+	HousekeepingIncrementalRepackPeriod       int               `json:"housekeeping_incremental_repack_period"`
+	HTMLEmailsEnabled                         bool              `json:"html_emails_enabled"`
+	ImportSources                             []string          `json:"import_sources"`
+	InstanceStatisticsVisibilityPrivate       bool              `json:"instance_statistics_visibility_private"`
+	LocalMarkdownVersion                      int               `json:"local_markdown_version"`
+	MaxArtifactsSize                          int               `json:"max_artifacts_size"`
+	MaxAttachmentSize                         int               `json:"max_attachment_size"`
+	MaxPagesSize                              int               `json:"max_pages_size"`
+	MetricsEnabled                            bool              `json:"metrics_enabled"`
+	MetricsHost                               string            `json:"metrics_host"`
+	MetricsMethodCallThreshold                int               `json:"metrics_method_call_threshold"`
+	MetricsPacketSize                         int               `json:"metrics_packet_size"`
+	MetricsPoolSize                           int               `json:"metrics_pool_size"`
+	MetricsPort                               int               `json:"metrics_port"`
+	MetricsSampleInterval                     int               `json:"metrics_sample_interval"`
+	MetricsTimeout                            int               `json:"metrics_timeout"`
+	MirrorAvailable                           bool              `json:"mirror_available"`
+	MirrorCapacityThreshold                   int               `json:"mirror_capacity_threshold"`
+	MirrorMaxCapacity                         int               `json:"mirror_max_capacity"`
+	MirrorMaxDelay                            int               `json:"mirror_max_delay"`
+	OutboundLocalRequestsWhitelist            []string          `json:"outbound_local_requests_whitelist"`
+	PagesDomainVerificationEnabled            bool              `json:"pages_domain_verification_enabled"`
+	PasswordAuthenticationEnabledForGit       bool              `json:"password_authentication_enabled_for_git"`
+	PasswordAuthenticationEnabledForWeb       bool              `json:"password_authentication_enabled_for_web"`
+	PerformanceBarAllowedGroupID              string            `json:"performance_bar_allowed_group_id"`
+	PerformanceBarAllowedGroupPath            string            `json:"performance_bar_allowed_group_path"`
+	PerformanceBarEnabled                     bool              `json:"performance_bar_enabled"`
+	PlantumlEnabled                           bool              `json:"plantuml_enabled"`
+	PlantumlURL                               string            `json:"plantuml_url"`
+	PollingIntervalMultiplier                 float64           `json:"polling_interval_multiplier,string"`
+	ProjectExportEnabled                      bool              `json:"project_export_enabled"`
+	PrometheusMetricsEnabled                  bool              `json:"prometheus_metrics_enabled"`
+	ProtectedCIVariables                      bool              `json:"protected_ci_variables"`
+	PseudonymizerEnabled                      bool              `json:"psedonymizer_enabled"`
+	PushEventHooksLimit                       int               `json:"push_event_hooks_limit"`
+	PushEventActivitiesLimit                  int               `json:"push_event_activities_limit"`
+	RecaptchaEnabled                          bool              `json:"recaptcha_enabled"`
+	RecaptchaPrivateKey                       string            `json:"recaptcha_private_key"`
+	RecaptchaSiteKey                          string            `json:"recaptcha_site_key"`
+	ReceiveMaxInputSize                       int               `json:"receive_max_input_size"`
+	RepositoryChecksEnabled                   bool              `json:"repository_checks_enabled"`
+	RepositorySizeLimit                       int               `json:"repository_size_limit"`
+	RepositoryStorages                        []string          `json:"repository_storages"`
+	RequireTwoFactorAuthentication            bool              `json:"require_two_factor_authentication"`
+	RestrictedVisibilityLevels                []VisibilityValue `json:"restricted_visibility_levels"`
+	RsaKeyRestriction                         int               `json:"rsa_key_restriction"`
+	SendUserConfirmationEmail                 bool              `json:"send_user_confirmation_email"`
+	SessionExpireDelay                        int               `json:"session_expire_delay"`
+	SharedRunnersEnabled                      bool              `json:"shared_runners_enabled"`
+	SharedRunnersMinutes                      int               `json:"shared_runners_minutes"`
+	SharedRunnersText                         string            `json:"shared_runners_text"`
+	SignInText                                string            `json:"sign_in_text"`
+	SignupEnabled                             bool              `json:"signup_enabled"`
+	SlackAppEnabled                           bool              `json:"slack_app_enabled"`
+	SlackAppID                                string            `json:"slack_app_id"`
+	SlackAppSecret                            string            `json:"slack_app_secret"`
+	SlackAppVerificationToken                 string            `json:"slack_app_verification_token"`
+	SnowplowCollectorHostname                 string            `json:"snowplow_collector_hostname"`
+	SnowplowCookieDomain                      string            `json:"snowplow_cookie_domain"`
+	SnowplowEnabled                           bool              `json:"snowplow_enabled"`
+	SnowplowSiteID                            string            `json:"snowplow_site_id"`
+	TerminalMaxSessionTime                    int               `json:"terminal_max_session_time"`
+	Terms                                     string            `json:"terms"`
+	ThrottleAuthenticatedAPIEnabled           bool              `json:"throttle_authenticated_api_enabled"`
+	ThrottleAuthenticatedAPIPeriodInSeconds   int               `json:"throttle_authenticated_api_period_in_seconds"`
+	ThrottleAuthenticatedAPIRequestsPerPeriod int               `json:"throttle_authenticated_api_requests_per_period"`
+	ThrottleAuthenticatedWebEnabled           bool              `json:"throttle_authenticated_web_enabled"`
+	ThrottleAuthenticatedWebPeriodInSeconds   int               `json:"throttle_authenticated_web_period_in_seconds"`
+	ThrottleAuthenticatedWebRequestsPerPeriod int               `json:"throttle_authenticated_web_requests_per_period"`
+	ThrottleUnauthenticatedEnabled            bool              `json:"throttle_unauthenticated_enabled"`
+	ThrottleUnauthenticatedPeriodInSeconds    int               `json:"throttle_unauthenticated_period_in_seconds"`
+	ThrottleUnauthenticatedRequestsPerPeriod  int               `json:"throttle_unauthenticated_requests_per_period"`
+	TimeTrackingLimitToHours                  bool              `json:"time_tracking_limit_to_hours"`
+	TwoFactorGracePeriod                      int               `json:"two_factor_grace_period"`
+	UniqueIPsLimitEnabled                     bool              `json:"unique_ips_limit_enabled"`
+	UniqueIPsLimitPerUser                     int               `json:"unique_ips_limit_per_user"`
+	UniqueIPsLimitTimeWindow                  int               `json:"unique_ips_limit_time_window"`
+	UsagePingEnabled                          bool              `json:"usage_ping_enabled"`
+	UserDefaultExternal                       bool              `json:"user_default_external"`
+	UserDefaultInternalRegex                  string            `json:"user_default_internal_regex"`
+	UserOauthApplications                     bool              `json:"user_oauth_applications"`
+	UserShowAddSSHKeyMessage                  bool              `json:"user_show_add_ssh_key_message"`
+	VersionCheckEnabled                       bool              `json:"version_check_enabled"`
+	WebIDEClientsidePreviewEnabled            bool              `json:"web_ide_clientside_preview_enabled"`
+}
+
+func (s Settings) String() string {
+	return Stringify(s)
+}
+
+// GetSettings gets the current application settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/settings.html#get-current-application.settings
+func (s *SettingsService) GetSettings(options ...OptionFunc) (*Settings, *Response, error) {
+	req, err := s.client.NewRequest("GET", "application/settings", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	as := new(Settings)
+	resp, err := s.client.Do(req, as)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return as, resp, err
+}
+
+// UpdateSettingsOptions represents the available UpdateSettings() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/settings.html#change-application.settings
+type UpdateSettingsOptions struct {
+	AdminNotificationEmail                    *string           `url:"admin_notification_email,omitempty" json:"admin_notification_email,omitempty"`
+	AfterSignOutPath                          *string           `url:"after_sign_out_path,omitempty" json:"after_sign_out_path,omitempty"`
+	AfterSignUpText                           *string           `url:"after_sign_up_text,omitempty" json:"after_sign_up_text,omitempty"`
+	AkismetAPIKey                             *string           `url:"akismet_api_key,omitempty" json:"akismet_api_key,omitempty"`
+	AkismetEnabled                            *bool             `url:"akismet_enabled,omitempty" json:"akismet_enabled,omitempty"`
+	AllowGroupOwnersToManageLDAP              *bool             `url:"allow_group_owners_to_manage_ldap,omitempty" json:"allow_group_owners_to_manage_ldap,omitempty"`
+	AllowLocalRequestsFromHooksAndServices    *bool             `url:"allow_local_requests_from_hooks_and_services,omitempty" json:"allow_local_requests_from_hooks_and_services,omitempty"`
+	AllowLocalRequestsFromSystemHooks         *bool             `url:"allow_local_requests_from_system_hooks,omitempty" json:"allow_local_requests_from_system_hooks,omitempty"`
+	AllowLocalRequestsFromWebHooksAndServices *bool             `url:"allow_local_requests_from_web_hooks_and_services,omitempty" json:"allow_local_requests_from_web_hooks_and_services,omitempty"`
+	ArchiveBuildsInHumanReadable              *string           `url:"archive_builds_in_human_readable,omitempty" json:"archive_builds_in_human_readable,omitempty"`
+	AssetProxyEnabled                         *bool             `url:"asset_proxy_enabled,omitempty" json:"asset_proxy_enabled,omitempty"`
+	AssetProxySecretKey                       *string           `url:"asset_proxy_secret_key,omitempty" json:"asset_proxy_secret_key,omitempty"`
+	AssetProxyURL                             *string           `url:"asset_proxy_url,omitempty" json:"asset_proxy_url,omitempty"`
+	AssetProxyWhitelist                       []string          `url:"asset_proxy_whitelist,omitempty" json:"asset_proxy_whitelist,omitempty"`
+	AuthorizedKeysEnabled                     *bool             `url:"authorized_keys_enabled,omitempty" json:"authorized_keys_enabled,omitempty"`
+	AutoDevOpsDomain                          *string           `url:"auto_devops_domain,omitempty" json:"auto_devops_domain,omitempty"`
+	AutoDevOpsEnabled                         *bool             `url:"auto_devops_enabled,omitempty" json:"auto_devops_enabled,omitempty"`
+	CheckNamespacePlan                        *bool             `url:"check_namespace_plan,omitempty" json:"check_namespace_plan,omitempty"`
+	CommitEmailHostname                       *string           `url:"commit_email_hostname,omitempty" json:"commit_email_hostname,omitempty"`
+	ContainerRegistryTokenExpireDelay         *int              `url:"container_registry_token_expire_delay,omitempty" json:"container_registry_token_expire_delay,omitempty"`
+	DefaultArtifactsExpireIn                  *string           `url:"default_artifacts_expire_in,omitempty" json:"default_artifacts_expire_in,omitempty"`
+	DefaultBranchProtection                   *int              `url:"default_branch_protection,omitempty" json:"default_branch_protection,omitempty"`
+	DefaultGroupVisibility                    *VisibilityValue  `url:"default_group_visibility,omitempty" json:"default_group_visibility,omitempty"`
+	DefaultProjectCreation                    *int              `url:"default_project_creation,omitempty" json:"default_project_creation,omitempty"`
+	DefaultProjectsLimit                      *int              `url:"default_projects_limit,omitempty" json:"default_projects_limit,omitempty"`
+	DefaultProjectVisibility                  *VisibilityValue  `url:"default_project_visibility,omitempty" json:"default_project_visibility,omitempty"`
+	DefaultSnippetVisibility                  *VisibilityValue  `url:"default_snippet_visibility,omitempty" json:"default_snippet_visibility,omitempty"`
+	DiffMaxPatchBytes                         *int              `url:"diff_max_patch_bytes,omitempty" json:"diff_max_patch_bytes,omitempty"`
+	DisabledOauthSignInSources                []string          `url:"disabled_oauth_sign_in_sources,omitempty" json:"disabled_oauth_sign_in_sources,omitempty"`
+	DNSRebindingProtectionEnabled             *bool             `url:"dns_rebinding_protection_enabled,omitempty" json:"dns_rebinding_protection_enabled,omitempty"`
+	DomainBlacklist                           []string          `url:"domain_blacklist,omitempty" json:"domain_blacklist,omitempty"`
+	DomainBlacklistEnabled                    *bool             `url:"domain_blacklist_enabled,omitempty" json:"domain_blacklist_enabled,omitempty"`
+	DomainWhitelist                           []string          `url:"domain_whitelist,omitempty" json:"domain_whitelist,omitempty"`
+	DSAKeyRestriction                         *int              `url:"dsa_key_restriction,omitempty" json:"dsa_key_restriction,omitempty"`
+	ECDSAKeyRestriction                       *int              `url:"ecdsa_key_restriction,omitempty" json:"ecdsa_key_restriction,omitempty"`
+	Ed25519KeyRestriction                     *int              `url:"ed25519_key_restriction,omitempty" json:"ed25519_key_restriction,omitempty"`
+	ElasticsearchAWSAccessKey                 *string           `url:"elasticsearch_aws_access_key,omitempty" json:"elasticsearch_aws_access_key,omitempty"`
+	ElasticsearchAWS                          *bool             `url:"elasticsearch_aws,omitempty" json:"elasticsearch_aws,omitempty"`
+	ElasticsearchAWSRegion                    *string           `url:"elasticsearch_aws_region,omitempty" json:"elasticsearch_aws_region,omitempty"`
+	ElasticsearchAWSSecretAccessKey           *string           `url:"elasticsearch_aws_secret_access_key,omitempty" json:"elasticsearch_aws_secret_access_key,omitempty"`
+	ElasticsearchIndexing                     *bool             `url:"elasticsearch_indexing,omitempty" json:"elasticsearch_indexing,omitempty"`
+	ElasticsearchLimitIndexing                *bool             `url:"elasticsearch_limit_indexing,omitempty" json:"elasticsearch_limit_indexing,omitempty"`
+	ElasticsearchNamespaceIDs                 []int             `url:"elasticsearch_namespace_ids,omitempty" json:"elasticsearch_namespace_ids,omitempty"`
+	ElasticsearchProjectIDs                   []int             `url:"elasticsearch_project_ids,omitempty" json:"elasticsearch_project_ids,omitempty"`
+	ElasticsearchSearch                       *bool             `url:"elasticsearch_search,omitempty" json:"elasticsearch_search,omitempty"`
+	ElasticsearchURL                          *string           `url:"elasticsearch_url,omitempty" json:"elasticsearch_url,omitempty"`
+	EmailAdditionalText                       *string           `url:"email_additional_text,omitempty" json:"email_additional_text,omitempty"`
+	EmailAuthorInBody                         *bool             `url:"email_author_in_body,omitempty" json:"email_author_in_body,omitempty"`
+	EnabledGitAccessProtocol                  *string           `url:"enabled_git_access_protocol,omitempty" json:"enabled_git_access_protocol,omitempty"`
+	EnforceTerms                              *bool             `url:"enforce_terms,omitempty" json:"enforce_terms,omitempty"`
+	ExternalAuthClientCert                    *string           `url:"external_auth_client_cert,omitempty" json:"external_auth_client_cert,omitempty"`
+	ExternalAuthClientKeyPass                 *string           `url:"external_auth_client_key_pass,omitempty" json:"external_auth_client_key_pass,omitempty"`
+	ExternalAuthClientKey                     *string           `url:"external_auth_client_key,omitempty" json:"external_auth_client_key,omitempty"`
+	ExternalAuthorizationServiceDefaultLabel  *string           `url:"external_authorization_service_default_label,omitempty" json:"external_authorization_service_default_label,omitempty"`
+	ExternalAuthorizationServiceEnabled       *bool             `url:"external_authorization_service_enabled,omitempty" json:"external_authorization_service_enabled,omitempty"`
+	ExternalAuthorizationServiceTimeout       *float64          `url:"external_authorization_service_timeout,omitempty" json:"external_authorization_service_timeout,omitempty"`
+	ExternalAuthorizationServiceURL           *string           `url:"external_authorization_service_url,omitempty" json:"external_authorization_service_url,omitempty"`
+	FileTemplateProjectID                     *int              `url:"file_template_project_id,omitempty" json:"file_template_project_id,omitempty"`
+	FirstDayOfWeek                            *int              `url:"first_day_of_week,omitempty" json:"first_day_of_week,omitempty"`
+	GeoNodeAllowedIPs                         *string           `url:"geo_node_allowed_ips,omitempty" json:"geo_node_allowed_ips,omitempty"`
+	GeoStatusTimeout                          *int              `url:"geo_status_timeout,omitempty" json:"geo_status_timeout,omitempty"`
+	GitalyTimeoutDefault                      *int              `url:"gitaly_timeout_default,omitempty" json:"gitaly_timeout_default,omitempty"`
+	GitalyTimeoutFast                         *int              `url:"gitaly_timeout_fast,omitempty" json:"gitaly_timeout_fast,omitempty"`
+	GitalyTimeoutMedium                       *int              `url:"gitaly_timeout_medium,omitempty" json:"gitaly_timeout_medium,omitempty"`
+	GrafanaEnabled                            *bool             `url:"grafana_enabled,omitempty" json:"grafana_enabled,omitempty"`
+	GrafanaURL                                *string           `url:"grafana_url,omitempty" json:"grafana_url,omitempty"`
+	GravatarEnabled                           *bool             `url:"gravatar_enabled,omitempty" json:"gravatar_enabled,omitempty"`
+	HashedStorageEnabled                      *bool             `url:"hashed_storage_enabled,omitempty" json:"hashed_storage_enabled,omitempty"`
+	HelpPageHideCommercialContent             *bool             `url:"help_page_hide_commercial_content,omitempty" json:"help_page_hide_commercial_content,omitempty"`
+	HelpPageSupportURL                        *string           `url:"help_page_support_url,omitempty" json:"help_page_support_url,omitempty"`
+	HelpPageText                              *string           `url:"help_page_text,omitempty" json:"help_page_text,omitempty"`
+	HelpText                                  *string           `url:"help_text,omitempty" json:"help_text,omitempty"`
+	HideThirdPartyOffers                      *bool             `url:"hide_third_party_offers,omitempty" json:"hide_third_party_offers,omitempty"`
+	HomePageURL                               *string           `url:"home_page_url,omitempty" json:"home_page_url,omitempty"`
+	HousekeepingBitmapsEnabled                *bool             `url:"housekeeping_bitmaps_enabled,omitempty" json:"housekeeping_bitmaps_enabled,omitempty"`
+	HousekeepingEnabled                       *bool             `url:"housekeeping_enabled,omitempty" json:"housekeeping_enabled,omitempty"`
+	HousekeepingFullRepackPeriod              *int              `url:"housekeeping_full_repack_period,omitempty" json:"housekeeping_full_repack_period,omitempty"`
+	HousekeepingGcPeriod                      *int              `url:"housekeeping_gc_period,omitempty" json:"housekeeping_gc_period,omitempty"`
+	HousekeepingIncrementalRepackPeriod       *int              `url:"housekeeping_incremental_repack_period,omitempty" json:"housekeeping_incremental_repack_period,omitempty"`
+	HTMLEmailsEnabled                         *bool             `url:"html_emails_enabled,omitempty" json:"html_emails_enabled,omitempty"`
+	ImportSources                             []string          `url:"import_sources,omitempty" json:"import_sources,omitempty"`
+	InstanceStatisticsVisibilityPrivate       *bool             `url:"instance_statistics_visibility_private,omitempty" json:"instance_statistics_visibility_private,omitempty"`
+	LocalMarkdownVersion                      *int              `url:"local_markdown_version,omitempty" json:"local_markdown_version,omitempty"`
+	MaxArtifactsSize                          *int              `url:"max_artifacts_size,omitempty" json:"max_artifacts_size,omitempty"`
+	MaxAttachmentSize                         *int              `url:"max_attachment_size,omitempty" json:"max_attachment_size,omitempty"`
+	MaxPagesSize                              *int              `url:"max_pages_size,omitempty" json:"max_pages_size,omitempty"`
+	MetricsEnabled                            *bool             `url:"metrics_enabled,omitempty" json:"metrics_enabled,omitempty"`
+	MetricsHost                               *string           `url:"metrics_host,omitempty" json:"metrics_host,omitempty"`
+	MetricsMethodCallThreshold                *int              `url:"metrics_method_call_threshold,omitempty" json:"metrics_method_call_threshold,omitempty"`
+	MetricsPacketSize                         *int              `url:"metrics_packet_size,omitempty" json:"metrics_packet_size,omitempty"`
+	MetricsPoolSize                           *int              `url:"metrics_pool_size,omitempty" json:"metrics_pool_size,omitempty"`
+	MetricsPort                               *int              `url:"metrics_port,omitempty" json:"metrics_port,omitempty"`
+	MetricsSampleInterval                     *int              `url:"metrics_sample_interval,omitempty" json:"metrics_sample_interval,omitempty"`
+	MetricsTimeout                            *int              `url:"metrics_timeout,omitempty" json:"metrics_timeout,omitempty"`
+	MirrorAvailable                           *bool             `url:"mirror_available,omitempty" json:"mirror_available,omitempty"`
+	MirrorCapacityThreshold                   *int              `url:"mirror_capacity_threshold,omitempty" json:"mirror_capacity_threshold,omitempty"`
+	MirrorMaxCapacity                         *int              `url:"mirror_max_capacity,omitempty" json:"mirror_max_capacity,omitempty"`
+	MirrorMaxDelay                            *int              `url:"mirror_max_delay,omitempty" json:"mirror_max_delay,omitempty"`
+	OutboundLocalRequestsWhitelist            []string          `url:"outbound_local_requests_whitelist,omitempty" json:"outbound_local_requests_whitelist,omitempty"`
+	PagesDomainVerificationEnabled            *bool             `url:"pages_domain_verification_enabled,omitempty" json:"pages_domain_verification_enabled,omitempty"`
+	PasswordAuthenticationEnabledForGit       *bool             `url:"password_authentication_enabled_for_git,omitempty" json:"password_authentication_enabled_for_git,omitempty"`
+	PasswordAuthenticationEnabledForWeb       *bool             `url:"password_authentication_enabled_for_web,omitempty" json:"password_authentication_enabled_for_web,omitempty"`
+	PerformanceBarAllowedGroupID              *string           `url:"performance_bar_allowed_group_id,omitempty" json:"performance_bar_allowed_group_id,omitempty"`
+	PerformanceBarAllowedGroupPath            *string           `url:"performance_bar_allowed_group_path,omitempty" json:"performance_bar_allowed_group_path,omitempty"`
+	PerformanceBarEnabled                     *bool             `url:"performance_bar_enabled,omitempty" json:"performance_bar_enabled,omitempty"`
+	PlantumlEnabled                           *bool             `url:"plantuml_enabled,omitempty" json:"plantuml_enabled,omitempty"`
+	PlantumlURL                               *string           `url:"plantuml_url,omitempty" json:"plantuml_url,omitempty"`
+	PollingIntervalMultiplier                 *float64          `url:"polling_interval_multiplier,omitempty" json:"polling_interval_multiplier,omitempty"`
+	ProjectExportEnabled                      *bool             `url:"project_export_enabled,omitempty" json:"project_export_enabled,omitempty"`
+	PrometheusMetricsEnabled                  *bool             `url:"prometheus_metrics_enabled,omitempty" json:"prometheus_metrics_enabled,omitempty"`
+	ProtectedCIVariables                      *bool             `url:"protected_ci_variables,omitempty" json:"protected_ci_variables,omitempty"`
+	PseudonymizerEnabled                      *bool             `url:"psedonymizer_enabled,omitempty" json:"psedonymizer_enabled,omitempty"`
+	PushEventHooksLimit                       *int              `url:"push_event_hooks_limit,omitempty" json:"push_event_hooks_limit,omitempty"`
+	PushEventActivitiesLimit                  *int              `url:"push_event_activities_limit,omitempty" json:"push_event_activities_limit,omitempty"`
+	RecaptchaEnabled                          *bool             `url:"recaptcha_enabled,omitempty" json:"recaptcha_enabled,omitempty"`
+	RecaptchaPrivateKey                       *string           `url:"recaptcha_private_key,omitempty" json:"recaptcha_private_key,omitempty"`
+	RecaptchaSiteKey                          *string           `url:"recaptcha_site_key,omitempty" json:"recaptcha_site_key,omitempty"`
+	ReceiveMaxInputSize                       *int              `url:"receive_max_input_size,omitempty" json:"receive_max_input_size,omitempty"`
+	RepositoryChecksEnabled                   *bool             `url:"repository_checks_enabled,omitempty" json:"repository_checks_enabled,omitempty"`
+	RepositorySizeLimit                       *int              `url:"repository_size_limit,omitempty" json:"repository_size_limit,omitempty"`
+	RepositoryStorages                        []string          `url:"repository_storages,omitempty" json:"repository_storages,omitempty"`
+	RequireTwoFactorAuthentication            *bool             `url:"require_two_factor_authentication,omitempty" json:"require_two_factor_authentication,omitempty"`
+	RestrictedVisibilityLevels                []VisibilityValue `url:"restricted_visibility_levels,omitempty" json:"restricted_visibility_levels,omitempty"`
+	RsaKeyRestriction                         *int              `url:"rsa_key_restriction,omitempty" json:"rsa_key_restriction,omitempty"`
+	SendUserConfirmationEmail                 *bool             `url:"send_user_confirmation_email,omitempty" json:"send_user_confirmation_email,omitempty"`
+	SessionExpireDelay                        *int              `url:"session_expire_delay,omitempty" json:"session_expire_delay,omitempty"`
+	SharedRunnersEnabled                      *bool             `url:"shared_runners_enabled,omitempty" json:"shared_runners_enabled,omitempty"`
+	SharedRunnersMinutes                      *int              `url:"shared_runners_minutes,omitempty" json:"shared_runners_minutes,omitempty"`
+	SharedRunnersText                         *string           `url:"shared_runners_text,omitempty" json:"shared_runners_text,omitempty"`
+	SignInText                                *string           `url:"sign_in_text,omitempty" json:"sign_in_text,omitempty"`
+	SignupEnabled                             *bool             `url:"signup_enabled,omitempty" json:"signup_enabled,omitempty"`
+	SlackAppEnabled                           *bool             `url:"slack_app_enabled,omitempty" json:"slack_app_enabled,omitempty"`
+	SlackAppID                                *string           `url:"slack_app_id,omitempty" json:"slack_app_id,omitempty"`
+	SlackAppSecret                            *string           `url:"slack_app_secret,omitempty" json:"slack_app_secret,omitempty"`
+	SlackAppVerificationToken                 *string           `url:"slack_app_verification_token,omitempty" json:"slack_app_verification_token,omitempty"`
+	SnowplowCollectorHostname                 *string           `url:"snowplow_collector_hostname,omitempty" json:"snowplow_collector_hostname,omitempty"`
+	SnowplowCookieDomain                      *string           `url:"snowplow_cookie_domain,omitempty" json:"snowplow_cookie_domain,omitempty"`
+	SnowplowEnabled                           *bool             `url:"snowplow_enabled,omitempty" json:"snowplow_enabled,omitempty"`
+	SnowplowSiteID                            *string           `url:"snowplow_site_id,omitempty" json:"snowplow_site_id,omitempty"`
+	TerminalMaxSessionTime                    *int              `url:"terminal_max_session_time,omitempty" json:"terminal_max_session_time,omitempty"`
+	Terms                                     *string           `url:"terms,omitempty" json:"terms,omitempty"`
+	ThrottleAuthenticatedAPIEnabled           *bool             `url:"throttle_authenticated_api_enabled,omitempty" json:"throttle_authenticated_api_enabled,omitempty"`
+	ThrottleAuthenticatedAPIPeriodInSeconds   *int              `url:"throttle_authenticated_api_period_in_seconds,omitempty" json:"throttle_authenticated_api_period_in_seconds,omitempty"`
+	ThrottleAuthenticatedAPIRequestsPerPeriod *int              `url:"throttle_authenticated_api_requests_per_period,omitempty" json:"throttle_authenticated_api_requests_per_period,omitempty"`
+	ThrottleAuthenticatedWebEnabled           *bool             `url:"throttle_authenticated_web_enabled,omitempty" json:"throttle_authenticated_web_enabled,omitempty"`
+	ThrottleAuthenticatedWebPeriodInSeconds   *int              `url:"throttle_authenticated_web_period_in_seconds,omitempty" json:"throttle_authenticated_web_period_in_seconds,omitempty"`
+	ThrottleAuthenticatedWebRequestsPerPeriod *int              `url:"throttle_authenticated_web_requests_per_period,omitempty" json:"throttle_authenticated_web_requests_per_period,omitempty"`
+	ThrottleUnauthenticatedEnabled            *bool             `url:"throttle_unauthenticated_enabled,omitempty" json:"throttle_unauthenticated_enabled,omitempty"`
+	ThrottleUnauthenticatedPeriodInSeconds    *int              `url:"throttle_unauthenticated_period_in_seconds,omitempty" json:"throttle_unauthenticated_period_in_seconds,omitempty"`
+	ThrottleUnauthenticatedRequestsPerPeriod  *int              `url:"throttle_unauthenticated_requests_per_period,omitempty" json:"throttle_unauthenticated_requests_per_period,omitempty"`
+	TimeTrackingLimitToHours                  *bool             `url:"time_tracking_limit_to_hours,omitempty" json:"time_tracking_limit_to_hours,omitempty"`
+	TwoFactorGracePeriod                      *int              `url:"two_factor_grace_period,omitempty" json:"two_factor_grace_period,omitempty"`
+	UniqueIPsLimitEnabled                     *bool             `url:"unique_ips_limit_enabled,omitempty" json:"unique_ips_limit_enabled,omitempty"`
+	UniqueIPsLimitPerUser                     *int              `url:"unique_ips_limit_per_user,omitempty" json:"unique_ips_limit_per_user,omitempty"`
+	UniqueIPsLimitTimeWindow                  *int              `url:"unique_ips_limit_time_window,omitempty" json:"unique_ips_limit_time_window,omitempty"`
+	UsagePingEnabled                          *bool             `url:"usage_ping_enabled,omitempty" json:"usage_ping_enabled,omitempty"`
+	UserDefaultExternal                       *bool             `url:"user_default_external,omitempty" json:"user_default_external,omitempty"`
+	UserDefaultInternalRegex                  *string           `url:"user_default_internal_regex,omitempty" json:"user_default_internal_regex,omitempty"`
+	UserOauthApplications                     *bool             `url:"user_oauth_applications,omitempty" json:"user_oauth_applications,omitempty"`
+	UserShowAddSSHKeyMessage                  *bool             `url:"user_show_add_ssh_key_message,omitempty" json:"user_show_add_ssh_key_message,omitempty"`
+	VersionCheckEnabled                       *bool             `url:"version_check_enabled,omitempty" json:"version_check_enabled,omitempty"`
+	WebIDEClientsidePreviewEnabled            *bool             `url:"web_ide_clientside_preview_enabled,omitempty" json:"web_ide_clientside_preview_enabled,omitempty"`
+}
+
+// UpdateSettings updates the application settings.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/settings.html#change-application.settings
+func (s *SettingsService) UpdateSettings(opt *UpdateSettingsOptions, options ...OptionFunc) (*Settings, *Response, error) {
+	req, err := s.client.NewRequest("PUT", "application/settings", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	as := new(Settings)
+	resp, err := s.client.Do(req, as)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return as, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/sidekiq_metrics.go b/vendor/github.com/xanzy/go-gitlab/sidekiq_metrics.go
new file mode 100644
index 000000000..83e770245
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/sidekiq_metrics.go
@@ -0,0 +1,154 @@
+//
+// Copyright 2018, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import "time"
+
+// SidekiqService handles communication with the sidekiq service
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/sidekiq_metrics.html
+type SidekiqService struct {
+	client *Client
+}
+
+// QueueMetrics represents the GitLab sidekiq queue metrics.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-queue-metrics
+type QueueMetrics struct {
+	Queues map[string]struct {
+		Backlog int `json:"backlog"`
+		Latency int `json:"latency"`
+	} `json:"queues"`
+}
+
+// GetQueueMetrics lists information about all the registered queues,
+// their backlog and their latency.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-queue-metrics
+func (s *SidekiqService) GetQueueMetrics(options ...OptionFunc) (*QueueMetrics, *Response, error) {
+	req, err := s.client.NewRequest("GET", "/sidekiq/queue_metrics", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	q := new(QueueMetrics)
+	resp, err := s.client.Do(req, q)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return q, resp, err
+}
+
+// ProcessMetrics represents the GitLab sidekiq process metrics.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-process-metrics
+type ProcessMetrics struct {
+	Processes []struct {
+		Hostname    string     `json:"hostname"`
+		Pid         int        `json:"pid"`
+		Tag         string     `json:"tag"`
+		StartedAt   *time.Time `json:"started_at"`
+		Queues      []string   `json:"queues"`
+		Labels      []string   `json:"labels"`
+		Concurrency int        `json:"concurrency"`
+		Busy        int        `json:"busy"`
+	} `json:"processes"`
+}
+
+// GetProcessMetrics lists information about all the Sidekiq workers registered
+// to process your queues.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-process-metrics
+func (s *SidekiqService) GetProcessMetrics(options ...OptionFunc) (*ProcessMetrics, *Response, error) {
+	req, err := s.client.NewRequest("GET", "/sidekiq/process_metrics", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	p := new(ProcessMetrics)
+	resp, err := s.client.Do(req, p)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return p, resp, err
+}
+
+// JobStats represents the GitLab sidekiq job stats.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-job-statistics
+type JobStats struct {
+	Jobs struct {
+		Processed int `json:"processed"`
+		Failed    int `json:"failed"`
+		Enqueued  int `json:"enqueued"`
+	} `json:"jobs"`
+}
+
+// GetJobStats list information about the jobs that Sidekiq has performed.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-job-statistics
+func (s *SidekiqService) GetJobStats(options ...OptionFunc) (*JobStats, *Response, error) {
+	req, err := s.client.NewRequest("GET", "/sidekiq/job_stats", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	j := new(JobStats)
+	resp, err := s.client.Do(req, j)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return j, resp, err
+}
+
+// CompoundMetrics represents the GitLab sidekiq compounded stats.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-a-compound-response-of-all-the-previously-mentioned-metrics
+type CompoundMetrics struct {
+	QueueMetrics
+	ProcessMetrics
+	JobStats
+}
+
+// GetCompoundMetrics lists all the currently available information about Sidekiq.
+// Get a compound response of all the previously mentioned metrics
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/sidekiq_metrics.html#get-the-current-job-statistics
+func (s *SidekiqService) GetCompoundMetrics(options ...OptionFunc) (*CompoundMetrics, *Response, error) {
+	req, err := s.client.NewRequest("GET", "/sidekiq/compound_metrics", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	c := new(CompoundMetrics)
+	resp, err := s.client.Do(req, c)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return c, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/snippets.go b/vendor/github.com/xanzy/go-gitlab/snippets.go
new file mode 100644
index 000000000..be232c8c2
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/snippets.go
@@ -0,0 +1,230 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"bytes"
+	"fmt"
+	"time"
+)
+
+// SnippetsService handles communication with the snippets
+// related methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/snippets.html
+type SnippetsService struct {
+	client *Client
+}
+
+// Snippet represents a GitLab snippet.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/snippets.html
+type Snippet struct {
+	ID          int    `json:"id"`
+	Title       string `json:"title"`
+	FileName    string `json:"file_name"`
+	Description string `json:"description"`
+	Author      struct {
+		ID        int        `json:"id"`
+		Username  string     `json:"username"`
+		Email     string     `json:"email"`
+		Name      string     `json:"name"`
+		State     string     `json:"state"`
+		CreatedAt *time.Time `json:"created_at"`
+	} `json:"author"`
+	UpdatedAt *time.Time `json:"updated_at"`
+	CreatedAt *time.Time `json:"created_at"`
+	WebURL    string     `json:"web_url"`
+	RawURL    string     `json:"raw_url"`
+}
+
+func (s Snippet) String() string {
+	return Stringify(s)
+}
+
+// ListSnippetsOptions represents the available ListSnippets() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/snippets.html#list-snippets
+type ListSnippetsOptions ListOptions
+
+// ListSnippets gets a list of snippets.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/snippets.html#list-snippets
+func (s *SnippetsService) ListSnippets(opt *ListSnippetsOptions, options ...OptionFunc) ([]*Snippet, *Response, error) {
+	req, err := s.client.NewRequest("GET", "snippets", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ps []*Snippet
+	resp, err := s.client.Do(req, &ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, err
+}
+
+// GetSnippet gets a single snippet
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/snippets.html#single-snippet
+func (s *SnippetsService) GetSnippet(snippet int, options ...OptionFunc) (*Snippet, *Response, error) {
+	u := fmt.Sprintf("snippets/%d", snippet)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ps := new(Snippet)
+	resp, err := s.client.Do(req, ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, err
+}
+
+// CreateSnippetOptions represents the available CreateSnippet() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/snippets.html#create-new-snippet
+type CreateSnippetOptions struct {
+	Title       *string          `url:"title,omitempty" json:"title,omitempty"`
+	FileName    *string          `url:"file_name,omitempty" json:"file_name,omitempty"`
+	Description *string          `url:"description,omitempty" json:"description,omitempty"`
+	Content     *string          `url:"content,omitempty" json:"content,omitempty"`
+	Visibility  *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+}
+
+// CreateSnippet creates a new snippet. The user must have permission
+// to create new snippets.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/snippets.html#create-new-snippet
+func (s *SnippetsService) CreateSnippet(opt *CreateSnippetOptions, options ...OptionFunc) (*Snippet, *Response, error) {
+	req, err := s.client.NewRequest("POST", "snippets", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ps := new(Snippet)
+	resp, err := s.client.Do(req, ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, err
+}
+
+// UpdateSnippetOptions represents the available UpdateSnippet() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/snippets.html#update-snippet
+type UpdateSnippetOptions struct {
+	Title       *string          `url:"title,omitempty" json:"title,omitempty"`
+	FileName    *string          `url:"file_name,omitempty" json:"file_name,omitempty"`
+	Description *string          `url:"description,omitempty" json:"description,omitempty"`
+	Content     *string          `url:"content,omitempty" json:"content,omitempty"`
+	Visibility  *VisibilityValue `url:"visibility,omitempty" json:"visibility,omitempty"`
+}
+
+// UpdateSnippet updates an existing snippet. The user must have
+// permission to change an existing snippet.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/snippets.html#update-snippet
+func (s *SnippetsService) UpdateSnippet(snippet int, opt *UpdateSnippetOptions, options ...OptionFunc) (*Snippet, *Response, error) {
+	u := fmt.Sprintf("snippets/%d", snippet)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	ps := new(Snippet)
+	resp, err := s.client.Do(req, ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, err
+}
+
+// DeleteSnippet deletes an existing snippet. This is an idempotent
+// function and deleting a non-existent snippet still returns a 200 OK status
+// code.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/snippets.html#delete-snippet
+func (s *SnippetsService) DeleteSnippet(snippet int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("snippets/%d", snippet)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// SnippetContent returns the raw snippet as plain text.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/snippets.html#snippet-content
+func (s *SnippetsService) SnippetContent(snippet int, options ...OptionFunc) ([]byte, *Response, error) {
+	u := fmt.Sprintf("snippets/%d/raw", snippet)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var b bytes.Buffer
+	resp, err := s.client.Do(req, &b)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return b.Bytes(), resp, err
+}
+
+// ExploreSnippetsOptions represents the available ExploreSnippets() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/snippets.html#explore-all-public-snippets
+type ExploreSnippetsOptions ListOptions
+
+// ExploreSnippets gets the list of public snippets.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/snippets.html#explore-all-public-snippets
+func (s *SnippetsService) ExploreSnippets(opt *ExploreSnippetsOptions, options ...OptionFunc) ([]*Snippet, *Response, error) {
+	req, err := s.client.NewRequest("GET", "snippets/public", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ps []*Snippet
+	resp, err := s.client.Do(req, &ps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ps, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/strings.go b/vendor/github.com/xanzy/go-gitlab/strings.go
new file mode 100644
index 000000000..aeefb6b8d
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/strings.go
@@ -0,0 +1,94 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"bytes"
+	"fmt"
+
+	"reflect"
+)
+
+// Stringify attempts to create a reasonable string representation of types in
+// the GitHub library.  It does things like resolve pointers to their values
+// and omits struct fields with nil values.
+func Stringify(message interface{}) string {
+	var buf bytes.Buffer
+	v := reflect.ValueOf(message)
+	stringifyValue(&buf, v)
+	return buf.String()
+}
+
+// stringifyValue was heavily inspired by the goprotobuf library.
+func stringifyValue(buf *bytes.Buffer, val reflect.Value) {
+	if val.Kind() == reflect.Ptr && val.IsNil() {
+		buf.WriteString("<nil>")
+		return
+	}
+
+	v := reflect.Indirect(val)
+
+	switch v.Kind() {
+	case reflect.String:
+		fmt.Fprintf(buf, `"%s"`, v)
+	case reflect.Slice:
+		buf.WriteByte('[')
+		for i := 0; i < v.Len(); i++ {
+			if i > 0 {
+				buf.WriteByte(' ')
+			}
+
+			stringifyValue(buf, v.Index(i))
+		}
+
+		buf.WriteByte(']')
+		return
+	case reflect.Struct:
+		if v.Type().Name() != "" {
+			buf.WriteString(v.Type().String())
+		}
+
+		buf.WriteByte('{')
+
+		var sep bool
+		for i := 0; i < v.NumField(); i++ {
+			fv := v.Field(i)
+			if fv.Kind() == reflect.Ptr && fv.IsNil() {
+				continue
+			}
+			if fv.Kind() == reflect.Slice && fv.IsNil() {
+				continue
+			}
+
+			if sep {
+				buf.WriteString(", ")
+			} else {
+				sep = true
+			}
+
+			buf.WriteString(v.Type().Field(i).Name)
+			buf.WriteByte(':')
+			stringifyValue(buf, fv)
+		}
+
+		buf.WriteByte('}')
+	default:
+		if v.CanInterface() {
+			fmt.Fprint(buf, v.Interface())
+		}
+	}
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/system_hooks.go b/vendor/github.com/xanzy/go-gitlab/system_hooks.go
new file mode 100644
index 000000000..d5209d4f7
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/system_hooks.go
@@ -0,0 +1,143 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"time"
+)
+
+// SystemHooksService handles communication with the system hooks related
+// methods of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/system_hooks.html
+type SystemHooksService struct {
+	client *Client
+}
+
+// Hook represents a GitLap system hook.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/system_hooks.html
+type Hook struct {
+	ID        int        `json:"id"`
+	URL       string     `json:"url"`
+	CreatedAt *time.Time `json:"created_at"`
+}
+
+func (h Hook) String() string {
+	return Stringify(h)
+}
+
+// ListHooks gets a list of system hooks.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/system_hooks.html#list-system-hooks
+func (s *SystemHooksService) ListHooks(options ...OptionFunc) ([]*Hook, *Response, error) {
+	req, err := s.client.NewRequest("GET", "hooks", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var h []*Hook
+	resp, err := s.client.Do(req, &h)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return h, resp, err
+}
+
+// AddHookOptions represents the available AddHook() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/system_hooks.html#add-new-system-hook-hook
+type AddHookOptions struct {
+	URL *string `url:"url,omitempty" json:"url,omitempty"`
+}
+
+// AddHook adds a new system hook hook.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/system_hooks.html#add-new-system-hook-hook
+func (s *SystemHooksService) AddHook(opt *AddHookOptions, options ...OptionFunc) (*Hook, *Response, error) {
+	req, err := s.client.NewRequest("POST", "hooks", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	h := new(Hook)
+	resp, err := s.client.Do(req, h)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return h, resp, err
+}
+
+// HookEvent represents an event trigger by a GitLab system hook.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/system_hooks.html
+type HookEvent struct {
+	EventName  string `json:"event_name"`
+	Name       string `json:"name"`
+	Path       string `json:"path"`
+	ProjectID  int    `json:"project_id"`
+	OwnerName  string `json:"owner_name"`
+	OwnerEmail string `json:"owner_email"`
+}
+
+func (h HookEvent) String() string {
+	return Stringify(h)
+}
+
+// TestHook tests a system hook.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/system_hooks.html#test-system-hook
+func (s *SystemHooksService) TestHook(hook int, options ...OptionFunc) (*HookEvent, *Response, error) {
+	u := fmt.Sprintf("hooks/%d", hook)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	h := new(HookEvent)
+	resp, err := s.client.Do(req, h)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return h, resp, err
+}
+
+// DeleteHook deletes a system hook. This is an idempotent API function and
+// returns 200 OK even if the hook is not available. If the hook is deleted it
+// is also returned as JSON.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/system_hooks.html#delete-system-hook
+func (s *SystemHooksService) DeleteHook(hook int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("hooks/%d", hook)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/tags.go b/vendor/github.com/xanzy/go-gitlab/tags.go
new file mode 100644
index 000000000..a6072dc76
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/tags.go
@@ -0,0 +1,243 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+)
+
+// TagsService handles communication with the tags related methods
+// of the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
+type TagsService struct {
+	client *Client
+}
+
+// Tag represents a GitLab tag.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
+type Tag struct {
+	Commit  *Commit      `json:"commit"`
+	Release *ReleaseNote `json:"release"`
+	Name    string       `json:"name"`
+	Message string       `json:"message"`
+}
+
+// ReleaseNote represents a GitLab version release.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/tags.html
+type ReleaseNote struct {
+	TagName     string `json:"tag_name"`
+	Description string `json:"description"`
+}
+
+func (t Tag) String() string {
+	return Stringify(t)
+}
+
+// ListTagsOptions represents the available ListTags() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#list-project-repository-tags
+type ListTagsOptions struct {
+	ListOptions
+	OrderBy *string `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort    *string `url:"sort,omitempty" json:"sort,omitempty"`
+}
+
+// ListTags gets a list of tags from a project, sorted by name in reverse
+// alphabetical order.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#list-project-repository-tags
+func (s *TagsService) ListTags(pid interface{}, opt *ListTagsOptions, options ...OptionFunc) ([]*Tag, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/tags", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var t []*Tag
+	resp, err := s.client.Do(req, &t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// GetTag a specific repository tag determined by its name. It returns 200 together
+// with the tag information if the tag exists. It returns 404 if the tag does not exist.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#get-a-single-repository-tag
+func (s *TagsService) GetTag(pid interface{}, tag string, options ...OptionFunc) (*Tag, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/tags/%s", pathEscape(project), url.PathEscape(tag))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var t *Tag
+	resp, err := s.client.Do(req, &t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// CreateTagOptions represents the available CreateTag() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#create-a-new-tag
+type CreateTagOptions struct {
+	TagName *string `url:"tag_name,omitempty" json:"tag_name,omitempty"`
+	Ref     *string `url:"ref,omitempty" json:"ref,omitempty"`
+	Message *string `url:"message,omitempty" json:"message,omitempty"`
+	// ReleaseDescription parameter was deprecated in GitLab 11.7
+	ReleaseDescription *string `url:"release_description:omitempty" json:"release_description,omitempty"`
+}
+
+// CreateTag creates a new tag in the repository that points to the supplied ref.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#create-a-new-tag
+func (s *TagsService) CreateTag(pid interface{}, opt *CreateTagOptions, options ...OptionFunc) (*Tag, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/tags", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t := new(Tag)
+	resp, err := s.client.Do(req, t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// DeleteTag deletes a tag of a repository with given name.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#delete-a-tag
+func (s *TagsService) DeleteTag(pid interface{}, tag string, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/tags/%s", pathEscape(project), url.PathEscape(tag))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// CreateReleaseNoteOptions represents the available CreateReleaseNote() options.
+//
+// Deprecated: This feature was deprecated in GitLab 11.7.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#create-a-new-release
+type CreateReleaseNoteOptions struct {
+	Description *string `url:"description:omitempty" json:"description,omitempty"`
+}
+
+// CreateReleaseNote Add release notes to the existing git tag.
+// If there already exists a release for the given tag, status code 409 is returned.
+//
+// Deprecated: This feature was deprecated in GitLab 11.7.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#create-a-new-release
+func (s *TagsService) CreateReleaseNote(pid interface{}, tag string, opt *CreateReleaseNoteOptions, options ...OptionFunc) (*ReleaseNote, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/tags/%s/release", pathEscape(project), url.PathEscape(tag))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	r := new(ReleaseNote)
+	resp, err := s.client.Do(req, r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
+
+// UpdateReleaseNoteOptions represents the available UpdateReleaseNote() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#update-a-release
+type UpdateReleaseNoteOptions struct {
+	Description *string `url:"description:omitempty" json:"description,omitempty"`
+}
+
+// UpdateReleaseNote Updates the release notes of a given release.
+//
+// Deprecated: This feature was deprecated in GitLab 11.7.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/tags.html#update-a-release
+func (s *TagsService) UpdateReleaseNote(pid interface{}, tag string, opt *UpdateReleaseNoteOptions, options ...OptionFunc) (*ReleaseNote, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/repository/tags/%s/release", pathEscape(project), url.PathEscape(tag))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	r := new(ReleaseNote)
+	resp, err := s.client.Do(req, r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/time_stats.go b/vendor/github.com/xanzy/go-gitlab/time_stats.go
new file mode 100644
index 000000000..437a4e01e
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/time_stats.go
@@ -0,0 +1,162 @@
+package gitlab
+
+import (
+	"fmt"
+)
+
+// timeStatsService handles communication with the time tracking related
+// methods of the GitLab API.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+type timeStatsService struct {
+	client *Client
+}
+
+// TimeStats represents the time estimates and time spent for an issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+type TimeStats struct {
+	HumanTimeEstimate   string `json:"human_time_estimate"`
+	HumanTotalTimeSpent string `json:"human_total_time_spent"`
+	TimeEstimate        int    `json:"time_estimate"`
+	TotalTimeSpent      int    `json:"total_time_spent"`
+}
+
+func (t TimeStats) String() string {
+	return Stringify(t)
+}
+
+// SetTimeEstimateOptions represents the available SetTimeEstimate()
+// options.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+type SetTimeEstimateOptions struct {
+	Duration *string `url:"duration,omitempty" json:"duration,omitempty"`
+}
+
+// setTimeEstimate sets the time estimate for a single project issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+func (s *timeStatsService) setTimeEstimate(pid interface{}, entity string, issue int, opt *SetTimeEstimateOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/time_estimate", pathEscape(project), entity, issue)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t := new(TimeStats)
+	resp, err := s.client.Do(req, t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// resetTimeEstimate resets the time estimate for a single project issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+func (s *timeStatsService) resetTimeEstimate(pid interface{}, entity string, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/reset_time_estimate", pathEscape(project), entity, issue)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t := new(TimeStats)
+	resp, err := s.client.Do(req, t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// AddSpentTimeOptions represents the available AddSpentTime() options.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+type AddSpentTimeOptions struct {
+	Duration *string `url:"duration,omitempty" json:"duration,omitempty"`
+}
+
+// addSpentTime adds spent time for a single project issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+func (s *timeStatsService) addSpentTime(pid interface{}, entity string, issue int, opt *AddSpentTimeOptions, options ...OptionFunc) (*TimeStats, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/add_spent_time", pathEscape(project), entity, issue)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t := new(TimeStats)
+	resp, err := s.client.Do(req, t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// resetSpentTime resets the spent time for a single project issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+func (s *timeStatsService) resetSpentTime(pid interface{}, entity string, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/reset_spent_time", pathEscape(project), entity, issue)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t := new(TimeStats)
+	resp, err := s.client.Do(req, t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// getTimeSpent gets the spent time for a single project issue.
+//
+// GitLab docs: https://docs.gitlab.com/ce/workflow/time_tracking.html
+func (s *timeStatsService) getTimeSpent(pid interface{}, entity string, issue int, options ...OptionFunc) (*TimeStats, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/%s/%d/time_stats", pathEscape(project), entity, issue)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t := new(TimeStats)
+	resp, err := s.client.Do(req, t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/todos.go b/vendor/github.com/xanzy/go-gitlab/todos.go
new file mode 100644
index 000000000..4c32d0858
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/todos.go
@@ -0,0 +1,176 @@
+package gitlab
+
+import "time"
+import "fmt"
+
+// TodosService handles communication with the todos related methods of
+// the Gitlab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html
+type TodosService struct {
+	client *Client
+}
+
+// TodoAction represents the available actions that can be performed on a todo.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html
+type TodoAction string
+
+// The available todo actions.
+const (
+	TodoAssigned          TodoAction = "assigned"
+	TodoMentioned         TodoAction = "mentioned"
+	TodoBuildFailed       TodoAction = "build_failed"
+	TodoMarked            TodoAction = "marked"
+	TodoApprovalRequired  TodoAction = "approval_required"
+	TodoDirectlyAddressed TodoAction = "directly_addressed"
+)
+
+// TodoTarget represents a todo target of type Issue or MergeRequest
+type TodoTarget struct {
+	// TODO: replace both Assignee and Author structs with v4 User struct
+	Assignee struct {
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		ID        int    `json:"id"`
+		State     string `json:"state"`
+		AvatarURL string `json:"avatar_url"`
+		WebURL    string `json:"web_url"`
+	} `json:"assignee"`
+	Author struct {
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		ID        int    `json:"id"`
+		State     string `json:"state"`
+		AvatarURL string `json:"avatar_url"`
+		WebURL    string `json:"web_url"`
+	} `json:"author"`
+	CreatedAt      *time.Time `json:"created_at"`
+	Description    string     `json:"description"`
+	Downvotes      int        `json:"downvotes"`
+	ID             int        `json:"id"`
+	IID            int        `json:"iid"`
+	Labels         []string   `json:"labels"`
+	Milestone      Milestone  `json:"milestone"`
+	ProjectID      int        `json:"project_id"`
+	State          string     `json:"state"`
+	Subscribed     bool       `json:"subscribed"`
+	Title          string     `json:"title"`
+	UpdatedAt      *time.Time `json:"updated_at"`
+	Upvotes        int        `json:"upvotes"`
+	UserNotesCount int        `json:"user_notes_count"`
+	WebURL         string     `json:"web_url"`
+
+	// Only available for type Issue
+	Confidential bool   `json:"confidential"`
+	DueDate      string `json:"due_date"`
+	Weight       int    `json:"weight"`
+
+	// Only available for type MergeRequest
+	ApprovalsBeforeMerge      int    `json:"approvals_before_merge"`
+	ForceRemoveSourceBranch   bool   `json:"force_remove_source_branch"`
+	MergeCommitSHA            string `json:"merge_commit_sha"`
+	MergeWhenPipelineSucceeds bool   `json:"merge_when_pipeline_succeeds"`
+	MergeStatus               string `json:"merge_status"`
+	SHA                       string `json:"sha"`
+	ShouldRemoveSourceBranch  bool   `json:"should_remove_source_branch"`
+	SourceBranch              string `json:"source_branch"`
+	SourceProjectID           int    `json:"source_project_id"`
+	Squash                    bool   `json:"squash"`
+	TargetBranch              string `json:"target_branch"`
+	TargetProjectID           int    `json:"target_project_id"`
+	WorkInProgress            bool   `json:"work_in_progress"`
+}
+
+// Todo represents a GitLab todo.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html
+type Todo struct {
+	ID      int `json:"id"`
+	Project struct {
+		ID                int    `json:"id"`
+		HTTPURLToRepo     string `json:"http_url_to_repo"`
+		WebURL            string `json:"web_url"`
+		Name              string `json:"name"`
+		NameWithNamespace string `json:"name_with_namespace"`
+		Path              string `json:"path"`
+		PathWithNamespace string `json:"path_with_namespace"`
+	} `json:"project"`
+	Author struct {
+		ID        int    `json:"id"`
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		State     string `json:"state"`
+		AvatarURL string `json:"avatar_url"`
+		WebURL    string `json:"web_url"`
+	} `json:"author"`
+	ActionName TodoAction `json:"action_name"`
+	TargetType string     `json:"target_type"`
+	Target     TodoTarget `json:"target"`
+	TargetURL  string     `json:"target_url"`
+	Body       string     `json:"body"`
+	State      string     `json:"state"`
+	CreatedAt  *time.Time `json:"created_at"`
+}
+
+func (t Todo) String() string {
+	return Stringify(t)
+}
+
+// ListTodosOptions represents the available ListTodos() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html#get-a-list-of-todos
+type ListTodosOptions struct {
+	ListOptions
+	Action    *TodoAction `url:"action,omitempty" json:"action,omitempty"`
+	AuthorID  *int        `url:"author_id,omitempty" json:"author_id,omitempty"`
+	ProjectID *int        `url:"project_id,omitempty" json:"project_id,omitempty"`
+	State     *string     `url:"state,omitempty" json:"state,omitempty"`
+	Type      *string     `url:"type,omitempty" json:"type,omitempty"`
+}
+
+// ListTodos lists all todos created by authenticated user.
+// When no filter is applied, it returns all pending todos for the current user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/todos.html#get-a-list-of-todos
+func (s *TodosService) ListTodos(opt *ListTodosOptions, options ...OptionFunc) ([]*Todo, *Response, error) {
+	req, err := s.client.NewRequest("GET", "todos", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var t []*Todo
+	resp, err := s.client.Do(req, &t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// MarkTodoAsDone marks a single pending todo given by its ID for the current user as done.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html#mark-a-todo-as-done
+func (s *TodosService) MarkTodoAsDone(id int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("todos/%d/mark_as_done", id)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// MarkAllTodosAsDone marks all pending todos for the current user as done.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/todos.html#mark-all-todos-as-done
+func (s *TodosService) MarkAllTodosAsDone(options ...OptionFunc) (*Response, error) {
+	req, err := s.client.NewRequest("POST", "todos/mark_as_done", nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/users.go b/vendor/github.com/xanzy/go-gitlab/users.go
new file mode 100644
index 000000000..c8015cda7
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/users.go
@@ -0,0 +1,871 @@
+//
+// Copyright 2017, Sander van Harmelen
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+import (
+	"errors"
+	"fmt"
+	"time"
+)
+
+// List a couple of standard errors.
+var (
+	ErrUserBlockPrevented   = errors.New("Cannot block a user that is already blocked by LDAP synchronization")
+	ErrUserNotFound         = errors.New("User does not exist")
+	ErrUserUnblockPrevented = errors.New("Cannot unblock a user that is blocked by LDAP synchronization")
+)
+
+// UsersService handles communication with the user related methods of
+// the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html
+type UsersService struct {
+	client *Client
+}
+
+// BasicUser included in other service responses (such as merge requests, pipelines, etc).
+type BasicUser struct {
+	ID        int        `json:"id"`
+	Username  string     `json:"username"`
+	Name      string     `json:"name"`
+	State     string     `json:"state"`
+	CreatedAt *time.Time `json:"created_at"`
+	AvatarURL string     `json:"avatar_url"`
+	WebURL    string     `json:"web_url"`
+}
+
+// User represents a GitLab user.
+//
+// GitLab API docs: https://docs.gitlab.com/ee/api/users.html
+type User struct {
+	ID                        int                `json:"id"`
+	Username                  string             `json:"username"`
+	Email                     string             `json:"email"`
+	Name                      string             `json:"name"`
+	State                     string             `json:"state"`
+	CreatedAt                 *time.Time         `json:"created_at"`
+	Bio                       string             `json:"bio"`
+	Location                  string             `json:"location"`
+	PublicEmail               string             `json:"public_email"`
+	Skype                     string             `json:"skype"`
+	Linkedin                  string             `json:"linkedin"`
+	Twitter                   string             `json:"twitter"`
+	WebsiteURL                string             `json:"website_url"`
+	Organization              string             `json:"organization"`
+	ExternUID                 string             `json:"extern_uid"`
+	Provider                  string             `json:"provider"`
+	ThemeID                   int                `json:"theme_id"`
+	LastActivityOn            *ISOTime           `json:"last_activity_on"`
+	ColorSchemeID             int                `json:"color_scheme_id"`
+	IsAdmin                   bool               `json:"is_admin"`
+	AvatarURL                 string             `json:"avatar_url"`
+	CanCreateGroup            bool               `json:"can_create_group"`
+	CanCreateProject          bool               `json:"can_create_project"`
+	ProjectsLimit             int                `json:"projects_limit"`
+	CurrentSignInAt           *time.Time         `json:"current_sign_in_at"`
+	LastSignInAt              *time.Time         `json:"last_sign_in_at"`
+	ConfirmedAt               *time.Time         `json:"confirmed_at"`
+	TwoFactorEnabled          bool               `json:"two_factor_enabled"`
+	Identities                []*UserIdentity    `json:"identities"`
+	External                  bool               `json:"external"`
+	PrivateProfile            bool               `json:"private_profile"`
+	SharedRunnersMinutesLimit int                `json:"shared_runners_minutes_limit"`
+	CustomAttributes          []*CustomAttribute `json:"custom_attributes"`
+}
+
+// UserIdentity represents a user identity.
+type UserIdentity struct {
+	Provider  string `json:"provider"`
+	ExternUID string `json:"extern_uid"`
+}
+
+// ListUsersOptions represents the available ListUsers() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-users
+type ListUsersOptions struct {
+	ListOptions
+	Active  *bool `url:"active,omitempty" json:"active,omitempty"`
+	Blocked *bool `url:"blocked,omitempty" json:"blocked,omitempty"`
+
+	// The options below are only available for admins.
+	Search               *string    `url:"search,omitempty" json:"search,omitempty"`
+	Username             *string    `url:"username,omitempty" json:"username,omitempty"`
+	ExternalUID          *string    `url:"extern_uid,omitempty" json:"extern_uid,omitempty"`
+	Provider             *string    `url:"provider,omitempty" json:"provider,omitempty"`
+	CreatedBefore        *time.Time `url:"created_before,omitempty" json:"created_before,omitempty"`
+	CreatedAfter         *time.Time `url:"created_after,omitempty" json:"created_after,omitempty"`
+	OrderBy              *string    `url:"order_by,omitempty" json:"order_by,omitempty"`
+	Sort                 *string    `url:"sort,omitempty" json:"sort,omitempty"`
+	WithCustomAttributes *bool      `url:"with_custom_attributes,omitempty" json:"with_custom_attributes,omitempty"`
+}
+
+// ListUsers gets a list of users.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-users
+func (s *UsersService) ListUsers(opt *ListUsersOptions, options ...OptionFunc) ([]*User, *Response, error) {
+	req, err := s.client.NewRequest("GET", "users", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var usr []*User
+	resp, err := s.client.Do(req, &usr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return usr, resp, err
+}
+
+// GetUser gets a single user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#single-user
+func (s *UsersService) GetUser(user int, options ...OptionFunc) (*User, *Response, error) {
+	u := fmt.Sprintf("users/%d", user)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	usr := new(User)
+	resp, err := s.client.Do(req, usr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return usr, resp, err
+}
+
+// CreateUserOptions represents the available CreateUser() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-creation
+type CreateUserOptions struct {
+	Email            *string `url:"email,omitempty" json:"email,omitempty"`
+	Password         *string `url:"password,omitempty" json:"password,omitempty"`
+	ResetPassword    *bool   `url:"reset_password,omitempty" json:"reset_password,omitempty"`
+	Username         *string `url:"username,omitempty" json:"username,omitempty"`
+	Name             *string `url:"name,omitempty" json:"name,omitempty"`
+	Skype            *string `url:"skype,omitempty" json:"skype,omitempty"`
+	Linkedin         *string `url:"linkedin,omitempty" json:"linkedin,omitempty"`
+	Twitter          *string `url:"twitter,omitempty" json:"twitter,omitempty"`
+	WebsiteURL       *string `url:"website_url,omitempty" json:"website_url,omitempty"`
+	Organization     *string `url:"organization,omitempty" json:"organization,omitempty"`
+	ProjectsLimit    *int    `url:"projects_limit,omitempty" json:"projects_limit,omitempty"`
+	ExternUID        *string `url:"extern_uid,omitempty" json:"extern_uid,omitempty"`
+	Provider         *string `url:"provider,omitempty" json:"provider,omitempty"`
+	Bio              *string `url:"bio,omitempty" json:"bio,omitempty"`
+	Location         *string `url:"location,omitempty" json:"location,omitempty"`
+	Admin            *bool   `url:"admin,omitempty" json:"admin,omitempty"`
+	CanCreateGroup   *bool   `url:"can_create_group,omitempty" json:"can_create_group,omitempty"`
+	SkipConfirmation *bool   `url:"skip_confirmation,omitempty" json:"skip_confirmation,omitempty"`
+	External         *bool   `url:"external,omitempty" json:"external,omitempty"`
+	PrivateProfile   *bool   `url:"private_profile,omitempty" json:"private_profile,omitempty"`
+}
+
+// CreateUser creates a new user. Note only administrators can create new users.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-creation
+func (s *UsersService) CreateUser(opt *CreateUserOptions, options ...OptionFunc) (*User, *Response, error) {
+	req, err := s.client.NewRequest("POST", "users", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	usr := new(User)
+	resp, err := s.client.Do(req, usr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return usr, resp, err
+}
+
+// ModifyUserOptions represents the available ModifyUser() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-modification
+type ModifyUserOptions struct {
+	Email              *string `url:"email,omitempty" json:"email,omitempty"`
+	Password           *string `url:"password,omitempty" json:"password,omitempty"`
+	Username           *string `url:"username,omitempty" json:"username,omitempty"`
+	Name               *string `url:"name,omitempty" json:"name,omitempty"`
+	Skype              *string `url:"skype,omitempty" json:"skype,omitempty"`
+	Linkedin           *string `url:"linkedin,omitempty" json:"linkedin,omitempty"`
+	Twitter            *string `url:"twitter,omitempty" json:"twitter,omitempty"`
+	WebsiteURL         *string `url:"website_url,omitempty" json:"website_url,omitempty"`
+	Organization       *string `url:"organization,omitempty" json:"organization,omitempty"`
+	ProjectsLimit      *int    `url:"projects_limit,omitempty" json:"projects_limit,omitempty"`
+	ExternUID          *string `url:"extern_uid,omitempty" json:"extern_uid,omitempty"`
+	Provider           *string `url:"provider,omitempty" json:"provider,omitempty"`
+	Bio                *string `url:"bio,omitempty" json:"bio,omitempty"`
+	Location           *string `url:"location,omitempty" json:"location,omitempty"`
+	Admin              *bool   `url:"admin,omitempty" json:"admin,omitempty"`
+	CanCreateGroup     *bool   `url:"can_create_group,omitempty" json:"can_create_group,omitempty"`
+	SkipReconfirmation *bool   `url:"skip_reconfirmation,omitempty" json:"skip_reconfirmation,omitempty"`
+	External           *bool   `url:"external,omitempty" json:"external,omitempty"`
+	PrivateProfile     *bool   `url:"private_profile,omitempty" json:"private_profile,omitempty"`
+}
+
+// ModifyUser modifies an existing user. Only administrators can change attributes
+// of a user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-modification
+func (s *UsersService) ModifyUser(user int, opt *ModifyUserOptions, options ...OptionFunc) (*User, *Response, error) {
+	u := fmt.Sprintf("users/%d", user)
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	usr := new(User)
+	resp, err := s.client.Do(req, usr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return usr, resp, err
+}
+
+// DeleteUser deletes a user. Available only for administrators. This is an
+// idempotent function, calling this function for a non-existent user id still
+// returns a status code 200 OK. The JSON response differs if the user was
+// actually deleted or not. In the former the user is returned and in the
+// latter not.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#user-deletion
+func (s *UsersService) DeleteUser(user int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("users/%d", user)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// CurrentUser gets currently authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#current-user
+func (s *UsersService) CurrentUser(options ...OptionFunc) (*User, *Response, error) {
+	req, err := s.client.NewRequest("GET", "user", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	usr := new(User)
+	resp, err := s.client.Do(req, usr)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return usr, resp, err
+}
+
+// SSHKey represents a SSH key.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-ssh-keys
+type SSHKey struct {
+	ID        int        `json:"id"`
+	Title     string     `json:"title"`
+	Key       string     `json:"key"`
+	CreatedAt *time.Time `json:"created_at"`
+}
+
+// ListSSHKeys gets a list of currently authenticated user's SSH keys.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-ssh-keys
+func (s *UsersService) ListSSHKeys(options ...OptionFunc) ([]*SSHKey, *Response, error) {
+	req, err := s.client.NewRequest("GET", "user/keys", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var k []*SSHKey
+	resp, err := s.client.Do(req, &k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, err
+}
+
+// ListSSHKeysForUserOptions represents the available ListSSHKeysForUser() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#list-ssh-keys-for-user
+type ListSSHKeysForUserOptions ListOptions
+
+// ListSSHKeysForUser gets a list of a specified user's SSH keys. Available
+// only for admin
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#list-ssh-keys-for-user
+func (s *UsersService) ListSSHKeysForUser(user int, opt *ListSSHKeysForUserOptions, options ...OptionFunc) ([]*SSHKey, *Response, error) {
+	u := fmt.Sprintf("users/%d/keys", user)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var k []*SSHKey
+	resp, err := s.client.Do(req, &k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, err
+}
+
+// GetSSHKey gets a single key.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#single-ssh-key
+func (s *UsersService) GetSSHKey(key int, options ...OptionFunc) (*SSHKey, *Response, error) {
+	u := fmt.Sprintf("user/keys/%d", key)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	k := new(SSHKey)
+	resp, err := s.client.Do(req, k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, err
+}
+
+// AddSSHKeyOptions represents the available AddSSHKey() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#add-ssh-key
+type AddSSHKeyOptions struct {
+	Title *string `url:"title,omitempty" json:"title,omitempty"`
+	Key   *string `url:"key,omitempty" json:"key,omitempty"`
+}
+
+// AddSSHKey creates a new key owned by the currently authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-ssh-key
+func (s *UsersService) AddSSHKey(opt *AddSSHKeyOptions, options ...OptionFunc) (*SSHKey, *Response, error) {
+	req, err := s.client.NewRequest("POST", "user/keys", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	k := new(SSHKey)
+	resp, err := s.client.Do(req, k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, err
+}
+
+// AddSSHKeyForUser creates new key owned by specified user. Available only for
+// admin.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-ssh-key-for-user
+func (s *UsersService) AddSSHKeyForUser(user int, opt *AddSSHKeyOptions, options ...OptionFunc) (*SSHKey, *Response, error) {
+	u := fmt.Sprintf("users/%d/keys", user)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	k := new(SSHKey)
+	resp, err := s.client.Do(req, k)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return k, resp, err
+}
+
+// DeleteSSHKey deletes key owned by currently authenticated user. This is an
+// idempotent function and calling it on a key that is already deleted or not
+// available results in 200 OK.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#delete-ssh-key-for-current-owner
+func (s *UsersService) DeleteSSHKey(key int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("user/keys/%d", key)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteSSHKeyForUser deletes key owned by a specified user. Available only
+// for admin.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#delete-ssh-key-for-given-user
+func (s *UsersService) DeleteSSHKeyForUser(user, key int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("users/%d/keys/%d", user, key)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// BlockUser blocks the specified user. Available only for admin.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#block-user
+func (s *UsersService) BlockUser(user int, options ...OptionFunc) error {
+	u := fmt.Sprintf("users/%d/block", user)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return err
+	}
+
+	resp, err := s.client.Do(req, nil)
+	if err != nil && resp == nil {
+		return err
+	}
+
+	switch resp.StatusCode {
+	case 201:
+		return nil
+	case 403:
+		return ErrUserBlockPrevented
+	case 404:
+		return ErrUserNotFound
+	default:
+		return fmt.Errorf("Received unexpected result code: %d", resp.StatusCode)
+	}
+}
+
+// UnblockUser unblocks the specified user. Available only for admin.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#unblock-user
+func (s *UsersService) UnblockUser(user int, options ...OptionFunc) error {
+	u := fmt.Sprintf("users/%d/unblock", user)
+
+	req, err := s.client.NewRequest("POST", u, nil, options)
+	if err != nil {
+		return err
+	}
+
+	resp, err := s.client.Do(req, nil)
+	if err != nil && resp == nil {
+		return err
+	}
+
+	switch resp.StatusCode {
+	case 201:
+		return nil
+	case 403:
+		return ErrUserUnblockPrevented
+	case 404:
+		return ErrUserNotFound
+	default:
+		return fmt.Errorf("Received unexpected result code: %d", resp.StatusCode)
+	}
+}
+
+// Email represents an Email.
+//
+// GitLab API docs: https://doc.gitlab.com/ce/api/users.html#list-emails
+type Email struct {
+	ID    int    `json:"id"`
+	Email string `json:"email"`
+}
+
+// ListEmails gets a list of currently authenticated user's Emails.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#list-emails
+func (s *UsersService) ListEmails(options ...OptionFunc) ([]*Email, *Response, error) {
+	req, err := s.client.NewRequest("GET", "user/emails", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var e []*Email
+	resp, err := s.client.Do(req, &e)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return e, resp, err
+}
+
+// ListEmailsForUserOptions represents the available ListEmailsForUser() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#list-emails-for-user
+type ListEmailsForUserOptions ListOptions
+
+// ListEmailsForUser gets a list of a specified user's Emails. Available
+// only for admin
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#list-emails-for-user
+func (s *UsersService) ListEmailsForUser(user int, opt *ListEmailsForUserOptions, options ...OptionFunc) ([]*Email, *Response, error) {
+	u := fmt.Sprintf("users/%d/emails", user)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var e []*Email
+	resp, err := s.client.Do(req, &e)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return e, resp, err
+}
+
+// GetEmail gets a single email.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#single-email
+func (s *UsersService) GetEmail(email int, options ...OptionFunc) (*Email, *Response, error) {
+	u := fmt.Sprintf("user/emails/%d", email)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	e := new(Email)
+	resp, err := s.client.Do(req, e)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return e, resp, err
+}
+
+// AddEmailOptions represents the available AddEmail() options.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#add-email
+type AddEmailOptions struct {
+	Email *string `url:"email,omitempty" json:"email,omitempty"`
+}
+
+// AddEmail creates a new email owned by the currently authenticated user.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-email
+func (s *UsersService) AddEmail(opt *AddEmailOptions, options ...OptionFunc) (*Email, *Response, error) {
+	req, err := s.client.NewRequest("POST", "user/emails", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	e := new(Email)
+	resp, err := s.client.Do(req, e)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return e, resp, err
+}
+
+// AddEmailForUser creates new email owned by specified user. Available only for
+// admin.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/users.html#add-email-for-user
+func (s *UsersService) AddEmailForUser(user int, opt *AddEmailOptions, options ...OptionFunc) (*Email, *Response, error) {
+	u := fmt.Sprintf("users/%d/emails", user)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	e := new(Email)
+	resp, err := s.client.Do(req, e)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return e, resp, err
+}
+
+// DeleteEmail deletes email owned by currently authenticated user. This is an
+// idempotent function and calling it on a key that is already deleted or not
+// available results in 200 OK.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#delete-email-for-current-owner
+func (s *UsersService) DeleteEmail(email int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("user/emails/%d", email)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// DeleteEmailForUser deletes email owned by a specified user. Available only
+// for admin.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#delete-email-for-given-user
+func (s *UsersService) DeleteEmailForUser(user, email int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("users/%d/emails/%d", user, email)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// ImpersonationToken represents an impersonation token.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-all-impersonation-tokens-of-a-user
+type ImpersonationToken struct {
+	ID        int        `json:"id"`
+	Name      string     `json:"name"`
+	Active    bool       `json:"active"`
+	Token     string     `json:"token"`
+	Scopes    []string   `json:"scopes"`
+	Revoked   bool       `json:"revoked"`
+	CreatedAt *time.Time `json:"created_at"`
+	ExpiresAt *ISOTime   `json:"expires_at"`
+}
+
+// GetAllImpersonationTokensOptions represents the available
+// GetAllImpersonationTokens() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-all-impersonation-tokens-of-a-user
+type GetAllImpersonationTokensOptions struct {
+	ListOptions
+	State *string `url:"state,omitempty" json:"state,omitempty"`
+}
+
+// GetAllImpersonationTokens retrieves all impersonation tokens of a user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-all-impersonation-tokens-of-a-user
+func (s *UsersService) GetAllImpersonationTokens(user int, opt *GetAllImpersonationTokensOptions, options ...OptionFunc) ([]*ImpersonationToken, *Response, error) {
+	u := fmt.Sprintf("users/%d/impersonation_tokens", user)
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var ts []*ImpersonationToken
+	resp, err := s.client.Do(req, &ts)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return ts, resp, err
+}
+
+// GetImpersonationToken retrieves an impersonation token of a user.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-an-impersonation-token-of-a-user
+func (s *UsersService) GetImpersonationToken(user, token int, options ...OptionFunc) (*ImpersonationToken, *Response, error) {
+	u := fmt.Sprintf("users/%d/impersonation_tokens/%d", user, token)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t := new(ImpersonationToken)
+	resp, err := s.client.Do(req, &t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// CreateImpersonationTokenOptions represents the available
+// CreateImpersonationToken() options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#create-an-impersonation-token
+type CreateImpersonationTokenOptions struct {
+	Name      *string    `url:"name,omitempty" json:"name,omitempty"`
+	Scopes    *[]string  `url:"scopes,omitempty" json:"scopes,omitempty"`
+	ExpiresAt *time.Time `url:"expires_at,omitempty" json:"expires_at,omitempty"`
+}
+
+// CreateImpersonationToken creates an impersonation token.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#create-an-impersonation-token
+func (s *UsersService) CreateImpersonationToken(user int, opt *CreateImpersonationTokenOptions, options ...OptionFunc) (*ImpersonationToken, *Response, error) {
+	u := fmt.Sprintf("users/%d/impersonation_tokens", user)
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t := new(ImpersonationToken)
+	resp, err := s.client.Do(req, &t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// RevokeImpersonationToken revokes an impersonation token.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#revoke-an-impersonation-token
+func (s *UsersService) RevokeImpersonationToken(user, token int, options ...OptionFunc) (*Response, error) {
+	u := fmt.Sprintf("users/%d/impersonation_tokens/%d", user, token)
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
+
+// UserActivity represents an entry in the user/activities response
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-user-activities-admin-only
+type UserActivity struct {
+	Username       string   `json:"username"`
+	LastActivityOn *ISOTime `json:"last_activity_on"`
+}
+
+// GetUserActivitiesOptions represents the options for GetUserActivities
+//
+// GitLap API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-user-activities-admin-only
+type GetUserActivitiesOptions struct {
+	From *ISOTime `url:"from,omitempty" json:"from,omitempty"`
+}
+
+// GetUserActivities retrieves user activities (admin only)
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-user-activities-admin-only
+func (s *UsersService) GetUserActivities(opt *GetUserActivitiesOptions, options ...OptionFunc) ([]*UserActivity, *Response, error) {
+	req, err := s.client.NewRequest("GET", "user/activities", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var t []*UserActivity
+	resp, err := s.client.Do(req, &t)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return t, resp, err
+}
+
+// UserStatus represents the current status of a user
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#user-status
+type UserStatus struct {
+	Emoji       string `json:"emoji"`
+	Message     string `json:"message"`
+	MessageHTML string `json:"message_html"`
+}
+
+// CurrentUserStatus retrieves the user status
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#user-status
+func (s *UsersService) CurrentUserStatus(options ...OptionFunc) (*UserStatus, *Response, error) {
+	req, err := s.client.NewRequest("GET", "user/status", nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	status := new(UserStatus)
+	resp, err := s.client.Do(req, status)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return status, resp, err
+}
+
+// GetUserStatus retrieves a user's status
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#get-the-status-of-a-user
+func (s *UsersService) GetUserStatus(user int, options ...OptionFunc) (*UserStatus, *Response, error) {
+	u := fmt.Sprintf("users/%d/status", user)
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	status := new(UserStatus)
+	resp, err := s.client.Do(req, status)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return status, resp, err
+}
+
+// UserStatusOptions represents the options required to set the status
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#set-user-status
+type UserStatusOptions struct {
+	Emoji   *string `url:"emoji,omitempty" json:"emoji,omitempty"`
+	Message *string `url:"message,omitempty" json:"message,omitempty"`
+}
+
+// SetUserStatus sets the user's status
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/users.html#set-user-status
+func (s *UsersService) SetUserStatus(opt *UserStatusOptions, options ...OptionFunc) (*UserStatus, *Response, error) {
+	req, err := s.client.NewRequest("PUT", "user/status", opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	status := new(UserStatus)
+	resp, err := s.client.Do(req, status)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return status, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/validate.go b/vendor/github.com/xanzy/go-gitlab/validate.go
new file mode 100644
index 000000000..a88e18840
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/validate.go
@@ -0,0 +1,40 @@
+package gitlab
+
+// ValidateService handles communication with the validation related methods of
+// the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/lint.html
+type ValidateService struct {
+	client *Client
+}
+
+// LintResult represents the linting results.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/lint.html
+type LintResult struct {
+	Status string   `json:"status"`
+	Errors []string `json:"errors"`
+}
+
+// Lint validates .gitlab-ci.yml content.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/lint.html
+func (s *ValidateService) Lint(content string, options ...OptionFunc) (*LintResult, *Response, error) {
+	var opts struct {
+		Content string `url:"content,omitempty" json:"content,omitempty"`
+	}
+	opts.Content = content
+
+	req, err := s.client.NewRequest("POST", "ci/lint", &opts, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	l := new(LintResult)
+	resp, err := s.client.Do(req, l)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return l, resp, nil
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/version.go b/vendor/github.com/xanzy/go-gitlab/version.go
new file mode 100644
index 000000000..f1a3a7f52
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/version.go
@@ -0,0 +1,56 @@
+//
+// Copyright 2017, Andrea Funto'
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package gitlab
+
+// VersionService handles communication with the GitLab server instance to
+// retrieve its version information via the GitLab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/version.md
+type VersionService struct {
+	client *Client
+}
+
+// Version represents a GitLab instance version.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/version.md
+type Version struct {
+	Version  string `json:"version"`
+	Revision string `json:"revision"`
+}
+
+func (s Version) String() string {
+	return Stringify(s)
+}
+
+// GetVersion gets a GitLab server instance version; it is only available to
+// authenticated users.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/version.md
+func (s *VersionService) GetVersion() (*Version, *Response, error) {
+	req, err := s.client.NewRequest("GET", "version", nil, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	v := new(Version)
+	resp, err := s.client.Do(req, v)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return v, resp, err
+}
diff --git a/vendor/github.com/xanzy/go-gitlab/wikis.go b/vendor/github.com/xanzy/go-gitlab/wikis.go
new file mode 100644
index 000000000..06117212d
--- /dev/null
+++ b/vendor/github.com/xanzy/go-gitlab/wikis.go
@@ -0,0 +1,204 @@
+// Copyright 2017, Stany MARCEL
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+)
+
+// WikisService handles communication with the wikis related methods of
+// the Gitlab API.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html
+type WikisService struct {
+	client *Client
+}
+
+// WikiFormat represents the available wiki formats.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html
+type WikiFormat string
+
+// The available wiki formats.
+const (
+	WikiFormatMarkdown WikiFormat = "markdown"
+	WikiFormatRFoc     WikiFormat = "rdoc"
+	WikiFormatASCIIDoc WikiFormat = "asciidoc"
+)
+
+// Wiki represents a GitLab wiki.
+//
+// GitLab API docs: https://docs.gitlab.com/ce/api/wikis.html
+type Wiki struct {
+	Content string     `json:"content"`
+	Format  WikiFormat `json:"format"`
+	Slug    string     `json:"slug"`
+	Title   string     `json:"title"`
+}
+
+func (w Wiki) String() string {
+	return Stringify(w)
+}
+
+// ListWikisOptions represents the available ListWikis options.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#list-wiki-pages
+type ListWikisOptions struct {
+	WithContent *bool `url:"with_content,omitempty" json:"with_content,omitempty"`
+}
+
+// ListWikis lists all pages of the wiki of the given project id.
+// When with_content is set, it also returns the content of the pages.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#list-wiki-pages
+func (s *WikisService) ListWikis(pid interface{}, opt *ListWikisOptions, options ...OptionFunc) ([]*Wiki, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/wikis", pathEscape(project))
+
+	req, err := s.client.NewRequest("GET", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var w []*Wiki
+	resp, err := s.client.Do(req, &w)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return w, resp, err
+}
+
+// GetWikiPage gets a wiki page for a given project.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#get-a-wiki-page
+func (s *WikisService) GetWikiPage(pid interface{}, slug string, options ...OptionFunc) (*Wiki, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/wikis/%s", pathEscape(project), url.PathEscape(slug))
+
+	req, err := s.client.NewRequest("GET", u, nil, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var w *Wiki
+	resp, err := s.client.Do(req, &w)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return w, resp, err
+}
+
+// CreateWikiPageOptions represents options to CreateWikiPage.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#create-a-new-wiki-page
+type CreateWikiPageOptions struct {
+	Content *string `url:"content" json:"content"`
+	Title   *string `url:"title" json:"title"`
+	Format  *string `url:"format,omitempty" json:"format,omitempty"`
+}
+
+// CreateWikiPage creates a new wiki page for the given repository with
+// the given title, slug, and content.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#create-a-new-wiki-page
+func (s *WikisService) CreateWikiPage(pid interface{}, opt *CreateWikiPageOptions, options ...OptionFunc) (*Wiki, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/wikis", pathEscape(project))
+
+	req, err := s.client.NewRequest("POST", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	w := new(Wiki)
+	resp, err := s.client.Do(req, w)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return w, resp, err
+}
+
+// EditWikiPageOptions represents options to EditWikiPage.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#edit-an-existing-wiki-page
+type EditWikiPageOptions struct {
+	Content *string `url:"content" json:"content"`
+	Title   *string `url:"title" json:"title"`
+	Format  *string `url:"format,omitempty" json:"format,omitempty"`
+}
+
+// EditWikiPage Updates an existing wiki page. At least one parameter is
+// required to update the wiki page.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#edit-an-existing-wiki-page
+func (s *WikisService) EditWikiPage(pid interface{}, slug string, opt *EditWikiPageOptions, options ...OptionFunc) (*Wiki, *Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, nil, err
+	}
+	u := fmt.Sprintf("projects/%s/wikis/%s", pathEscape(project), url.PathEscape(slug))
+
+	req, err := s.client.NewRequest("PUT", u, opt, options)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	w := new(Wiki)
+	resp, err := s.client.Do(req, w)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return w, resp, err
+}
+
+// DeleteWikiPage deletes a wiki page with a given slug.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/api/wikis.html#delete-a-wiki-page
+func (s *WikisService) DeleteWikiPage(pid interface{}, slug string, options ...OptionFunc) (*Response, error) {
+	project, err := parseID(pid)
+	if err != nil {
+		return nil, err
+	}
+	u := fmt.Sprintf("projects/%s/wikis/%s", pathEscape(project), url.PathEscape(slug))
+
+	req, err := s.client.NewRequest("DELETE", u, nil, options)
+	if err != nil {
+		return nil, err
+	}
+
+	return s.client.Do(req, nil)
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 729e7f88f..b2317cda2 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -638,6 +638,9 @@ github.com/unknwon/paginater
 github.com/urfave/cli
 # github.com/willf/bitset v1.1.10
 github.com/willf/bitset
+# github.com/xanzy/go-gitlab v0.22.1
+## explicit
+github.com/xanzy/go-gitlab
 # github.com/xanzy/ssh-agent v0.2.1
 github.com/xanzy/ssh-agent
 # github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53
diff --git a/web_src/js/index.js b/web_src/js/index.js
index ad5e115cd..2bf34a956 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -1162,7 +1162,7 @@ function initMigration() {
     const authUserName = $('#auth_username').val();
     const cloneAddr = $('#clone_addr').val();
     if (!$('#mirror').is(':checked') && (authUserName && authUserName.length > 0) &&
-        (cloneAddr !== undefined && (cloneAddr.startsWith('https://github.com') || cloneAddr.startsWith('http://github.com')))) {
+        (cloneAddr !== undefined && (cloneAddr.startsWith('https://github.com') || cloneAddr.startsWith('http://github.com') || cloneAddr.startsWith('http://gitlab.com') || cloneAddr.startsWith('https://gitlab.com')))) {
       $('#migrate_items').show();
     } else {
       $('#migrate_items').hide();