summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG27
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee63
-rw-r--r--app/assets/javascripts/users_select.js.coffee3
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss2
-rw-r--r--app/assets/stylesheets/framework/mobile.scss7
-rw-r--r--app/assets/stylesheets/pages/commits.scss5
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss7
-rw-r--r--app/assets/stylesheets/pages/issuable.scss51
-rw-r--r--app/assets/stylesheets/pages/issues.scss40
-rw-r--r--app/controllers/autocomplete_controller.rb9
-rw-r--r--app/controllers/help_controller.rb1
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/helpers/issuables_helper.rb9
-rw-r--r--app/helpers/selects_helper.rb16
-rw-r--r--app/models/external_issue.rb6
-rw-r--r--app/models/project.rb33
-rw-r--r--app/models/project_import_data.rb14
-rw-r--r--app/models/repository.rb7
-rw-r--r--app/services/projects/participants_service.rb43
-rw-r--r--app/views/help/ui.html.haml12
-rw-r--r--app/views/projects/_builds_settings.html.haml3
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml127
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml32
-rw-r--r--app/views/projects/merge_requests/_show.html.haml6
-rw-r--r--app/views/projects/merge_requests/dropdowns/_branch.html.haml5
-rw-r--r--app/views/projects/merge_requests/dropdowns/_project.html.haml5
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml59
-rw-r--r--app/views/projects/merge_requests/update_branches.html.haml8
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb7
-rw-r--r--db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb131
-rw-r--r--db/schema.rb3
-rw-r--r--doc/ci/quick_start/README.md2
-rw-r--r--doc/update/8.5-to-8.6.md2
-rw-r--r--features/steps/shared/issuable.rb2
-rw-r--r--lib/gitlab/backend/shell.rb53
-rw-r--r--lib/gitlab/bitbucket_import/client.rb15
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb5
-rw-r--r--lib/gitlab/bitbucket_import/key_deleter.rb5
-rw-r--r--lib/gitlab/bitbucket_import/project_creator.rb3
-rw-r--r--lib/gitlab/fogbugz_import/importer.rb23
-rw-r--r--lib/gitlab/fogbugz_import/project_creator.rb8
-rw-r--r--lib/gitlab/github_import/importer.rb14
-rw-r--r--lib/gitlab/github_import/project_creator.rb5
-rw-r--r--lib/gitlab/gitlab_import/importer.rb11
-rw-r--r--lib/gitlab/gitlab_import/project_creator.rb1
-rw-r--r--lib/gitlab/google_code_import/project_creator.rb7
-rw-r--r--lib/gitlab/import_url.rb41
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb24
-rw-r--r--spec/features/issues/move_spec.rb10
-rw-r--r--spec/features/participants_autocomplete_spec.rb98
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb8
-rw-r--r--spec/lib/gitlab/github_import/project_creator_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/wiki_formatter_spec.rb7
-rw-r--r--spec/lib/gitlab/import_url_spec.rb21
-rw-r--r--spec/models/external_issue_spec.rb15
-rw-r--r--spec/models/repository_spec.rb6
-rw-r--r--spec/services/delete_tag_service_spec.rb11
59 files changed, 772 insertions, 378 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 69b464bdc6b..de520330781 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,12 +1,12 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased)
- - The Projects::HousekeepingService class has extra instrumentation (Yorick Peterse)
- - Fix revoking of authorized OAuth applications (Connor Shea)
- - 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)
+ - The Projects::HousekeepingService class has extra instrumentation
+ - All service classes (those residing in app/services) are now instrumented
+ - Developers can now add custom tags to transactions
+ - Loading of an issue's referenced merge requests and related branches is now done asynchronously
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
+ - Project switcher uses new dropdown styling
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea).
@@ -14,7 +14,7 @@ v 8.7.0 (unreleased)
- Add setting for customizing the list of trusted proxies !3524
- Allow projects to be transfered to a lower visibility level group
- Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
- - Improved Markdown rendering performance !3389 (Yorick Peterse)
+ - Improved Markdown rendering performance !3389
- 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
@@ -42,6 +42,7 @@ v 8.7.0 (unreleased)
- Add default scope to projects to exclude projects pending deletion
- Allow to close merge requests which source projects(forks) are deleted.
- Ensure empty recipients are rejected in BuildsEmailService
+ - Use rugged to change HEAD in Project#change_head (P.S.V.R)
- API: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
- API: Fix milestone filtering by `iid` (Robert Schilling)
- API: Delete notes of issues, snippets, and merge requests (Robert Schilling)
@@ -50,6 +51,7 @@ v 8.7.0 (unreleased)
- 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.)
+ - Allow issues and merge requests to be assigned to the author !2765
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu)
@@ -67,13 +69,22 @@ v 8.7.0 (unreleased)
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms
+ - Delete tags using Rugged for performance reasons (Robert Schilling)
- Diffs load at the correct point when linking from from number
- Selected diff rows highlight
- - Fix emoji catgories in the emoji picker
+ - Fix emoji categories in the emoji picker
+ - Add encrypted credentials for imported projects and migrate old ones
+ - Author and participants are displayed first on users autocompletion
+ - Show number sign on external issue reference text (Florent Baldino)
v 8.6.6
+ - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413
+ - Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654
+ - Fix revoking of authorized OAuth applications (Connor Shea). !3690
- Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
- Project switcher uses new dropdown styling
+ - Issuable header is consistent between issues and merge requests
+ - Improved spacing in issuable header on mobile
v 8.6.5
- Fix importing from GitHub Enterprise. !3529
@@ -273,7 +284,7 @@ v 8.5.1
v 8.5.0
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- - Cache various Repository methods to improve performance (Yorick Peterse)
+ - Cache various Repository methods to improve performance
- Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu)
- Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu)
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 4718bcf7a1e..61e3f811e73 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -2,6 +2,8 @@
window.GitLab ?= {}
GitLab.GfmAutoComplete =
+ dataLoading: false
+
dataSource: ''
# Emoji
@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete =
template: '<li><small>${id}</small> ${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input.
- setup: ->
- input = $('.js-gfm-input')
+ setup: (wrap) ->
+ @input = $('.js-gfm-input')
+
+ # destroy previous instances
+ @destroyAtWho()
+
+ # set up instances
+ @setupAtWho()
+
+ if @dataSource
+ if !@dataLoading
+ @dataLoading = true
+ # We should wait until initializations are done
+ # and only trigger the last .setup since
+ # The previous .dataSource belongs to the previous issuable
+ # and the last one will have the **proper** .dataSource property
+ # TODO: Make this a singleton and turn off events when moving to another page
+ setTimeout( =>
+ fetch = @fetchData(@dataSource)
+ fetch.done (data) =>
+ @dataLoading = false
+ @loadData(data)
+ , 1000)
+
+
+ setupAtWho: ->
# Emoji
- input.atwho
+ @input.atwho
at: ':'
displayTpl: @Emoji.template
insertTpl: ':${name}:'
# Team Members
- input.atwho
+ @input.atwho
at: '@'
displayTpl: @Members.template
insertTpl: '${atwho-at}${username}'
@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete =
title: sanitize(title)
search: sanitize("#{m.username} #{m.name}")
- input.atwho
+ @input.atwho
at: '#'
alias: 'issues'
searchKey: 'search'
@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete =
title: sanitize(i.title)
search: "#{i.iid} #{i.title}"
- input.atwho
+ @input.atwho
at: '!'
alias: 'mergerequests'
searchKey: 'search'
@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title)
search: "#{m.iid} #{m.title}"
- if @dataSource
- $.getJSON(@dataSource).done (data) ->
- # load members
- input.atwho 'load', '@', data.members
- # load issues
- input.atwho 'load', 'issues', data.issues
- # load merge requests
- input.atwho 'load', 'mergerequests', data.mergerequests
- # load emojis
- input.atwho 'load', ':', data.emojis
+ destroyAtWho: ->
+ @input.atwho('destroy')
+
+ fetchData: (dataSource) ->
+ $.getJSON(dataSource)
+
+ loadData: (data) ->
+ # load members
+ @input.atwho 'load', '@', data.members
+ # load issues
+ @input.atwho 'load', 'issues', data.issues
+ # load merge requests
+ @input.atwho 'load', 'mergerequests', data.mergerequests
+ # load emojis
+ @input.atwho 'load', ':', data.emojis
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index eee9b6e690e..a7e934936e9 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -12,6 +12,7 @@ class @UsersSelect
showNullUser = $dropdown.data('null-user')
showAnyUser = $dropdown.data('any-user')
firstUser = $dropdown.data('first-user')
+ @authorId = $dropdown.data('author-id')
selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label')
issueURL = $dropdown.data('issueUpdate')
@@ -207,6 +208,7 @@ class @UsersSelect
@projectId = $(select).data('project-id')
@groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user')
+ @authorId = $(select).data('author-id')
showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user')
@@ -312,6 +314,7 @@ class @UsersSelect
project_id: @projectId
group_id: @groupId
current_user: @showCurrentUser
+ author_id: @authorId
dataType: "json"
).done (users) ->
callback(users)
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 7f7b7c806e7..8bfc0d583c5 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -5,7 +5,7 @@
*/
.status-box {
-
+
/* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */
padding: 5px 11px;
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 66180f38a4f..7eb451c124e 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -70,13 +70,6 @@
display: none;
}
- .issue-details {
- .creator,
- .page-title .btn-close {
- display: none;
- }
- }
-
%ul.notes .note-role, .note-actions {
display: none;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 6453c91d955..c8c6bbde084 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -75,6 +75,11 @@ li.commit {
}
}
+ .item-title {
+ display: inline-block;
+ max-width: 70%;
+ }
+
.commit-row-description {
font-size: 14px;
border-left: 1px solid #eee;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 5917f089720..2c2ac903f29 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -1,5 +1,5 @@
.detail-page-header {
- padding: 11px 0;
+ padding: $gl-padding-top 0;
border-bottom: 1px solid $border-color;
color: #5c5d5e;
font-size: 16px;
@@ -16,11 +16,6 @@
.issue_created_ago, .author_link {
white-space: nowrap;
}
-
- .issue-meta {
- display: inline-block;
- line-height: 20px;
- }
}
.detail-page-description {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 6bd90a23620..5bf44c1cdb6 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -273,10 +273,6 @@
}
}
-.btn-default.gutter-toggle {
- margin-top: 4px;
-}
-
.detail-page-description {
small {
color: $gray-darkest;
@@ -322,3 +318,50 @@
padding-top: 7px;
}
}
+
+.issuable-status-box {
+ float: none;
+ display: inline-block;
+ margin-top: 0;
+
+ @media (max-width: $screen-xs-max) {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+}
+
+.issuable-header {
+ position: relative;
+ padding-left: 45px;
+ padding-right: 45px;
+ line-height: 35px;
+
+ @media (min-width: $screen-sm-min) {
+ float: left;
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
+
+.issuable-actions {
+ padding-top: 10px;
+
+ @media (min-width: $screen-sm-min) {
+ float: right;
+ padding-top: 0;
+ }
+}
+
+.issuable-gutter-toggle {
+ @media (max-width: $screen-sm-max) {
+ position: absolute;
+ top: 0;
+ right: 0;
+ }
+}
+
+.issuable-meta {
+ display: inline-block;
+ line-height: 18px;
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 6a1d28590c2..fc9db97132d 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -86,41 +86,9 @@ form.edit-issue {
@media (max-width: $screen-xs-max) {
.issue-btn-group {
width: 100%;
- margin-top: 5px;
-
- .btn-group {
- width: 100%;
-
- ul {
- width: 100%;
- text-align: center;
- }
- }
.btn {
width: 100%;
-
- &:first-child:not(:last-child) {
-
- }
-
- &:not(:first-child):not(:last-child) {
- margin-top: 10px;
- }
-
- &:last-child:not(:first-child) {
- margin-top: 10px;
- }
- }
- }
-
- .issue {
- &:hover .issue-actions {
- display: none !important;
- }
-
- .issue-updated-at {
- display: none;
}
}
}
@@ -133,11 +101,3 @@ form.edit-issue {
color: $gl-text-color;
margin-left: 52px;
}
-
-.editor-details {
- display: block;
-
- @media (min-width: $screen-sm-min) {
- display: inline-block;
- }
-}
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 81ba58ce49c..eb0abc80ab4 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController
if params[:search].blank?
# Include current user if available to filter by "Me"
if params[:current_user] && current_user
- @users = [*@users, current_user].uniq
+ @users = [*@users, current_user]
end
+
+ if params[:author_id].present?
+ author = User.find_by_id(params[:author_id])
+ @users = [author, *@users] if author
+ end
+
+ @users.uniq!
end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 55050615473..9b5c43b17e2 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -51,6 +51,7 @@ class HelpController < ApplicationController
end
def ui
+ @user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
end
private
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index c26cfeccf1d..38214f04793 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -60,8 +60,8 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
- @note = @project.notes.new(noteable: @issue)
- @notes = @issue.notes.nonawards.with_associations.fresh
+ @note = @project.notes.new(noteable: @issue)
+ @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
respond_to do |format|
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index b14b8218d02..49b6b79ce35 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -55,6 +55,15 @@ module IssuablesHelper
h(milestone_title.presence || default_label)
end
+ def issuable_meta(issuable, project, text)
+ output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier"
+ output << " opened #{time_ago_with_tooltip(issuable.created_at)} by".html_safe
+ output << content_tag(:strong) do
+ author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs")
+ author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
+ end
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 05386d790ca..4fc6de59a8b 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -6,12 +6,13 @@ module SelectsHelper
value = opts[:selected] || ''
placeholder = opts[:placeholder] || 'Search for a user'
- null_user = opts[:null_user] || false
- any_user = opts[:any_user] || false
- email_user = opts[:email_user] || false
- first_user = opts[:first_user] && current_user ? current_user.username : false
- current_user = opts[:current_user] || false
- project = opts[:project] || @project
+ null_user = opts[:null_user] || false
+ any_user = opts[:any_user] || false
+ email_user = opts[:email_user] || false
+ first_user = opts[:first_user] && current_user ? current_user.username : false
+ current_user = opts[:current_user] || false
+ author_id = opts[:author_id] || ''
+ project = opts[:project] || @project
html = {
class: css_class,
@@ -21,7 +22,8 @@ module SelectsHelper
any_user: any_user,
email_user: email_user,
first_user: first_user,
- current_user: current_user
+ current_user: current_user,
+ author_id: author_id
}
}
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index b8585d4e577..b7894c99846 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -37,4 +37,10 @@ class ExternalIssue
def to_reference(_from_project = nil)
id
end
+
+ def reference_link_text(from_project = nil)
+ return "##{id}" if /^\d+$/.match(id)
+
+ id
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index fadc8bb2c9e..8f20922e3c5 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -409,6 +409,35 @@ class Project < ActiveRecord::Base
self.import_data.destroy if self.import_data
end
+ def import_url=(value)
+ import_url = Gitlab::ImportUrl.new(value)
+ create_or_update_import_data(credentials: import_url.credentials)
+ super(import_url.sanitized_url)
+ end
+
+ def import_url
+ if import_data && super
+ import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials)
+ import_url.full_url
+ else
+ super
+ end
+ end
+
+ def create_or_update_import_data(data: nil, credentials: nil)
+ project_import_data = import_data || build_import_data
+ if data
+ project_import_data.data ||= {}
+ project_import_data.data = project_import_data.data.merge(data)
+ end
+ if credentials
+ project_import_data.credentials ||= {}
+ project_import_data.credentials = project_import_data.credentials.merge(credentials)
+ end
+
+ project_import_data.save
+ end
+
def import?
external_import? || forked?
end
@@ -865,7 +894,9 @@ class Project < ActiveRecord::Base
def change_head(branch)
repository.before_change_head
- gitlab_shell.update_repository_head(self.path_with_namespace, branch)
+ repository.rugged.references.create('HEAD',
+ "refs/heads/#{branch}",
+ force: true)
reload_default_branch
end
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index cd3319f077e..79efb403058 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -12,8 +12,20 @@ require 'file_size_validator'
class ProjectImportData < ActiveRecord::Base
belongs_to :project
-
+ attr_encrypted :credentials,
+ key: Gitlab::Application.secrets.db_key_base,
+ marshal: true,
+ encode: true,
+ mode: :per_attribute_iv_and_salt
+
serialize :data, JSON
validates :project, presence: true
+
+ before_validation :symbolize_credentials
+
+ def symbolize_credentials
+ # bang doesn't work here - attr_encrypted makes it not to work
+ self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank?
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 89062170481..308c590e3f8 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -169,7 +169,12 @@ class Repository
def rm_tag(tag_name)
before_remove_tag
- gitlab_shell.rm_tag(path_with_namespace, tag_name)
+ begin
+ rugged.tags.delete(tag_name)
+ true
+ rescue Rugged::ReferenceError
+ false
+ end
end
def branch_names
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 0004a399f47..02c4eee3d02 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -1,28 +1,37 @@
module Projects
class ParticipantsService < BaseService
- def execute(note_type, note_id)
- participating =
- if note_type && note_id
- participants_in(note_type, note_id)
- else
- []
- end
+ def execute(noteable_type, noteable_id)
+ @noteable_type = noteable_type
+ @noteable_id = noteable_id
project_members = sorted(project.team.members)
- participants = all_members + groups + project_members + participating
+ participants = target_owner + participants_in_target + all_members + groups + project_members
participants.uniq
end
- def participants_in(type, id)
- target =
- case type
+ def target
+ @target ||=
+ case @noteable_type
when "Issue"
- project.issues.find_by_iid(id)
+ project.issues.find_by_iid(@noteable_id)
when "MergeRequest"
- project.merge_requests.find_by_iid(id)
+ project.merge_requests.find_by_iid(@noteable_id)
when "Commit"
- project.commit(id)
+ project.commit(@noteable_id)
+ else
+ nil
end
-
+ end
+
+ def target_owner
+ return [] unless target && target.author.present?
+
+ [{
+ name: target.author.name,
+ username: target.author.username
+ }]
+ end
+
+ def participants_in_target
return [] unless target
users = target.participants(current_user)
@@ -30,13 +39,13 @@ module Projects
end
def sorted(users)
- users.uniq.to_a.compact.sort_by(&:username).map do |user|
+ users.uniq.to_a.compact.sort_by(&:username).map do |user|
{ username: user.username, name: user.name }
end
end
def groups
- current_user.authorized_groups.sort_by(&:path).map do |group|
+ current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
{ username: group.path, name: group.name, count: count }
end
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index d084559abc3..f12df5c8ffe 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -345,11 +345,11 @@
%ul
%li
%a.dropdown-menu-user-link.is-active{href: "#"}
- = link_to_member_avatar(current_user, size: 30)
+ = link_to_member_avatar(@user, size: 30)
%strong.dropdown-menu-user-full-name
- = current_user.name
+ = @user.name
.dropdown-menu-user-username
- = current_user.to_reference
+ = @user.to_reference
.example
%div
@@ -372,11 +372,11 @@
%ul
%li
%a.dropdown-menu-user-link.is-active{href: "#"}
- = link_to_member_avatar(current_user, size: 30)
+ = link_to_member_avatar(@user, size: 30)
%strong.dropdown-menu-user-full-name
- = current_user.name
+ = @user.name
.dropdown-menu-user-username
- = current_user.to_reference
+ = @user.to_reference
.dropdown-page-two
.dropdown-title
%button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}}
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
index 9ae6964aaac..b6074373e2b 100644
--- a/app/views/projects/_builds_settings.html.haml
+++ b/app/views/projects/_builds_settings.html.haml
@@ -52,6 +52,9 @@
%li
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
+ %li
+ gcovr (C/C++) -
+ %code ^TOTAL.*\s+(\d+\%)$
.form-group
.col-sm-offset-2.col-sm-10
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 7da89231243..34f27f1e793 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -11,7 +11,7 @@
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title
- %span.item-title.str-truncated
+ %span.item-title
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description?
%a.text-expander.js-toggle-button ...
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 5fe5ddc0819..bde80bbb54b 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,82 +1,79 @@
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
+- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
-= render "header_title"
+.clearfix.detail-page-header
+ .issuable-header
+ .issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
+ = icon('check', class: "hidden-sm hidden-md hidden-lg")
+ %span.hidden-xs
+ Closed
+ .issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
+ = icon('circle-o', class: "hidden-sm hidden-md hidden-lg")
+ %span.hidden-xs Open
-.issue
- .detail-page-header.issuable-header
- .pull-left
- .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"}
- %span.hidden-xs
- Closed
- %span.hidden-sm.hidden-md.hidden-lg
- = icon('check')
- .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"}
- %span.hidden-xs
- Open
- %span.hidden-sm.hidden-md.hidden-lg
- = icon('circle-o')
-
- %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
+ %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
= icon('angle-double-left')
- .issue-meta
+ .issuable-meta
= confidential_icon(@issue)
- %strong.identifier
- Issue ##{@issue.iid}
- %span.creator
- opened
- .editor-details
- .editor-details
- = time_ago_with_tooltip(@issue.created_at)
- by
- %strong
- = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
- %strong
- = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
- by_username: true, avatar: false)
+ = issuable_meta(@issue, @project, "Issue")
- .pull-right.issue-btn-group
- - if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
- = icon('plus')
- New issue
- - if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
- = icon('pencil-square-o')
- Edit
+ - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
+ .issuable-actions
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
+ %span.caret
+ Options
+ .dropdown-menu.dropdown-menu-align-right.hidden-lg
+ %ul
+ - if can?(current_user, :create_issue, @project)
+ %li
+ = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
+ - if can?(current_user, :update_issue, @issue)
+ %li
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ %li
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ %li
+ = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
+ - if can?(current_user, :create_issue, @project)
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
+ = icon('plus')
+ New issue
+ - if can?(current_user, :update_issue, @issue)
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do
+ = icon('pencil-square-o')
+ Edit
- .issue-details.issuable-details
- .detail-page-description.content-block
- %h2.title
- = markdown escape_once(@issue.title), pipeline: :single_line
- %div
- - if @issue.description.present?
- .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
- .wiki
- = preserve do
- = markdown(@issue.description, cache_key: [@issue, "description"])
- %textarea.hidden.js-task-list-field
- = @issue.description
- = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
+.issue-details.issuable-details
+ .detail-page-description.content-block
+ %h2.title
+ = markdown escape_once(@issue.title), pipeline: :single_line
+ - if @issue.description.present?
+ .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
+ .wiki
+ = preserve do
+ = markdown(@issue.description, cache_key: [@issue, "description"])
+ %textarea.hidden.js-task-list-field
+ = @issue.description
+ = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
- #merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)}
- // This element is filled in using JavaScript.
+ #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.
+ #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'
- = render 'votes/votes_block', votable: @issue
+ .content-block.content-block-small
+ = render 'new_branch'
+ = render 'votes/votes_block', votable: @issue
- .row
- %section.col-md-12
- .issuable-discussion
- = render 'projects/issues/discussion'
+ %section.issuable-discussion
+ = render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable: @issue
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 7d7c487e970..b08524574e4 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -16,11 +16,9 @@
= dropdown_title("Select source project")
= dropdown_filter("Search projects")
= dropdown_content do
- - is_active = f.object.source_project_id == @merge_request.source_project.id
- %ul
- %li
- %a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
- = @merge_request.source_project_path
+ = render 'projects/merge_requests/dropdowns/project',
+ projects: [@merge_request.source_project],
+ selected: f.object.source_project_id
.merge-request-select.dropdown
= f.hidden_field :source_branch
= dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
@@ -28,11 +26,9 @@
= dropdown_title("Select source branch")
= dropdown_filter("Search branches")
= dropdown_content do
- %ul
- - @merge_request.source_branches.each do |branch|
- %li
- %a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
- = branch
+ = render 'projects/merge_requests/dropdowns/branch',
+ branches: @merge_request.source_branches,
+ selected: f.object.source_branch
.panel-footer
= icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit
@@ -50,11 +46,9 @@
= dropdown_title("Select target project")
= dropdown_filter("Search projects")
= dropdown_content do
- %ul
- - projects.each do |project|
- %li
- %a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
- = project.path_with_namespace
+ = render 'projects/merge_requests/dropdowns/project',
+ projects: projects,
+ selected: f.object.target_project_id
.merge-request-select.dropdown
= f.hidden_field :target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" }
@@ -62,11 +56,9 @@
= dropdown_title("Select target branch")
= dropdown_filter("Search branches")
= dropdown_content do
- %ul
- - @merge_request.target_branches.each do |branch|
- %li
- %a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
- = branch
+ = render 'projects/merge_requests/dropdowns/branch',
+ branches: @merge_request.target_branches,
+ selected: f.object.target_branch
.panel-footer
= icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 07037a14f51..285ad26316c 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,8 +1,7 @@
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
-
-= render "header_title"
+- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
- if params[:view] == 'parallel'
- fluid_layout true
@@ -32,8 +31,7 @@
%span Request to merge
%span.label-branch= source_branch_with_namespace(@merge_request)
%span into
- = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
- = @merge_request.target_branch
+ = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
diff --git a/app/views/projects/merge_requests/dropdowns/_branch.html.haml b/app/views/projects/merge_requests/dropdowns/_branch.html.haml
new file mode 100644
index 00000000000..ba8d9a5835c
--- /dev/null
+++ b/app/views/projects/merge_requests/dropdowns/_branch.html.haml
@@ -0,0 +1,5 @@
+%ul
+ - branches.each do |branch|
+ %li
+ %a{ href: '#', class: "#{('is-active' if selected == branch)}", data: { id: branch } }
+ = branch
diff --git a/app/views/projects/merge_requests/dropdowns/_project.html.haml b/app/views/projects/merge_requests/dropdowns/_project.html.haml
new file mode 100644
index 00000000000..25d5dc92f8a
--- /dev/null
+++ b/app/views/projects/merge_requests/dropdowns/_project.html.haml
@@ -0,0 +1,5 @@
+%ul
+ - projects.each do |project|
+ %li
+ %a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } }
+ = project.path_with_namespace
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index ab4b1f14be5..0a99e8c9591 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,35 +1,32 @@
-.detail-page-header
- .status-box{ class: status_box_class(@merge_request) }
- %span.hidden-xs
- = @merge_request.state_human_name
- %span.hidden-sm.hidden-md.hidden-lg
- = icon(@merge_request.state_icon_name)
- %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
- = icon('angle-double-left')
- .issue-meta
- %strong.identifier
- %span.hidden-sm.hidden-md.hidden-lg
- MR
+.clearfix.detail-page-header
+ .issuable-header
+ .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
+ = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
%span.hidden-xs
- Merge Request
- !#{@merge_request.iid}
- %span.creator
- opened
- .editor-details
- = time_ago_with_tooltip(@merge_request.created_at)
- by
- %strong
- = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
- %strong
- = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
- by_username: true, avatar: false)
+ = @merge_request.state_human_name
- .issue-btn-group.pull-right
- - if can?(current_user, :update_merge_request, @merge_request)
- - if @merge_request.open?
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
- = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
+ %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
+ = icon('angle-double-left')
+
+ .issuable-meta
+ = issuable_meta(@merge_request, @project, "Merge Request")
+
+ - if can?(current_user, :update_merge_request, @merge_request)
+ .issuable-actions
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
+ %span.caret
+ Options
+ .dropdown-menu.dropdown-menu-align-right.hidden-lg
+ %ul
+ %li{ class: issue_button_visibility(@merge_request, true) }
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
+ %li{ class: issue_button_visibility(@merge_request, false) }
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
+ %li
+ = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit', id: 'edit_merge_request'
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
+ = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit", id: 'edit_merge_request' do
= icon('pencil-square-o')
Edit
- - if @merge_request.closed?
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/update_branches.html.haml
index 1b93188a10c..64482973a89 100644
--- a/app/views/projects/merge_requests/update_branches.html.haml
+++ b/app/views/projects/merge_requests/update_branches.html.haml
@@ -1,5 +1,3 @@
-%ul
- - @target_branches.each do |branch|
- %li
- %a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
- = branch
+= render 'projects/merge_requests/dropdowns/branch',
+branches: @target_branches,
+selected: nil
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 56c8eaa0597..08bfd93f4e6 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -49,7 +49,7 @@
.selectbox.hide-collapsed
= f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
- = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
+ = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
.block.milestone
.sidebar-collapsed-icon
diff --git a/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb b/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb
new file mode 100644
index 00000000000..ffcd64266e3
--- /dev/null
+++ b/db/migrate/20160302151724_add_import_credentials_to_project_import_data.rb
@@ -0,0 +1,7 @@
+class AddImportCredentialsToProjectImportData < ActiveRecord::Migration
+ def change
+ add_column :project_import_data, :encrypted_credentials, :text
+ add_column :project_import_data, :encrypted_credentials_iv, :string
+ add_column :project_import_data, :encrypted_credentials_salt, :string
+ end
+end
diff --git a/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
new file mode 100644
index 00000000000..8a351cf27a3
--- /dev/null
+++ b/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
@@ -0,0 +1,131 @@
+# Loops through old importer projects that kept a token/password in the import URL
+# and encrypts the credentials into a separate field in project#import_data
+# #down method not supported
+class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration
+
+ class ProjectImportDataFake
+ extend AttrEncrypted
+ attr_accessor :credentials
+ attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt
+ end
+
+ def up
+ say("Encrypting and migrating project import credentials...")
+
+ # This should cover GitHub, GitLab, Bitbucket user:password, token@domain, and other similar URLs.
+ in_transaction(message: "Projects including GitHub and GitLab projects with an unsecured URL.") { process_projects_with_wrong_url }
+
+ in_transaction(message: "Migrating Bitbucket credentials...") { process_project(import_type: 'bitbucket', credentials_keys: ['bb_session']) }
+
+ in_transaction(message: "Migrating FogBugz credentials...") { process_project(import_type: 'fogbugz', credentials_keys: ['fb_session']) }
+
+ end
+
+ def process_projects_with_wrong_url
+ projects_with_wrong_import_url.each do |project|
+ begin
+ import_url = Gitlab::ImportUrl.new(project["import_url"])
+
+ update_import_url(import_url, project)
+ update_import_data(import_url, project)
+ rescue URI::InvalidURIError
+ nullify_import_url(project)
+ end
+ end
+ end
+
+ def process_project(import_type:, credentials_keys: [])
+ unencrypted_import_data(import_type: import_type).each do |data|
+ replace_data_credentials(data, credentials_keys)
+ end
+ end
+
+ def replace_data_credentials(data, credentials_keys)
+ data_hash = JSON.load(data['data']) if data['data']
+ unless data_hash.blank?
+ encrypted_data_hash = encrypt_data(data_hash, credentials_keys)
+ unencrypted_data = data_hash.empty? ? ' NULL ' : quote(data_hash.to_json)
+ update_with_encrypted_data(encrypted_data_hash, data['id'], unencrypted_data)
+ end
+ end
+
+ def encrypt_data(data_hash, credentials_keys)
+ new_data_hash = {}
+ credentials_keys.each do |key|
+ new_data_hash[key.to_sym] = data_hash.delete(key) if data_hash[key]
+ end
+ new_data_hash.deep_symbolize_keys
+ end
+
+ def in_transaction(message:)
+ say_with_time(message) do
+ ActiveRecord::Base.transaction do
+ yield
+ end
+ end
+ end
+
+ def update_import_data(import_url, project)
+ fake_import_data = ProjectImportDataFake.new
+ fake_import_data.credentials = import_url.credentials
+ import_data_id = project['import_data_id']
+ if import_data_id
+ execute(update_import_data_sql(import_data_id, fake_import_data))
+ else
+ execute(insert_import_data_sql(project['id'], fake_import_data))
+ end
+ end
+
+ def update_with_encrypted_data(data_hash, import_data_id, unencrypted_data = ' NULL ')
+ fake_import_data = ProjectImportDataFake.new
+ fake_import_data.credentials = data_hash
+ execute(update_import_data_sql(import_data_id, fake_import_data, unencrypted_data))
+ end
+
+ def update_import_url(import_url, project)
+ execute("UPDATE projects SET import_url = #{quote(import_url.sanitized_url)} WHERE id = #{project['id']}")
+ end
+
+ def nullify_import_url(project)
+ execute("UPDATE projects SET import_url = NULL WHERE id = #{project['id']}")
+ end
+
+ def insert_import_data_sql(project_id, fake_import_data)
+ %(
+ INSERT INTO project_import_data
+ (encrypted_credentials,
+ project_id,
+ encrypted_credentials_iv,
+ encrypted_credentials_salt)
+ VALUES ( #{quote(fake_import_data.encrypted_credentials)},
+ '#{project_id}',
+ #{quote(fake_import_data.encrypted_credentials_iv)},
+ #{quote(fake_import_data.encrypted_credentials_salt)})
+ ).squish
+ end
+
+ def update_import_data_sql(id, fake_import_data, data = 'NULL')
+ %(
+ UPDATE project_import_data
+ SET encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)},
+ encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)},
+ encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)},
+ data = #{data}
+ WHERE id = '#{id}'
+ ).squish
+ end
+
+ #GitHub projects with token, and any user:password@ based URL
+ def projects_with_wrong_import_url
+ select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.project_id WHERE p.import_url <> '' AND p.import_url LIKE '%//%@%'")
+ end
+
+ # All imports with data for import_type
+ def unencrypted_import_data(import_type: )
+ select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url <> '' AND p.import_type = '#{import_type}' ")
+ end
+
+ def quote(value)
+ ActiveRecord::Base.connection.quote(value)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d36e2b235e5..42c261003bb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -704,6 +704,9 @@ ActiveRecord::Schema.define(version: 20160412140240) do
create_table "project_import_data", force: :cascade do |t|
t.integer "project_id"
t.text "data"
+ t.text "encrypted_credentials"
+ t.text "encrypted_credentials_iv"
+ t.text "encrypted_credentials_salt"
end
create_table "projects", force: :cascade do |t|
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 9aba4326e11..6a42a935abd 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -13,7 +13,7 @@ GitLab offers a [continuous integration][ci] service. If you
and configure your GitLab project to use a [Runner], then each merge request or
push triggers a build.
-The `.gitlab-ci.yml` file tells the GitLab runner what do to. By default it
+The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it
runs three [stages]: `build`, `test`, and `deploy`.
If everything runs OK (no non-zero return values), you'll get a nice green
diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md
index b9abcbd2c12..6267f14eba4 100644
--- a/doc/update/8.5-to-8.6.md
+++ b/doc/update/8.5-to-8.6.md
@@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-6-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all
-sudo -u git -H git checkout v2.6.11
+sudo -u git -H git checkout v2.6.12
```
### 5. Update gitlab-workhorse
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 24b3fb6eacb..a58b3cb7e16 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -2,7 +2,7 @@ module SharedIssuable
include Spinach::DSL
def edit_issuable
- find(:css, '.issuable-edit').click
+ find('.issuable-edit', visible: true).click
end
step 'project "Community" has "Community issue" open issue' do
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index b9bb6e76081..5e2fb863a8f 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -54,19 +54,6 @@ module Gitlab
"#{path}.git", "#{new_path}.git"])
end
- # Update HEAD for repository
- #
- # path - project path with namespace
- # branch - repository branch name
- #
- # Ex.
- # update_repository_head("gitlab/gitlab-ci", "3-1-stable")
- #
- def update_repository_head(path, branch)
- Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head',
- "#{path}.git", branch])
- end
-
# Fork repository to new namespace
#
# path - project path with namespace
@@ -92,33 +79,6 @@ module Gitlab
'rm-project', "#{name}.git"])
end
- # Add repository branch from passed ref
- #
- # path - project path with namespace
- # branch_name - new branch name
- # ref - HEAD for new branch
- #
- # Ex.
- # add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
- #
- def add_branch(path, branch_name, ref)
- Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch',
- "#{path}.git", branch_name, ref])
- end
-
- # Remove repository branch
- #
- # path - project path with namespace
- # branch_name - branch name to remove
- #
- # Ex.
- # rm_branch("gitlab/gitlab-ci", "4-0-stable")
- #
- def rm_branch(path, branch_name)
- Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch',
- "#{path}.git", branch_name])
- end
-
# Add repository tag from passed ref
#
# path - project path with namespace
@@ -137,19 +97,6 @@ module Gitlab
Gitlab::Utils.system_silent(cmd)
end
- # Remove repository tag
- #
- # path - project path with namespace
- # tag_name - tag name to remove
- #
- # Ex.
- # rm_tag("gitlab/gitlab-ci", "v4.0")
- #
- def rm_tag(path, tag_name)
- Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag',
- "#{path}.git", tag_name])
- end
-
# Gc repository
#
# path - project path with namespace
diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb
index d88a6eaac6b..9bb507b5edd 100644
--- a/lib/gitlab/bitbucket_import/client.rb
+++ b/lib/gitlab/bitbucket_import/client.rb
@@ -5,6 +5,17 @@ module Gitlab
attr_reader :consumer, :api
+ def self.from_project(project)
+ import_data_credentials = project.import_data.credentials if project.import_data
+ if import_data_credentials && import_data_credentials[:bb_session]
+ token = import_data_credentials[:bb_session][:bitbucket_access_token]
+ token_secret = import_data_credentials[:bb_session][:bitbucket_access_token_secret]
+ new(token, token_secret)
+ else
+ raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
+ end
+ end
+
def initialize(access_token = nil, access_token_secret = nil)
@consumer = ::OAuth::Consumer.new(
config.app_id,
@@ -54,7 +65,7 @@ module Gitlab
def issues(project_identifier)
all_issues = []
offset = 0
- per_page = 50 # Maximum number allowed by Bitbucket
+ per_page = 50 # Maximum number allowed by Bitbucket
index = 0
begin
@@ -120,7 +131,7 @@ module Gitlab
end
def config
- Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"}
+ Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket" }
end
def bitbucket_options
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 46e51a4bf6d..7beaecd1cf0 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -5,10 +5,7 @@ module Gitlab
def initialize(project)
@project = project
- import_data = project.import_data.try(:data)
- bb_session = import_data["bb_session"] if import_data
- @client = Client.new(bb_session["bitbucket_access_token"],
- bb_session["bitbucket_access_token_secret"])
+ @client = Client.from_project(@project)
@formatter = Gitlab::ImportFormatter.new
end
diff --git a/lib/gitlab/bitbucket_import/key_deleter.rb b/lib/gitlab/bitbucket_import/key_deleter.rb
index f4dd393ad29..e03c3155b3e 100644
--- a/lib/gitlab/bitbucket_import/key_deleter.rb
+++ b/lib/gitlab/bitbucket_import/key_deleter.rb
@@ -6,10 +6,7 @@ module Gitlab
def initialize(project)
@project = project
@current_user = project.creator
- import_data = project.import_data.try(:data)
- bb_session = import_data["bb_session"] if import_data
- @client = Client.new(bb_session["bitbucket_access_token"],
- bb_session["bitbucket_access_token_secret"])
+ @client = Client.from_project(@project)
end
def execute
diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb
index 03aac1a025a..941f818b847 100644
--- a/lib/gitlab/bitbucket_import/project_creator.rb
+++ b/lib/gitlab/bitbucket_import/project_creator.rb
@@ -23,7 +23,8 @@ module Gitlab
import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git",
).execute
- project.create_import_data(data: { "bb_session" => session_data } )
+ project.create_or_update_import_data(credentials: { bb_session: session_data })
+
project
end
end
diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb
index db580b5e578..501d5a95547 100644
--- a/lib/gitlab/fogbugz_import/importer.rb
+++ b/lib/gitlab/fogbugz_import/importer.rb
@@ -8,17 +8,17 @@ module Gitlab
import_data = project.import_data.try(:data)
repo_data = import_data['repo'] if import_data
- @repo = FogbugzImport::Repository.new(repo_data)
-
- @known_labels = Set.new
+ if repo_data
+ @repo = FogbugzImport::Repository.new(repo_data)
+ @known_labels = Set.new
+ else
+ raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
+ end
end
def execute
return true unless repo.valid?
-
- data = project.import_data.try(:data)
-
- client = Gitlab::FogbugzImport::Client.new(token: data['fb_session']['token'], uri: data['fb_session']['uri'])
+ client = Gitlab::FogbugzImport::Client.new(token: fb_session[:token], uri: fb_session[:uri])
@cases = client.cases(@repo.id.to_i)
@categories = client.categories
@@ -30,6 +30,10 @@ module Gitlab
private
+ def fb_session
+ @import_data_credentials ||= project.import_data.credentials[:fb_session] if project.import_data && project.import_data.credentials
+ end
+
def user_map
@user_map ||= begin
user_map = Hash.new
@@ -236,9 +240,8 @@ module Gitlab
end
def build_attachment_url(rel_url)
- data = project.import_data.try(:data)
- uri = data['fb_session']['uri']
- token = data['fb_session']['token']
+ uri = fb_session[:uri]
+ token = fb_session[:token]
"#{uri}/#{rel_url}&token=#{token}"
end
diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb
index e0163499e30..3840765db87 100644
--- a/lib/gitlab/fogbugz_import/project_creator.rb
+++ b/lib/gitlab/fogbugz_import/project_creator.rb
@@ -24,13 +24,7 @@ module Gitlab
import_url: Project::UNKNOWN_IMPORT_URL
).execute
- project.create_import_data(
- data: {
- 'repo' => repo.raw_data,
- 'user_map' => user_map,
- 'fb_session' => fb_session
- }
- )
+ project.create_or_update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map }, credentials: { fb_session: fb_session })
project
end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 172c5441e36..0b1ed510229 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -7,10 +7,12 @@ module Gitlab
def initialize(project)
@project = project
- import_data = project.import_data.try(:data)
- github_session = import_data["github_session"] if import_data
- @client = Client.new(github_session["github_access_token"])
- @formatter = Gitlab::ImportFormatter.new
+ if import_data_credentials
+ @client = Client.new(import_data_credentials[:user])
+ @formatter = Gitlab::ImportFormatter.new
+ else
+ raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
+ end
end
def execute
@@ -19,6 +21,10 @@ module Gitlab
private
+ def import_data_credentials
+ @import_data_credentials ||= project.import_data.credentials if project.import_data
+ end
+
def import_issues
client.list_issues(project.import_source, state: :all,
sort: :created,
diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb
index 474927069a5..f4221003db5 100644
--- a/lib/gitlab/github_import/project_creator.rb
+++ b/lib/gitlab/github_import/project_creator.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def execute
- project = ::Projects::CreateService.new(
+ ::Projects::CreateService.new(
current_user,
name: repo.name,
path: repo.name,
@@ -23,9 +23,6 @@ module Gitlab
import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"),
wiki_enabled: !repo.has_wiki? # If repo has wiki we'll import it later
).execute
-
- project.create_import_data(data: { "github_session" => session_data } )
- project
end
end
end
diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb
index 850b73244c6..96717b42bae 100644
--- a/lib/gitlab/gitlab_import/importer.rb
+++ b/lib/gitlab/gitlab_import/importer.rb
@@ -5,10 +5,13 @@ module Gitlab
def initialize(project)
@project = project
- import_data = project.import_data.try(:data)
- gitlab_session = import_data["gitlab_session"] if import_data
- @client = Client.new(gitlab_session["gitlab_access_token"])
- @formatter = Gitlab::ImportFormatter.new
+ credentials = import_data
+ if credentials && credentials[:password]
+ @client = Client.new(credentials[:password])
+ @formatter = Gitlab::ImportFormatter.new
+ else
+ raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
+ end
end
def execute
diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb
index 7baaadb813c..77c33db4b59 100644
--- a/lib/gitlab/gitlab_import/project_creator.rb
+++ b/lib/gitlab/gitlab_import/project_creator.rb
@@ -23,7 +23,6 @@ module Gitlab
import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{@session_data[:gitlab_access_token]}@")
).execute
- project.create_import_data(data: { "gitlab_session" => session_data } )
project
end
end
diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb
index 87821c23460..0abb7a64c17 100644
--- a/lib/gitlab/google_code_import/project_creator.rb
+++ b/lib/gitlab/google_code_import/project_creator.rb
@@ -24,12 +24,7 @@ module Gitlab
import_url: repo.import_url
).execute
- project.create_import_data(
- data: {
- "repo" => repo.raw_data,
- "user_map" => user_map
- }
- )
+ project.create_or_update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map })
project
end
diff --git a/lib/gitlab/import_url.rb b/lib/gitlab/import_url.rb
new file mode 100644
index 00000000000..d23b013c1f5
--- /dev/null
+++ b/lib/gitlab/import_url.rb
@@ -0,0 +1,41 @@
+module Gitlab
+ class ImportUrl
+ def initialize(url, credentials: nil)
+ @url = URI.parse(URI.encode(url))
+ @credentials = credentials
+ end
+
+ def sanitized_url
+ @sanitized_url ||= safe_url.to_s
+ end
+
+ def credentials
+ @credentials ||= { user: @url.user, password: @url.password }
+ end
+
+ def full_url
+ @full_url ||= generate_full_url.to_s
+ end
+
+ private
+
+ def generate_full_url
+ return @url unless valid_credentials?
+ @full_url = @url.dup
+ @full_url.user = credentials[:user]
+ @full_url.password = credentials[:password]
+ @full_url
+ end
+
+ def safe_url
+ safe_url = @url.dup
+ safe_url.password = nil
+ safe_url.user = nil
+ safe_url
+ end
+
+ def valid_credentials?
+ credentials && credentials.is_a?(Hash) && credentials.any?
+ end
+ end
+end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 410b993fdfb..28cf804c1b2 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -12,13 +12,13 @@ describe AutocompleteController do
project.team << [user, :master]
end
- let(:body) { JSON.parse(response.body) }
-
describe 'GET #users with project ID' do
before do
get(:users, project_id: project.id)
end
+ let(:body) { JSON.parse(response.body) }
+
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 }
it { expect(body.map { |u| u["username"] }).to include(user.username) }
@@ -143,4 +143,24 @@ describe AutocompleteController do
it { expect(body.size).to eq 0 }
end
end
+
+ context 'author of issuable included' do
+ before do
+ sign_in(user)
+ end
+
+ let(:body) { JSON.parse(response.body) }
+
+ it 'includes the author' do
+ get(:users, author_id: non_member.id)
+
+ expect(body.first["username"]).to eq non_member.username
+ end
+
+ it 'rejects non existent user ids' do
+ get(:users, author_id: 99999)
+
+ expect(body.collect { |u| u['id'] }).not_to include(99999)
+ end
+ end
end
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 6fda0c31866..84c8e20ebaa 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -42,11 +42,9 @@ feature 'issue move to another project' do
expect(current_url).to include project_path(new_project)
- page.within('.issue') do
- expect(page).to have_content("Text with #{cross_reference}!1")
- expect(page).to have_content("Moved from #{cross_reference}#1")
- expect(page).to have_content(issue.title)
- end
+ expect(page).to have_content("Text with #{cross_reference}!1")
+ expect(page).to have_content("Moved from #{cross_reference}#1")
+ expect(page).to have_content(issue.title)
end
context 'projects user does not have permission to move issue to exist' do
@@ -74,7 +72,7 @@ feature 'issue move to another project' do
def edit_issue(issue)
visit issue_path(issue)
- page.within('.issuable-header') { click_link 'Edit' }
+ page.within('.issuable-actions') { first(:link, 'Edit').click }
end
def issue_path(issue)
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
new file mode 100644
index 00000000000..1adab7e9c6c
--- /dev/null
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -0,0 +1,98 @@
+require 'spec_helper'
+
+feature 'Member autocomplete', feature: true do
+ let(:project) { create(:project, :public) }
+ let(:user) { create(:user) }
+ let(:participant) { create(:user) }
+ let(:author) { create(:user) }
+
+ before do
+ allow_any_instance_of(Commit).to receive(:author).and_return(author)
+ login_as user
+ end
+
+ shared_examples "open suggestions" do
+ it 'suggestions are displayed' do
+ expect(page).to have_selector('.atwho-view', visible: true)
+ end
+
+ it 'author is suggested' do
+ page.within('.atwho-view', visible: true) do
+ expect(page).to have_content(author.username)
+ end
+ end
+
+ it 'participant is suggested' do
+ page.within('.atwho-view', visible: true) do
+ expect(page).to have_content(participant.username)
+ end
+ end
+ end
+
+ context 'adding a new note on a Issue', js: true do
+ before do
+ issue = create(:issue, author: author, project: project)
+ create(:note, note: 'Ultralight Beam', noteable: issue, author: participant)
+ visit_issue(project, issue)
+ end
+
+ context 'when typing @' do
+ include_examples "open suggestions"
+ before do
+ open_member_suggestions
+ end
+ end
+ end
+
+ context 'adding a new note on a Merge Request ', js: true do
+ before do
+ merge = create(:merge_request, source_project: project, target_project: project, author: author)
+ create(:note, note: 'Ultralight Beam', noteable: merge, author: participant)
+ visit_merge_request(project, merge)
+ end
+
+ context 'when typing @' do
+ include_examples "open suggestions"
+ before do
+ open_member_suggestions
+ end
+ end
+ end
+
+ context 'adding a new note on a Commit ', js: true do
+ let(:commit) { project.commit }
+
+ before do
+ allow(commit).to receive(:author).and_return(author)
+ create(:note_on_commit, author: participant, project: project, commit_id: project.repository.commit.id, note: 'No More Parties in LA')
+ visit_commit(project, commit)
+ end
+
+ context 'when typing @' do
+ include_examples "open suggestions"
+ before do
+ open_member_suggestions
+ end
+ end
+ end
+
+ def open_member_suggestions
+ sleep 1
+ page.within('.new-note') do
+ sleep 1
+ find('#note_note').native.send_keys('@')
+ end
+ end
+
+ def visit_issue(project, issue)
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ def visit_merge_request(project, merge)
+ visit namespace_project_merge_request_path(project.namespace, project, merge)
+ end
+
+ def visit_commit(project, commit)
+ visit namespace_project_commit_path(project.namespace, project, commit)
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index c413132abe5..1a833f255a5 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -34,9 +34,9 @@ describe Gitlab::BitbucketImport::Importer, lib: true do
let(:project_identifier) { 'namespace/repo' }
let(:data) do
{
- bb_session: {
- bitbucket_access_token: "123456",
- bitbucket_access_token_secret: "secret"
+ 'bb_session' => {
+ 'bitbucket_access_token' => "123456",
+ 'bitbucket_access_token_secret' => "secret"
}
}
end
@@ -44,7 +44,7 @@ describe Gitlab::BitbucketImport::Importer, lib: true do
create(
:project,
import_source: project_identifier,
- import_data: ProjectImportData.new(data: data)
+ import_data: ProjectImportData.new(credentials: data)
)
end
let(:importer) { Gitlab::BitbucketImport::Importer.new(project) }
diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb
index c93a3ebdaec..0f363b8b0aa 100644
--- a/spec/lib/gitlab/github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/github_import/project_creator_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do
owner: OpenStruct.new(login: "john")
)
end
- let(:namespace){ create(:group, owner: user) }
+ let(:namespace) { create(:group, owner: user) }
let(:token) { "asdffg" }
let(:access_params) { { github_access_token: token } }
@@ -27,6 +27,8 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do
project = project_creator.execute
expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git")
+ expect(project.safe_import_url).to eq("https://*****@gitlab.com/asd/vim.git")
+ expect(project.import_data.credentials).to eq(user: "asdffg", password: nil)
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
diff --git a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
index aed2aa39e3a..1bd29b8a563 100644
--- a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
@@ -2,11 +2,12 @@ require 'spec_helper'
describe Gitlab::GithubImport::WikiFormatter, lib: true do
let(:project) do
- create(:project, namespace: create(:namespace, path: 'gitlabhq'),
- import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git')
+ create(:project,
+ namespace: create(:namespace, path: 'gitlabhq'),
+ import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git')
end
- subject(:wiki) { described_class.new(project)}
+ subject(:wiki) { described_class.new(project) }
describe '#path_with_namespace' do
it 'appends .wiki to project path' do
diff --git a/spec/lib/gitlab/import_url_spec.rb b/spec/lib/gitlab/import_url_spec.rb
new file mode 100644
index 00000000000..f758cb8693c
--- /dev/null
+++ b/spec/lib/gitlab/import_url_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::ImportUrl do
+
+ let(:credentials) { { user: 'blah', password: 'password' } }
+ let(:import_url) do
+ Gitlab::ImportUrl.new("https://github.com/me/project.git", credentials: credentials)
+ end
+
+ describe :full_url do
+ it { expect(import_url.full_url).to eq("https://blah:password@github.com/me/project.git") }
+ end
+
+ describe :sanitized_url do
+ it { expect(import_url.sanitized_url).to eq("https://github.com/me/project.git") }
+ end
+
+ describe :credentials do
+ it { expect(import_url.credentials).to eq(credentials) }
+ end
+end
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index 9b144dd1ecc..4fc3b065592 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -36,4 +36,19 @@ describe ExternalIssue, models: true do
expect(issue.title).to eq "External Issue #{issue}"
end
end
+
+ describe '#reference_link_text' do
+ context 'if issue id has a prefix' do
+ it 'returns the issue ID' do
+ expect(issue.reference_link_text).to eq 'EXT-1234'
+ end
+ end
+
+ context 'if issue id is a number' do
+ let(:issue) { described_class.new('1234', project) }
+ it 'returns the issue ID prefixed by #' do
+ expect(issue.reference_link_text).to eq '#1234'
+ end
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 86f68b3a0a0..c163001b7c1 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -770,11 +770,9 @@ describe Repository, models: true do
describe '#rm_tag' do
it 'removes a tag' do
expect(repository).to receive(:before_remove_tag)
+ expect(repository.rugged.tags).to receive(:delete).with('v1.1.0')
- expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
- with(repository.path_with_namespace, '8.5')
-
- repository.rm_tag('8.5')
+ repository.rm_tag('v1.1.0')
end
end
diff --git a/spec/services/delete_tag_service_spec.rb b/spec/services/delete_tag_service_spec.rb
index 5b7ba521812..477551f5036 100644
--- a/spec/services/delete_tag_service_spec.rb
+++ b/spec/services/delete_tag_service_spec.rb
@@ -6,21 +6,12 @@ describe DeleteTagService, services: true do
let(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
- let(:tag) { double(:tag, name: '8.5', target: 'abc123') }
-
describe '#execute' do
- before do
- allow(repository).to receive(:find_tag).and_return(tag)
- end
-
it 'removes the tag' do
- expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
- and_return(true)
-
expect(repository).to receive(:before_remove_tag)
expect(service).to receive(:success)
- service.execute('8.5')
+ service.execute('v1.1.0')
end
end
end