Commit deae7c98 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into ce_upstream

parents 942b11ab 1051f0c5
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.9.0 (unreleased) v 8.9.0 (unreleased)
- Fix pipeline status when there are no builds in pipeline
- Fix Error 500 when using closes_issues API with an external issue tracker - Fix Error 500 when using closes_issues API with an external issue tracker
- Add more information into RSS feed for issues (Alexander Matyushentsev) - Add more information into RSS feed for issues (Alexander Matyushentsev)
- Bulk assign/unassign labels to issues. - Bulk assign/unassign labels to issues.
...@@ -12,6 +13,7 @@ v 8.9.0 (unreleased) ...@@ -12,6 +13,7 @@ v 8.9.0 (unreleased)
- Fix an issue where note polling stopped working if a window was in the - Fix an issue where note polling stopped working if a window was in the
background during a refresh. background during a refresh.
- Make EmailsOnPushWorker use Sidekiq mailers queue - Make EmailsOnPushWorker use Sidekiq mailers queue
- Redesign all Devise emails. !4297
- Fix wiki page events' webhook to point to the wiki repository - Fix wiki page events' webhook to point to the wiki repository
- Don't show tags for revert and cherry-pick operations - Don't show tags for revert and cherry-pick operations
- Fix issue todo not remove when leave project !4150 (Long Nguyen) - Fix issue todo not remove when leave project !4150 (Long Nguyen)
...@@ -28,6 +30,7 @@ v 8.9.0 (unreleased) ...@@ -28,6 +30,7 @@ v 8.9.0 (unreleased)
- Add a metric for the number of new Redis connections created by a transaction - Add a metric for the number of new Redis connections created by a transaction
- Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark - Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark
- Redesign navigation for project pages - Redesign navigation for project pages
- Fix images in sign-up confirmation email
- Added shortcut 'y' for copying a files content hash URL #14470 - Added shortcut 'y' for copying a files content hash URL #14470
- Fix groups API to list only user's accessible projects - Fix groups API to list only user's accessible projects
- Fix horizontal scrollbar for long commit message. - Fix horizontal scrollbar for long commit message.
...@@ -38,7 +41,9 @@ v 8.9.0 (unreleased) ...@@ -38,7 +41,9 @@ v 8.9.0 (unreleased)
- `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix - `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix
- Bump nokogiri to 1.6.8 - Bump nokogiri to 1.6.8
- Use gitlab-shell v3.0.0 - Use gitlab-shell v3.0.0
- Fixed alignment of download dropdown in merge requests
- Upgrade to jQuery 2 - Upgrade to jQuery 2
- Adds selected branch name to the dropdown toggle
- Use Knapsack to evenly distribute tests across multiple nodes - Use Knapsack to evenly distribute tests across multiple nodes
- Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged
- Don't allow MRs to be merged when commits were added since the last review / page load - Don't allow MRs to be merged when commits were added since the last review / page load
...@@ -63,7 +68,9 @@ v 8.9.0 (unreleased) ...@@ -63,7 +68,9 @@ v 8.9.0 (unreleased)
- Use downcased path to container repository as this is expected path by Docker - Use downcased path to container repository as this is expected path by Docker
- Projects pending deletion will render a 404 page - Projects pending deletion will render a 404 page
- Measure queue duration between gitlab-workhorse and Rails - Measure queue duration between gitlab-workhorse and Rails
- Added Gfm autocomplete for labels
- Make Omniauth providers specs to not modify global configuration - Make Omniauth providers specs to not modify global configuration
- Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir)
- Make authentication service for Container Registry to be compatible with < Docker 1.11 - Make authentication service for Container Registry to be compatible with < Docker 1.11
- Add Application Setting to configure Container Registry token expire delay (default 5min) - Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav - Cache assigned issue and merge request counts in sidebar nav
...@@ -107,6 +114,8 @@ v 8.9.0 (unreleased) ...@@ -107,6 +114,8 @@ v 8.9.0 (unreleased)
- Include user relationships when retrieving award_emoji - Include user relationships when retrieving award_emoji
- Various associations are now eager loaded when parsing issue references to reduce the number of queries executed - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed
- Set inverse_of for Project/Service association to reduce the number of queries - Set inverse_of for Project/Service association to reduce the number of queries
- Update tanuki logo highlight/loading colors
- Use Git cached counters for branches and tags on project page
v 8.8.5 v 8.8.5
- Import GitHub repositories respecting the API rate limit !4166 - Import GitHub repositories respecting the API rate limit !4166
...@@ -118,6 +127,8 @@ v 8.8.5 ...@@ -118,6 +127,8 @@ v 8.8.5
- Prevent unauthorized access for projects build traces - Prevent unauthorized access for projects build traces
- Forbid scripting for wiki files - Forbid scripting for wiki files
- Only show notes through JSON on confidential issues that the user has access to - Only show notes through JSON on confidential issues that the user has access to
- Banzai::Filter::UploadLinkFilter use XPath instead CSS expressions
- Banzai::Filter::ExternalLinkFilter use XPath instead CSS expressions
v 8.8.4 v 8.8.4
- Fix LDAP-based login for users with 2FA enabled. !4493 - Fix LDAP-based login for users with 2FA enabled. !4493
......
...@@ -56,7 +56,7 @@ gem "browser", '~> 2.0.3' ...@@ -56,7 +56,7 @@ gem "browser", '~> 2.0.3'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 10.0' gem "gitlab_git", '~> 10.2'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
......
...@@ -299,7 +299,7 @@ GEM ...@@ -299,7 +299,7 @@ GEM
gitlab-license (0.0.4) gitlab-license (0.0.4)
gitlab_emoji (0.3.1) gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1) gemojione (~> 2.2, >= 2.2.1)
gitlab_git (10.1.3) gitlab_git (10.2.0)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -905,7 +905,7 @@ DEPENDENCIES ...@@ -905,7 +905,7 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 0.0.4) gitlab-license (~> 0.0.4)
gitlab_emoji (~> 0.3.0) gitlab_emoji (~> 0.3.0)
gitlab_git (~> 10.0) gitlab_git (~> 10.2)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0) gollum-lib (~> 4.1.0)
......
class @BlobGitignoreSelector #= require blob/template_selector
constructor: (opts) ->
{
@dropdown
@editor
@$wrapper = @dropdown.closest('.gitignore-selector')
@$filenameInput = $('#file_name')
@data = @dropdown.data('filenames')
} = opts
@dropdown.glDropdown( class @BlobGitignoreSelector extends TemplateSelector
data: @data, requestFile: (query) ->
filterable: true, Api.gitignoreText query.name, @requestFileSuccess.bind(@)
selectable: true,
search:
fields: ['name']
clicked: @onClick
text: (gitignore) ->
gitignore.name
)
@toggleGitignoreSelector()
@bindEvents()
bindEvents: ->
@$filenameInput
.on 'keyup blur', (e) =>
@toggleGitignoreSelector()
toggleGitignoreSelector: ->
filename = @$filenameInput.val() or $('.editor-file-name').text().trim()
@$wrapper.toggleClass 'hidden', filename isnt '.gitignore'
onClick: (item, el, e) =>
e.preventDefault()
@requestIgnoreFile(item.name)
requestIgnoreFile: (name) ->
Api.gitignoreText name, @requestIgnoreFileSuccess.bind(@)
requestIgnoreFileSuccess: (gitignore) ->
@editor.setValue(gitignore.content, 1)
@editor.focus()
class @BlobGitignoreSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-gitignore-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobGitignoreSelector(
dropdown: $dropdown,
editor: @editor
)
class @BlobGitignoreSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-gitignore-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobGitignoreSelector(
pattern: /(.gitignore)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
dropdown: $dropdown,
editor: @editor
)
class @BlobLicenseSelector #= require blob/template_selector
licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i
constructor: (editor) -> class @BlobLicenseSelector extends TemplateSelector
@$licenseSelector = $('.js-license-selector') requestFile: (query) ->
$fileNameInput = $('#file_name') data =
project: @dropdown.data('project')
fullname: @dropdown.data('fullname')
initialFileNameValue = if $fileNameInput.length Api.licenseText query.id, data, @requestFileSuccess.bind(@)
$fileNameInput.val()
else if $('.editor-file-name').length
$('.editor-file-name').text().trim()
@toggleLicenseSelector(initialFileNameValue)
if $fileNameInput
$fileNameInput.on 'keyup blur', (e) =>
@toggleLicenseSelector($(e.target).val())
$('select.license-select').on 'change', (e) ->
data =
project: $(this).data('project')
fullname: $(this).data('fullname')
Api.licenseText $(this).val(), data, (license) ->
editor.setValue(license.content, -1)
toggleLicenseSelector: (fileName) =>
if @licenseRegex.test(fileName)
@$licenseSelector.show()
else
@$licenseSelector.hide()
class @BlobLicenseSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-license-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobLicenseSelector(
pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-license-selector-wrap'),
dropdown: $dropdown,
editor: @editor
)
...@@ -12,8 +12,9 @@ class @EditBlob ...@@ -12,8 +12,9 @@ class @EditBlob
$("#file-content").val(@editor.getValue()) $("#file-content").val(@editor.getValue())
@initModePanesAndLinks() @initModePanesAndLinks()
new BlobLicenseSelector(@editor)
new BlobGitignoreSelectors(editor: @editor) new BlobLicenseSelectors { @editor }
new BlobGitignoreSelectors { @editor }
initModePanesAndLinks: -> initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane") @$editModePanes = $(".js-edit-mode-pane")
......
class @TemplateSelector
constructor: (opts = {}) ->
{
@dropdown,
@data,
@pattern,
@wrapper,
@editor,
@fileEndpoint,
@$input = $('#file_name')
} = opts
@buildDropdown()
@bindEvents()
@onFilenameUpdate()
buildDropdown: ->
@dropdown.glDropdown(
data: @data,
filterable: true,
selectable: true,
search:
fields: ['name']
clicked: @onClick
text: (item) ->
item.name
)
bindEvents: ->
@$input.on('keyup blur', (e) =>
@onFilenameUpdate()
)
onFilenameUpdate: ->
return unless @$input.length
filenameMatches = @pattern.test(@$input.val().trim())
if not filenameMatches
@wrapper.addClass('hidden')
return
@wrapper.removeClass('hidden')
onClick: (item, el, e) =>
e.preventDefault()
@requestFile(item)
requestFile: (item) ->
# To be implemented on the extending class
# e.g.
# Api.gitignoreText item.name, @requestFileSuccess.bind(@)
requestFileSuccess: (file) ->
@editor.setValue(file.content, 1)
@editor.focus()
...@@ -15,6 +15,9 @@ GitLab.GfmAutoComplete = ...@@ -15,6 +15,9 @@ GitLab.GfmAutoComplete =
Members: Members:
template: '<li>${username} <small>${title}</small></li>' template: '<li>${username} <small>${title}</small></li>'
Labels:
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
# Issues and MergeRequests # Issues and MergeRequests
Issues: Issues:
template: '<li><small>${id}</small> ${title}</li>' template: '<li><small>${id}</small> ${title}</li>'
...@@ -176,6 +179,25 @@ GitLab.GfmAutoComplete = ...@@ -176,6 +179,25 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title) title: sanitize(m.title)
search: "#{m.iid} #{m.title}" search: "#{m.iid} #{m.title}"
@input.atwho
at: '~'
alias: 'labels'
searchKey: 'search'
displayTpl: @Labels.template
insertTpl: '${atwho-at}${title}'
callbacks:
beforeSave: (merges) ->
sanitizeLabelTitle = (title)->
if /\w+\s+\w+/g.test(title)
"\"#{sanitize(title)}\""
else
sanitize(title)
$.map merges, (m) ->
title: sanitizeLabelTitle(m.title)
color: m.color
search: "#{m.title}"
destroyAtWho: -> destroyAtWho: ->
@input.atwho('destroy') @input.atwho('destroy')
...@@ -195,6 +217,8 @@ GitLab.GfmAutoComplete = ...@@ -195,6 +217,8 @@ GitLab.GfmAutoComplete =
@input.atwho 'load', 'mergerequests', data.mergerequests @input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis # load emojis
@input.atwho 'load', ':', data.emojis @input.atwho 'load', ':', data.emojis
# load labels
@input.atwho 'load', '~', data.labels
# This trigger at.js again # This trigger at.js again
# otherwise we would be stuck with loading until the user types # otherwise we would be stuck with loading until the user types
......
...@@ -9,7 +9,7 @@ class @MergeRequest ...@@ -9,7 +9,7 @@ class @MergeRequest
# Options: # Options:
# action - String, current controller action # action - String, current controller action
# #
constructor: (@opts) -> constructor: (@opts = {}) ->
this.$el = $('.merge-request') this.$el = $('.merge-request')
this.$('.show-all-commits').on 'click', => this.$('.show-all-commits').on 'click', =>
......
...@@ -88,7 +88,7 @@ class @MergeRequestTabs ...@@ -88,7 +88,7 @@ class @MergeRequestTabs
scrollToElement: (container) -> scrollToElement: (container) ->
if window.location.hash if window.location.hash
navBarHeight = $('.navbar-gitlab').outerHeight() navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
$el = $("#{container} #{window.location.hash}:not(.match)") $el = $("#{container} #{window.location.hash}:not(.match)")
$.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
......
...@@ -91,6 +91,10 @@ ...@@ -91,6 +91,10 @@
background-color: $white-light; background-color: $white-light;
border-top: none; border-top: none;
} }
&.top-block .container-fluid {
background-color: inherit;
}
} }
.cover-block { .cover-block {
......
...@@ -2,6 +2,17 @@ ...@@ -2,6 +2,17 @@
* Application Header * Application Header
* *
*/ */
@mixin tanuki-logo-colors($path-color) {
fill: $path-color;
transition: all 0.8s;
&:hover,
&.highlight {
fill: lighten($path-color, 25%);
transition: all 0.1s;
}
}
header { header {
transition: padding $sidebar-transition-duration; transition: padding $sidebar-transition-duration;
...@@ -191,13 +202,24 @@ header { ...@@ -191,13 +202,24 @@ header {
} }
} }
.tanuki-shape { #tanuki-logo {
transition: all 0.8s;
&:hover, &.highlight { #tanuki-left-ear,
fill: rgb(255, 255, 255); #tanuki-right-ear,
transition: all 0.1s; #tanuki-nose {
@include tanuki-logo-colors($tanuki-red);
}
#tanuki-left-eye,
#tanuki-right-eye {
@include tanuki-logo-colors($tanuki-orange);
} }
#tanuki-left-cheek,
#tanuki-right-cheek {
@include tanuki-logo-colors($tanuki-yellow);
}
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
......
...@@ -55,17 +55,6 @@ ...@@ -55,17 +55,6 @@
} }
} }
.tanuki-shape {
transition: all 0.8s;
&:hover, &.highlight {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
}
.nav-sidebar { .nav-sidebar {
position: absolute; position: absolute;
top: 50px; top: 50px;
......
...@@ -156,6 +156,11 @@ $warning-message-border: #f0e2bb; ...@@ -156,6 +156,11 @@ $warning-message-border: #f0e2bb;
/* header */ /* header */
$light-grey-header: #faf9f9; $light-grey-header: #faf9f9;
/* tanuki logo colors */
$tanuki-red: #e24329;
$tanuki-orange: #fc6d26;
$tanuki-yellow: #fca326;
/* /*
* State colors: * State colors:
*/ */
......
...@@ -38,6 +38,10 @@ table { ...@@ -38,6 +38,10 @@ table {
margin: 0 auto; margin: 0 auto;
text-align: left; text-align: left;
width: 600px; width: 600px;
& > td {
text-align: center;
}
} }
&#body { &#body {
......
...@@ -66,8 +66,7 @@ ...@@ -66,8 +66,7 @@
font-family: $regular_font; font-family: $regular_font;
} }
.gitignore-selector { .gitignore-selector, .license-selector {
.dropdown { .dropdown {
line-height: 21px; line-height: 21px;
} }
......
...@@ -244,6 +244,10 @@ ...@@ -244,6 +244,10 @@
.panel-footer { .panel-footer {
padding: 5px 10px; padding: 5px 10px;
.btn {
min-width: auto;
}
} }
.commit { .commit {
...@@ -252,9 +256,7 @@ ...@@ -252,9 +256,7 @@
} }
.avatar { .avatar {
width: 20px; margin-left: 0;
height: 20px;
margin-right: 5px;
} }
.commit-row-info { .commit-row-info {
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
.container-fluid { .container-fluid {
position: relative; position: relative;
@media (min-width: $screen-md-max) { @media (min-width: $screen-lg-min) {
.row { .row {
display: flex; display: flex;
-ms-flex-align: center; -ms-flex-align: center;
...@@ -224,7 +224,7 @@ ...@@ -224,7 +224,7 @@
right: 16px; right: 16px;
bottom: 0; bottom: 0;
@media (max-width: $screen-lg-min) { @media (max-width: $screen-md-max) {
top: 0; top: 0;
} }
...@@ -233,7 +233,7 @@ ...@@ -233,7 +233,7 @@
right: 0; right: 0;
bottom: 61px; bottom: 61px;
@media (max-width: $screen-lg-min) { @media (max-width: $screen-md-max) {
position: relative; position: relative;
bottom: 0; bottom: 0;
margin-right: 10px; margin-right: 10px;
......
...@@ -144,6 +144,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -144,6 +144,7 @@ class ProjectsController < Projects::ApplicationController
issues: autocomplete.issues, issues: autocomplete.issues,
milestones: autocomplete.milestones, milestones: autocomplete.milestones,
mergerequests: autocomplete.merge_requests, mergerequests: autocomplete.merge_requests,
labels: autocomplete.labels,
members: participants members: participants
} }
......
...@@ -180,8 +180,8 @@ module BlobHelper ...@@ -180,8 +180,8 @@ module BlobHelper
licenses = Licensee::License.all licenses = Licensee::License.all
@licenses_for_select = { @licenses_for_select = {
Popular: licenses.select(&:featured).map { |license| [license.name, license.key] }, Popular: licenses.select(&:featured).map { |license| { name: license.name, id: license.key } },
Other: licenses.reject(&:featured).map { |license| [license.name, license.key] } Other: licenses.reject(&:featured).map { |license| { name: license.name, id: license.key } }
} }
end end
......
...@@ -6,12 +6,6 @@ module MembersHelper ...@@ -6,12 +6,6 @@ module MembersHelper
"#{action}_#{member.type.underscore}".to_sym "#{action}_#{member.type.underscore}".to_sym
end end
def can_see_member_roles?(source:, user: nil)
return false unless user
user.is_admin? || source.members.exists?(user_id: user.id)
end
def remove_member_message(member, user: nil) def remove_member_message(member, user: nil)
user = current_user if defined?(current_user) user = current_user if defined?(current_user)
......
...@@ -94,10 +94,13 @@ module Ci ...@@ -94,10 +94,13 @@ module Ci
end end
def create_builds(user, trigger_request = nil) def create_builds(user, trigger_request = nil)
##
# We persist pipeline only if there are builds available
#
return unless config_processor return unless config_processor
config_processor.stages.any? do |stage|
CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present? build_builds_for_stages(config_processor.stages, user,
end 'success', trigger_request) && save
end end
def create_next_builds(build) def create_next_builds(build)
...@@ -115,10 +118,10 @@ module Ci ...@@ -115,10 +118,10 @@ module Ci
prior_builds = latest_builds.where.not(stage: next_stages) prior_builds = latest_builds.where.not(stage: next_stages)
prior_status = prior_builds.status prior_status = prior_builds.status
# create builds for next stages based # build builds for next stage that has builds available
next_stages.any? do |stage| # and save pipeline if we have builds
CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present? build_builds_for_stages(next_stages, build.user, prior_status,
end build.trigger_request) && save
end end
def retried def retried
...@@ -139,10 +142,10 @@ module Ci ...@@ -139,10 +142,10 @@ module Ci
@config_processor ||= begin @config_processor ||= begin
Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error(e.message) self.yaml_errors = e.message
nil nil
rescue rescue
save_yaml_error("Undefined error") self.yaml_errors = 'Undefined error'
nil nil
end end
end end
...@@ -169,6 +172,17 @@ module Ci ...@@ -169,6 +172,17 @@ module Ci
private private
def build_builds_for_stages(stages, user, status, trigger_request)
##
# Note that `Array#any?` implements a short circuit evaluation, so we
# build builds only for the first stage that has builds available.
#
stages.any? do |stage|
CreateBuildsService.new(self)
.execute(stage, user, status, trigger_request).present?
end
end
def update_state def update_state
statuses.reload statuses.reload
self.status = if yaml_errors.blank? self.status = if yaml_errors.blank?
...@@ -181,11 +195,5 @@ module Ci ...@@ -181,11 +195,5 @@ module Ci
self.duration = statuses.latest.duration self.duration = statuses.latest.duration
save save
end end
def save_yaml_error(error)
return if self.yaml_errors?
self.yaml_errors = error
update_state
end
end end
end end
...@@ -9,6 +9,12 @@ class Group < Namespace ...@@ -9,6 +9,12 @@ class Group < Namespace
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
alias_method :members, :group_members alias_method :members, :group_members
has_many :users, -> { where(members: { requested_at: nil }) }, through: :group_members has_many :users, -> { where(members: { requested_at: nil }) }, through: :group_members
has_many :owners,
-> { where(members: { access_level: Gitlab::Access::OWNER }) },
through: :group_members,
source: :user
has_many :project_group_links, dependent: :destroy has_many :project_group_links, dependent: :destroy
has_many :shared_projects, through: :project_group_links, source: :project has_many :shared_projects, through: :project_group_links, source: :project
has_many :ldap_group_links, foreign_key: 'group_id', dependent: :destroy has_many :ldap_group_links, foreign_key: 'group_id', dependent: :destroy
......
class JiraIssue < ExternalIssue
end
...@@ -294,7 +294,23 @@ class Project < ActiveRecord::Base ...@@ -294,7 +294,23 @@ class Project < ActiveRecord::Base
# #
# Returns a Project, or nil if no project could be found. # Returns a Project, or nil if no project could be found.
def find_with_namespace(path) def find_with_namespace(path)
where_paths_in([path]).reorder(nil).take namespace_path, project_path = path.split('/', 2)
return unless namespace_path && project_path
namespace_path = connection.quote(namespace_path)
project_path = connection.quote(project_path)
# On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
# any literal matches come first, for this we have to use "BINARY".
# Without this there's still no guarantee in what order MySQL will return
# rows.
binary = Gitlab::Database.mysql? ? 'BINARY' : ''
order_sql = "(CASE WHEN #{binary} namespaces.path = #{namespace_path} " \
"AND #{binary} projects.path = #{project_path} THEN 0 ELSE 1 END)"
where_paths_in([path]).reorder(order_sql).take
end end
# Builds a relation to find multiple projects by their full paths. # Builds a relation to find multiple projects by their full paths.
......
...@@ -295,7 +295,7 @@ class Repository ...@@ -295,7 +295,7 @@ class Repository
end end
def cache_keys def cache_keys
%i(size branch_names tag_names commit_count %i(size branch_names tag_names branch_count tag_count commit_count
readme version contribution_guide changelog readme version contribution_guide changelog
license_blob license_key gitignore) license_blob license_key gitignore)
end end
......
...@@ -2,10 +2,11 @@ module Ci ...@@ -2,10 +2,11 @@ module Ci
class CreateBuildsService class CreateBuildsService
def initialize(pipeline) def initialize(pipeline)
@pipeline = pipeline @pipeline = pipeline
@config = pipeline.config_processor
end end
def execute(stage, user, status, trigger_request = nil) def execute(stage, user, status, trigger_request = nil)
builds_attrs = config_processor.builds_for_stage_and_ref(stage, @pipeline.ref, @pipeline.tag, trigger_request) builds_attrs = @config.builds_for_stage_and_ref(stage, @pipeline.ref, @pipeline.tag, trigger_request)
# check when to create next build # check when to create next build
builds_attrs = builds_attrs.select do |build_attrs| builds_attrs = builds_attrs.select do |build_attrs|
...@@ -19,34 +20,37 @@ module Ci ...@@ -19,34 +20,37 @@ module Ci
end end
end end
# don't create the same build twice
builds_attrs.reject! do |build_attrs|
@pipeline.builds.find_by(ref: @pipeline.ref,
tag: @pipeline.tag,
trigger_request: trigger_request,
name: build_attrs[:name])
end
builds_attrs.map do |build_attrs| builds_attrs.map do |build_attrs|
# don't create the same build twice build_attrs.slice!(:name,
unless @pipeline.builds.find_by(ref: @pipeline.ref, tag: @pipeline.tag, :commands,
trigger_request: trigger_request, name: build_attrs[:name]) :tag_list,
build_attrs.slice!(:name, :options,
:commands, :allow_failure,
:tag_list, :stage,
:options, :stage_idx,
:allow_failure, :environment)
:stage,
:stage_idx,
:environment)
build_attrs.merge!(ref: @pipeline.ref, build_attrs.merge!(pipeline: @pipeline,
tag: @pipeline.tag, ref: @pipeline.ref,
trigger_request: trigger_request, tag: @pipeline.tag,
user: user, trigger_request: trigger_request,
project: @pipeline.project) user: user,
project: @pipeline.project)
@pipeline.builds.create!(build_attrs) ##
end # We do not persist new builds here.
# Those will be persisted when @pipeline is saved.
#
@pipeline.builds.new(build_attrs)
end end
end end
private
def config_processor
@config_processor ||= @pipeline.config_processor
end
end end
end end
...@@ -8,7 +8,9 @@ module Ci ...@@ -8,7 +8,9 @@ module Ci
return pipeline return pipeline
end end
unless commit if commit
pipeline.sha = commit.id
else
pipeline.errors.add(:base, 'Commit not found') pipeline.errors.add(:base, 'Commit not found')
return pipeline return pipeline
end end
...@@ -18,22 +20,18 @@ module Ci ...@@ -18,22 +20,18 @@ module Ci
return pipeline return pipeline
end end
begin unless pipeline.config_processor
Ci::Pipeline.transaction do pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file')
pipeline.sha = commit.id return pipeline
end
unless pipeline.config_processor pipeline.save!
pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file')
raise ActiveRecord::Rollback
end
pipeline.save! unless pipeline.create_builds(current_user)
pipeline.create_builds(current_user) pipeline.errors.add(:base, 'No builds for this pipeline.')
end
rescue
pipeline.errors.add(:base, 'The pipeline could not be created. Please try again.')
end end
pipeline.save
pipeline pipeline
end end
......
class CreateCommitBuildsService class CreateCommitBuildsService
def execute(project, user, params, mirror_update: false) def execute(project, user, params, mirror_update: false)
return false unless project.builds_enabled? return unless project.builds_enabled?
return false if !project.mirror_trigger_builds? && mirror_update return false if !project.mirror_trigger_builds? && mirror_update
...@@ -9,10 +9,6 @@ class CreateCommitBuildsService ...@@ -9,10 +9,6 @@ class CreateCommitBuildsService
sha = params[:checkout_sha] || params[:after] sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref] origin_ref = params[:ref]
unless origin_ref && sha.present?
return false
end
ref = Gitlab::Git.ref_name(origin_ref) ref = Gitlab::Git.ref_name(origin_ref)
tag = Gitlab::Git.tag_ref?(origin_ref) tag = Gitlab::Git.tag_ref?(origin_ref)
...@@ -21,23 +17,50 @@ class CreateCommitBuildsService ...@@ -21,23 +17,50 @@ class CreateCommitBuildsService
return false return false
end end
pipeline = Ci::Pipeline.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag) @pipeline = Ci::Pipeline.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag)
# Skip creating pipeline when no gitlab-ci.yml is found ##
unless pipeline.ci_yaml_file # Skip creating pipeline if no gitlab-ci.yml is found
#
unless @pipeline.ci_yaml_file
return false return false
end end
# Create a new pipeline ##
pipeline.save!
# Skip creating builds for commits that have [ci skip] # Skip creating builds for commits that have [ci skip]
unless pipeline.skip_ci? # but save pipeline object
# Create builds for commit #
pipeline.create_builds(user) if @pipeline.skip_ci?
return save_pipeline!
end
##
# Skip creating builds when CI config is invalid
# but save pipeline object
#
unless @pipeline.config_processor
return save_pipeline!
end end
pipeline.touch ##
pipeline # Skip creating pipeline object if there are no builds for it.
#
unless @pipeline.create_builds(user)
@pipeline.errors.add(:base, 'No builds created')
return false
end
save_pipeline!
end
private
##
# Create a new pipeline and touch object to calculate status
#
def save_pipeline!
@pipeline.save!
@pipeline.touch
@pipeline
end end
end end
...@@ -11,5 +11,9 @@ module Projects ...@@ -11,5 +11,9 @@ module Projects
def merge_requests def merge_requests
@project.merge_requests.opened.select([:iid, :title]) @project.merge_requests.opened.select([:iid, :title])
end end
def labels
@project.labels.select([:title, :color])
end
end end
end end
.center
#content
%h2 Hello, #{@resource.name}!
%p
The password for your GitLab account on
#{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}
has successfully been changed.
%p
If you did not initiate this change, please contact your administrator
immediately.
Hello, <%= @resource.name %>!
The password for your GitLab account on <%= Gitlab.config.gitlab.url %>
has successfully been changed.
If you did not initiate this change, please contact your administrator
immediately.
<p>Hello <%= @resource.email %>!</p>
<p>Someone has requested a link to change your password, and you can do this through the link below.</p>
<p><%= link_to 'Change your password', edit_password_url(@resource, reset_password_token: @token) %></p>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>
.center
#content
%h2 Hello, #{@resource.name}!
%p
Someone, hopefully you, has requested to reset the password for your
GitLab account on #{link_to(Gitlab.config.gitlab.url, Gitlab.config.gitlab.url)}.
%p
If you did not perform this request, you can safely ignore this email.
%p
Otherwise, click the link below to complete the process.
#cta
= link_to('Reset password', edit_password_url(@resource, reset_password_token: @token))
Hello, <%= @resource.name %>!
Someone, hopefully you, has requested to reset the password for your GitLab
account on <%= Gitlab.config.gitlab.url %>
If you did not perform this request, you can safely ignore this email.
Otherwise, click the link below to complete the process:
<%= edit_password_url(@resource, reset_password_token: @token) %>
%p .center
Hello #{@resource.name}! #content
%h2 Hello, #{@resource.name}!
%p %p
Your GitLab account has been locked due to an excessive amount of unsuccessful Your GitLab account has been locked due to an excessive amount of unsuccessful
sign in attempts. Your account will automatically unlock in sign in attempts. Your account will automatically unlock in #{time_ago_in_words(Devise.unlock_in.from_now)}
= time_ago_in_words(Devise.unlock_in.from_now) or you may click the link below to unlock now.
or you may click the link below to unlock now. #cta
= link_to('Unlock account', unlock_url(@resource, unlock_token: @token))
%p= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token)
Hello, <%= @resource.name %>!
Your GitLab account has been locked due to an excessive amount of unsuccessful
sign in attempts. Your account will automatically unlock in <%= time_ago_in_words(Devise.unlock_in.from_now) %>
or you may click the link below to unlock now.
<%= unlock_url(@resource, unlock_token: @token) %>
:plain :plain
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render(@group_member, member: @group_member))}'); $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
- if event = last_push_event - if event = last_push_event
- if show_last_push_widget?(event) - if show_last_push_widget?(event)
.row-content-block.top-block.clear-block.hidden-xs .row-content-block.top-block.clear-block.hidden-xs
.event-last-push %div{ class: (container_class) }
.event-last-push-text .event-last-push
%span You pushed to .event-last-push-text
= link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do %span You pushed to
%strong= event.ref_name = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
branch %strong= event.ref_name
#{time_ago_with_tooltip(event.created_at)} branch
#{time_ago_with_tooltip(event.created_at)}
.pull-right .pull-right
= link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
Create Merge Request Create Merge Request
...@@ -13,12 +13,10 @@ ...@@ -13,12 +13,10 @@
required: true, class: 'form-control new-file-name' required: true, class: 'form-control new-file-name'
.pull-right .pull-right
.license-selector.js-license-selector.hide .license-selector.js-license-selector-wrap.hidden
= select_tag :license_type, grouped_options_for_select(licenses_for_select, @project.repository.license_key), include_blank: true, class: 'select2 license-select', data: {placeholder: 'Choose a license template', project: @project.name, fullname: @project.namespace.human_name} = dropdown_tag("Choose a License template", options: { toggle_class: 'js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.hidden
.gitignore-selector.hidden = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
= dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { filenames: gitignore_names } } )
.encoding-selector .encoding-selector
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
......
...@@ -48,16 +48,16 @@ ...@@ -48,16 +48,16 @@
- if @build.active? - if @build.active?
.autoscroll-container .autoscroll-container
%button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
#js-build-scroll.scroll-controls
= link_to '#build-trace', class: 'btn' do
%i.fa.fa-angle-up
= link_to '#down-build-trace', class: 'btn' do
%i.fa.fa-angle-down
- if @build.erased? - if @build.erased?
.erased.alert.alert-warning .erased.alert.alert-warning
- erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by
Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)}
- else - else
#js-build-scroll.scroll-controls
= link_to '#build-trace', class: 'btn' do
%i.fa.fa-angle-up
= link_to '#down-build-trace', class: 'btn' do
%i.fa.fa-angle-down
%pre.build-trace#build-trace %pre.build-trace#build-trace
%code.bash.js-build-output %code.bash.js-build-output
= icon("refresh spin", class: "js-build-refresh") = icon("refresh spin", class: "js-build-refresh")
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
selected: f.object.source_project_id selected: f.object.source_project_id
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :source_branch = 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" } = dropdown_toggle f.object.source_branch || "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-branch .dropdown-menu.dropdown-menu-selectable.dropdown-source-branch
= dropdown_title("Select source branch") = dropdown_title("Select source branch")
= dropdown_filter("Search branches") = dropdown_filter("Search branches")
......
...@@ -17,11 +17,11 @@ ...@@ -17,11 +17,11 @@
= link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do = link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do
Check out branch Check out branch
%span.dropdown %span.dropdown.inline.prepend-left-5
%a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} } %a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
Download as Download as
%span.caret %span.caret
%ul.dropdown-menu %ul.dropdown-menu.dropdown-menu-align-right
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
.normal .normal
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
= render "projects/merge_requests/widget/show.html.haml" = render "projects/merge_requests/widget/show.html.haml"
- if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user) - if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user)
.light.prepend-top-default .light.prepend-top-default.append-bottom-default
You can also accept this merge request manually using the You can also accept this merge request manually using the
= succeed '.' do = succeed '.' do
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
......
...@@ -23,10 +23,10 @@ ...@@ -23,10 +23,10 @@
#{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)}) #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
%li %li
= link_to namespace_project_branches_path(@project.namespace, @project) do = link_to namespace_project_branches_path(@project.namespace, @project) do
#{'Branch'.pluralize(@repository.branch_names.count)} (#{number_with_delimiter(@repository.branch_names.count)}) #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%li %li
= link_to namespace_project_tags_path(@project.namespace, @project) do = link_to namespace_project_tags_path(@project.namespace, @project) do
#{'Tag'.pluralize(@repository.tag_names.count)} (#{number_with_delimiter(@repository.tag_names.count)}) #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
- if default_project_view != 'readme' && @repository.readme - if default_project_view != 'readme' && @repository.readme
%li %li
......
%tr{ class: "tree-item #{tree_hex_class(blob_item)}" } %tr{ class: "tree-item #{tree_hex_class(blob_item)}" }
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon(type, blob_item.mode, blob_item.name) = tree_icon(type, blob_item.mode, blob_item.name)
%span.str-truncated - file_name = blob_item.name
= link_to blob_item.name, namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)) = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do
%span.str-truncated= file_name
%td.tree_time_ago.cgray %td.tree_time_ago.cgray
= render 'projects/tree/spinner' = render 'projects/tree/spinner'
%td.hidden-xs.tree_commit %td.hidden-xs.tree_commit
%tr{ class: "tree-item #{tree_hex_class(tree_item)}" } %tr{ class: "tree-item #{tree_hex_class(tree_item)}" }
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon(type, tree_item.mode, tree_item.name) = tree_icon(type, tree_item.mode, tree_item.name)
%span.str-truncated - path = flatten_tree(tree_item)
- path = flatten_tree(tree_item) = link_to namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)), title: path do
= link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)) %span.str-truncated= path
%td.tree_time_ago.cgray %td.tree_time_ago.cgray
= render 'projects/tree/spinner' = render 'projects/tree/spinner'
%td.hidden-xs.tree_commit %td.hidden-xs.tree_commit
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
method: :post, method: :post,
class: 'btn-xs btn' class: 'btn-xs btn'
- if show_roles && can_see_member_roles?(source: member.source, user: current_user) - if show_roles
%span.pull-right %span.pull-right
%strong= member.human_access %strong= member.human_access
- if show_controls - if show_controls
......
...@@ -9,3 +9,4 @@ unless Gitlab.config.gitlab_on_standard_port? ...@@ -9,3 +9,4 @@ unless Gitlab.config.gitlab_on_standard_port?
end end
Rails.application.routes.default_url_options = default_url_options Rails.application.routes.default_url_options = default_url_options
ActionMailer::Base.asset_host = Settings.gitlab['base_url']
...@@ -103,7 +103,7 @@ module Banzai ...@@ -103,7 +103,7 @@ module Banzai
ref_pattern = object_class.reference_pattern ref_pattern = object_class.reference_pattern
link_pattern = object_class.link_reference_pattern link_pattern = object_class.link_reference_pattern
each_node do |node| nodes.each do |node|
if text_node?(node) && ref_pattern if text_node?(node) && ref_pattern
replace_text_when_pattern_matches(node, ref_pattern) do |content| replace_text_when_pattern_matches(node, ref_pattern) do |content|
object_link_filter(content, ref_pattern) object_link_filter(content, ref_pattern)
...@@ -206,6 +206,55 @@ module Banzai ...@@ -206,6 +206,55 @@ module Banzai
text text
end end
# Returns a Hash containing all object references (e.g. issue IDs) per the
# project they belong to.
def references_per_project
@references_per_project ||= begin
refs = Hash.new { |hash, key| hash[key] = Set.new }
regex = Regexp.union(object_class.reference_pattern,
object_class.link_reference_pattern)
nodes.each do |node|
node.to_html.scan(regex) do
project = $~[:project] || current_project_path
refs[project] << $~[object_sym]
end
end
refs
end
end
# Returns a Hash containing referenced projects grouped per their full
# path.
def projects_per_reference
@projects_per_reference ||= begin
hash = {}
refs = Set.new
references_per_project.each do |project_ref, _|
refs << project_ref
end
find_projects_for_paths(refs.to_a).each do |project|
hash[project.path_with_namespace] = project
end
hash
end
end
# Returns the projects for the given paths.
def find_projects_for_paths(paths)
Project.where_paths_in(paths).includes(:namespace)
end
def current_project_path
@current_project_path ||= project.path_with_namespace
end
private private
def project_refs_cache def project_refs_cache
......
...@@ -3,17 +3,8 @@ module Banzai ...@@ -3,17 +3,8 @@ module Banzai
# HTML Filter to modify the attributes of external links # HTML Filter to modify the attributes of external links
class ExternalLinkFilter < HTML::Pipeline::Filter class ExternalLinkFilter < HTML::Pipeline::Filter
def call def call
doc.search('a').each do |node| # Skip non-HTTP(S) links and internal links
link = node.attr('href') doc.xpath("descendant-or-self::a[starts-with(@href, 'http') and not(starts-with(@href, '#{internal_url}'))]").each do |node|
next unless link
# Skip non-HTTP(S) links
next unless link.start_with?('http')
# Skip internal links
next if link.start_with?(internal_url)
node.set_attribute('rel', 'nofollow noreferrer') node.set_attribute('rel', 'nofollow noreferrer')
node.set_attribute('target', '_blank') node.set_attribute('target', '_blank')
end end
......
...@@ -11,13 +11,40 @@ module Banzai ...@@ -11,13 +11,40 @@ module Banzai
Issue Issue
end end
def find_object(project, id) def find_object(project, iid)
project.get_issue(id) issues_per_project[project][iid]
end end
def url_for_object(issue, project) def url_for_object(issue, project)
IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path]) IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path])
end end
def project_from_ref(ref)
projects_per_reference[ref || current_project_path]
end
# Returns a Hash containing the issues per Project instance.
def issues_per_project
@issues_per_project ||= begin
hash = Hash.new { |h, k| h[k] = {} }
projects_per_reference.each do |path, project|
issue_ids = references_per_project[path]
next unless project.default_issues_tracker?
project.issues.where(iid: issue_ids.to_a).each do |issue|
hash[project][issue.iid] = issue
end
end
hash
end
end
def find_projects_for_paths(paths)
super(paths).includes(:gitlab_issue_tracker_service)
end
end end
end end
end end
...@@ -10,11 +10,11 @@ module Banzai ...@@ -10,11 +10,11 @@ module Banzai
def call def call
return doc unless project return doc unless project
doc.search('a').each do |el| doc.xpath('descendant-or-self::a[starts-with(@href, "/uploads/")]').each do |el|
process_link_attr el.attribute('href') process_link_attr el.attribute('href')
end end
doc.search('img').each do |el| doc.xpath('descendant-or-self::img[starts-with(@src, "/uploads/")]').each do |el|
process_link_attr el.attribute('src') process_link_attr el.attribute('src')
end end
...@@ -24,12 +24,7 @@ module Banzai ...@@ -24,12 +24,7 @@ module Banzai
protected protected
def process_link_attr(html_attr) def process_link_attr(html_attr)
return if html_attr.blank? html_attr.value = build_url(html_attr.value).to_s
uri = html_attr.value
if uri.starts_with?("/uploads/")
html_attr.value = build_url(uri).to_s
end
end end
def build_url(uri) def build_url(uri)
......
...@@ -30,7 +30,10 @@ module Ci ...@@ -30,7 +30,10 @@ module Ci
end end
def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil) def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
builds.select{|build| build[:stage] == stage && process?(build[:only], build[:except], ref, tag, trigger_request)} builds.select do |build|
build[:stage] == stage &&
process?(build[:only], build[:except], ref, tag, trigger_request)
end
end end
def builds def builds
......
require 'spec_helper' require 'spec_helper'
feature 'project owner creates a license file', feature: true, js: true do feature 'project owner creates a license file', feature: true, js: true do
include Select2Helper include WaitForAjax
let(:project_master) { create(:user) } let(:project_master) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
...@@ -21,7 +21,7 @@ feature 'project owner creates a license file', feature: true, js: true do ...@@ -21,7 +21,7 @@ feature 'project owner creates a license file', feature: true, js: true do
expect(page).to have_selector('.license-selector') expect(page).to have_selector('.license-selector')
select2('mit', from: '#license_type') select_template('MIT License')
file_content = find('.file-content') file_content = find('.file-content')
expect(file_content).to have_content('The MIT License (MIT)') expect(file_content).to have_content('The MIT License (MIT)')
...@@ -44,7 +44,7 @@ feature 'project owner creates a license file', feature: true, js: true do ...@@ -44,7 +44,7 @@ feature 'project owner creates a license file', feature: true, js: true do
expect(find('#file_name').value).to eq('LICENSE') expect(find('#file_name').value).to eq('LICENSE')
expect(page).to have_selector('.license-selector') expect(page).to have_selector('.license-selector')
select2('mit', from: '#license_type') select_template('MIT License')
file_content = find('.file-content') file_content = find('.file-content')
expect(file_content).to have_content('The MIT License (MIT)') expect(file_content).to have_content('The MIT License (MIT)')
...@@ -58,4 +58,12 @@ feature 'project owner creates a license file', feature: true, js: true do ...@@ -58,4 +58,12 @@ feature 'project owner creates a license file', feature: true, js: true do
expect(page).to have_content('The MIT License (MIT)') expect(page).to have_content('The MIT License (MIT)')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}") expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end end
def select_template(template)
page.within('.js-license-selector-wrap') do
click_button 'Choose a License template'
click_link template
wait_for_ajax
end
end
end end
require 'spec_helper' require 'spec_helper'
feature 'project owner sees a link to create a license file in empty project', feature: true, js: true do feature 'project owner sees a link to create a license file in empty project', feature: true, js: true do
include Select2Helper include WaitForAjax
let(:project_master) { create(:user) } let(:project_master) { create(:user) }
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
...@@ -20,7 +20,7 @@ feature 'project owner sees a link to create a license file in empty project', f ...@@ -20,7 +20,7 @@ feature 'project owner sees a link to create a license file in empty project', f
expect(find('#file_name').value).to eq('LICENSE') expect(find('#file_name').value).to eq('LICENSE')
expect(page).to have_selector('.license-selector') expect(page).to have_selector('.license-selector')
select2('mit', from: '#license_type') select_template('MIT License')
file_content = find('.file-content') file_content = find('.file-content')
expect(file_content).to have_content('The MIT License (MIT)') expect(file_content).to have_content('The MIT License (MIT)')
...@@ -36,4 +36,12 @@ feature 'project owner sees a link to create a license file in empty project', f ...@@ -36,4 +36,12 @@ feature 'project owner sees a link to create a license file in empty project', f
expect(page).to have_content('The MIT License (MIT)') expect(page).to have_content('The MIT License (MIT)')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}") expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end end
def select_template(template)
page.within('.js-license-selector-wrap') do
click_button 'Choose a License template'
click_link template
wait_for_ajax
end
end
end end
...@@ -77,6 +77,7 @@ feature 'Prioritize labels', feature: true do ...@@ -77,6 +77,7 @@ feature 'Prioritize labels', feature: true do
end end
visit current_url visit current_url
wait_for_ajax
page.within('.prioritized-labels') do page.within('.prioritized-labels') do
expect(first('li')).to have_content('wontfix') expect(first('li')).to have_content('wontfix')
......
...@@ -49,6 +49,13 @@ describe NotesFinder do ...@@ -49,6 +49,13 @@ describe NotesFinder do
user = create(:user) user = create(:user)
expect { NotesFinder.new.execute(project, user, params) }.to raise_error(ActiveRecord::RecordNotFound) expect { NotesFinder.new.execute(project, user, params) }.to raise_error(ActiveRecord::RecordNotFound)
end end
it 'raises an error for project members with guest role' do
user = create(:user)
project.team << [user, :guest]
expect { NotesFinder.new.execute(project, user, params) }.to raise_error(ActiveRecord::RecordNotFound)
end
end end
end end
end end
...@@ -9,22 +9,6 @@ describe MembersHelper do ...@@ -9,22 +9,6 @@ describe MembersHelper do
it { expect(action_member_permission(:admin, group_member)).to eq :admin_group_member } it { expect(action_member_permission(:admin, group_member)).to eq :admin_group_member }
end end
describe '#can_see_member_roles?' do
let(:project) { create(:empty_project) }
let(:group) { create(:group) }
let(:user) { build(:user) }
let(:admin) { build(:user, :admin) }
let(:project_member) { create(:project_member, project: project) }
let(:group_member) { create(:group_member, group: group) }
it { expect(can_see_member_roles?(source: project, user: nil)).to be_falsy }
it { expect(can_see_member_roles?(source: group, user: nil)).to be_falsy }
it { expect(can_see_member_roles?(source: project, user: admin)).to be_truthy }
it { expect(can_see_member_roles?(source: group, user: admin)).to be_truthy }
it { expect(can_see_member_roles?(source: project, user: project_member.user)).to be_truthy }
it { expect(can_see_member_roles?(source: group, user: group_member.user)).to be_truthy }
end
describe '#remove_member_message' do describe '#remove_member_message' do
let(:requester) { build(:user) } let(:requester) { build(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -33,9 +33,9 @@ describe MergeRequestsHelper do ...@@ -33,9 +33,9 @@ describe MergeRequestsHelper do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:issues) do let(:issues) do
[ [
JiraIssue.new('JIRA-123', project), ExternalIssue.new('JIRA-123', project),
JiraIssue.new('JIRA-456', project), ExternalIssue.new('JIRA-456', project),
JiraIssue.new('FOOBAR-7890', project) ExternalIssue.new('FOOBAR-7890', project)
] ]
end end
......
...@@ -6,7 +6,7 @@ describe 'MergeRequest', -> ...@@ -6,7 +6,7 @@ describe 'MergeRequest', ->
beforeEach -> beforeEach ->
fixture.load('merge_requests_show.html') fixture.load('merge_requests_show.html')
@merge = new MergeRequest({}) @merge = new MergeRequest()
it 'modifies the Markdown field', -> it 'modifies the Markdown field', ->
spyOn(jQuery, 'ajax').and.stub() spyOn(jQuery, 'ajax').and.stub()
......
require 'spec_helper'
describe Banzai::Filter::AbstractReferenceFilter do
let(:project) { create(:empty_project) }
describe '#references_per_project' do
it 'returns a Hash containing references grouped per project paths' do
doc = Nokogiri::HTML.fragment("#1 #{project.to_reference}#2")
filter = described_class.new(doc, project: project)
expect(filter).to receive(:object_class).twice.and_return(Issue)
expect(filter).to receive(:object_sym).twice.and_return(:issue)
refs = filter.references_per_project
expect(refs).to be_an_instance_of(Hash)
expect(refs[project.to_reference]).to eq(Set.new(%w[1 2]))
end
end
describe '#projects_per_reference' do
it 'returns a Hash containing projects grouped per project paths' do
doc = Nokogiri::HTML.fragment('')
filter = described_class.new(doc, project: project)
expect(filter).to receive(:references_per_project).
and_return({ project.path_with_namespace => Set.new(%w[1]) })
expect(filter.projects_per_reference).
to eq({ project.path_with_namespace => project })
end
end
describe '#find_projects_for_paths' do
it 'returns a list of Projects for a list of paths' do
doc = Nokogiri::HTML.fragment('')
filter = described_class.new(doc, project: project)
expect(filter.find_projects_for_paths([project.path_with_namespace])).
to eq([project])
end
end
describe '#current_project_path' do
it 'returns the path of the current project' do
doc = Nokogiri::HTML.fragment('')
filter = described_class.new(doc, project: project)
expect(filter.current_project_path).to eq(project.path_with_namespace)
end
end
end
...@@ -19,19 +19,31 @@ describe Banzai::Filter::ExternalLinkFilter, lib: true do ...@@ -19,19 +19,31 @@ describe Banzai::Filter::ExternalLinkFilter, lib: true do
expect(filter(act).to_html).to eq exp expect(filter(act).to_html).to eq exp
end end
it 'adds rel="nofollow" to external links' do context 'for root links on document' do
act = %q(<a href="https://google.com/">Google</a>) let(:doc) { filter %q(<a href="https://google.com/">Google</a>) }
doc = filter(act)
it 'adds rel="nofollow" to external links' do
expect(doc.at_css('a')).to have_attribute('rel') expect(doc.at_css('a')).to have_attribute('rel')
expect(doc.at_css('a')['rel']).to include 'nofollow' expect(doc.at_css('a')['rel']).to include 'nofollow'
end
it 'adds rel="noreferrer" to external links' do
expect(doc.at_css('a')).to have_attribute('rel')
expect(doc.at_css('a')['rel']).to include 'noreferrer'
end
end end
it 'adds rel="noreferrer" to external links' do context 'for nested links on document' do
act = %q(<a href="https://google.com/">Google</a>) let(:doc) { filter %q(<p><a href="https://google.com/">Google</a></p>) }
doc = filter(act)
it 'adds rel="nofollow" to external links' do
expect(doc.at_css('a')).to have_attribute('rel')
expect(doc.at_css('a')['rel']).to include 'nofollow'
end
expect(doc.at_css('a')).to have_attribute('rel') it 'adds rel="noreferrer" to external links' do
expect(doc.at_css('a')['rel']).to include 'noreferrer' expect(doc.at_css('a')).to have_attribute('rel')
expect(doc.at_css('a')['rel']).to include 'noreferrer'
end
end end
end end
...@@ -25,7 +25,9 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do ...@@ -25,7 +25,9 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
let(:reference) { issue.to_reference } let(:reference) { issue.to_reference }
it 'ignores valid references when using non-default tracker' do it 'ignores valid references when using non-default tracker' do
expect(project).to receive(:get_issue).with(issue.iid).and_return(nil) expect_any_instance_of(described_class).to receive(:find_object).
with(project, issue.iid).
and_return(nil)
exp = act = "Issue #{reference}" exp = act = "Issue #{reference}"
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
...@@ -107,8 +109,9 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do ...@@ -107,8 +109,9 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
let(:reference) { issue.to_reference(project) } let(:reference) { issue.to_reference(project) }
it 'ignores valid references when cross-reference project uses external tracker' do it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(Project).to receive(:get_issue). expect_any_instance_of(described_class).to receive(:find_object).
with(issue.iid).and_return(nil) with(project2, issue.iid).
and_return(nil)
exp = act = "Issue #{reference}" exp = act = "Issue #{reference}"
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
......
...@@ -23,6 +23,14 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do ...@@ -23,6 +23,14 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
%(<a href="#{path}">#{path}</a>) %(<a href="#{path}">#{path}</a>)
end end
def nested_image(path)
%(<div><img src="#{path}" /></div>)
end
def nested_link(path)
%(<div><a href="#{path}">#{path}</a></div>)
end
let(:project) { create(:project) } let(:project) { create(:project) }
shared_examples :preserve_unchanged do shared_examples :preserve_unchanged do
...@@ -47,11 +55,19 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do ...@@ -47,11 +55,19 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
expect(doc.at_css('a')['href']). expect(doc.at_css('a')['href']).
to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
doc = filter(nested_link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
expect(doc.at_css('a')['href']).
to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
end end
it 'rebuilds relative URL for an image' do it 'rebuilds relative URL for an image' do
doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
expect(doc.at_css('a')['href']). expect(doc.at_css('img')['src']).
to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
doc = filter(nested_image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
expect(doc.at_css('img')['src']).
to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
end end
......
...@@ -105,7 +105,8 @@ describe Gitlab::ReferenceExtractor, lib: true do ...@@ -105,7 +105,8 @@ describe Gitlab::ReferenceExtractor, lib: true do
it 'returns JIRA issues for a JIRA-integrated project' do it 'returns JIRA issues for a JIRA-integrated project' do
subject.analyze('JIRA-123 and FOOBAR-4567') subject.analyze('JIRA-123 and FOOBAR-4567')
expect(subject.issues).to eq [JiraIssue.new('JIRA-123', project), JiraIssue.new('FOOBAR-4567', project)] expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
ExternalIssue.new('FOOBAR-4567', project)]
end end
end end
......
class DeviseMailerPreview < ActionMailer::Preview class DeviseMailerPreview < ActionMailer::Preview
def confirmation_instructions_for_signup def confirmation_instructions_for_signup
user = User.new(name: 'Jane Doe', email: 'signup@example.com') DeviseMailer.confirmation_instructions(unsaved_user, 'faketoken', {})
DeviseMailer.confirmation_instructions(user, 'faketoken', {})
end end
def confirmation_instructions_for_new_email def confirmation_instructions_for_new_email
user = User.last user = User.last
user.unconfirmed_email = 'unconfirmed@example.com'
DeviseMailer.confirmation_instructions(user, 'faketoken', {}) DeviseMailer.confirmation_instructions(user, 'faketoken', {})
end end
def reset_password_instructions
DeviseMailer.reset_password_instructions(unsaved_user, 'faketoken', {})
end
def unlock_instructions
DeviseMailer.unlock_instructions(unsaved_user, 'faketoken', {})
end
def password_change
DeviseMailer.password_change(unsaved_user, {})
end
private
def unsaved_user
User.new(name: 'Jane Doe', email: 'jdoe@example.com')
end
end end
...@@ -258,6 +258,19 @@ describe Ci::Pipeline, models: true do ...@@ -258,6 +258,19 @@ describe Ci::Pipeline, models: true do
end end
end end
end end
context 'when no builds created' do
let(:pipeline) { build(:ci_pipeline) }
before do
stub_ci_pipeline_yaml_file(YAML.dump(before_script: ['ls']))
end
it 'returns false' do
expect(pipeline.create_builds(nil)).to be_falsey
expect(pipeline).not_to be_persisted
end
end
end end
describe "#finished_at" do describe "#finished_at" do
......
...@@ -158,6 +158,18 @@ describe Group, models: true do ...@@ -158,6 +158,18 @@ describe Group, models: true do
it { expect(group.has_master?(@members[:requester])).to be_falsey } it { expect(group.has_master?(@members[:requester])).to be_falsey }
end end
describe '#owners' do
let(:owner) { create(:user) }
let(:developer) { create(:user) }
it 'returns the owners of a Group' do
group.add_owner(owner)
group.add_developer(developer)
expect(group.owners).to eq([owner])
end
end
def setup_group_members(group) def setup_group_members(group)
members = { members = {
owner: create(:user), owner: create(:user),
......
require 'spec_helper'
describe JiraIssue do
let(:project) { create(:project) }
subject { JiraIssue.new('JIRA-123', project) }
describe 'id' do
subject { super().id }
it { is_expected.to eq('JIRA-123') }
end
describe 'iid' do
subject { super().iid }
it { is_expected.to eq('JIRA-123') }
end
describe 'to_s' do
subject { super().to_s }
it { is_expected.to eq('JIRA-123') }
end
describe :== do
specify { expect(subject).to eq(JiraIssue.new('JIRA-123', project)) }
specify { expect(subject).not_to eq(JiraIssue.new('JIRA-124', project)) }
it 'only compares with JiraIssues' do
expect(subject).not_to eq('JIRA-123')
end
end
end
...@@ -76,7 +76,8 @@ describe JiraService, models: true do ...@@ -76,7 +76,8 @@ describe JiraService, models: true do
end end
it "should call JIRA API" do it "should call JIRA API" do
@jira_service.execute(merge_request, JiraIssue.new("JIRA-123", project)) @jira_service.execute(merge_request,
ExternalIssue.new("JIRA-123", project))
expect(WebMock).to have_requested(:post, @comment_url).with( expect(WebMock).to have_requested(:post, @comment_url).with(
body: /Issue solved with/ body: /Issue solved with/
).once ).once
...@@ -84,7 +85,8 @@ describe JiraService, models: true do ...@@ -84,7 +85,8 @@ describe JiraService, models: true do
it "calls the api with jira_issue_transition_id" do it "calls the api with jira_issue_transition_id" do
@jira_service.jira_issue_transition_id = 'this-is-a-custom-id' @jira_service.jira_issue_transition_id = 'this-is-a-custom-id'
@jira_service.execute(merge_request, JiraIssue.new("JIRA-123", project)) @jira_service.execute(merge_request,
ExternalIssue.new("JIRA-123", project))
expect(WebMock).to have_requested(:post, @api_url).with( expect(WebMock).to have_requested(:post, @api_url).with(
body: /this-is-a-custom-id/ body: /this-is-a-custom-id/
).once ).once
......
...@@ -229,7 +229,7 @@ describe Project, models: true do ...@@ -229,7 +229,7 @@ describe Project, models: true do
end end
end end
describe :find_with_namespace do describe '.find_with_namespace' do
context 'with namespace' do context 'with namespace' do
before do before do
@group = create :group, name: 'gitlab' @group = create :group, name: 'gitlab'
...@@ -240,6 +240,22 @@ describe Project, models: true do ...@@ -240,6 +240,22 @@ describe Project, models: true do
it { expect(Project.find_with_namespace('GitLab/GitlabHQ')).to eq(@project) } it { expect(Project.find_with_namespace('GitLab/GitlabHQ')).to eq(@project) }
it { expect(Project.find_with_namespace('gitlab-ci')).to be_nil } it { expect(Project.find_with_namespace('gitlab-ci')).to be_nil }
end end
context 'when multiple projects using a similar name exist' do
let(:group) { create(:group, name: 'gitlab') }
let!(:project1) do
create(:empty_project, name: 'gitlab1', path: 'gitlab', namespace: group)
end
let!(:project2) do
create(:empty_project, name: 'gitlab2', path: 'GITLAB', namespace: group)
end
it 'returns the row where the path matches literally' do
expect(Project.find_with_namespace('gitlab/GITLAB')).to eq(project2)
end
end
end end
describe :to_param do describe :to_param do
......
...@@ -9,7 +9,7 @@ describe Ci::CreateBuildsService, services: true do ...@@ -9,7 +9,7 @@ describe Ci::CreateBuildsService, services: true do
# #
subject do subject do
described_class.new(pipeline).execute('test', nil, user, status) described_class.new(pipeline).execute('test', user, status, nil)
end end
context 'next builds available' do context 'next builds available' do
...@@ -17,6 +17,10 @@ describe Ci::CreateBuildsService, services: true do ...@@ -17,6 +17,10 @@ describe Ci::CreateBuildsService, services: true do
it { is_expected.to be_an_instance_of Array } it { is_expected.to be_an_instance_of Array }
it { is_expected.to all(be_an_instance_of Ci::Build) } it { is_expected.to all(be_an_instance_of Ci::Build) }
it 'does not persist created builds' do
expect(subject.first).not_to be_persisted
end
end end
context 'builds skipped' do context 'builds skipped' do
......
...@@ -39,7 +39,7 @@ describe CreateCommitBuildsService, services: true do ...@@ -39,7 +39,7 @@ describe CreateCommitBuildsService, services: true do
end end
it "creates commit if there is no appropriate job but deploy job has right ref setting" do it "creates commit if there is no appropriate job but deploy job has right ref setting" do
config = YAML.dump({ deploy: { deploy: "ls", only: ["0_1"] } }) config = YAML.dump({ deploy: { script: "ls", only: ["0_1"] } })
stub_ci_pipeline_yaml_file(config) stub_ci_pipeline_yaml_file(config)
result = service.execute(project, user, result = service.execute(project, user,
...@@ -81,7 +81,7 @@ describe CreateCommitBuildsService, services: true do ...@@ -81,7 +81,7 @@ describe CreateCommitBuildsService, services: true do
expect(pipeline.yaml_errors).not_to be_nil expect(pipeline.yaml_errors).not_to be_nil
end end
describe :ci_skip? do context 'when commit contains a [ci skip] directive' do
let(:message) { "some message[ci skip]" } let(:message) { "some message[ci skip]" }
before do before do
...@@ -171,5 +171,24 @@ describe CreateCommitBuildsService, services: true do ...@@ -171,5 +171,24 @@ describe CreateCommitBuildsService, services: true do
expect(pipeline.status).to eq("failed") expect(pipeline.status).to eq("failed")
expect(pipeline.builds.any?).to be false expect(pipeline.builds.any?).to be false
end end
context 'when there are no jobs for this pipeline' do
before do
config = YAML.dump({ test: { script: 'ls', only: ['feature'] } })
stub_ci_pipeline_yaml_file(config)
end
it 'does not create a new pipeline' do
result = service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
commits: [{ message: 'some msg' }])
expect(result).to be_falsey
expect(Ci::Build.all).to be_empty
expect(Ci::Pipeline.count).to eq(0)
end
end
end end
end end
...@@ -328,7 +328,8 @@ describe GitPushService, services: true do ...@@ -328,7 +328,8 @@ describe GitPushService, services: true do
end end
it "doesn't close issues when external issue tracker is in use" do it "doesn't close issues when external issue tracker is in use" do
allow(project).to receive(:default_issues_tracker?).and_return(false) allow_any_instance_of(Project).to receive(:default_issues_tracker?).
and_return(false)
# The push still shouldn't create cross-reference notes. # The push still shouldn't create cross-reference notes.
expect do expect do
......
...@@ -536,7 +536,7 @@ describe SystemNoteService, services: true do ...@@ -536,7 +536,7 @@ describe SystemNoteService, services: true do
let(:author) { create(:user) } let(:author) { create(:user) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) } let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) }
let(:jira_issue) { JiraIssue.new("JIRA-1", project)} let(:jira_issue) { ExternalIssue.new("JIRA-1", project)}
let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? } let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? }
let(:commit) { project.commit } let(:commit) { project.commit }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment