summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG5
-rw-r--r--app/assets/javascripts/issue.js.coffee23
-rw-r--r--app/assets/stylesheets/pages/labels.scss25
-rw-r--r--app/controllers/projects/issues_controller.rb40
-rw-r--r--app/models/project_services/bamboo_service.rb20
-rw-r--r--app/models/project_services/teamcity_service.rb33
-rw-r--r--app/views/projects/issues/show.html.haml8
-rw-r--r--app/views/shared/milestones/_labels_tab.html.haml13
-rw-r--r--app/workers/post_receive.rb2
-rw-r--r--config/routes.rb2
-rw-r--r--doc/api/issues.md109
-rw-r--r--doc/api/merge_requests.md148
-rw-r--r--features/steps/shared/issuable.rb7
-rw-r--r--lib/api/issues.rb36
-rw-r--r--lib/api/merge_requests.rb36
-rw-r--r--lib/gitlab/exclusive_lease.rb5
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb262
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb251
-rw-r--r--spec/requests/api/issues_spec.rb43
-rw-r--r--spec/requests/api/merge_requests_spec.rb42
-rw-r--r--spec/workers/post_receive_spec.rb43
21 files changed, 987 insertions, 166 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 2ab0cc11248..21f24b5b61a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@ v 8.7.0 (unreleased)
- The Projects::HousekeepingService class has extra instrumentation (Yorick Peterse)
- All service classes (those residing in app/services) are now instrumented (Yorick Peterse)
- Developers can now add custom tags to transactions (Yorick Peterse)
+ - Loading of an issue's referenced merge requests and related branches is now done asynchronously (Yorick Peterse)
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea).
@@ -12,6 +13,7 @@ v 8.7.0 (unreleased)
- Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
- Improved Markdown rendering performance !3389 (Yorick Peterse)
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
+ - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
- Expose project badges in project settings
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
@@ -20,11 +22,13 @@ v 8.7.0 (unreleased)
- API: Ability to update a group (Robert Schilling)
- API: Ability to move issues (Robert Schilling)
- Fix Error 500 after renaming a project path (Stan Hu)
+ - Fix a bug whith trailing slash in teamcity_url (Charles May)
- Fix avatar stretching by providing a cropping feature
- API: Expose `subscribed` for issues and merge requests (Robert Schilling)
- Allow SAML to handle external users based on user's information !3530
- Allow Omniauth providers to be marked as `external` !3657
- Add endpoints to archive or unarchive a project !3372
+ - Fix a bug whith trailing slash in bamboo_url
- Add links to CI setup documentation from project settings and builds pages
- Handle nil descriptions in Slack issue messages (Stan Hu)
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
@@ -37,6 +41,7 @@ v 8.7.0 (unreleased)
- API: Delete notes of issues, snippets, and merge requests (Robert Schilling)
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Better errors handling when creating milestones inside groups
+ - Fix high CPU usage when PostReceive receives refs/merge-requests/<id>
- Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 946d83b7bdd..c7d74a12f99 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -10,6 +10,9 @@ class @Issue
@initTaskList()
@initIssueBtnEventListeners()
+ @initMergeRequests()
+ @initRelatedBranches()
+
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
@@ -69,3 +72,23 @@ class @Issue
type: 'PATCH'
url: $('form.js-issuable-update').attr('action')
data: patchData
+
+ initMergeRequests: ->
+ $container = $('#merge-requests')
+
+ $.getJSON($container.data('url'))
+ .error ->
+ new Flash('Failed to load referenced merge requests', 'alert')
+ .success (data) ->
+ if 'html' of data
+ $container.html(data.html)
+
+ initRelatedBranches: ->
+ $container = $('#related-branches')
+
+ $.getJSON($container.data('url'))
+ .error ->
+ new Flash('Failed to load related branches', 'alert')
+ .success (data) ->
+ if 'html' of data
+ $container.html(data.html)
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 3e0a3140be7..da20fa28802 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -79,19 +79,30 @@
color: $white-light;
}
+@mixin labels-mobile {
+ @media (max-width: $screen-xs-min) {
+ display: block;
+ width: 100%;
+ margin-left: 0;
+ padding: 10px 0;
+ }
+}
+
+
.manage-labels-list {
- .prepend-left-10 {
+ .prepend-left-10, .prepend-description-left {
display: inline-block;
width: 40%;
vertical-align: middle;
- @media (max-width: $screen-xs-min) {
- display: block;
- width: 100%;
- margin-left: 0;
- padding: 10px 0;
- }
+ @include labels-mobile;
+ }
+
+ .prepend-description-left {
+ width: 57%;
+
+ @include labels-mobile;
}
.pull-info-right {
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 6d649e72f84..c26cfeccf1d 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions
before_action :module_enabled
- before_action :issue, only: [:edit, :update, :show]
+ before_action :issue,
+ only: [:edit, :update, :show, :referenced_merge_requests, :related_branches]
# Allow read any issue
before_action :authorize_read_issue!, only: [:show]
@@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update]
- # Cross-reference merge requests
- before_action :closed_by_merge_requests, only: [:show]
-
respond_to :html
def index
@@ -65,8 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
- @merge_requests = @issue.referenced_merge_requests(current_user)
- @related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
respond_to do |format|
format.html
@@ -118,15 +114,39 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
+ def referenced_merge_requests
+ @merge_requests = @issue.referenced_merge_requests(current_user)
+ @closed_by_merge_requests = @issue.closed_by_merge_requests(current_user)
+
+ respond_to do |format|
+ format.json do
+ render json: {
+ html: view_to_html_string('projects/issues/_merge_requests')
+ }
+ end
+ end
+ end
+
+ def related_branches
+ merge_requests = @issue.referenced_merge_requests(current_user)
+
+ @related_branches = @issue.related_branches -
+ merge_requests.map(&:source_branch)
+
+ respond_to do |format|
+ format.json do
+ render json: {
+ html: view_to_html_string('projects/issues/_related_branches')
+ }
+ end
+ end
+ end
+
def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end
- def closed_by_merge_requests
- @closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
- end
-
protected
def issue
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 9e7f642180e..060062aaf7a 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -82,17 +82,17 @@ class BambooService < CiService
end
def build_info(sha)
- url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
+ url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s
if username.blank? && password.blank?
- @response = HTTParty.get(parsed_url.to_s, verify: false)
+ @response = HTTParty.get(url, verify: false)
else
- get_url = "#{url}&os_authType=basic"
+ url << '&os_authType=basic'
auth = {
- username: username,
- password: password,
+ username: username,
+ password: password
}
- @response = HTTParty.get(get_url, verify: false, basic_auth: auth)
+ @response = HTTParty.get(url, verify: false, basic_auth: auth)
end
end
@@ -101,11 +101,11 @@ class BambooService < CiService
if @response.code != 200 || @response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page.
- "#{bamboo_url}/browse/#{build_key}"
+ URI.join(bamboo_url, "/browse/#{build_key}").to_s
else
# If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key']
- "#{bamboo_url}/browse/#{result_key}"
+ URI.join(bamboo_url, "/browse/#{result_key}").to_s
end
end
@@ -134,7 +134,7 @@ class BambooService < CiService
return unless supported_events.include?(data[:object_kind])
# Bamboo requires a GET and does not take any data.
- self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
- verify: false)
+ url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s
+ self.class.get(url, verify: false)
end
end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index b8e9416131a..8dceee5e2c5 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -85,13 +85,15 @@ class TeamcityService < CiService
end
def build_info(sha)
- url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\
- "branch:unspecified:any,number:#{sha}")
+ url = URI.join(
+ teamcity_url,
+ "/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}"
+ ).to_s
auth = {
username: username,
- password: password,
+ password: password
}
- @response = HTTParty.get("#{url}", verify: false, basic_auth: auth)
+ @response = HTTParty.get(url, verify: false, basic_auth: auth)
end
def build_page(sha, ref)
@@ -100,12 +102,14 @@ class TeamcityService < CiService
if @response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
- "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}"
+ URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s
else
# If actual build link is available, go to build result page.
built_id = @response['build']['id']
- "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\
- "&buildTypeId=#{build_type}"
+ URI.join(
+ teamcity_url,
+ "/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}"
+ ).to_s
end
end
@@ -140,12 +144,13 @@ class TeamcityService < CiService
branch = Gitlab::Git.ref_name(data[:ref])
- self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
- body: "<build branchName=\"#{branch}\">"\
- "<buildType id=\"#{build_type}\"/>"\
- '</build>',
- headers: { 'Content-type' => 'application/xml' },
- basic_auth: auth
- )
+ self.class.post(
+ URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s,
+ body: "<build branchName=\"#{branch}\">"\
+ "<buildType id=\"#{build_type}\"/>"\
+ '</build>',
+ headers: { 'Content-type' => 'application/xml' },
+ basic_auth: auth
+ )
end
end
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 6fa059cbe68..5fe5ddc0819 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -64,9 +64,11 @@
= @issue.description
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
- .merge-requests
- = render 'merge_requests'
- = render 'related_branches'
+ #merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)}
+ // This element is filled in using JavaScript.
+
+ #related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)}
+ // This element is filled in using JavaScript.
.content-block.content-block-small
= render 'new_branch'
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
index 868b2357003..b15e8ea73fe 100644
--- a/app/views/shared/milestones/_labels_tab.html.haml
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -4,15 +4,16 @@
%li
%span.label-row
- = link_to milestones_label_path(options) do
- - render_colored_label(label, tooltip: false)
- %span.prepend-left-10
+ %span.label-name
+ = link_to milestones_label_path(options) do
+ - render_colored_label(label, tooltip: false)
+ %span.prepend-description-left
= markdown(label.description, pipeline: :single_line)
- .pull-right
- %strong.issues-count
+ .pull-info-right
+ %span.append-right-20
= link_to milestones_label_path(options.merge(state: 'opened')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
- %strong.issues-count
+ %span.append-right-20
= link_to milestones_label_path(options.merge(state: 'closed')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 3cc232ef1ae..9e1215b21a6 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -40,7 +40,7 @@ class PostReceive
if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref)
- else
+ elsif Gitlab::Git.branch_ref?(ref)
GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end
end
diff --git a/config/routes.rb b/config/routes.rb
index 48601b7567b..688b83d2c95 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -701,6 +701,8 @@ Rails.application.routes.draw do
resources :issues, constraints: { id: /\d+/ } do
member do
post :toggle_subscription
+ get :referenced_merge_requests
+ get :related_branches
end
collection do
post :bulk_update
diff --git a/doc/api/issues.md b/doc/api/issues.md
index f09847aef95..42024becc36 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -406,6 +406,115 @@ Example response:
}
```
+## Subscribe to an issue
+
+Subscribes the authenticated user to an issue to receive notifications. If the
+operation is successful, status code `201` together with the updated issue is
+returned. If the user is already subscribed to the issue, the status code `304`
+is returned. If the project or issue is not found, status code `404` is
+returned.
+
+```
+POST /projects/:id/issues/:issue_id/subscription
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_id` | integer | yes | The ID of a project's issue |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription
+```
+
+Example response:
+
+```json
+{
+ "id": 92,
+ "iid": 11,
+ "project_id": 5,
+ "title": "Sit voluptas tempora quisquam aut doloribus et.",
+ "description": "Repellat voluptas quibusdam voluptatem exercitationem.",
+ "state": "opened",
+ "created_at": "2016-04-05T21:41:45.652Z",
+ "updated_at": "2016-04-07T12:20:17.596Z",
+ "labels": [],
+ "milestone": null,
+ "assignee": {
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/axel.block"
+ },
+ "author": {
+ "name": "Kris Steuber",
+ "username": "solon.cremin",
+ "id": 10,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/solon.cremin"
+ }
+}
+```
+
+## Unsubscribe from an issue
+
+Unsubscribes the authenticated user from the issue to not receive notifications
+from it. If the operation is successful, status code `200` together with the
+updated issue is returned. If the user is not subscribed to the issue, the
+status code `304` is returned. If the project or issue is not found, status code
+`404` is returned.
+
+```
+DELETE /projects/:id/issues/:issue_id/subscription
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_id` | integer | yes | The ID of a project's issue |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription
+```
+
+Example response:
+
+```json
+{
+ "id": 93,
+ "iid": 12,
+ "project_id": 5,
+ "title": "Incidunt et rerum ea expedita iure quibusdam.",
+ "description": "Et cumque architecto sed aut ipsam.",
+ "state": "opened",
+ "created_at": "2016-04-05T21:41:45.217Z",
+ "updated_at": "2016-04-07T13:02:37.905Z",
+ "labels": [],
+ "milestone": null,
+ "assignee": {
+ "name": "Edwardo Grady",
+ "username": "keyon",
+ "id": 21,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/3e6f06a86cf27fa8b56f3f74f7615987?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/keyon"
+ },
+ "author": {
+ "name": "Vivian Hermann",
+ "username": "orville",
+ "id": 11,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon",
+ "web_url": "http://lgitlab.example.com/u/orville"
+ },
+ "subscribed": false
+}
+```
+
## Comments on issues
Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 20db73ea6c0..2057f9d77aa 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -606,3 +606,151 @@ Example response:
},
]
```
+
+## Subscribe to a merge request
+
+Subscribes the authenticated user to a merge request to receive notification. If
+the operation is successful, status code `201` together with the updated merge
+request is returned. If the user is already subscribed to the merge request, the
+status code `304` is returned. If the project or merge request is not found,
+status code `404` is returned.
+
+```
+POST /projects/:id/merge_requests/:merge_request_id/subscription
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_id` | integer | yes | The ID of the merge request |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription
+```
+
+Example response:
+
+```json
+{
+ "id": 17,
+ "iid": 1,
+ "project_id": 5,
+ "title": "Et et sequi est impedit nulla ut rem et voluptatem.",
+ "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.",
+ "state": "opened",
+ "created_at": "2016-04-05T21:42:23.233Z",
+ "updated_at": "2016-04-05T22:11:52.900Z",
+ "target_branch": "ui-dev-kit",
+ "source_branch": "version-1-9",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "name": "Eileen Skiles",
+ "username": "leila",
+ "id": 19,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/leila"
+ },
+ "assignee": {
+ "name": "Celine Wehner",
+ "username": "carli",
+ "id": 16,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/carli"
+ },
+ "source_project_id": 5,
+ "target_project_id": 5,
+ "labels": [],
+ "work_in_progress": false,
+ "milestone": {
+ "id": 7,
+ "iid": 1,
+ "project_id": 5,
+ "title": "v2.0",
+ "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.",
+ "state": "closed",
+ "created_at": "2016-04-05T21:41:40.905Z",
+ "updated_at": "2016-04-05T21:41:40.905Z",
+ "due_date": null
+ },
+ "merge_when_build_succeeds": false,
+ "merge_status": "cannot_be_merged",
+ "subscribed": true
+}
+```
+
+## Unsubscribe from a merge request
+
+Unsubscribes the authenticated user from a merge request to not receive
+notifications from that merge request. If the operation is successful, status
+code `200` together with the updated merge request is returned. If the user is
+not subscribed to the merge request, the status code `304` is returned. If the
+project or merge request is not found, status code `404` is returned.
+
+```
+DELETE /projects/:id/merge_requests/:merge_request_id/subscription
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_id` | integer | yes | The ID of the merge request |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription
+```
+
+Example response:
+
+```json
+{
+ "id": 17,
+ "iid": 1,
+ "project_id": 5,
+ "title": "Et et sequi est impedit nulla ut rem et voluptatem.",
+ "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.",
+ "state": "opened",
+ "created_at": "2016-04-05T21:42:23.233Z",
+ "updated_at": "2016-04-05T22:11:52.900Z",
+ "target_branch": "ui-dev-kit",
+ "source_branch": "version-1-9",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "name": "Eileen Skiles",
+ "username": "leila",
+ "id": 19,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/leila"
+ },
+ "assignee": {
+ "name": "Celine Wehner",
+ "username": "carli",
+ "id": 16,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/carli"
+ },
+ "source_project_id": 5,
+ "target_project_id": 5,
+ "labels": [],
+ "work_in_progress": false,
+ "milestone": {
+ "id": 7,
+ "iid": 1,
+ "project_id": 5,
+ "title": "v2.0",
+ "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.",
+ "state": "closed",
+ "created_at": "2016-04-05T21:41:40.905Z",
+ "updated_at": "2016-04-05T21:41:40.905Z",
+ "due_date": null
+ },
+ "merge_when_build_succeeds": false,
+ "merge_status": "cannot_be_merged",
+ "subscribed": false
+}
+```
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index b6d70a26c21..24b3fb6eacb 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -71,13 +71,16 @@ module SharedIssuable
step 'I should not see any related merge requests' do
page.within '.issue-details' do
- expect(page).not_to have_content('.merge-requests')
+ expect(page).not_to have_content('#merge-requests .merge-requests-title')
end
end
step 'I should see the "Enterprise fix" related merge request' do
- page.within '.merge-requests' do
+ page.within '#merge-requests .merge-requests-title' do
expect(page).to have_content('1 Related Merge Request')
+ end
+
+ page.within '#merge-requests ul' do
expect(page).to have_content('Enterprise fix')
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 850e99981ff..4cdecadfe0f 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -231,6 +231,42 @@ module API
authorize!(:destroy_issue, issue)
issue.destroy
end
+
+ # Subscribes to a project issue
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # issue_id (required) - The ID of a project issue
+ # Example Request:
+ # POST /projects/:id/issues/:issue_id/subscription
+ post ':id/issues/:issue_id/subscription' do
+ issue = user_project.issues.find(params[:issue_id])
+
+ if issue.subscribed?(current_user)
+ not_modified!
+ else
+ issue.toggle_subscription(current_user)
+ present issue, with: Entities::Issue, current_user: current_user
+ end
+ end
+
+ # Unsubscribes from a project issue
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # issue_id (required) - The ID of a project issue
+ # Example Request:
+ # DELETE /projects/:id/issues/:issue_id/subscription
+ delete ':id/issues/:issue_id/subscription' do
+ issue = user_project.issues.find(params[:issue_id])
+
+ if issue.subscribed?(current_user)
+ issue.unsubscribe(current_user)
+ present issue, with: Entities::Issue, current_user: current_user
+ else
+ not_modified!
+ end
+ end
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 4e7de8867b4..7e78609ecb9 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -327,6 +327,42 @@ module API
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: Entities::Issue, current_user: current_user
end
+
+ # Subscribes to a merge request
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of a merge request
+ # Example Request:
+ # POST /projects/:id/issues/:merge_request_id/subscription
+ post "#{path}/subscription" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ if merge_request.subscribed?(current_user)
+ not_modified!
+ else
+ merge_request.toggle_subscription(current_user)
+ present merge_request, with: Entities::MergeRequest, current_user: current_user
+ end
+ end
+
+ # Unsubscribes from a merge request
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of a merge request
+ # Example Request:
+ # DELETE /projects/:id/merge_requests/:merge_request_id/subscription
+ delete "#{path}/subscription" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ if merge_request.subscribed?(current_user)
+ merge_request.unsubscribe(current_user)
+ present merge_request, with: Entities::MergeRequest, current_user: current_user
+ else
+ not_modified!
+ end
+ end
end
end
end
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index c2260a5f7ac..ffe49364379 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -52,11 +52,6 @@ module Gitlab
private
- def redis
- # Maybe someday we want to use a connection pool...
- @redis ||= Redis.new(url: Gitlab::RedisConfig.url)
- end
-
def redis_key
"gitlab:exclusive_lease:#{@key}"
end
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index c34b2487ecf..31b2c90122d 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -21,74 +21,232 @@
require 'spec_helper'
describe BambooService, models: true do
- describe "Associations" do
+ describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
- describe "Execute" do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
-
- context "when a password was previously set" do
- before do
- @bamboo_service = BambooService.create(
- project: create(:project),
- properties: {
- bamboo_url: 'http://gitlab.com',
- username: 'mic',
- password: "password"
- }
- )
+ describe 'Validations' do
+ describe '#bamboo_url' do
+ it 'does not validate the presence of bamboo_url if service is not active' do
+ bamboo_service = service
+ bamboo_service.active = false
+
+ expect(bamboo_service).not_to validate_presence_of(:bamboo_url)
+ end
+
+ it 'validates the presence of bamboo_url if service is active' do
+ bamboo_service = service
+ bamboo_service.active = true
+
+ expect(bamboo_service).to validate_presence_of(:bamboo_url)
+ end
+ end
+
+ describe '#build_key' do
+ it 'does not validate the presence of build_key if service is not active' do
+ bamboo_service = service
+ bamboo_service.active = false
+
+ expect(bamboo_service).not_to validate_presence_of(:build_key)
end
-
- it "reset password if url changed" do
- @bamboo_service.bamboo_url = 'http://gitlab1.com'
- @bamboo_service.save
- expect(@bamboo_service.password).to be_nil
+
+ it 'validates the presence of build_key if service is active' do
+ bamboo_service = service
+ bamboo_service.active = true
+
+ expect(bamboo_service).to validate_presence_of(:build_key)
+ end
+ end
+
+ describe '#username' do
+ it 'does not validate the presence of username if service is not active' do
+ bamboo_service = service
+ bamboo_service.active = false
+
+ expect(bamboo_service).not_to validate_presence_of(:username)
+ end
+
+ it 'does not validate the presence of username if username is nil' do
+ bamboo_service = service
+ bamboo_service.active = true
+ bamboo_service.password = nil
+
+ expect(bamboo_service).not_to validate_presence_of(:username)
+ end
+
+ it 'validates the presence of username if service is active and username is present' do
+ bamboo_service = service
+ bamboo_service.active = true
+ bamboo_service.password = 'secret'
+
+ expect(bamboo_service).to validate_presence_of(:username)
end
-
- it "does not reset password if username changed" do
- @bamboo_service.username = "some_name"
- @bamboo_service.save
- expect(@bamboo_service.password).to eq("password")
+ end
+
+ describe '#password' do
+ it 'does not validate the presence of password if service is not active' do
+ bamboo_service = service
+ bamboo_service.active = false
+
+ expect(bamboo_service).not_to validate_presence_of(:password)
end
- it "does not reset password if new url is set together with password, even if it's the same password" do
- @bamboo_service.bamboo_url = 'http://gitlab_edited.com'
- @bamboo_service.password = 'password'
- @bamboo_service.save
- expect(@bamboo_service.password).to eq("password")
- expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com")
+ it 'does not validate the presence of password if username is nil' do
+ bamboo_service = service
+ bamboo_service.active = true
+ bamboo_service.username = nil
+
+ expect(bamboo_service).not_to validate_presence_of(:password)
end
- it "should reset password if url changed, even if setter called multiple times" do
- @bamboo_service.bamboo_url = 'http://gitlab1.com'
- @bamboo_service.bamboo_url = 'http://gitlab1.com'
- @bamboo_service.save
- expect(@bamboo_service.password).to be_nil
+ it 'validates the presence of password if service is active and username is present' do
+ bamboo_service = service
+ bamboo_service.active = true
+ bamboo_service.username = 'john'
+
+ expect(bamboo_service).to validate_presence_of(:password)
end
end
-
- context "when no password was previously set" do
- before do
- @bamboo_service = BambooService.create(
- project: create(:project),
- properties: {
- bamboo_url: 'http://gitlab.com',
- username: 'mic'
- }
- )
+ end
+
+ describe 'Callbacks' do
+ describe 'before_update :reset_password' do
+ context 'when a password was previously set' do
+ it 'resets password if url changed' do
+ bamboo_service = service
+
+ bamboo_service.bamboo_url = 'http://gitlab1.com'
+ bamboo_service.save
+
+ expect(bamboo_service.password).to be_nil
+ end
+
+ it 'does not reset password if username changed' do
+ bamboo_service = service
+
+ bamboo_service.username = 'some_name'
+ bamboo_service.save
+
+ expect(bamboo_service.password).to eq('password')
+ end
+
+ it "does not reset password if new url is set together with password, even if it's the same password" do
+ bamboo_service = service
+
+ bamboo_service.bamboo_url = 'http://gitlab_edited.com'
+ bamboo_service.password = 'password'
+ bamboo_service.save
+
+ expect(bamboo_service.password).to eq('password')
+ expect(bamboo_service.bamboo_url).to eq('http://gitlab_edited.com')
+ end
end
- it "saves password if new url is set together with password" do
- @bamboo_service.bamboo_url = 'http://gitlab_edited.com'
- @bamboo_service.password = 'password'
- @bamboo_service.save
- expect(@bamboo_service.password).to eq("password")
- expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com")
+ it 'saves password if new url is set together with password when no password was previously set' do
+ bamboo_service = service
+ bamboo_service.password = nil
+
+ bamboo_service.bamboo_url = 'http://gitlab_edited.com'
+ bamboo_service.password = 'password'
+ bamboo_service.save
+
+ expect(bamboo_service.password).to eq('password')
+ expect(bamboo_service.bamboo_url).to eq('http://gitlab_edited.com')
end
+ end
+ end
+
+ describe '#build_page' do
+ it 'returns a specific URL when status is 500' do
+ stub_request(status: 500)
+
+ expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/browse/foo')
+ end
+
+ it 'returns a specific URL when response has no results' do
+ stub_request(body: %Q({"results":{"results":{"size":"0"}}}))
+
+ expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/browse/foo')
+ end
+
+ it 'returns a build URL when bamboo_url has no trailing slash' do
+ stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}}))
+
+ expect(service(bamboo_url: 'http://gitlab.com').build_page('123', 'unused')).to eq('http://gitlab.com/browse/42')
+ end
+
+ it 'returns a build URL when bamboo_url has a trailing slash' do
+ stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}}))
+
+ expect(service(bamboo_url: 'http://gitlab.com/').build_page('123', 'unused')).to eq('http://gitlab.com/browse/42')
+ end
+ end
+
+ describe '#commit_status' do
+ it 'sets commit status to :error when status is 500' do
+ stub_request(status: 500)
+
+ expect(service.commit_status('123', 'unused')).to eq(:error)
+ end
+
+ it 'sets commit status to "pending" when status is 404' do
+ stub_request(status: 404)
+
+ expect(service.commit_status('123', 'unused')).to eq('pending')
+ end
+
+ it 'sets commit status to "pending" when response has no results' do
+ stub_request(body: %Q({"results":{"results":{"size":"0"}}}))
+
+ expect(service.commit_status('123', 'unused')).to eq('pending')
+ end
+
+ it 'sets commit status to "success" when build state contains Success' do
+ stub_request(build_state: 'YAY Success!')
+ expect(service.commit_status('123', 'unused')).to eq('success')
end
+
+ it 'sets commit status to "failed" when build state contains Failed' do
+ stub_request(build_state: 'NO Failed!')
+
+ expect(service.commit_status('123', 'unused')).to eq('failed')
+ end
+
+ it 'sets commit status to "pending" when build state contains Pending' do
+ stub_request(build_state: 'NO Pending!')
+
+ expect(service.commit_status('123', 'unused')).to eq('pending')
+ end
+
+ it 'sets commit status to :error when build state is unknown' do
+ stub_request(build_state: 'FOO BAR!')
+
+ expect(service.commit_status('123', 'unused')).to eq(:error)
+ end
+ end
+
+ def service(bamboo_url: 'http://gitlab.com')
+ described_class.create(
+ project: build_stubbed(:empty_project),
+ properties: {
+ bamboo_url: bamboo_url,
+ username: 'mic',
+ password: 'password',
+ build_key: 'foo'
+ }
+ )
+ end
+
+ def stub_request(status: 200, body: nil, build_state: 'success')
+ bamboo_full_url = 'http://mic:password@gitlab.com/rest/api/latest/result?label=123&os_authType=basic'
+ body ||= %Q({"results":{"results":{"result":{"buildState":"#{build_state}"}}}})
+
+ WebMock.stub_request(:get, bamboo_full_url).to_return(
+ status: status,
+ headers: { 'Content-Type' => 'application/json' },
+ body: body
+ )
end
end
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index f26b47a856c..bc7423cee69 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -21,73 +21,220 @@
require 'spec_helper'
describe TeamcityService, models: true do
- describe "Associations" do
+ describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
- describe "Execute" do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
-
- context "when a password was previously set" do
- before do
- @teamcity_service = TeamcityService.create(
- project: create(:project),
- properties: {
- teamcity_url: 'http://gitlab.com',
- username: 'mic',
- password: "password"
- }
- )
+ describe 'Validations' do
+ describe '#teamcity_url' do
+ it 'does not validate the presence of teamcity_url if service is not active' do
+ teamcity_service = service
+ teamcity_service.active = false
+
+ expect(teamcity_service).not_to validate_presence_of(:teamcity_url)
end
-
- it "reset password if url changed" do
- @teamcity_service.teamcity_url = 'http://gitlab1.com'
- @teamcity_service.save
- expect(@teamcity_service.password).to be_nil
+
+ it 'validates the presence of teamcity_url if service is active' do
+ teamcity_service = service
+ teamcity_service.active = true
+
+ expect(teamcity_service).to validate_presence_of(:teamcity_url)
+ end
+ end
+
+ describe '#build_type' do
+ it 'does not validate the presence of build_type if service is not active' do
+ teamcity_service = service
+ teamcity_service.active = false
+
+ expect(teamcity_service).not_to validate_presence_of(:build_type)
+ end
+
+ it 'validates the presence of build_type if service is active' do
+ teamcity_service = service
+ teamcity_service.active = true
+
+ expect(teamcity_service).to validate_presence_of(:build_type)
end
-
- it "does not reset password if username changed" do
- @teamcity_service.username = "some_name"
- @teamcity_service.save
- expect(@teamcity_service.password).to eq("password")
+ end
+
+ describe '#username' do
+ it 'does not validate the presence of username if service is not active' do
+ teamcity_service = service
+ teamcity_service.active = false
+
+ expect(teamcity_service).not_to validate_presence_of(:username)
end
- it "does not reset password if new url is set together with password, even if it's the same password" do
- @teamcity_service.teamcity_url = 'http://gitlab_edited.com'
- @teamcity_service.password = 'password'
- @teamcity_service.save
- expect(@teamcity_service.password).to eq("password")
- expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com")
+ it 'does not validate the presence of username if username is nil' do
+ teamcity_service = service
+ teamcity_service.active = true
+ teamcity_service.password = nil
+
+ expect(teamcity_service).not_to validate_presence_of(:username)
end
- it "should reset password if url changed, even if setter called multiple times" do
- @teamcity_service.teamcity_url = 'http://gitlab1.com'
- @teamcity_service.teamcity_url = 'http://gitlab1.com'
- @teamcity_service.save
- expect(@teamcity_service.password).to be_nil
+ it 'validates the presence of username if service is active and username is present' do
+ teamcity_service = service
+ teamcity_service.active = true
+ teamcity_service.password = 'secret'
+
+ expect(teamcity_service).to validate_presence_of(:username)
end
end
-
- context "when no password was previously set" do
- before do
- @teamcity_service = TeamcityService.create(
- project: create(:project),
- properties: {
- teamcity_url: 'http://gitlab.com',
- username: 'mic'
- }
- )
+
+ describe '#password' do
+ it 'does not validate the presence of password if service is not active' do
+ teamcity_service = service
+ teamcity_service.active = false
+
+ expect(teamcity_service).not_to validate_presence_of(:password)
+ end
+
+ it 'does not validate the presence of password if username is nil' do
+ teamcity_service = service
+ teamcity_service.active = true
+ teamcity_service.username = nil
+
+ expect(teamcity_service).not_to validate_presence_of(:password)
end
- it "saves password if new url is set together with password" do
- @teamcity_service.teamcity_url = 'http://gitlab_edited.com'
- @teamcity_service.password = 'password'
- @teamcity_service.save
- expect(@teamcity_service.password).to eq("password")
- expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com")
+ it 'validates the presence of password if service is active and username is present' do
+ teamcity_service = service
+ teamcity_service.active = true
+ teamcity_service.username = 'john'
+
+ expect(teamcity_service).to validate_presence_of(:password)
end
end
end
+
+ describe 'Callbacks' do
+ describe 'before_update :reset_password' do
+ context 'when a password was previously set' do
+ it 'resets password if url changed' do
+ teamcity_service = service
+
+ teamcity_service.teamcity_url = 'http://gitlab1.com'
+ teamcity_service.save
+
+ expect(teamcity_service.password).to be_nil
+ end
+
+ it 'does not reset password if username changed' do
+ teamcity_service = service
+
+ teamcity_service.username = 'some_name'
+ teamcity_service.save
+
+ expect(teamcity_service.password).to eq('password')
+ end
+
+ it "does not reset password if new url is set together with password, even if it's the same password" do
+ teamcity_service = service
+
+ teamcity_service.teamcity_url = 'http://gitlab_edited.com'
+ teamcity_service.password = 'password'
+ teamcity_service.save
+
+ expect(teamcity_service.password).to eq('password')
+ expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com')
+ end
+ end
+
+ it 'saves password if new url is set together with password when no password was previously set' do
+ teamcity_service = service
+ teamcity_service.password = nil
+
+ teamcity_service.teamcity_url = 'http://gitlab_edited.com'
+ teamcity_service.password = 'password'
+ teamcity_service.save
+
+ expect(teamcity_service.password).to eq('password')
+ expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com')
+ end
+ end
+ end
+
+ describe '#build_page' do
+ it 'returns a specific URL when status is 500' do
+ stub_request(status: 500)
+
+ expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildTypeId=foo')
+ end
+
+ it 'returns a build URL when teamcity_url has no trailing slash' do
+ stub_request(body: %Q({"build":{"id":"666"}}))
+
+ expect(service(teamcity_url: 'http://gitlab.com').build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildId=666&buildTypeId=foo')
+ end
+
+ it 'returns a build URL when teamcity_url has a trailing slash' do
+ stub_request(body: %Q({"build":{"id":"666"}}))
+
+ expect(service(teamcity_url: 'http://gitlab.com/').build_page('123', 'unused')).to eq('http://gitlab.com/viewLog.html?buildId=666&buildTypeId=foo')
+ end
+ end
+
+ describe '#commit_status' do
+ it 'sets commit status to :error when status is 500' do
+ stub_request(status: 500)
+
+ expect(service.commit_status('123', 'unused')).to eq(:error)
+ end
+
+ it 'sets commit status to "pending" when status is 404' do
+ stub_request(status: 404)
+
+ expect(service.commit_status('123', 'unused')).to eq('pending')
+ end
+
+ it 'sets commit status to "success" when build status contains SUCCESS' do
+ stub_request(build_status: 'YAY SUCCESS!')
+
+ expect(service.commit_status('123', 'unused')).to eq('success')
+ end
+
+ it 'sets commit status to "failed" when build status contains FAILURE' do
+ stub_request(build_status: 'NO FAILURE!')
+
+ expect(service.commit_status('123', 'unused')).to eq('failed')
+ end
+
+ it 'sets commit status to "pending" when build status contains Pending' do
+ stub_request(build_status: 'NO Pending!')
+
+ expect(service.commit_status('123', 'unused')).to eq('pending')
+ end
+
+ it 'sets commit status to :error when build status is unknown' do
+ stub_request(build_status: 'FOO BAR!')
+
+ expect(service.commit_status('123', 'unused')).to eq(:error)
+ end
+ end
+
+ def service(teamcity_url: 'http://gitlab.com')
+ described_class.create(
+ project: build_stubbed(:empty_project),
+ properties: {
+ teamcity_url: teamcity_url,
+ username: 'mic',
+ password: 'password',
+ build_type: 'foo'
+ }
+ )
+ end
+
+ def stub_request(status: 200, body: nil, build_status: 'success')
+ teamcity_full_url = 'http://mic:password@gitlab.com/httpAuth/app/rest/builds/branch:unspecified:any,number:123'
+ body ||= %Q({"build":{"status":"#{build_status}","id":"666"}})
+
+ WebMock.stub_request(:get, teamcity_full_url).to_return(
+ status: status,
+ headers: { 'Content-Type' => 'application/json' },
+ body: body
+ )
+ end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 3d7a31cbb6a..86ea223f206 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
+ let(:user2) { create(:user) }
let(:non_member) { create(:user) }
let(:author) { create(:author) }
let(:assignee) { create(:assignee) }
@@ -569,4 +570,46 @@ describe API::API, api: true do
end
end
end
+
+ describe 'POST :id/issues/:issue_id/subscription' do
+ it 'subscribes to an issue' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2)
+
+ expect(response.status).to eq(201)
+ expect(json_response['subscribed']).to eq(true)
+ end
+
+ it 'returns 304 if already subscribed' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user)
+
+ expect(response.status).to eq(304)
+ end
+
+ it 'returns 404 if the issue is not found' do
+ post api("/projects/#{project.id}/issues/123/subscription", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ describe 'DELETE :id/issues/:issue_id/subscription' do
+ it 'unsubscribes from an issue' do
+ delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['subscribed']).to eq(false)
+ end
+
+ it 'returns 304 if not subscribed' do
+ delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2)
+
+ expect(response.status).to eq(304)
+ end
+
+ it 'returns 404 if the issue is not found' do
+ delete api("/projects/#{project.id}/issues/123/subscription", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 25fa30b2f21..1fa7e76894f 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -516,6 +516,48 @@ describe API::API, api: true do
end
end
+ describe 'POST :id/merge_requests/:merge_request_id/subscription' do
+ it 'subscribes to a merge request' do
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin)
+
+ expect(response.status).to eq(201)
+ expect(json_response['subscribed']).to eq(true)
+ end
+
+ it 'returns 304 if already subscribed' do
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user)
+
+ expect(response.status).to eq(304)
+ end
+
+ it 'returns 404 if the merge request is not found' do
+ post api("/projects/#{project.id}/merge_requests/123/subscription", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do
+ it 'unsubscribes from a merge request' do
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['subscribed']).to eq(false)
+ end
+
+ it 'returns 304 if not subscribed' do
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin)
+
+ expect(response.status).to eq(304)
+ end
+
+ it 'returns 404 if the merge request is not found' do
+ post api("/projects/#{project.id}/merge_requests/123/subscription", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
def mr_with_later_created_and_updated_at_time
merge_request
merge_request.created_at += 1.hour
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 0265dbe9c66..94ff3457902 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -4,6 +4,9 @@ describe PostReceive do
let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
+ let(:project) { create(:project) }
+ let(:key) { create(:key, user: project.owner) }
+ let(:key_id) { key.shell_id }
context "as a resque worker" do
it "reponds to #perform" do
@@ -11,11 +14,43 @@ describe PostReceive do
end
end
- context "webhook" do
- let(:project) { create(:project) }
- let(:key) { create(:key, user: project.owner) }
- let(:key_id) { key.shell_id }
+ describe "#process_project_changes" do
+ before do
+ allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner)
+ end
+ context "branches" do
+ let(:changes) { "123456 789012 refs/heads/tést" }
+
+ it "should call GitTagPushService" do
+ expect_any_instance_of(GitPushService).to receive(:execute).and_return(true)
+ expect_any_instance_of(GitTagPushService).not_to receive(:execute)
+ PostReceive.new.perform(pwd(project), key_id, base64_changes)
+ end
+ end
+
+ context "tags" do
+ let(:changes) { "123456 789012 refs/tags/tag" }
+
+ it "should call GitTagPushService" do
+ expect_any_instance_of(GitPushService).not_to receive(:execute)
+ expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true)
+ PostReceive.new.perform(pwd(project), key_id, base64_changes)
+ end
+ end
+
+ context "merge-requests" do
+ let(:changes) { "123456 789012 refs/merge-requests/123" }
+
+ it "should not call any of the services" do
+ expect_any_instance_of(GitPushService).not_to receive(:execute)
+ expect_any_instance_of(GitTagPushService).not_to receive(:execute)
+ PostReceive.new.perform(pwd(project), key_id, base64_changes)
+ end
+ end
+ end
+
+ context "webhook" do
it "fetches the correct project" do
expect(Project).to receive(:find_with_namespace).with(project.path_with_namespace).and_return(project)
PostReceive.new.perform(pwd(project), key_id, base64_changes)