diff options
148 files changed, 2335 insertions, 462 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 86b30d2832d..e03123111c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.14.3 (2016-12-02) + +- Pass commit data to ProcessCommitWorker to reduce Git overhead. !7744 +- Speed up issuable dashboards. +- Don't change relative URLs to absolute URLs in the Help page. +- Fixes "ActionView::Template::Error: undefined method `text?` for nil:NilClass" on MR pages. +- Fix branch validation for GitHub PR where repo/fork was renamed/deleted. +- Validate state param when filtering issuables. + ## 8.14.2 (2016-12-01) - Remove caching of events data. !6578 @@ -242,6 +251,11 @@ entry. - Fix "Without projects" filter. !6611 (Ben Bodenmiller) - Fix 404 when visit /projects page +## 8.13.8 (2016-12-02) + +- Pass tag SHA to post-receive hook when tag is created via UI. !7700 +- Validate state param when filtering issuables. + ## 8.13.7 (2016-11-28) - fixes 500 error on project show when user is not logged in and project is still empty. !7376 diff --git a/app/assets/stylesheets/pages/errors.scss b/app/assets/stylesheets/pages/errors.scss deleted file mode 100644 index 11309817d31..00000000000 --- a/app/assets/stylesheets/pages/errors.scss +++ /dev/null @@ -1,16 +0,0 @@ -.error-page { - max-width: 400px; - margin: 0 auto; - - h1, - h2, - h3 { - text-align: center; - } - - h1 { - font-size: 56px; - line-height: 100px; - font-weight: 300; - } -} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index dc5945e1139..6234779ac19 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -129,6 +129,11 @@ margin-bottom: 4px; } + .btn-grouped { + float: none; + margin-right: 0; + } + .accept-action { width: 100%; text-align: center; diff --git a/app/assets/stylesheets/pages/votes.scss b/app/assets/stylesheets/pages/votes.scss deleted file mode 100644 index dc9a7d71e8b..00000000000 --- a/app/assets/stylesheets/pages/votes.scss +++ /dev/null @@ -1,4 +0,0 @@ -.votes-inline { - display: inline-block; - margin: 0 8px; -} diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb index 6546a07b41c..fdb05bb3228 100644 --- a/app/controllers/concerns/merge_requests_action.rb +++ b/app/controllers/concerns/merge_requests_action.rb @@ -6,7 +6,12 @@ module MergeRequestsAction @label = merge_requests_finder.labels.first @merge_requests = merge_requests_collection - .non_archived .page(params[:page]) end + + private + + def filter_params + super.merge(non_archived: true) + end end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 001c83ccb4b..c9bee01b9ad 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -7,7 +7,7 @@ # current_user - which user use # params: # scope: 'created-by-me' or 'assigned-to-me' or 'all' -# state: 'open' or 'closed' or 'all' +# state: 'opened' or 'closed' or 'all' # group_id: integer # project_id: integer # milestone_title: string @@ -15,6 +15,7 @@ # search: string # label_name: string # sort: string +# non_archived: boolean # class IssuableFinder NONE = '0' @@ -38,6 +39,7 @@ class IssuableFinder items = by_author(items) items = by_label(items) items = by_due_date(items) + items = by_non_archived(items) sort(items) end @@ -207,10 +209,13 @@ class IssuableFinder end def by_state(items) - params[:state] ||= 'all' - - if items.respond_to?(params[:state]) - items.public_send(params[:state]) + case params[:state].to_s + when 'closed' + items.closed + when 'merged' + items.respond_to?(:merged) ? items.merged : items.closed + when 'opened' + items.opened else items end @@ -353,6 +358,10 @@ class IssuableFinder end end + def by_non_archived(items) + params[:non_archived].present? ? items.non_archived : items + end + def current_user_related? params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' end diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index 3b254e7d9d5..8b82255445e 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -14,6 +14,7 @@ # search: string # label_name: string # sort: string +# non_archived: boolean # class MergeRequestsFinder < IssuableFinder def klass diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index abcf84b4d15..8e19752a8a1 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -5,8 +5,9 @@ module CiStatusHelper end def ci_status_with_icon(status, target = nil) - content = ci_icon_for_status(status) + ci_label_for_status(status) + content = ci_icon_for_status(status) + ci_text_for_status(status) klass = "ci-status ci-#{status}" + if target link_to content, target, class: klass else @@ -14,7 +15,19 @@ module CiStatusHelper end end + def ci_text_for_status(status) + if detailed_status?(status) + status.text + else + status + end + end + def ci_label_for_status(status) + if detailed_status?(status) + return status.label + end + case status when 'success' 'passed' @@ -31,6 +44,10 @@ module CiStatusHelper end def ci_icon_for_status(status) + if detailed_status?(status) + return custom_icon(status.icon) + end + icon_name = case status when 'success' @@ -94,4 +111,10 @@ module CiStatusHelper class: klass, title: title, data: data end end + + def detailed_status?(status) + status.respond_to?(:text) && + status.respond_to?(:label) && + status.respond_to?(:icon) + end end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 0772d848289..eb435cc1783 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -174,7 +174,7 @@ module GitlabMarkdownHelper # Returns a String def cross_project_reference(project, entity) if entity.respond_to?(:to_reference) - "#{project.to_reference}#{entity.to_reference}" + entity.to_reference(project) else '' end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 4f180456b16..e5b1e6e8bc7 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -82,12 +82,6 @@ module LabelsHelper span.html_safe end - def render_colored_cross_project_label(label, source_project = nil, tooltip: true) - label_suffix = source_project ? source_project.name_with_namespace : label.project.name_with_namespace - label_suffix = " <i>in #{escape_once(label_suffix)}</i>" - render_colored_label(label, label_suffix, tooltip: tooltip) - end - def suggested_colors [ '#0033CC', @@ -166,6 +160,5 @@ module LabelsHelper end # Required for Banzai::Filter::LabelReferenceFilter - module_function :render_colored_label, :render_colored_cross_project_label, - :text_color_for_bg, :escape_once + module_function :render_colored_label, :text_color_for_bg, :escape_once end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index fabbf97d4db..caf6908505e 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -320,6 +320,10 @@ module Ci .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id } end + def detailed_status + Gitlab::Ci::Status::Pipeline::Factory.new(self).fabricate! + end + private def pipeline_data diff --git a/app/models/commit.rb b/app/models/commit.rb index 176c524cf7b..248140f421b 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -92,19 +92,11 @@ class Commit end def to_reference(from_project = nil) - if cross_project_reference?(from_project) - project.to_reference + self.class.reference_prefix + self.id - else - self.id - end + commit_reference(from_project, id) end def reference_link_text(from_project = nil) - if cross_project_reference?(from_project) - project.to_reference + self.class.reference_prefix + self.short_id - else - self.short_id - end + commit_reference(from_project, short_id) end def diff_line_count @@ -329,6 +321,16 @@ class Commit private + def commit_reference(from_project, referable_commit_id) + reference = project.to_reference(from_project) + + if reference.present? + "#{reference}#{self.class.reference_prefix}#{referable_commit_id}" + else + referable_commit_id + end + end + def find_author_by_any_email User.find_by_any_email(author_email.downcase) end diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index ac2477fd973..d9af7f6c139 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -90,21 +90,24 @@ class CommitRange alias_method :id, :to_s def to_reference(from_project = nil) - if cross_project_reference?(from_project) - project.to_reference + self.class.reference_prefix + self.id + project_reference = project.to_reference(from_project) + + if project_reference.present? + project_reference + self.class.reference_prefix + self.id else self.id end end def reference_link_text(from_project = nil) - reference = ref_from + notation + ref_to + project_reference = project.to_reference(from_project) + reference = ref_from + notation + ref_to - if cross_project_reference?(from_project) - reference = project.to_reference + self.class.reference_prefix + reference + if project_reference.present? + project_reference + self.class.reference_prefix + reference + else + reference end - - reference end # Return a Hash of parameters for passing to a URL helper diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index dee940a3f88..8ba009fe04f 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -72,17 +72,4 @@ module Referable }x end end - - private - - # Check if a reference is being done cross-project - # - # from_project - Refering Project object - def cross_project_reference?(from_project) - if self.is_a?(Project) - self != from_project - else - from_project && self.project && self.project != from_project - end - end end diff --git a/app/models/issue.rb b/app/models/issue.rb index fbf07040301..7fe92051037 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -153,11 +153,7 @@ class Issue < ActiveRecord::Base def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" - if cross_project_reference?(from_project) - reference = project.to_reference + reference - end - - reference + "#{project.to_reference(from_project)}#{reference}" end def referenced_merge_requests(current_user = nil) diff --git a/app/models/label.rb b/app/models/label.rb index d9287f2dc29..d38c37344c9 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -144,9 +144,10 @@ class Label < ActiveRecord::Base # # Examples: # - # Label.first.to_reference # => "~1" - # Label.first.to_reference(format: :name) # => "~\"bug\"" - # Label.first.to_reference(project1, project2) # => "gitlab-org/gitlab-ce~1" + # Label.first.to_reference # => "~1" + # Label.first.to_reference(format: :name) # => "~\"bug\"" + # Label.first.to_reference(project, same_namespace_project) # => "gitlab-ce~1" + # Label.first.to_reference(project, another_namespace_project) # => "gitlab-org/gitlab-ce~1" # # Returns a String # @@ -154,8 +155,8 @@ class Label < ActiveRecord::Base format_reference = label_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" - if cross_project_reference?(source_project, target_project) - source_project.to_reference + reference + if source_project + "#{source_project.to_reference(target_project)}#{reference}" else reference end @@ -169,10 +170,6 @@ class Label < ActiveRecord::Base private - def cross_project_reference?(source_project, target_project) - source_project && target_project && source_project != target_project - end - def issues_count(user, params = {}) params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all') IssuesFinder.new(user, params.with_indifferent_access).execute.count diff --git a/app/models/member.rb b/app/models/member.rb index df93aaee847..3b65587c66b 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -63,6 +63,7 @@ class Member < ActiveRecord::Base after_create :send_request, if: :request?, unless: :importing? after_create :create_notification_setting, unless: [:pending?, :importing?] after_create :post_create_hook, unless: [:pending?, :importing?] + after_create :refresh_member_authorized_projects, if: :importing? after_update :post_update_hook, unless: [:pending?, :importing?] after_destroy :post_destroy_hook, unless: :pending? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a6fc9bb120d..33b578e12c1 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -176,11 +176,7 @@ class MergeRequest < ActiveRecord::Base def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" - if cross_project_reference?(from_project) - reference = project.to_reference + reference - end - - reference + "#{project.to_reference(from_project)}#{reference}" end def first_commit diff --git a/app/models/milestone.rb b/app/models/milestone.rb index c774e69080c..45ca97adad1 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -113,19 +113,16 @@ class Milestone < ActiveRecord::Base # # Examples: # - # Milestone.first.to_reference # => "%1" - # Milestone.first.to_reference(format: :name) # => "%\"goal\"" - # Milestone.first.to_reference(project) # => "gitlab-org/gitlab-ce%1" + # Milestone.first.to_reference # => "%1" + # Milestone.first.to_reference(format: :name) # => "%\"goal\"" + # Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1" + # Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1" # def to_reference(from_project = nil, format: :iid) format_reference = milestone_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" - if cross_project_reference?(from_project) - project.to_reference + reference - else - reference - end + "#{project.to_reference(from_project)}#{reference}" end def reference_link_text(from_project = nil) diff --git a/app/models/project.rb b/app/models/project.rb index f01cb613b85..9d58aff4033 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -419,7 +419,11 @@ class Project < ActiveRecord::Base def reference_pattern name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR - %r{(?<project>#{name_pattern}/#{name_pattern})} + + %r{ + ((?<namespace>#{name_pattern})\/)? + (?<project>#{name_pattern}) + }x end def trending @@ -650,8 +654,20 @@ class Project < ActiveRecord::Base end end - def to_reference(_from_project = nil) - path_with_namespace + def to_reference(from_project = nil) + if cross_namespace_reference?(from_project) + path_with_namespace + elsif cross_project_reference?(from_project) + path + end + end + + def to_human_reference(from_project = nil) + if cross_namespace_reference?(from_project) + name_with_namespace + elsif cross_project_reference?(from_project) + name + end end def web_url @@ -1327,10 +1343,21 @@ class Project < ActiveRecord::Base private + # Check if a reference is being done cross-project + # + # from_project - Refering Project object + def cross_project_reference?(from_project) + from_project && self != from_project + end + def pushes_since_gc_redis_key "projects/#{id}/pushes_since_gc" end + def cross_namespace_reference?(from_project) + from_project && namespace != from_project.namespace + end + def default_branch_protected? current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 8ff4e7ae718..aa2e3a1ff18 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -67,11 +67,11 @@ class Snippet < ActiveRecord::Base def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{id}" - if cross_project_reference?(from_project) - reference = project.to_reference + reference + if project.present? + "#{project.to_reference(from_project)}#{reference}" + else + reference end - - reference end def self.content_types diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb index 8b25332b73c..7b1752df0e1 100644 --- a/app/policies/ci/build_policy.rb +++ b/app/policies/ci/build_policy.rb @@ -1,6 +1,8 @@ module Ci class BuildPolicy < CommitStatusPolicy def rules + can! :read_build if @subject.project.public_builds? + super # If we can't read build we should also not have that diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 8ac4bd9df6d..d5aadfce76a 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -12,9 +12,6 @@ class ProjectPolicy < BasePolicy guest_access! public_access! - # Allow to read builds for internal projects - can! :read_build if project.public_builds? - if project.request_access_enabled && !(owner || user.admin? || project.team.member?(user) || project_group_member?(user)) can! :request_access @@ -46,6 +43,11 @@ class ProjectPolicy < BasePolicy can! :create_note can! :upload_file can! :read_cycle_analytics + + if project.public_builds? + can! :read_pipeline + can! :read_build + end end def reporter_access! diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index 84b9ceb23b3..dd6b7303493 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -7,7 +7,7 @@ %ul.well-list - @user.groups.each do |group| %li - %strong= group.name + %strong= link_to group.name, admin_group_path(group) – access to #{pluralize(group.projects.count, 'project')} diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index f7edb47b666..f3539fd372d 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -31,7 +31,7 @@ = link_to merge_requests_group_path(@group), title: 'Merge Requests' do %span Merge Requests - - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute + - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute %span.badge.count= number_with_delimiter(merge_requests.count) = nav_link(controller: [:group_members]) do = link_to group_group_members_path(@group), title: 'Members' do diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 4c7b14a04db..0f08f4e8592 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -1,12 +1,13 @@ - status = pipeline.status +- detailed_status = pipeline.detailed_status - show_commit = local_assigns.fetch(:show_commit, true) - show_branch = local_assigns.fetch(:show_branch, true) %tr.commit %td.commit-link - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{status}" do - = ci_icon_for_status(status) - = ci_label_for_status(status) + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{detailed_status}" do + = ci_icon_for_status(detailed_status) + = ci_text_for_status(detailed_status) %td = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 6d9b91ad0e7..9ab7971b56c 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -8,14 +8,13 @@ = link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline' = ci_label_for_status(status) for - - commit = @merge_request.diff_head_commit = succeed "." do = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace" %span.ci-coverage - elsif @merge_request.has_ci? - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX - - # Remove in later versions when services like Jenkins will set CI status via Commit status API + - # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API .mr-widget-heading - %w[success skipped canceled failed running pending].each do |status| .ci_widget{class: "ci-#{status}", style: "display:none"} diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 095bd254d6b..229bdfb0e8d 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -1,6 +1,6 @@ .page-content-header .header-main-content - = ci_status_with_icon(@pipeline.status) + = ci_status_with_icon(@pipeline.detailed_status) %strong Pipeline ##{@commit.pipelines.last.id} triggered #{time_ago_with_tooltip(@commit.authored_date)} by = author_avatar(@commit, size: 24) diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index 5527a2f889a..0af92b59584 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -4,22 +4,22 @@ %ul.nav-links.issues-state-filters %li{class: ("active" if params[:state] == 'opened')} - = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do + = link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened." do #{issuables_state_counter_text(type, :opened)} - if type == :merge_requests %li{class: ("active" if params[:state] == 'merged')} - = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do + = link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.' do #{issuables_state_counter_text(type, :merged)} %li{class: ("active" if params[:state] == 'closed')} - = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do + = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.' do #{issuables_state_counter_text(type, :closed)} - else %li{class: ("active" if params[:state] == 'closed')} - = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do + = link_to page_filter_path(state: 'closed', label: true), id: 'state-all', title: 'Filter by issues that are currently closed.' do #{issuables_state_counter_text(type, :closed)} %li{class: ("active" if params[:state] == 'all')} - = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do + = link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}." do #{issuables_state_counter_text(type, :all)} diff --git a/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml b/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml deleted file mode 100644 index 01b19a47ecd..00000000000 --- a/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Speed up issuable dashboards -merge_request: -author: diff --git a/changelogs/unreleased/24733-archived-project-merge-request-count.yml b/changelogs/unreleased/24733-archived-project-merge-request-count.yml new file mode 100644 index 00000000000..2bc7e91825a --- /dev/null +++ b/changelogs/unreleased/24733-archived-project-merge-request-count.yml @@ -0,0 +1,4 @@ +--- +title: Fix Archived project merge requests add to group's Merge Requests +merge_request: 7790 +author: Jacopo Beschi @jacopo-beschi diff --git a/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml b/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml deleted file mode 100644 index 58efd9113f2..00000000000 --- a/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Don't change relative URLs to absolute URLs in the Help page -merge_request: -author: diff --git a/changelogs/unreleased/25202-fix-mr-widget-content-wrapping.yml b/changelogs/unreleased/25202-fix-mr-widget-content-wrapping.yml new file mode 100644 index 00000000000..7afc794866b --- /dev/null +++ b/changelogs/unreleased/25202-fix-mr-widget-content-wrapping.yml @@ -0,0 +1,5 @@ +--- +title: Centered Accept Merge Request button within MR widget and added padding for + viewports smaller than 768px +merge_request: +author: Ryan Harris diff --git a/changelogs/unreleased/feature-admin-user-groups-link.yml b/changelogs/unreleased/feature-admin-user-groups-link.yml new file mode 100644 index 00000000000..b89c08f82d7 --- /dev/null +++ b/changelogs/unreleased/feature-admin-user-groups-link.yml @@ -0,0 +1,4 @@ +--- +title: The admin user projects view now has a clickable group link +merge_request: 7620 +author: James Gregory diff --git a/changelogs/unreleased/fix-authorize-users-into-imported-gitlab-project.yml b/changelogs/unreleased/fix-authorize-users-into-imported-gitlab-project.yml new file mode 100644 index 00000000000..9f14463fdd1 --- /dev/null +++ b/changelogs/unreleased/fix-authorize-users-into-imported-gitlab-project.yml @@ -0,0 +1,4 @@ +--- +title: Authorize users into imported GitLab project +merge_request: +author: diff --git a/changelogs/unreleased/fix-github-branch-formatter.yml b/changelogs/unreleased/fix-github-branch-formatter.yml deleted file mode 100644 index c8698f507de..00000000000 --- a/changelogs/unreleased/fix-github-branch-formatter.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix branch validation for GitHub PR where repo/fork was renamed/deleted -merge_request: -author: diff --git a/changelogs/unreleased/glm-shorthand-reference.yml b/changelogs/unreleased/glm-shorthand-reference.yml new file mode 100644 index 00000000000..6d60f23c798 --- /dev/null +++ b/changelogs/unreleased/glm-shorthand-reference.yml @@ -0,0 +1,4 @@ +--- +title: Add shorthand support to gitlab markdown references +merge_request: 7255 +author: Oswaldo Ferreira diff --git a/changelogs/unreleased/process-commit-worker-improvements.yml b/changelogs/unreleased/process-commit-worker-improvements.yml deleted file mode 100644 index 0038c6e34e6..00000000000 --- a/changelogs/unreleased/process-commit-worker-improvements.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Pass commit data to ProcessCommitWorker to reduce Git overhead -merge_request: 7744 -author: diff --git a/changelogs/unreleased/validate-state-param-when-filtering-issuables.yml b/changelogs/unreleased/validate-state-param-when-filtering-issuables.yml new file mode 100644 index 00000000000..3fb025806b0 --- /dev/null +++ b/changelogs/unreleased/validate-state-param-when-filtering-issuables.yml @@ -0,0 +1,4 @@ +--- +title: Validate state param when filtering issuables +merge_request: +author: diff --git a/changelogs/unreleased/zj-guest-reads-public-builds.yml b/changelogs/unreleased/zj-guest-reads-public-builds.yml new file mode 100644 index 00000000000..1859addd606 --- /dev/null +++ b/changelogs/unreleased/zj-guest-reads-public-builds.yml @@ -0,0 +1,4 @@ +--- +title: Guests can read builds when public +merge_request: 6842 +author: diff --git a/doc/api/projects.md b/doc/api/projects.md index 132be644b59..0bc2a5210aa 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -347,7 +347,8 @@ Parameters: ### Get single project Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME, which is owned by the authenticated user. -If using namespaced projects call make sure that the NAMESPACE/PROJECT_NAME is URL-encoded, eg. `/api/v3/projects/diaspora%2Fdiaspora` (where `/` is represented by `%2F`). +If using namespaced projects call make sure that the NAMESPACE/PROJECT_NAME is URL-encoded, eg. `/api/v3/projects/diaspora%2Fdiaspora` (where `/` is represented by `%2F`). This endpoint can be accessed without authentication if +the project is publicly accessible. ``` GET /projects/:id @@ -436,10 +437,47 @@ Parameters: } ``` +## Get project users + +Get the users list of a project. + + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `search` | string | no | Search for specific users | + +``` +GET /projects/:id/users +``` + +```json +[ + { + "id": 1, + "username": "john_smith", + "name": "John Smith", + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", + "web_url": "http://localhost:3000/john_smith" + }, + { + "id": 2, + "username": "jack_smith", + "name": "Jack Smith", + "state": "blocked", + "avatar_url": "http://gravatar.com/../e32131cd8.jpeg", + "web_url": "http://localhost:3000/jack_smith" + } +] +``` + ### Get project events -Get the events for the specified project. -Sorted from newest to oldest +Get the events for the specified project sorted from newest to oldest. This +endpoint can be accessed without authentication if the project is publicly +accessible. ``` GET /projects/:id/events @@ -1344,7 +1382,9 @@ Parameter: ## Search for projects by name -Search for projects by name which are accessible to the authenticated user. +Search for projects by name which are accessible to the authenticated user. This +endpoint can be accessed without authentication if the project is publicly +accessible. ``` GET /projects/search/:query diff --git a/doc/ci/README.md b/doc/ci/README.md index db236ce2a66..73bd2516d46 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -11,7 +11,8 @@ - [Configure a Runner, the application that runs your builds](runners/README.md) - [Use Docker images with GitLab Runner](docker/using_docker_images.md) - [Use CI to build Docker images](docker/using_docker_build.md) -- [Use variables in your `.gitlab-ci.yml`](variables/README.md) +- [CI Variables](variables/README.md) - Learn how to use variables defined in + your `.gitlab-ci.yml` or secured ones defined in your project's settings - [Use SSH keys in your build environment](ssh_keys/README.md) - [Trigger builds through the API](triggers/README.md) - [Build artifacts](../user/project/builds/artifacts.md) @@ -25,4 +26,6 @@ ## Breaking changes -- [New CI build permissions model](../user/project/new_ci_build_permissions_model.md) Read about what changed in GitLab 8.12 and how that affects your builds. There's a new way to access your Git submodules and LFS objects in builds. +- [New CI build permissions model](../user/project/new_ci_build_permissions_model.md) + Read about what changed in GitLab 8.12 and how that affects your builds. + There's a new way to access your Git submodules and LFS objects in builds. diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index a4c3a731a20..d142fe266a2 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -1,22 +1,30 @@ -## Variables +# Variables -When receiving a build from GitLab CI, the runner prepares the build environment. -It starts by setting a list of **predefined variables** (Environment Variables) and a list of **user-defined variables** +When receiving a build from GitLab CI, the [Runner] prepares the build environment. +It starts by setting a list of **predefined variables** (environment variables) +and a list of **user-defined variables**. -The variables can be overwritten. They take precedence over each other in this order: -1. Trigger variables -1. Secure variables -1. YAML-defined job-level variables -1. YAML-defined global variables -1. Predefined variables +## Priority of variables -For example, if you define: -1. `API_TOKEN=SECURE` as Secure Variable -1. `API_TOKEN=YAML` as YAML-defined variable +The variables can be overwritten and they take precedence over each other in +this order: -The `API_TOKEN` will take the Secure Variable value: `SECURE`. +1. [Trigger variables][triggers] (take precedence over all) +1. [Secret variables](#secret-variables) +1. YAML-defined [job-level variables](../yaml/README.md#job-variables) +1. YAML-defined [global variables](../yaml/README.md#variables) +1. [Predefined variables](#predefined-variables-environment-variables) (are the + lowest in the chain) -### Predefined variables (Environment Variables) +For example, if you define `API_TOKEN=secure` as a secret variable and +`API_TOKEN=yaml` in your `.gitlab-ci.yml`, the `API_TOKEN` will take the value +`secure` as the secret variables are higher in the chain. + +## Predefined variables (Environment variables) + +Some of the predefined environment variables are available only if a minimum +version of [GitLab Runner][runner] is used. Consult the table below to find the +version of Runner required. | Variable | GitLab | Runner | Description | |-------------------------|--------|--------|-------------| @@ -52,7 +60,6 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | -**Some of the variables are only available when using runner with at least defined version.** Example values: @@ -60,7 +67,7 @@ Example values: export CI_BUILD_ID="50" export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a" export CI_BUILD_REF_NAME="master" -export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/gitlab-org/gitlab-ce.git" +export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git" export CI_BUILD_TAG="1.0.0" export CI_BUILD_NAME="spec:other" export CI_BUILD_STAGE="test" @@ -73,9 +80,9 @@ export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce" export CI_PROJECT_NAME="gitlab-ce" export CI_PROJECT_NAMESPACE="gitlab-org" export CI_PROJECT_PATH="gitlab-org/gitlab-ce" -export CI_PROJECT_URL="https://gitlab.com/gitlab-org/gitlab-ce" -export CI_REGISTRY="registry.gitlab.com" -export CI_REGISTRY_IMAGE="registry.gitlab.com/gitlab-org/gitlab-ce" +export CI_PROJECT_URL="https://example.com/gitlab-org/gitlab-ce" +export CI_REGISTRY="registry.example.com" +export CI_REGISTRY_IMAGE="registry.example.com/gitlab-org/gitlab-ce" export CI_RUNNER_ID="10" export CI_RUNNER_DESCRIPTION="my runner" export CI_RUNNER_TAGS="docker, linux" @@ -84,32 +91,66 @@ export CI_SERVER_NAME="GitLab" export CI_SERVER_REVISION="70606bf" export CI_SERVER_VERSION="8.9.0" export GITLAB_USER_ID="42" -export GITLAB_USER_EMAIL="alexzander@sporer.com" +export GITLAB_USER_EMAIL="user@example.com" ``` -### YAML-defined variables -**This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher ** +## `.gitlab-ci.yaml` defined variables + +>**Note:** +This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher. + +GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in the +build environment. The variables are hence saved in the repository, and they +are meant to store non-sensitive project configuration, e.g., `RAILS_ENV` or +`DATABASE_URL`. -GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build environment. -The variables are stored in repository and are meant to store non-sensitive project configuration, ie. RAILS_ENV or DATABASE_URL. +For example, if you set the variable below globally (not inside a job), it will +be used in all executed commands and scripts: ```yaml variables: DATABASE_URL: "postgres://postgres@postgres/my_database" ``` -These variables can be later used in all executed commands and scripts. +The YAML-defined variables are also set to all created +[service containers](../docker/using_docker_images.md), thus allowing to fine +tune them. + +Variables can be defined at a global level, but also at a job level. To turn off +global defined variables in your job, define an empty array: + +```yaml +job_name: + variables: [] +``` + +## Secret variables -The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them. +>**Notes:** +- This feature requires GitLab Runner 0.4.0 or higher. +- Be aware that secret variables are not masked, and their values can be shown + in the build logs if explicitly asked to do so. If your project is public or + internal, you can set the pipelines private from your project's Pipelines + settings. Follow the discussion in issue [#13784][ce-13784] for masking the + secret variables. -Variables can be defined at a global level, but also at a job level. +GitLab CI allows you to define per-project **secret variables** that are set in +the build environment. The secret variables are stored out of the repository +(`.gitlab-ci.yml`) and are securely passed to GitLab Runner making them +available in the build environment. It's the recommended method to use for +storing things like passwords, secret keys and credentials. -More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). +Secret variables can be added by going to your project's +**Settings âž” Variables âž” Add variable**. -#### Debug tracing +Once you set them, they will be available for all subsequent builds. +## Debug tracing + +> Introduced in GitLab Runner 1.7. +> > **WARNING:** Enabling debug tracing can have severe security implications. The - output **will** contain the content of all your secure variables and any other + output **will** contain the content of all your secret variables and any other secrets! The output **will** be uploaded to the GitLab server and made visible in build traces! @@ -124,55 +165,162 @@ trace, resulting in a verbose build trace listing all commands that were run, variables that were set, etc. Before enabling this, you should ensure builds are visible to -[team members only](../../../user/permissions.md#project-features). You should -also [erase](../pipelines.md#seeing-build-traces) all generated build traces +[team members only](../../user/permissions.md#project-features). You should +also [erase](../pipelines.md#seeing-build-status) all generated build traces before making them visible again. To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`: ```yaml -job1: +job_name: variables: CI_DEBUG_TRACE: "true" ``` -The [example project](https://gitlab.com/gitlab-examples/ci-debug-trace) -demonstrates a working configuration, including build trace examples. +Example truncated output with debug trace set to true: -### User-defined variables (Secure Variables) -**This feature requires GitLab Runner 0.4.0 or higher** - -GitLab CI allows you to define per-project **Secure Variables** that are set in -the build environment. -The secure variables are stored out of the repository (the `.gitlab-ci.yml`). -The variables are securely passed to GitLab Runner and are available in the -build environment. -It's desired method to use them for storing passwords, secret keys or whatever -you want. +```bash +... + +export CI_SERVER_TLS_CA_FILE="/builds/gitlab-examples/ci-debug-trace.tmp/CI_SERVER_TLS_CA_FILE" +if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then + echo $'\''\x1b[32;1mFetching changes...\x1b[0;m'\'' + $'\''cd'\'' "/builds/gitlab-examples/ci-debug-trace" + $'\''git'\'' "config" "fetch.recurseSubmodules" "false" + $'\''rm'\'' "-f" ".git/index.lock" + $'\''git'\'' "clean" "-ffdx" + $'\''git'\'' "reset" "--hard" + $'\''git'\'' "remote" "set-url" "origin" "https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git" + $'\''git'\'' "fetch" "origin" "--prune" "+refs/heads/*:refs/remotes/origin/*" "+refs/tags/*:refs/tags/*" +else + $'\''mkdir'\'' "-p" "/builds/gitlab-examples/ci-debug-trace.tmp/git-template" + $'\''rm'\'' "-r" "-f" "/builds/gitlab-examples/ci-debug-trace" + $'\''git'\'' "config" "-f" "/builds/gitlab-examples/ci-debug-trace.tmp/git-template/config" "fetch.recurseSubmodules" "false" + echo $'\''\x1b[32;1mCloning repository...\x1b[0;m'\'' + $'\''git'\'' "clone" "--no-checkout" "https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git" "/builds/gitlab-examples/ci-debug-trace" "--template" "/builds/gitlab-examples/ci-debug-trace.tmp/git-template" + $'\''cd'\'' "/builds/gitlab-examples/ci-debug-trace" +fi +echo $'\''\x1b[32;1mChecking out dd648b2e as master...\x1b[0;m'\'' +$'\''git'\'' "checkout" "-f" "-q" "dd648b2e48ce6518303b0bb580b2ee32fadaf045" +' ++++ hostname +++ echo 'Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-machine-1480971377-317a7d0f-digital-ocean-4gb...' +Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-machine-1480971377-317a7d0f-digital-ocean-4gb... +++ export CI=true +++ CI=true +++ export CI_DEBUG_TRACE=false +++ CI_DEBUG_TRACE=false +++ export CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ export CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ export CI_BUILD_REF_NAME=master +++ CI_BUILD_REF_NAME=master +++ export CI_BUILD_ID=7046507 +++ CI_BUILD_ID=7046507 +++ export CI_BUILD_REPO=https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git +++ CI_BUILD_REPO=https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@example.com/gitlab-examples/ci-debug-trace.git +++ export CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx +++ CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx +++ export CI_PROJECT_ID=1796893 +++ CI_PROJECT_ID=1796893 +++ export CI_PROJECT_DIR=/builds/gitlab-examples/ci-debug-trace +++ CI_PROJECT_DIR=/builds/gitlab-examples/ci-debug-trace +++ export CI_SERVER=yes +++ CI_SERVER=yes +++ export 'CI_SERVER_NAME=GitLab CI' +++ CI_SERVER_NAME='GitLab CI' +++ export CI_SERVER_VERSION= +++ CI_SERVER_VERSION= +++ export CI_SERVER_REVISION= +++ CI_SERVER_REVISION= +++ export GITLAB_CI=true +++ GITLAB_CI=true +++ export CI=true +++ CI=true +++ export GITLAB_CI=true +++ GITLAB_CI=true +++ export CI_BUILD_ID=7046507 +++ CI_BUILD_ID=7046507 +++ export CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx +++ CI_BUILD_TOKEN=xxxxxxxxxxxxxxxxxxxx +++ export CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ CI_BUILD_REF=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ export CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ CI_BUILD_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045 +++ export CI_BUILD_REF_NAME=master +++ CI_BUILD_REF_NAME=master +++ export CI_BUILD_NAME=debug_trace +++ CI_BUILD_NAME=debug_trace +++ export CI_BUILD_STAGE=test +++ CI_BUILD_STAGE=test +++ export CI_SERVER_NAME=GitLab +++ CI_SERVER_NAME=GitLab +++ export CI_SERVER_VERSION=8.14.3-ee +++ CI_SERVER_VERSION=8.14.3-ee +++ export CI_SERVER_REVISION=82823 +++ CI_SERVER_REVISION=82823 +++ export CI_PROJECT_ID=17893 +++ CI_PROJECT_ID=17893 +++ export CI_PROJECT_NAME=ci-debug-trace +++ CI_PROJECT_NAME=ci-debug-trace +++ export CI_PROJECT_PATH=gitlab-examples/ci-debug-trace +++ CI_PROJECT_PATH=gitlab-examples/ci-debug-trace +++ export CI_PROJECT_NAMESPACE=gitlab-examples +++ CI_PROJECT_NAMESPACE=gitlab-examples +++ export CI_PROJECT_URL=https://example.com/gitlab-examples/ci-debug-trace +++ CI_PROJECT_URL=https://example.com/gitlab-examples/ci-debug-trace +++ export CI_PIPELINE_ID=52666 +++ CI_PIPELINE_ID=52666 +++ export CI_RUNNER_ID=1337 +++ CI_RUNNER_ID=1337 +++ export CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com +++ CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com +++ export 'CI_RUNNER_TAGS=shared, docker, linux, ruby, mysql, postgres, mongo, git-annex' +++ CI_RUNNER_TAGS='shared, docker, linux, ruby, mysql, postgres, mongo, git-annex' +++ export CI_REGISTRY=registry.example.com +++ CI_REGISTRY=registry.example.com +++ export CI_DEBUG_TRACE=true +++ CI_DEBUG_TRACE=true +++ export GITLAB_USER_ID=42 +++ GITLAB_USER_ID=42 +++ export GITLAB_USER_EMAIL=user@example.com +++ GITLAB_USER_EMAIL=axilleas@axilleas.me +++ export VERY_SECURE_VARIABLE=imaverysecurevariable +++ VERY_SECURE_VARIABLE=imaverysecurevariable +++ mkdir -p /builds/gitlab-examples/ci-debug-trace.tmp +++ echo -n '-----BEGIN CERTIFICATE----- +MIIFQzCCBCugAwIBAgIRAL/ElDjuf15xwja1ZnCocWAwDQYJKoZIhvcNAQELBQAw' + +... +``` -**The value of the variable can be shown in build log if explicitly asked to do so.** -If your project is public or internal you can make the builds private. +## Using the CI variables in your job scripts -Secure Variables can added by going to `Project > Variables > Add Variable`. +All variables are set as environment variables in the build environment, and +they are accessible with normal methods that are used to access such variables. +In most cases `bash` or `sh` is used to execute the build script. -They will be available for all subsequent builds. +To access the variables (predefined and user-defined) in a `bash`/`sh` environment, +prefix the variable name with the dollar sign (`$`): -### Use variables -The variables are set as environment variables in build environment and are accessible with normal methods that are used to access such variables. -In most cases the **bash** is used to execute build script. -To access variables (predefined and user-defined) in bash environment, prefix the variable name with `$`: ``` job_name: script: - echo $CI_BUILD_ID ``` -You can also list all environment variables with `export` command, -but be aware that this will also expose value of all **Secure Variables** in build log: +You can also list all environment variables with the `export` command, +but be aware that this will also expose the values of all the secret variables +you set, in the build log: + ``` job_name: script: - export ``` +[ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 +[runner]: https://docs.gitlab.com/runner/ [triggered]: ../triggers/README.md +[triggers]: ../triggers/README.md#pass-build-variables-to-a-trigger diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 338c9a27789..5f88974d360 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -406,14 +406,20 @@ except master. ### job variables It is possible to define build variables using a `variables` keyword on a job -level. It works basically the same way as its global-level equivalent but -allows you to define job-specific build variables. +level. It works basically the same way as its [global-level equivalent](#variables) +but allows you to define job-specific build variables. When the `variables` keyword is used on a job level, it overrides global YAML -build variables and predefined variables. +build variables and predefined variables. To turn off global defined variables +in your job, define an empty array: -Build variables priority is defined in -[variables documentation](../variables/README.md). +```yaml +job_name: + variables: [] +``` + +Build variables priority is defined in the +[variables documentation][variables]. ### tags diff --git a/doc/development/ux_guide/basics.md b/doc/development/ux_guide/basics.md index 62ac56a6bce..a29cfa096b2 100644 --- a/doc/development/ux_guide/basics.md +++ b/doc/development/ux_guide/basics.md @@ -35,26 +35,15 @@ This is the typeface used for code blocks. GitLab uses the OS default font. ## Icons GitLab uses Font Awesome icons throughout our interface. -![Trash icon](img/icon-trash.png) -The trash icon is used for destructive actions that deletes information. - -![Edit icon](img/icon-edit.png) -The pencil icon is used for editing content such as comments. - -![Notification icon](img/icon-notification.png) -The bell icon is for notifications, such as Todos. - -![Subscribe icon](img/icon-subscribe.png) -The eye icon is for subscribing to updates. For example, you can subscribe to a label and get updated on issues with that label. - -![RSS icon](img/icon-rss.png) -The standard RSS icon is used for linking to RSS/atom feeds. - -![Close icon](img/icon-close.png) -An 'x' is used for closing UI elements such as dropdowns. - -![Add icon](img/icon-add.png) -A plus is used when creating new objects, such as issues, projects, etc. +| | | +| :-----------: | :---- | +| ![Trash icon](img/icon-trash.png) | The trash icon is used for destructive actions that deletes information. | +| ![Edit icon](img/icon-edit.png) | The pencil icon is used for editing content such as comments.| +| ![Notification icon](img/icon-notification.png) | The bell icon is for notifications, such as Todos. | +| ![Subscribe icon](img/icon-subscribe.png) | The eye icon is for subscribing to updates. For example, you can subscribe to a label and get updated on issues with that label. | +| ![RSS icon](img/icon-rss.png) | The standard RSS icon is used for linking to RSS/atom feeds. | +| ![Close icon](img/icon-close.png) | An 'x' is used for closing UI elements such as dropdowns. | +| ![Add icon](img/icon-add.png) | A plus is used when creating new objects, such as issues, projects, etc. | > TODO: update this section, add more general guidance to icon usage and personality, etc. @@ -62,20 +51,13 @@ A plus is used when creating new objects, such as issues, projects, etc. ## Color -![Blue](img/color-blue.png) -Blue is used to highlight primary active elements (such as current tab), as well as other organization and managing commands. - -![Green](img/color-green.png) -Green is for actions that create new objects. - -![Orange](img/color-orange.png) -Orange is used for warnings - -![Red](img/color-red.png) -Red is reserved for delete and other destructive commands - -![Grey](img/color-grey.png) -Grey, and white (depending on context) is used for netral, secondary elements +| | | +| :------: | :------- | +| ![Blue](img/color-blue.png) | Blue is used to highlight primary active elements (such as the current tab), as well as other organizational and managing commands.| +| ![Green](img/color-green.png) | Green is for actions that create new objects. | +| ![Orange](img/color-orange.png) | Orange is used for warnings | +| ![Red](img/color-red.png) | Red is reserved for delete and other destructive commands | +| ![Grey](img/color-grey.png) | Grey is used for neutral secondary elements. Depending on context, white is sometimes used instead. | > TODO: Establish a perspective for color in terms of our personality and rationalize with Marketing usage. diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md index 8e51edd23ef..28383ad7dfc 100644 --- a/doc/development/ux_guide/components.md +++ b/doc/development/ux_guide/components.md @@ -75,10 +75,29 @@ Text should be in sentence case, where only the first word is capitalized. "Crea > TODO: Rationalize this. Ensure that we still believe this. ### Colors -Follow the color guidance on the [basics](basics.md#color) page. The default color treatment is the white/grey button. +The default color treatment is the white/grey button. Follow the guidance on the [basics](basics.md#color) page to add meaningful color to a button. + +### Secondary states + +Primary buttons darken the color of their background and border for hover, focus and active states. An inner shadow is added to the active state to denote the button being pressed. + +| Values | Info | Success | Warning | Danger | +| :------ | :------: | :------: | :------: | :------: | +| Background: `$color-light` <br> Border: `$border-color-light` | ![](img/button-info--resting.png) | ![](img/button-success--resting.png) | ![](img/button-warning--resting.png) | ![](img/button-danger--resting.png) | +| Background: `$color-normal` <br> Border: `$border-color-normal` | ![](img/button-info--hover.png) | ![](img/button-success--hover.png) | ![](img/button-warning--hover.png) | ![](img/button-danger--hover.png) | +| Background: `$color-dark` <br> Border: `$border-color-dark` | ![](img/button-info--active.png) | ![](img/button-success--active.png) | ![](img/button-warning--active.png) | ![](img/button-danger--active.png) | + +Since secondary buttons only have a border on their resting state, their hover and focus states add a background color, which gets darkened on active. + +| Values | Success Secondary | Close | Spam | +| :------ | :------: | :------: | :------: | +| Font: `$border-color-light` <br> Border: `$border-color-light` | ![](img/button-success-secondary--resting.png) | ![](img/button-close--resting.png) | ![](img/button-spam--resting.png) | +| Background: `$color-light` <br> Border: `$border-color-light` | ![](img/button-success-secondary--hover.png) | ![](img/button-close--hover.png) | ![](img/button-spam--hover.png) | +| Background: `$color-normal` <br> Border: `$border-color-normal` | ![](img/button-success-secondary--active.png) | ![](img/button-close--active.png) | ![](img/button-spam--active.png) | --- + ## Dropdowns Dropdowns are used to allow users to choose one (or many) options from a list of options. If this list of options is more 20, there should generally be a way to search through and filter the options (see the complex filter dropdowns below.) diff --git a/doc/development/ux_guide/img/button-close--active.png b/doc/development/ux_guide/img/button-close--active.png Binary files differnew file mode 100644 index 00000000000..824bfc8f31b --- /dev/null +++ b/doc/development/ux_guide/img/button-close--active.png diff --git a/doc/development/ux_guide/img/button-close--hover.png b/doc/development/ux_guide/img/button-close--hover.png Binary files differnew file mode 100644 index 00000000000..0291e121894 --- /dev/null +++ b/doc/development/ux_guide/img/button-close--hover.png diff --git a/doc/development/ux_guide/img/button-close--resting.png b/doc/development/ux_guide/img/button-close--resting.png Binary files differnew file mode 100644 index 00000000000..986d7174ce7 --- /dev/null +++ b/doc/development/ux_guide/img/button-close--resting.png diff --git a/doc/development/ux_guide/img/button-danger--active.png b/doc/development/ux_guide/img/button-danger--active.png Binary files differnew file mode 100644 index 00000000000..d3c64424b26 --- /dev/null +++ b/doc/development/ux_guide/img/button-danger--active.png diff --git a/doc/development/ux_guide/img/button-danger--hover.png b/doc/development/ux_guide/img/button-danger--hover.png Binary files differnew file mode 100644 index 00000000000..8506e093306 --- /dev/null +++ b/doc/development/ux_guide/img/button-danger--hover.png diff --git a/doc/development/ux_guide/img/button-danger--resting.png b/doc/development/ux_guide/img/button-danger--resting.png Binary files differnew file mode 100644 index 00000000000..69ad6bb796b --- /dev/null +++ b/doc/development/ux_guide/img/button-danger--resting.png diff --git a/doc/development/ux_guide/img/button-info--active.png b/doc/development/ux_guide/img/button-info--active.png Binary files differnew file mode 100644 index 00000000000..23be20b225c --- /dev/null +++ b/doc/development/ux_guide/img/button-info--active.png diff --git a/doc/development/ux_guide/img/button-info--hover.png b/doc/development/ux_guide/img/button-info--hover.png Binary files differnew file mode 100644 index 00000000000..4cb4e38558c --- /dev/null +++ b/doc/development/ux_guide/img/button-info--hover.png diff --git a/doc/development/ux_guide/img/button-info--resting.png b/doc/development/ux_guide/img/button-info--resting.png Binary files differnew file mode 100644 index 00000000000..5883340aa83 --- /dev/null +++ b/doc/development/ux_guide/img/button-info--resting.png diff --git a/doc/development/ux_guide/img/button-spam--active.png b/doc/development/ux_guide/img/button-spam--active.png Binary files differnew file mode 100644 index 00000000000..55b44898684 --- /dev/null +++ b/doc/development/ux_guide/img/button-spam--active.png diff --git a/doc/development/ux_guide/img/button-spam--hover.png b/doc/development/ux_guide/img/button-spam--hover.png Binary files differnew file mode 100644 index 00000000000..3dc8ed34c54 --- /dev/null +++ b/doc/development/ux_guide/img/button-spam--hover.png diff --git a/doc/development/ux_guide/img/button-spam--resting.png b/doc/development/ux_guide/img/button-spam--resting.png Binary files differnew file mode 100644 index 00000000000..b6bf10a5b64 --- /dev/null +++ b/doc/development/ux_guide/img/button-spam--resting.png diff --git a/doc/development/ux_guide/img/button-success--active.png b/doc/development/ux_guide/img/button-success--active.png Binary files differnew file mode 100644 index 00000000000..895a52831cb --- /dev/null +++ b/doc/development/ux_guide/img/button-success--active.png diff --git a/doc/development/ux_guide/img/button-success--hover.png b/doc/development/ux_guide/img/button-success--hover.png Binary files differnew file mode 100644 index 00000000000..e4c74bd9778 --- /dev/null +++ b/doc/development/ux_guide/img/button-success--hover.png diff --git a/doc/development/ux_guide/img/button-success--resting.png b/doc/development/ux_guide/img/button-success--resting.png Binary files differnew file mode 100644 index 00000000000..2fa971b5347 --- /dev/null +++ b/doc/development/ux_guide/img/button-success--resting.png diff --git a/doc/development/ux_guide/img/button-success-secondary--active.png b/doc/development/ux_guide/img/button-success-secondary--active.png Binary files differnew file mode 100644 index 00000000000..e7383b36946 --- /dev/null +++ b/doc/development/ux_guide/img/button-success-secondary--active.png diff --git a/doc/development/ux_guide/img/button-success-secondary--hover.png b/doc/development/ux_guide/img/button-success-secondary--hover.png Binary files differnew file mode 100644 index 00000000000..4af2a68cf1b --- /dev/null +++ b/doc/development/ux_guide/img/button-success-secondary--hover.png diff --git a/doc/development/ux_guide/img/button-success-secondary--resting.png b/doc/development/ux_guide/img/button-success-secondary--resting.png Binary files differnew file mode 100644 index 00000000000..a5a4ec512c8 --- /dev/null +++ b/doc/development/ux_guide/img/button-success-secondary--resting.png diff --git a/doc/development/ux_guide/img/button-warning--active.png b/doc/development/ux_guide/img/button-warning--active.png Binary files differnew file mode 100644 index 00000000000..5877d46c94d --- /dev/null +++ b/doc/development/ux_guide/img/button-warning--active.png diff --git a/doc/development/ux_guide/img/button-warning--hover.png b/doc/development/ux_guide/img/button-warning--hover.png Binary files differnew file mode 100644 index 00000000000..308e1adc8a3 --- /dev/null +++ b/doc/development/ux_guide/img/button-warning--hover.png diff --git a/doc/development/ux_guide/img/button-warning--resting.png b/doc/development/ux_guide/img/button-warning--resting.png Binary files differnew file mode 100644 index 00000000000..28e5e601520 --- /dev/null +++ b/doc/development/ux_guide/img/button-warning--resting.png diff --git a/doc/development/ux_guide/img/color-blue.png b/doc/development/ux_guide/img/color-blue.png Binary files differindex 2ca360173eb..844e926f1f5 100644 --- a/doc/development/ux_guide/img/color-blue.png +++ b/doc/development/ux_guide/img/color-blue.png diff --git a/doc/development/ux_guide/img/color-green.png b/doc/development/ux_guide/img/color-green.png Binary files differindex 489db8f4343..5c4c23c7067 100644 --- a/doc/development/ux_guide/img/color-green.png +++ b/doc/development/ux_guide/img/color-green.png diff --git a/doc/development/ux_guide/img/color-grey.png b/doc/development/ux_guide/img/color-grey.png Binary files differindex 58c474d5ce9..5247649a0ce 100644 --- a/doc/development/ux_guide/img/color-grey.png +++ b/doc/development/ux_guide/img/color-grey.png diff --git a/doc/development/ux_guide/img/color-orange.png b/doc/development/ux_guide/img/color-orange.png Binary files differindex 4c4b772d438..1103c715225 100644 --- a/doc/development/ux_guide/img/color-orange.png +++ b/doc/development/ux_guide/img/color-orange.png diff --git a/doc/development/ux_guide/img/color-red.png b/doc/development/ux_guide/img/color-red.png Binary files differindex 3440ad48f05..77ecbbc0a20 100644 --- a/doc/development/ux_guide/img/color-red.png +++ b/doc/development/ux_guide/img/color-red.png diff --git a/doc/development/ux_guide/img/icon-add.png b/doc/development/ux_guide/img/icon-add.png Binary files differindex 0d4c1a7692a..bcad5e84591 100644 --- a/doc/development/ux_guide/img/icon-add.png +++ b/doc/development/ux_guide/img/icon-add.png diff --git a/doc/development/ux_guide/img/icon-close.png b/doc/development/ux_guide/img/icon-close.png Binary files differindex 88d2b3b0c6d..dfe1495f5fa 100644 --- a/doc/development/ux_guide/img/icon-close.png +++ b/doc/development/ux_guide/img/icon-close.png diff --git a/doc/development/ux_guide/img/icon-edit.png b/doc/development/ux_guide/img/icon-edit.png Binary files differindex f73be7a10fb..50f6f841868 100644 --- a/doc/development/ux_guide/img/icon-edit.png +++ b/doc/development/ux_guide/img/icon-edit.png diff --git a/doc/development/ux_guide/img/icon-notification.png b/doc/development/ux_guide/img/icon-notification.png Binary files differindex 4758632edd7..6ddfaa44f66 100644 --- a/doc/development/ux_guide/img/icon-notification.png +++ b/doc/development/ux_guide/img/icon-notification.png diff --git a/doc/development/ux_guide/img/icon-rss.png b/doc/development/ux_guide/img/icon-rss.png Binary files differindex c7ac9fb1349..b766488b32d 100644 --- a/doc/development/ux_guide/img/icon-rss.png +++ b/doc/development/ux_guide/img/icon-rss.png diff --git a/doc/development/ux_guide/img/icon-subscribe.png b/doc/development/ux_guide/img/icon-subscribe.png Binary files differindex 5cb277bfd5d..650168296c6 100644 --- a/doc/development/ux_guide/img/icon-subscribe.png +++ b/doc/development/ux_guide/img/icon-subscribe.png diff --git a/doc/development/ux_guide/img/icon-trash.png b/doc/development/ux_guide/img/icon-trash.png Binary files differindex 357289a6fff..b02178ca992 100644 --- a/doc/development/ux_guide/img/icon-trash.png +++ b/doc/development/ux_guide/img/icon-trash.png diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md index eb9bbb67e7d..696c1011eeb 100644 --- a/doc/integration/shibboleth.md +++ b/doc/integration/shibboleth.md @@ -2,7 +2,7 @@ This documentation is for enabling shibboleth with omnibus-gitlab package. -In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however this is difficult to configure using the bundled NIGNX provided in the omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider. +In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however this is difficult to configure using the bundled Nginx provided in the omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider. To enable the Shibboleth OmniAuth provider you must: diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 85b165ac44f..4d24eb21976 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -267,6 +267,18 @@ GFM also recognizes certain cross-project references: | `namespace/project@9ba12248...b19a04f5` | commit range comparison | | `namespace/project~"Some label"` | issues with given label | +It also has a shorthand version to reference other projects from the same namespace: + +| input | references | +|:------------------------------|:------------------------| +| `project#123` | issue | +| `project!123` | merge request | +| `project%123` | milestone | +| `project$123` | snippet | +| `project@9ba12248` | specific commit | +| `project@9ba12248...b19a04f5` | commit range comparison | +| `project~"Some label"` | issues with given label | + ### Task Lists > If this is not rendered correctly, see diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index cab85a48396..b51152c79c6 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -9,7 +9,7 @@ module SharedProject step "project exists in some group namespace" do @group = create(:group, name: 'some group') - @project = create(:project, namespace: @group) + @project = create(:project, namespace: @group, public_builds: false) end # Create a specific project called "Shop" diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 3740d4fb4cd..d904a8bd4ae 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -33,7 +33,7 @@ module Banzai # Returns a String replaced with the return of the block. def self.references_in(text, pattern = object_class.reference_pattern) text.gsub(pattern) do |match| - yield match, $~[object_sym].to_i, $~[:project], $~ + yield match, $~[object_sym].to_i, $~[:project], $~[:namespace], $~ end end @@ -145,8 +145,9 @@ module Banzai # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. def object_link_filter(text, pattern, link_content: nil) - references_in(text, pattern) do |match, id, project_ref, matches| - project = project_from_ref_cached(project_ref) + references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches| + project_path = full_project_path(namespace_ref, project_ref) + project = project_from_ref_cached(project_path) if project && object = find_object_cached(project, id) title = object_link_title(object) @@ -217,10 +218,9 @@ module Banzai nodes.each do |node| node.to_html.scan(regex) do - project = $~[:project] || current_project_path + project_path = full_project_path($~[:namespace], $~[:project]) symbol = $~[object_sym] - - refs[project] << symbol if object_class.reference_valid?(symbol) + refs[project_path] << symbol if object_class.reference_valid?(symbol) end end @@ -272,8 +272,19 @@ module Banzai @current_project_path ||= project.path_with_namespace end + def current_project_namespace_path + @current_project_namespace_path ||= project.namespace.path + end + private + def full_project_path(namespace, project_ref) + return current_project_path unless project_ref + + namespace_ref = namespace || current_project_namespace_path + "#{namespace_ref}/#{project_ref}" + end + def project_refs_cache RequestStore[:banzai_project_refs] ||= {} end diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb index 4358bf45549..eaacb9591b1 100644 --- a/lib/banzai/filter/commit_range_reference_filter.rb +++ b/lib/banzai/filter/commit_range_reference_filter.rb @@ -12,7 +12,7 @@ module Banzai def self.references_in(text, pattern = CommitRange.reference_pattern) text.gsub(pattern) do |match| - yield match, $~[:commit_range], $~[:project], $~ + yield match, $~[:commit_range], $~[:project], $~[:namespace], $~ end end diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb index a26dd09c25a..69c06117eda 100644 --- a/lib/banzai/filter/commit_reference_filter.rb +++ b/lib/banzai/filter/commit_reference_filter.rb @@ -12,7 +12,7 @@ module Banzai def self.references_in(text, pattern = Commit.reference_pattern) text.gsub(pattern) do |match| - yield match, $~[:commit], $~[:project], $~ + yield match, $~[:commit], $~[:project], $~[:namespace], $~ end end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 9f9a96cdc65..a605dea149e 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -14,16 +14,18 @@ module Banzai def self.references_in(text, pattern = Label.reference_pattern) unescape_html_entities(text).gsub(pattern) do |match| - yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~ + yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~[:namespace], $~ end end def references_in(text, pattern = Label.reference_pattern) unescape_html_entities(text).gsub(pattern) do |match| - label = find_label($~[:project], $~[:label_id], $~[:label_name]) + namespace, project = $~[:namespace], $~[:project] + project_path = full_project_path(namespace, project) + label = find_label(project_path, $~[:label_id], $~[:label_name]) if label - yield match, label.id, $~[:project], $~ + yield match, label.id, project, namespace, $~ else match end @@ -64,48 +66,12 @@ module Banzai end def object_link_text(object, matches) - if same_group?(object) && namespace_match?(matches) - render_same_project_label(object) - elsif same_project?(object) - render_same_project_label(object) - else - render_cross_project_label(object, matches) - end - end - - def same_group?(object) - object.is_a?(GroupLabel) && object.group == project.group - end - - def namespace_match?(matches) - matches[:project].blank? || matches[:project] == project.path_with_namespace - end - - def same_project?(object) - object.is_a?(ProjectLabel) && object.project == project - end - - def user - context[:current_user] || context[:author] - end - - def project - context[:project] - end - - def render_same_project_label(object) - LabelsHelper.render_colored_label(object) - end - - def render_cross_project_label(object, matches) - source_project = - if matches[:project] - Project.find_with_namespace(matches[:project]) - else - object.project - end + project_path = full_project_path(matches[:namespace], matches[:project]) + project_from_ref = project_from_ref_cached(project_path) + reference = project_from_ref.to_human_reference(project) + label_suffix = " <i>in #{reference}</i>" if reference.present? - LabelsHelper.render_colored_cross_project_label(object, source_project) + LabelsHelper.render_colored_label(object, label_suffix) end def unescape_html_entities(text) diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 58fff496d00..f12014e191f 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -19,18 +19,20 @@ module Banzai return super(text, pattern) if pattern != Milestone.reference_pattern text.gsub(pattern) do |match| - milestone = find_milestone($~[:project], $~[:milestone_iid], $~[:milestone_name]) + milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name]) if milestone - yield match, milestone.iid, $~[:project], $~ + yield match, milestone.iid, $~[:project], $~[:namespace], $~ else match end end end - def find_milestone(project_ref, milestone_id, milestone_name) - project = project_from_ref(project_ref) + def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name) + project_path = full_project_path(namespace_ref, project_ref) + project = project_from_ref(project_path) + return unless project milestone_params = milestone_params(milestone_id, milestone_name) @@ -52,11 +54,13 @@ module Banzai end def object_link_text(object, matches) - if context[:project] == object.project - super + milestone_link = escape_once(super) + reference = object.project.to_reference(project) + + if reference.present? + "#{milestone_link} <i>in #{reference}</i>".html_safe else - "#{escape_once(super)} <i>in #{escape_once(object.project.path_with_namespace)}</i>". - html_safe + milestone_link end end diff --git a/lib/gitlab/ci/status/canceled.rb b/lib/gitlab/ci/status/canceled.rb new file mode 100644 index 00000000000..dd6d99e9075 --- /dev/null +++ b/lib/gitlab/ci/status/canceled.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Canceled < Status::Core + def text + 'canceled' + end + + def label + 'canceled' + end + + def icon + 'icon_status_canceled' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb new file mode 100644 index 00000000000..ce4108fdcf2 --- /dev/null +++ b/lib/gitlab/ci/status/core.rb @@ -0,0 +1,58 @@ +module Gitlab + module Ci + module Status + # Base abstract class fore core status + # + class Core + include Gitlab::Routing.url_helpers + + def initialize(subject) + @subject = subject + end + + def icon + raise NotImplementedError + end + + def label + raise NotImplementedError + end + + def title + "#{@subject.class.name.demodulize}: #{label}" + end + + # Deprecation warning: this method is here because we need to maintain + # backwards compatibility with legacy statuses. We often do something + # like "ci-status ci-status-#{status}" to set CSS class. + # + # `to_s` method should be renamed to `group` at some point, after + # phasing legacy satuses out. + # + def to_s + self.class.name.demodulize.downcase.underscore + end + + def has_details? + raise NotImplementedError + end + + def details_path + raise NotImplementedError + end + + def has_action? + raise NotImplementedError + end + + def action_icon + raise NotImplementedError + end + + def action_path + raise NotImplementedError + end + end + end + end +end diff --git a/lib/gitlab/ci/status/created.rb b/lib/gitlab/ci/status/created.rb new file mode 100644 index 00000000000..6596d7e01ca --- /dev/null +++ b/lib/gitlab/ci/status/created.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Created < Status::Core + def text + 'created' + end + + def label + 'created' + end + + def icon + 'icon_status_created' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/extended.rb b/lib/gitlab/ci/status/extended.rb new file mode 100644 index 00000000000..6bfb5d38c1f --- /dev/null +++ b/lib/gitlab/ci/status/extended.rb @@ -0,0 +1,11 @@ +module Gitlab + module Ci + module Status + module Extended + def matches?(_subject) + raise NotImplementedError + end + end + end + end +end diff --git a/lib/gitlab/ci/status/failed.rb b/lib/gitlab/ci/status/failed.rb new file mode 100644 index 00000000000..c5b5e3203ad --- /dev/null +++ b/lib/gitlab/ci/status/failed.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Failed < Status::Core + def text + 'failed' + end + + def label + 'failed' + end + + def icon + 'icon_status_failed' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/pending.rb b/lib/gitlab/ci/status/pending.rb new file mode 100644 index 00000000000..d30f35a59a2 --- /dev/null +++ b/lib/gitlab/ci/status/pending.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Pending < Status::Core + def text + 'pending' + end + + def label + 'pending' + end + + def icon + 'icon_status_pending' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb new file mode 100644 index 00000000000..25e52bec3da --- /dev/null +++ b/lib/gitlab/ci/status/pipeline/common.rb @@ -0,0 +1,23 @@ +module Gitlab + module Ci + module Status + module Pipeline + module Common + def has_details? + true + end + + def details_path + namespace_project_pipeline_path(@subject.project.namespace, + @subject.project, + @subject) + end + + def has_action? + false + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/pipeline/factory.rb b/lib/gitlab/ci/status/pipeline/factory.rb new file mode 100644 index 00000000000..71d27bf7cf5 --- /dev/null +++ b/lib/gitlab/ci/status/pipeline/factory.rb @@ -0,0 +1,39 @@ +module Gitlab + module Ci + module Status + module Pipeline + class Factory + EXTENDED_STATUSES = [Pipeline::SuccessWithWarnings] + + def initialize(pipeline) + @pipeline = pipeline + @status = pipeline.status || :created + end + + def fabricate! + if extended_status + extended_status.new(core_status) + else + core_status + end + end + + private + + def core_status + Gitlab::Ci::Status + .const_get(@status.capitalize) + .new(@pipeline) + .extend(Status::Pipeline::Common) + end + + def extended_status + @extended ||= EXTENDED_STATUSES.find do |status| + status.matches?(@pipeline) + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb new file mode 100644 index 00000000000..4b040d60df8 --- /dev/null +++ b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb @@ -0,0 +1,31 @@ +module Gitlab + module Ci + module Status + module Pipeline + class SuccessWithWarnings < SimpleDelegator + extend Status::Extended + + def text + 'passed' + end + + def label + 'passed with warnings' + end + + def icon + 'icon_status_warning' + end + + def to_s + 'success_with_warnings' + end + + def self.matches?(pipeline) + pipeline.success? && pipeline.has_warnings? + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/running.rb b/lib/gitlab/ci/status/running.rb new file mode 100644 index 00000000000..2aba3c373c7 --- /dev/null +++ b/lib/gitlab/ci/status/running.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Running < Status::Core + def text + 'running' + end + + def label + 'running' + end + + def icon + 'icon_status_running' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/skipped.rb b/lib/gitlab/ci/status/skipped.rb new file mode 100644 index 00000000000..16282aefd03 --- /dev/null +++ b/lib/gitlab/ci/status/skipped.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Skipped < Status::Core + def text + 'skipped' + end + + def label + 'skipped' + end + + def icon + 'icon_status_skipped' + end + end + end + end +end diff --git a/lib/gitlab/ci/status/success.rb b/lib/gitlab/ci/status/success.rb new file mode 100644 index 00000000000..c09c5f006e3 --- /dev/null +++ b/lib/gitlab/ci/status/success.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + module Status + class Success < Status::Core + def text + 'passed' + end + + def label + 'passed' + end + + def icon + 'icon_status_success' + end + end + end + end +end diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh index 7c4e8276902..62236ed539a 100755 --- a/scripts/lint-doc.sh +++ b/scripts/lint-doc.sh @@ -3,7 +3,7 @@ cd "$(dirname "$0")/.." # Use long options (e.g. --header instead of -H) for curl examples in documentation. -grep --perl-regexp --recursive --color=auto 'curl (.+ )?-[^- ].*' doc/ +grep --extended-regexp --recursive --color=auto 'curl (.+ )?-[^- ].*' doc/ if [ $? == 0 ] then echo '✖ ERROR: Short options should not be used in documentation!' >&2 diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index cb3191dfdde..e31325ce47b 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -225,4 +225,32 @@ describe "Admin::Users", feature: true do end end end + + describe "GET /admin/users/:id/projects" do + before do + @group = create(:group) + @project = create(:project, group: @group) + @simple_user = create(:user) + @group.add_developer(@simple_user) + + visit projects_admin_user_path(@simple_user) + end + + it "lists group projects" do + within(:css, '.append-bottom-default + .panel') do + expect(page).to have_content 'Group projects' + expect(page).to have_link @group.name, admin_group_path(@group) + end + end + + it 'allows navigation to the group details' do + within(:css, '.append-bottom-default + .panel') do + click_link @group.name + end + within(:css, 'h3.page-title') do + expect(page).to have_content "Group: #{@group.name}" + end + expect(page).to have_content @project.name + end + end end diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index a2791b57544..30b80aa82b0 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -2,7 +2,35 @@ require 'spec_helper' feature 'Group merge requests page', feature: true do let(:path) { merge_requests_group_path(group) } - let(:issuable) { create(:merge_request, source_project: project, target_project: project, title: "this is my created issuable")} + let(:issuable) { create(:merge_request, source_project: project, target_project: project, title: 'this is my created issuable') } include_examples 'project features apply to issuables', MergeRequest + + context 'archived issuable' do + let(:project_archived) { create(:project, group: group, merge_requests_access_level: ProjectFeature::ENABLED, archived: true) } + let(:issuable_archived) { create(:merge_request, source_project: project_archived, target_project: project_archived, title: 'issuable of an archived project') } + let(:access_level) { ProjectFeature::ENABLED } + let(:user) { user_in_group } + + before do + issuable_archived + visit path + end + + it 'hides archived merge requests' do + expect(page).to have_content(issuable.title) + expect(page).not_to have_content(issuable_archived.title) + end + + it 'ignores archived merge request count badges in navbar' do + expect( page.find('[title="Merge Requests"] span.badge.count').text).to eq("1") + end + + it 'ignores archived merge request count badges in state-filters' do + expect(page.find('#state-opened span.badge').text).to eq("1") + expect(page.find('#state-merged span.badge').text).to eq("0") + expect(page.find('#state-closed span.badge').text).to eq("0") + expect(page.find('#state-all span.badge').text).to eq("1") + end + end end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index c9bec05a9da..f89b4db9e62 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -28,7 +28,7 @@ feature 'issue move to another project' do let(:new_project) { create(:project) } let(:new_project_search) { create(:project) } let(:text) { "Text with #{mr.to_reference}" } - let(:cross_reference) { old_project.to_reference } + let(:cross_reference) { old_project.to_reference(new_project) } background do old_project.team << [user, :reporter] diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index 702869b6e8b..f1b68a39343 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -40,7 +40,7 @@ feature 'Create New Merge Request', feature: true, js: true do visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id }) - expect(page).not_to have_content private_project.to_reference + expect(page).not_to have_content private_project.path_with_namespace end end diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb index c22441f8929..8120a51c515 100644 --- a/spec/features/projects/guest_navigation_menu_spec.rb +++ b/spec/features/projects/guest_navigation_menu_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe "Guest navigation menu" do - let(:project) { create :empty_project, :private } - let(:guest) { create :user } + let(:project) { create(:empty_project, :private, public_builds: false) } + let(:guest) { create(:user) } before do project.team << [guest, :guest] diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 290ddb4c6dd..f52e23f9433 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe "Private Project Access", feature: true do include AccessMatchers - let(:project) { create(:project, :private) } + let(:project) { create(:project, :private, public_builds: false) } describe "Project should be private" do describe '#private?' do @@ -260,6 +260,18 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } + + context 'when public builds is enabled' do + before do + project.update(public_builds: true) + end + + it { is_expected.to be_allowed_for(:guest).of(project) } + end + + context 'when public buils are disabled' do + it { is_expected.to be_denied_for(:guest).of(project) } + end end describe "GET /:project_path/pipelines/:id" do @@ -275,6 +287,18 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } + + context 'when public builds is enabled' do + before do + project.update(public_builds: true) + end + + it { is_expected.to be_allowed_for(:guest).of(project) } + end + + context 'when public buils are disabled' do + it { is_expected.to be_denied_for(:guest).of(project) } + end end describe "GET /:project_path/builds" do @@ -289,6 +313,18 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } + + context 'when public builds is enabled' do + before do + project.update(public_builds: true) + end + + it { is_expected.to be_allowed_for(:guest).of(project) } + end + + context 'when public buils are disabled' do + it { is_expected.to be_denied_for(:guest).of(project) } + end end describe "GET /:project_path/builds/:id" do @@ -305,6 +341,23 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } + + context 'when public builds is enabled' do + before do + project.update(public_builds: true) + end + + it { is_expected.to be_allowed_for(:guest).of(project) } + end + + context 'when public buils are disabled' do + before do + project.public_builds = false + project.save + end + + it { is_expected.to be_denied_for(:guest).of(project) } + end end describe "GET /:project_path/environments" do diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 40bccb8e50b..7f69e888f32 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -10,6 +10,7 @@ describe IssuesFinder do let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone, title: 'gitlab') } let(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'gitlab') } let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2) } + let(:closed_issue) { create(:issue, author: user2, assignee: user2, project: project2, state: 'closed') } let!(:label_link) { create(:label_link, label: label, target: issue2) } before do @@ -25,7 +26,7 @@ describe IssuesFinder do describe '#execute' do let(:search_user) { user } let(:params) { {} } - let(:issues) { IssuesFinder.new(search_user, params.merge(scope: scope, state: 'opened')).execute } + let(:issues) { IssuesFinder.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute } context 'scope: all' do let(:scope) { 'all' } @@ -143,6 +144,40 @@ describe IssuesFinder do end end + context 'filtering by state' do + context 'with opened' do + let(:params) { { state: 'opened' } } + + it 'returns only opened issues' do + expect(issues).to contain_exactly(issue1, issue2, issue3) + end + end + + context 'with closed' do + let(:params) { { state: 'closed' } } + + it 'returns only closed issues' do + expect(issues).to contain_exactly(closed_issue) + end + end + + context 'with all' do + let(:params) { { state: 'all' } } + + it 'returns all issues' do + expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue) + end + end + + context 'with invalid state' do + let(:params) { { state: 'invalid_state' } } + + it 'returns all issues' do + expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue) + end + end + end + context 'when the user is unauthorized' do let(:search_user) { nil } diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 535aabfc18d..88361e27102 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -6,14 +6,17 @@ describe MergeRequestsFinder do let(:project1) { create(:project) } let(:project2) { create(:project, forked_from_project: project1) } + let(:project3) { create(:project, forked_from_project: project1, archived: true) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') } let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2) } + let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3) } before do project1.team << [user, :master] project2.team << [user, :developer] + project3.team << [user, :developer] project2.team << [user2, :developer] end @@ -21,7 +24,7 @@ describe MergeRequestsFinder do it 'filters by scope' do params = { scope: 'authored', state: 'opened' } merge_requests = MergeRequestsFinder.new(user, params).execute - expect(merge_requests.size).to eq(2) + expect(merge_requests.size).to eq(3) end it 'filters by project' do @@ -29,5 +32,11 @@ describe MergeRequestsFinder do merge_requests = MergeRequestsFinder.new(user, params).execute expect(merge_requests.size).to eq(1) end + + it 'filters by non_archived' do + params = { non_archived: true } + merge_requests = MergeRequestsFinder.new(user, params).execute + expect(merge_requests.size).to eq(3) + end end end diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index d30daf47543..7cf535fadae 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -7,7 +7,7 @@ describe LabelsHelper do context 'without subject' do it "uses the label's project" do - expect(link_to_label(label)).to match %r{<a href="/#{label.project.to_reference}/issues\?label_name%5B%5D=#{label.name}">.*</a>} + expect(link_to_label(label)).to match %r{<a href="/#{label.project.path_with_namespace}/issues\?label_name%5B%5D=#{label.name}">.*</a>} end end @@ -32,7 +32,7 @@ describe LabelsHelper do ['issue', :issue, 'merge_request', :merge_request].each do |type| context "set to #{type}" do it 'links to correct page' do - expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.to_reference}/#{type.to_s.pluralize}\?label_name%5B%5D=#{label.name}">.*</a>} + expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.path_with_namespace}/#{type.to_s.pluralize}\?label_name%5B%5D=#{label.name}">.*</a>} end end end diff --git a/spec/javascripts/extensions/element_spec.js.es6 b/spec/javascripts/extensions/element_spec.js.es6 new file mode 100644 index 00000000000..c5b86d35204 --- /dev/null +++ b/spec/javascripts/extensions/element_spec.js.es6 @@ -0,0 +1,38 @@ +/*= require extensions/element */ + +(() => { + describe('Element extensions', function () { + beforeEach(() => { + this.element = document.createElement('ul'); + }); + + describe('matches', () => { + it('returns true if element matches the selector', () => { + expect(this.element.matches('ul')).toBeTruthy(); + }); + + it("returns false if element doesn't match the selector", () => { + expect(this.element.matches('.not-an-element')).toBeFalsy(); + }); + }); + + describe('closest', () => { + beforeEach(() => { + this.childElement = document.createElement('li'); + this.element.appendChild(this.childElement); + }); + + it('returns the closest parent that matches the selector', () => { + expect(this.childElement.closest('ul').toString()).toBe(this.element.toString()); + }); + + it('returns itself if it matches the selector', () => { + expect(this.childElement.closest('li').toString()).toBe(this.childElement.toString()); + }); + + it('returns undefined if nothing matches the selector', () => { + expect(this.childElement.closest('.no-an-element')).toBeFalsy(); + }); + }); + }); +})(); diff --git a/spec/lib/banzai/filter/abstract_link_filter_spec.rb b/spec/lib/banzai/filter/abstract_link_filter_spec.rb index 1ee31a603e4..70a87fbc01e 100644 --- a/spec/lib/banzai/filter/abstract_link_filter_spec.rb +++ b/spec/lib/banzai/filter/abstract_link_filter_spec.rb @@ -5,7 +5,7 @@ describe Banzai::Filter::AbstractReferenceFilter do 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") + doc = Nokogiri::HTML.fragment("#1 #{project.path_with_namespace}#2") filter = described_class.new(doc, project: project) expect(filter).to receive(:object_class).exactly(4).times.and_return(Issue) @@ -14,7 +14,7 @@ describe Banzai::Filter::AbstractReferenceFilter do 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])) + expect(refs[project.path_with_namespace]).to eq(Set.new(%w[1 2])) end end diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb index e6c90ad87ee..9703e2315b8 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -59,9 +59,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do it 'ignores invalid commit IDs' do exp = act = "See #{commit1.id.reverse}...#{commit2.id}" - expect(project).to receive(:valid_repo?).and_return(true) - expect(project.repository).to receive(:commit).with(commit1.id.reverse) - expect(project.repository).to receive(:commit).with(commit2.id) + allow(project.repository).to receive(:commit).with(commit1.id.reverse) expect(reference_filter(act).to_html).to eq exp end @@ -100,14 +98,44 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do end end - context 'cross-project reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:project, :public, namespace: namespace) } - let(:reference) { range.to_reference(project) } + context 'cross-project / cross-namespace complete reference' do + let(:project2) { create(:project, :public) } + let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" } - before do - range.project = project2 + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) + end + + it 'link has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.css('a').first.text). + to eql("#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eql("Fixed (#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}.)") + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Fixed #{project2.path_with_namespace}@#{commit1.id.reverse}...#{commit2.id}" + expect(reference_filter(act).to_html).to eq exp + + exp = act = "Fixed #{project2.path_with_namespace}@#{commit1.id}...#{commit2.id.reverse}" + expect(reference_filter(act).to_html).to eq exp end + end + + context 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, path: "same-namespace", namespace: namespace) } + let(:reference) { "#{project2.path}@#{commit1.id}...#{commit2.id}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}") @@ -116,24 +144,65 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) end - it 'links with adjacent text' do + it 'link has valid text' do doc = reference_filter("Fixed (#{reference}.)") - exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}") - expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) + expect(doc.css('a').first.text). + to eql("#{project2.path}@#{commit1.short_id}...#{commit2.short_id}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eql("Fixed (#{project2.path}@#{commit1.short_id}...#{commit2.short_id}.)") end it 'ignores invalid commit IDs on the referenced project' do - exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" + exp = act = "Fixed #{project2.path}@#{commit1.id.reverse}...#{commit2.id}" expect(reference_filter(act).to_html).to eq exp - exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" + exp = act = "Fixed #{project2.path}@#{commit1.id}...#{commit2.id.reverse}" + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'cross-project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:project, :public, namespace: namespace) } + let(:project2) { create(:project, :public, path: "same-namespace", namespace: namespace) } + let(:reference) { "#{project2.path}@#{commit1.id}...#{commit2.id}" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) + end + + it 'link has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.css('a').first.text). + to eql("#{project2.path}@#{commit1.short_id}...#{commit2.short_id}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eql("Fixed (#{project2.path}@#{commit1.short_id}...#{commit2.short_id}.)") + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Fixed #{project2.path}@#{commit1.id.reverse}...#{commit2.id}" + expect(reference_filter(act).to_html).to eq exp + + exp = act = "Fixed #{project2.path}@#{commit1.id}...#{commit2.id.reverse}" expect(reference_filter(act).to_html).to eq exp end end context 'cross-project URL reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:namespace) { create(:namespace) } let(:project2) { create(:project, :public, namespace: namespace) } let(:range) { CommitRange.new("#{commit1.id}...master", project) } let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') } diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index e0f08282551..2e6dcc3a434 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -41,6 +41,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do it 'links with adjacent text' do doc = reference_filter("See (#{reference}.)") + expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/) end @@ -48,8 +49,6 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do invalid = invalidate_reference(reference) exp = act = "See #{invalid}" - expect(project).to receive(:valid_repo?).and_return(true) - expect(project.repository).to receive(:commit).with(invalid) expect(reference_filter(act).to_html).to eq exp end @@ -95,34 +94,85 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do end end - context 'cross-project reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } + context 'cross-project / cross-namespace complete reference' do + let(:namespace) { create(:namespace) } let(:project2) { create(:project, :public, namespace: namespace) } let(:commit) { project2.commit } - let(:reference) { commit.to_reference(project) } + let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } - it 'links to a valid reference' do - doc = reference_filter("See #{reference}") + it 'link has valid text' do + doc = reference_filter("See (#{reference}.)") - expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id) + expect(doc.css('a').first.text).to eql("#{project2.path_with_namespace}@#{commit.short_id}") end - it 'links with adjacent text' do - doc = reference_filter("Fixed (#{reference}.)") + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text).to eql("See (#{project2.path_with_namespace}@#{commit.short_id}.)") + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Committed #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:commit) { project2.commit } + let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } + + it 'link has valid text' do + doc = reference_filter("See (#{reference}.)") - exp = Regexp.escape(project2.to_reference) - expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/) + expect(doc.css('a').first.text).to eql("#{project2.path}@#{commit.short_id}") + end + + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text).to eql("See (#{project2.path}@#{commit.short_id}.)") end it 'ignores invalid commit IDs on the referenced project' do exp = act = "Committed #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'cross-project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, namespace: namespace) } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:commit) { project2.commit } + let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" } + + it 'link has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.css('a').first.text).to eql("#{project2.path}@#{commit.short_id}") + end + + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text).to eql("See (#{project2.path}@#{commit.short_id}.)") + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Committed #{invalidate_reference(reference)}" + expect(reference_filter(act).to_html).to eq exp end end context 'cross-project URL reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:namespace) { create(:namespace) } let(:project2) { create(:project, :public, namespace: namespace) } let(:commit) { project2.commit } let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) } diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 8f0b2db3e8e..456dbac0698 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -8,7 +8,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end let(:project) { create(:empty_project, :public) } - let(:issue) { create(:issue, project: project) } + let(:issue) { create(:issue, project: project) } it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) @@ -24,7 +24,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do context 'internal reference' do it_behaves_like 'a reference containing an element node' - let(:reference) { issue.to_reference } + let(:reference) { "##{issue.iid}" } it 'ignores valid references when using non-default tracker' do allow(project).to receive(:default_issues_tracker?).and_return(false) @@ -42,7 +42,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do it 'links with adjacent text' do doc = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + expect(doc.text).to eql("Fixed (#{reference}.)") end it 'ignores invalid issue IDs' do @@ -116,13 +116,56 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end end - context 'cross-project reference' do + context 'cross-project / cross-namespace complete reference' do it_behaves_like 'a reference containing an element node' - let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" } + + it 'ignores valid references when cross-reference project uses external tracker' do + expect_any_instance_of(described_class).to receive(:find_object). + with(project2, issue.iid). + and_return(nil) + + exp = act = "Issue #{reference}" + expect(reference_filter(act).to_html).to eq exp + end + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq helper.url_for_issue(issue.iid, project2) + end + + it 'link has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.css('a').first.text).to eql("#{project2.path_with_namespace}##{issue.iid}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eq("Fixed (#{project2.path_with_namespace}##{issue.iid}.)") + end + + it 'ignores invalid issue IDs on the referenced project' do + exp = act = "Fixed #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'cross-project / same-namespace complete reference' do + it_behaves_like 'a reference containing an element node' + + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } - let(:reference) { issue.to_reference(project) } + let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" } it 'ignores valid references when cross-reference project uses external tracker' do expect_any_instance_of(described_class).to receive(:find_object). @@ -140,9 +183,16 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do to eq helper.url_for_issue(issue.iid, project2) end - it 'links with adjacent text' do + it 'link has valid text' do doc = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + + expect(doc.css('a').first.text).to eql("#{project2.path}##{issue.iid}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eq("Fixed (#{project2.path}##{issue.iid}.)") end it 'ignores invalid issue IDs on the referenced project' do @@ -150,9 +200,47 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do expect(reference_filter(act).to_html).to eq exp end + end + + context 'cross-project shorthand reference' do + it_behaves_like 'a reference containing an element node' - it 'ignores out-of-bounds issue IDs on the referenced project' do - exp = act = "Fixed ##{Gitlab::Database::MAX_INT_VALUE + 1}" + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { "#{project2.path}##{issue.iid}" } + + it 'ignores valid references when cross-reference project uses external tracker' do + expect_any_instance_of(described_class).to receive(:find_object). + with(project2, issue.iid). + and_return(nil) + + exp = act = "Issue #{reference}" + expect(reference_filter(act).to_html).to eq exp + end + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq helper.url_for_issue(issue.iid, project2) + end + + it 'link has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.css('a').first.text).to eql("#{project2.path}##{issue.iid}") + end + + it 'has valid text' do + doc = reference_filter("Fixed (#{reference}.)") + + expect(doc.text).to eq("Fixed (#{project2.path}##{issue.iid}.)") + end + + it 'ignores invalid issue IDs on the referenced project' do + exp = act = "Fixed #{invalidate_reference(reference)}" expect(reference_filter(act).to_html).to eq exp end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 9c09f00ae8a..284641fb20a 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -4,7 +4,7 @@ require 'html/pipeline' describe Banzai::Filter::LabelReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:empty_project, :public) } + let(:project) { create(:empty_project, :public, name: 'sample-project') } let(:label) { create(:label, project: project) } let(:reference) { label.to_reference } @@ -48,6 +48,14 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name) end + context 'project that does not exist referenced' do + let(:result) { reference_filter('aaa/bbb~ccc') } + + it 'does not link reference' do + expect(result.to_html).to eq 'aaa/bbb~ccc' + end + end + describe 'label span element' do it 'includes default classes' do doc = reference_filter("Label #{reference}") @@ -334,14 +342,14 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end context 'with project reference' do - let(:reference) { project.to_reference + group_label.to_reference(format: :name) } + let(:reference) { "#{project.to_reference}#{group_label.to_reference(format: :name)}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}", project: project) expect(doc.css('a').first.attr('href')).to eq urls. namespace_project_issues_url(project.namespace, project, label_name: group_label.name) - expect(doc.text).to eq 'See gfm references' + expect(doc.text).to eq "See gfm references" end it 'links with adjacent text' do @@ -357,68 +365,247 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end end - describe 'cross project label references' do - context 'valid project referenced' do - let(:another_project) { create(:empty_project, :public) } - let(:project_name) { another_project.name_with_namespace } - let(:label) { create(:label, project: another_project, color: '#00ff00') } - let(:reference) { label.to_reference(project) } + describe 'cross-project / cross-namespace complete reference' do + let(:project2) { create(:empty_project) } + let(:label) { create(:label, project: project2, color: '#00ff00') } + let(:reference) { "#{project2.path_with_namespace}~#{label.name}" } + let!(:result) { reference_filter("See #{reference}") } - let!(:result) { reference_filter("See #{reference}") } + it 'links to a valid reference' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(project2.namespace, + project2, + label_name: label.name) + end - it 'points to referenced project issues page' do - expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(another_project.namespace, - another_project, - label_name: label.name) - end + it 'has valid color' do + expect(result.css('a span').first.attr('style')).to match /background-color: #00ff00/ + end - it 'has valid color' do - expect(result.css('a span').first.attr('style')) - .to match /background-color: #00ff00/ - end + it 'has valid link text' do + expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name_with_namespace}" + end - it 'contains cross project content' do - expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}" - end + it 'has valid text' do + expect(result.text).to eq "See #{label.name} in #{project2.name_with_namespace}" end - context 'project that does not exist referenced' do - let(:result) { reference_filter('aaa/bbb~ccc') } + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" - it 'does not link reference' do - expect(result.to_html).to eq 'aaa/bbb~ccc' - end + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, namespace: namespace) } + let(:project2) { create(:empty_project, namespace: namespace) } + let(:label) { create(:label, project: project2, color: '#00ff00') } + let(:reference) { "#{project2.path_with_namespace}~#{label.name}" } + let!(:result) { reference_filter("See #{reference}") } + + it 'links to a valid reference' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(project2.namespace, + project2, + label_name: label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')).to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name}" + end + + it 'has valid text' do + expect(result.text).to eq "See #{label.name} in #{project2.name}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'cross-project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, namespace: namespace) } + let(:project2) { create(:empty_project, namespace: namespace) } + let(:label) { create(:label, project: project2, color: '#00ff00') } + let(:reference) { "#{project2.path}~#{label.name}" } + let!(:result) { reference_filter("See #{reference}") } + + it 'links to a valid reference' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(project2.namespace, + project2, + label_name: label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')). + to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name}" + end + + it 'has valid text' do + expect(result.text).to eq "See #{label.name} in #{project2.name}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp end end describe 'cross group label references' do - context 'valid project referenced' do - let(:group) { create(:group) } - let(:project) { create(:empty_project, :public, namespace: group) } - let(:another_group) { create(:group) } - let(:another_project) { create(:empty_project, :public, namespace: another_group) } - let(:project_name) { another_project.name_with_namespace } - let(:group_label) { create(:group_label, group: another_group, color: '#00ff00') } - let(:reference) { another_project.to_reference + group_label.to_reference } - - let!(:result) { reference_filter("See #{reference}", project: project) } - - it 'points to referenced project issues page' do - expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(another_project.namespace, - another_project, - label_name: group_label.name) - end + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:another_group) { create(:group) } + let(:another_project) { create(:empty_project, :public, namespace: another_group) } + let(:group_label) { create(:group_label, group: another_group, color: '#00ff00') } + let(:reference) { "#{another_project.path_with_namespace}~#{group_label.name}" } + let!(:result) { reference_filter("See #{reference}", project: project) } - it 'has valid color' do - expect(result.css('a span').first.attr('style')) - .to match /background-color: #00ff00/ - end + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(another_project.namespace, + another_project, + label_name: group_label.name) + end - it 'contains cross project content' do - expect(result.css('a').first.text).to eq "#{group_label.name} in #{project_name}" - end + it 'has valid color' do + expect(result.css('a span').first.attr('style')). + to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text). + to eq "#{group_label.name} in #{another_project.name_with_namespace}" + end + + it 'has valid text' do + expect(result.text). + to eq "See #{group_label.name} in #{another_project.name_with_namespace}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'cross-project / same-group_label complete reference' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:another_project) { create(:empty_project, :public, namespace: group) } + let(:group_label) { create(:group_label, group: group, color: '#00ff00') } + let(:reference) { "#{another_project.path_with_namespace}~#{group_label.name}" } + let!(:result) { reference_filter("See #{reference}", project: project) } + + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')). + to eq urls.namespace_project_issues_url(another_project.namespace, + another_project, + label_name: group_label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')). + to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text). + to eq "#{group_label.name} in #{another_project.name}" + end + + it 'has valid text' do + expect(result.text). + to eq "See #{group_label.name} in #{another_project.name}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'same project / same group_label complete reference' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:group_label) { create(:group_label, group: group, color: '#00ff00') } + let(:reference) { "#{project.path_with_namespace}~#{group_label.name}" } + let!(:result) { reference_filter("See #{reference}", project: project) } + + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(project.namespace, + project, + label_name: group_label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')) + .to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text).to eq group_label.name + end + + it 'has valid text' do + expect(result.text).to eq "See #{group_label.name}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'same project / same group_label shorthand reference' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:group_label) { create(:group_label, group: group, color: '#00ff00') } + let(:reference) { "#{project.path}~#{group_label.name}" } + let!(:result) { reference_filter("See #{reference}", project: project) } + + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(project.namespace, + project, + label_name: group_label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')). + to match /background-color: #00ff00/ + end + + it 'has valid link text' do + expect(result.css('a').first.text).to eq group_label.name + end + + it 'has valid text' do + expect(result.text).to eq "See #{group_label.name}" + end + + it 'ignores invalid IDs on the referenced label' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp end end end diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index 274258a045c..275010c1a2c 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do include FilterSpecHelper - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:merge) { create(:merge_request, source_project: project) } it 'requires project context' do @@ -86,23 +86,97 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do end end - context 'cross-project reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } - let(:project2) { create(:project, :public, namespace: namespace) } - let(:merge) { create(:merge_request, source_project: project2) } - let(:reference) { merge.to_reference(project) } + context 'cross-project / cross-namespace complete reference' do + let(:project2) { create(:empty_project, :public) } + let(:merge) { create(:merge_request, source_project: project2) } + let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')). to eq urls.namespace_project_merge_request_url(project2.namespace, - project, merge) + project2, merge) end - it 'links with adjacent text' do + it 'link has valid text' do doc = reference_filter("Merge (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + + expect(doc.css('a').first.text).to eq(reference) + end + + it 'has valid text' do + doc = reference_filter("Merge (#{reference}.)") + + expect(doc.text).to eq("Merge (#{reference}.)") + end + + it 'ignores invalid merge IDs on the referenced project' do + exp = act = "Merge #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let!(:merge) { create(:merge_request, source_project: project2) } + let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_merge_request_url(project2.namespace, + project2, merge) + end + + it 'link has valid text' do + doc = reference_filter("Merge (#{reference}.)") + + expect(doc.css('a').first.text).to eq("#{project2.path}!#{merge.iid}") + end + + it 'has valid text' do + doc = reference_filter("Merge (#{reference}.)") + + expect(doc.text).to eq("Merge (#{project2.path}!#{merge.iid}.)") + end + + it 'ignores invalid merge IDs on the referenced project' do + exp = act = "Merge #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'cross-project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let!(:merge) { create(:merge_request, source_project: project2) } + let(:reference) { "#{project2.path}!#{merge.iid}" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_merge_request_url(project2.namespace, + project2, merge) + end + + it 'link has valid text' do + doc = reference_filter("Merge (#{reference}.)") + + expect(doc.css('a').first.text).to eq("#{project2.path}!#{merge.iid}") + end + + it 'has valid text' do + doc = reference_filter("Merge (#{reference}.)") + + expect(doc.text).to eq("Merge (#{project2.path}!#{merge.iid}.)") end it 'ignores invalid merge IDs on the referenced project' do diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 7419863d848..73b5edb99b3 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -148,13 +148,51 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do end end - describe 'cross project milestone references' do - let(:another_project) { create(:empty_project, :public) } - let(:project_path) { another_project.path_with_namespace } - let(:milestone) { create(:milestone, project: another_project) } - let(:reference) { milestone.to_reference(project) } + describe 'cross-project / cross-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:another_project) { create(:empty_project, :public, namespace: namespace) } + let(:milestone) { create(:milestone, project: another_project) } + let(:reference) { "#{another_project.path_with_namespace}%#{milestone.iid}" } + let!(:result) { reference_filter("See #{reference}") } - let!(:result) { reference_filter("See #{reference}") } + it 'points to referenced project milestone page' do + expect(result.css('a').first.attr('href')).to eq urls. + namespace_project_milestone_url(another_project.namespace, + another_project, + milestone) + end + + it 'link has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.css('a').first.text). + to eq("#{milestone.name} in #{another_project.path_with_namespace}") + end + + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text). + to eq("See (#{milestone.name} in #{another_project.path_with_namespace}.)") + end + + it 'escapes the name attribute' do + allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="}) + + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.text). + to eq "#{milestone.name} in #{another_project.path_with_namespace}" + end + end + + describe 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:another_project) { create(:empty_project, :public, namespace: namespace) } + let(:milestone) { create(:milestone, project: another_project) } + let(:reference) { "#{another_project.path_with_namespace}%#{milestone.iid}" } + let!(:result) { reference_filter("See #{reference}") } it 'points to referenced project milestone page' do expect(result.css('a').first.attr('href')).to eq urls. @@ -163,14 +201,66 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do milestone) end - it 'contains cross project content' do - expect(result.css('a').first.text).to eq "#{milestone.name} in #{project_path}" + it 'link has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.css('a').first.text). + to eq("#{milestone.name} in #{another_project.path}") + end + + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text). + to eq("See (#{milestone.name} in #{another_project.path}.)") end it 'escapes the name attribute' do allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="}) + + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.text). + to eq "#{milestone.name} in #{another_project.path}" + end + end + + describe 'cross project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:another_project) { create(:empty_project, :public, namespace: namespace) } + let(:milestone) { create(:milestone, project: another_project) } + let(:reference) { "#{another_project.path}%#{milestone.iid}" } + let!(:result) { reference_filter("See #{reference}") } + + it 'points to referenced project milestone page' do + expect(result.css('a').first.attr('href')).to eq urls. + namespace_project_milestone_url(another_project.namespace, + another_project, + milestone) + end + + it 'link has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.css('a').first.text). + to eq("#{milestone.name} in #{another_project.path}") + end + + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text). + to eq("See (#{milestone.name} in #{another_project.path}.)") + end + + it 'escapes the name attribute' do + allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="}) + doc = reference_filter("See #{reference}") - expect(doc.css('a').first.text).to eq "#{milestone.name} in #{project_path}" + + expect(doc.css('a').first.text). + to eq "#{milestone.name} in #{another_project.path}" end end end diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb index 9b92d1a3926..e036514d283 100644 --- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb @@ -79,11 +79,11 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do end end - context 'cross-project reference' do - let(:namespace) { create(:namespace, name: 'cross-reference') } + context 'cross-project / cross-namespace complete reference' do + let(:namespace) { create(:namespace) } let(:project2) { create(:empty_project, :public, namespace: namespace) } - let(:snippet) { create(:project_snippet, project: project2) } - let(:reference) { snippet.to_reference(project) } + let!(:snippet) { create(:project_snippet, project: project2) } + let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" } it 'links to a valid reference' do doc = reference_filter("See #{reference}") @@ -92,9 +92,82 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) end - it 'links with adjacent text' do + it 'link has valid text' do doc = reference_filter("See (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + + expect(doc.css('a').first.text).to eql(reference) + end + + it 'has valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text).to eql("See (#{reference}.)") + end + + it 'ignores invalid snippet IDs on the referenced project' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'cross-project / same-namespace complete reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let!(:snippet) { create(:project_snippet, project: project2) } + let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) + end + + it 'link has valid text' do + doc = reference_filter("See (#{project2.path}$#{snippet.id}.)") + + expect(doc.css('a').first.text).to eql("#{project2.path}$#{snippet.id}") + end + + it 'has valid text' do + doc = reference_filter("See (#{project2.path}$#{snippet.id}.)") + + expect(doc.text).to eql("See (#{project2.path}$#{snippet.id}.)") + end + + it 'ignores invalid snippet IDs on the referenced project' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'cross-project shorthand reference' do + let(:namespace) { create(:namespace) } + let(:project) { create(:empty_project, :public, namespace: namespace) } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let!(:snippet) { create(:project_snippet, project: project2) } + let(:reference) { "#{project2.path}$#{snippet.id}" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) + end + + it 'link has valid text' do + doc = reference_filter("See (#{project2.path}$#{snippet.id}.)") + + expect(doc.css('a').first.text).to eql("#{project2.path}$#{snippet.id}") + end + + it 'has valid text' do + doc = reference_filter("See (#{project2.path}$#{snippet.id}.)") + + expect(doc.text).to eql("See (#{project2.path}$#{snippet.id}.)") end it 'ignores invalid snippet IDs on the referenced project' do diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb new file mode 100644 index 00000000000..619ecbcba67 --- /dev/null +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Canceled do + subject { described_class.new(double('subject')) } + + describe '#text' do + it { expect(subject.label).to eq 'canceled' } + end + + describe '#label' do + it { expect(subject.label).to eq 'canceled' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_canceled' } + end + + describe '#title' do + it { expect(subject.title).to eq 'Double: canceled' } + end +end diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb new file mode 100644 index 00000000000..157302c65a8 --- /dev/null +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Created do + subject { described_class.new(double('subject')) } + + describe '#text' do + it { expect(subject.label).to eq 'created' } + end + + describe '#label' do + it { expect(subject.label).to eq 'created' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_created' } + end + + describe '#title' do + it { expect(subject.title).to eq 'Double: created' } + end +end diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb new file mode 100644 index 00000000000..120e121aae5 --- /dev/null +++ b/spec/lib/gitlab/ci/status/extended_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Extended do + subject do + Class.new.extend(described_class) + end + + it 'requires subclass to implement matcher' do + expect { subject.matches?(double) } + .to raise_error(NotImplementedError) + end +end diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb new file mode 100644 index 00000000000..0b3cb8168e6 --- /dev/null +++ b/spec/lib/gitlab/ci/status/failed_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Failed do + subject { described_class.new(double('subject')) } + + describe '#text' do + it { expect(subject.label).to eq 'failed' } + end + + describe '#label' do + it { expect(subject.label).to eq 'failed' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_failed' } + end + + describe '#title' do + it { expect(subject.title).to eq 'Double: failed' } + end +end diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb new file mode 100644 index 00000000000..57c901c1202 --- /dev/null +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Pending do + subject { described_class.new(double('subject')) } + + describe '#text' do + it { expect(subject.label).to eq 'pending' } + end + + describe '#label' do + it { expect(subject.label).to eq 'pending' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_pending' } + end + + describe '#title' do + it { expect(subject.title).to eq 'Double: pending' } + end +end diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb new file mode 100644 index 00000000000..21adee3f8e7 --- /dev/null +++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Pipeline::Common do + let(:pipeline) { create(:ci_pipeline) } + + subject do + Class.new(Gitlab::Ci::Status::Core) + .new(pipeline).extend(described_class) + end + + it 'does not have action' do + expect(subject).not_to have_action + end + + it 'has details' do + expect(subject).to have_details + end + + it 'links to the pipeline details page' do + expect(subject.details_path) + .to include "pipelines/#{pipeline.id}" + end +end diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb new file mode 100644 index 00000000000..d6243940f2e --- /dev/null +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Pipeline::Factory do + subject do + described_class.new(pipeline) + end + + let(:status) do + subject.fabricate! + end + + context 'when pipeline has a core status' do + HasStatus::AVAILABLE_STATUSES.each do |core_status| + context "when core status is #{core_status}" do + let(:pipeline) do + create(:ci_pipeline, status: core_status) + end + + it "fabricates a core status #{core_status}" do + expect(status).to be_a( + Gitlab::Ci::Status.const_get(core_status.capitalize)) + end + + it 'extends core status with common pipeline methods' do + expect(status).to have_details + expect(status).not_to have_action + expect(status.details_path) + .to include "pipelines/#{pipeline.id}" + end + end + end + end + + context 'when pipeline has warnings' do + let(:pipeline) do + create(:ci_pipeline, status: :success) + end + + before do + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline) + end + + it 'fabricates extended "success with warnings" status' do + expect(status) + .to be_a Gitlab::Ci::Status::Pipeline::SuccessWithWarnings + end + + it 'extends core status with common pipeline methods' do + expect(status).to have_details + end + end +end diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb new file mode 100644 index 00000000000..02e526e3de2 --- /dev/null +++ b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do + subject do + described_class.new(double('status')) + end + + describe '#test' do + it { expect(subject.text).to eq 'passed' } + end + + describe '#label' do + it { expect(subject.label).to eq 'passed with warnings' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_warning' } + end + + describe '.matches?' do + context 'when pipeline is successful' do + let(:pipeline) do + create(:ci_pipeline, status: :success) + end + + context 'when pipeline has warnings' do + before do + allow(pipeline).to receive(:has_warnings?).and_return(true) + end + + it 'is a correct match' do + expect(described_class.matches?(pipeline)).to eq true + end + end + + context 'when pipeline does not have warnings' do + it 'does not match' do + expect(described_class.matches?(pipeline)).to eq false + end + end + end + + context 'when pipeline is not successful' do + let(:pipeline) do + create(:ci_pipeline, status: :skipped) + end + + context 'when pipeline has warnings' do + before do + allow(pipeline).to receive(:has_warnings?).and_return(true) + end + + it 'does not match' do + expect(described_class.matches?(pipeline)).to eq false + end + end + + context 'when pipeline does not have warnings' do + it 'does not match' do + expect(described_class.matches?(pipeline)).to eq false + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb new file mode 100644 index 00000000000..c023f1872cc --- /dev/null +++ b/spec/lib/gitlab/ci/status/running_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Running do + subject { described_class.new(double('subject')) } + + describe '#text' do + it { expect(subject.label).to eq 'running' } + end + + describe '#label' do + it { expect(subject.label).to eq 'running' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_running' } + end + + describe '#title' do + it { expect(subject.title).to eq 'Double: running' } + end +end diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb new file mode 100644 index 00000000000..d4f7f4b3b70 --- /dev/null +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Skipped do + subject { described_class.new(double('subject')) } + + describe '#text' do + it { expect(subject.label).to eq 'skipped' } + end + + describe '#label' do + it { expect(subject.label).to eq 'skipped' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_skipped' } + end + + describe '#title' do + it { expect(subject.title).to eq 'Double: skipped' } + end +end diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb new file mode 100644 index 00000000000..9e261a3aa5f --- /dev/null +++ b/spec/lib/gitlab/ci/status/success_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Success do + subject { described_class.new(double('subject')) } + + describe '#text' do + it { expect(subject.label).to eq 'passed' } + end + + describe '#label' do + it { expect(subject.label).to eq 'passed' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_success' } + end + + describe '#title' do + it { expect(subject.title).to eq 'Double: passed' } + end +end diff --git a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb index dc4f7dc69db..2d85e712db0 100644 --- a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::CycleAnalytics::Permissions do - let(:project) { create(:empty_project) } + let(:project) { create(:empty_project, public_builds: false) } let(:user) { create(:user) } subject { described_class.get(user: user, project: project) } diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 6b3dfebd85d..d619e401897 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -64,7 +64,7 @@ describe Gitlab::Gfm::ReferenceRewriter do context 'description with project labels' do let!(:label) { create(:label, id: 123, name: 'test', project: old_project) } - let(:project_ref) { old_project.to_reference } + let(:project_ref) { old_project.to_reference(new_project) } context 'label referenced by id' do let(:text) { '#1 and ~123' } @@ -80,7 +80,7 @@ describe Gitlab::Gfm::ReferenceRewriter do context 'description with group labels' do let(:old_group) { create(:group) } let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) } - let(:project_ref) { old_project.to_reference } + let(:project_ref) { old_project.to_reference(new_project) } before do old_project.update(namespace: old_group) diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index 770e8b0c2f4..1cb02f8e318 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe Gitlab::ImportExport::MembersMapper, services: true do describe 'map members' do - let(:user) { create(:user) } + let(:user) { create(:user, authorized_projects_populated: true) } let(:project) { create(:project, :public, name: 'searchable_project') } - let(:user2) { create(:user) } + let(:user2) { create(:user, authorized_projects_populated: true) } let(:exported_user_id) { 99 } let(:exported_members) do [{ @@ -67,5 +67,12 @@ describe Gitlab::ImportExport::MembersMapper, services: true do expect(ProjectMember.find_by_invite_email('invite@test.com')).not_to be_nil end + + it 'authorizes the users to the project' do + members_mapper.map + + expect(user.authorized_project?(project)).to be true + expect(user2.authorized_project?(project)).to be true + end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 0d2b4920835..3f93d9ddf19 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -404,6 +404,76 @@ describe Ci::Pipeline, models: true do end end + describe '#detailed_status' do + context 'when pipeline is created' do + let(:pipeline) { create(:ci_pipeline, status: :created) } + + it 'returns detailed status for created pipeline' do + expect(pipeline.detailed_status.text).to eq 'created' + end + end + + context 'when pipeline is pending' do + let(:pipeline) { create(:ci_pipeline, status: :pending) } + + it 'returns detailed status for pending pipeline' do + expect(pipeline.detailed_status.text).to eq 'pending' + end + end + + context 'when pipeline is running' do + let(:pipeline) { create(:ci_pipeline, status: :running) } + + it 'returns detailed status for running pipeline' do + expect(pipeline.detailed_status.text).to eq 'running' + end + end + + context 'when pipeline is successful' do + let(:pipeline) { create(:ci_pipeline, status: :success) } + + it 'returns detailed status for successful pipeline' do + expect(pipeline.detailed_status.text).to eq 'passed' + end + end + + context 'when pipeline is failed' do + let(:pipeline) { create(:ci_pipeline, status: :failed) } + + it 'returns detailed status for failed pipeline' do + expect(pipeline.detailed_status.text).to eq 'failed' + end + end + + context 'when pipeline is canceled' do + let(:pipeline) { create(:ci_pipeline, status: :canceled) } + + it 'returns detailed status for canceled pipeline' do + expect(pipeline.detailed_status.text).to eq 'canceled' + end + end + + context 'when pipeline is skipped' do + let(:pipeline) { create(:ci_pipeline, status: :skipped) } + + it 'returns detailed status for skipped pipeline' do + expect(pipeline.detailed_status.text).to eq 'skipped' + end + end + + context 'when pipeline is successful but with warnings' do + let(:pipeline) { create(:ci_pipeline, status: :success) } + + before do + create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline) + end + + it 'retruns detailed status for successful pipeline with warnings' do + expect(pipeline.detailed_status.label).to eq 'passed with warnings' + end + end + end + describe '#cancelable?' do %i[created running pending].each do |status0| context "when there is a build #{status0}" do diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index c41359b55a3..d89d4342dea 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -45,7 +45,7 @@ describe CommitRange, models: true do end describe '#to_reference' do - let(:cross) { create(:project) } + let(:cross) { create(:empty_project, namespace: project.namespace) } it 'returns a String reference to the object' do expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}" @@ -56,12 +56,12 @@ describe CommitRange, models: true do end it 'supports a cross-project reference' do - expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}" + expect(range.to_reference(cross)).to eq "#{project.path}@#{full_sha_from}...#{full_sha_to}" end end describe '#reference_link_text' do - let(:cross) { create(:project) } + let(:cross) { create(:empty_project, namespace: project.namespace) } it 'returns a String reference to the object' do expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}" @@ -72,7 +72,7 @@ describe CommitRange, models: true do end it 'supports a cross-project reference' do - expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}" + expect(range.reference_link_text(cross)).to eq "#{project.path}@#{sha_from}...#{sha_to}" end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 7194c20d3bf..eb482c7f913 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -34,24 +34,30 @@ describe Commit, models: true do end describe '#to_reference' do + let(:project) { create(:project, path: 'sample-project') } + let(:commit) { project.commit } + it 'returns a String reference to the object' do expect(commit.to_reference).to eq commit.id end it 'supports a cross-project reference' do - cross = double('project') - expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.id}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(commit.to_reference(another_project)).to eq "sample-project@#{commit.id}" end end describe '#reference_link_text' do + let(:project) { create(:project, path: 'sample-project') } + let(:commit) { project.commit } + it 'returns a String reference to the object' do expect(commit.reference_link_text).to eq commit.short_id end it 'supports a cross-project reference' do - cross = double('project') - expect(commit.reference_link_text(cross)).to eq "#{project.to_reference}@#{commit.short_id}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(commit.reference_link_text(another_project)).to eq "sample-project@#{commit.short_id}" end end diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb index 2369658bf78..668aa6fb357 100644 --- a/spec/models/group_label_spec.rb +++ b/spec/models/group_label_spec.rb @@ -37,6 +37,16 @@ describe GroupLabel, models: true do end end + context 'cross-project' do + let(:namespace) { build_stubbed(:namespace) } + let(:source_project) { build_stubbed(:empty_project, name: 'project-1', namespace: namespace) } + let(:target_project) { build_stubbed(:empty_project, name: 'project-2', namespace: namespace) } + + it 'returns a String reference to the object' do + expect(label.to_reference(source_project, target_project)).to eq %(project-1~#{label.id}) + end + end + context 'using invalid format' do it 'raises error' do expect { label.to_reference(format: :invalid) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 89e93dce8c5..24e216329a9 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -43,14 +43,16 @@ describe Issue, models: true do end describe '#to_reference' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:issue) { build(:issue, iid: 1, project: project) } + it 'returns a String reference to the object' do - expect(subject.to_reference).to eq "##{subject.iid}" + expect(issue.to_reference).to eq "#1" end it 'supports a cross-project reference' do - cross = double('project') - expect(subject.to_reference(cross)). - to eq "#{subject.project.to_reference}##{subject.iid}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(issue.to_reference(another_project)).to eq "sample-project#1" end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 925232169f1..8b730be91fd 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -142,13 +142,16 @@ describe MergeRequest, models: true do end describe '#to_reference' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:merge_request) { build(:merge_request, target_project: project, iid: 1) } + it 'returns a String reference to the object' do - expect(subject.to_reference).to eq "!#{subject.iid}" + expect(merge_request.to_reference).to eq "!1" end it 'supports a cross-project reference' do - cross = double('project') - expect(subject.to_reference(cross)).to eq "#{subject.source_project.to_reference}!#{subject.iid}" + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(merge_request.to_reference(another_project)).to eq "sample-project!1" end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index a4bfe851dfb..0cc2efae5f9 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -246,4 +246,18 @@ describe Milestone, models: true do end end end + + describe '#to_reference' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:milestone) { build(:milestone, iid: 1, project: project) } + + it 'returns a String reference to the object' do + expect(milestone.to_reference).to eq "%1" + end + + it 'supports a cross-project reference' do + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(milestone.to_reference(another_project)).to eq "sample-project%1" + end + end end diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 18c9d449ee5..4d538cac007 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -105,14 +105,14 @@ describe ProjectLabel, models: true do context 'using name' do it 'returns cross reference with label name' do expect(label.to_reference(project, format: :name)) - .to eq %Q(#{label.project.to_reference}~"#{label.name}") + .to eq %Q(#{label.project.path_with_namespace}~"#{label.name}") end end context 'using id' do it 'returns cross reference with label id' do expect(label.to_reference(project, format: :id)) - .to eq %Q(#{label.project.to_reference}~#{label.id}) + .to eq %Q(#{label.project.path_with_namespace}~#{label.id}) end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8abcce42ce0..587ca1936a3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -258,10 +258,70 @@ describe Project, models: true do end describe '#to_reference' do - let(:project) { create(:empty_project) } + let(:owner) { create(:user, name: 'Gitlab') } + let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } + let(:project) { create(:empty_project, path: 'sample-project', namespace: namespace) } + + context 'when nil argument' do + it 'returns nil' do + expect(project.to_reference).to be_nil + end + end + + context 'when same project argument' do + it 'returns nil' do + expect(project.to_reference(project)).to be_nil + end + end - it 'returns a String reference to the object' do - expect(project.to_reference).to eq project.path_with_namespace + context 'when cross namespace project argument' do + let(:another_namespace_project) { create(:empty_project, name: 'another-project') } + + it 'returns complete path to the project' do + expect(project.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project' + end + end + + context 'when same namespace / cross-project argument' do + let(:another_project) { create(:empty_project, namespace: namespace) } + + it 'returns complete path to the project' do + expect(project.to_reference(another_project)).to eq 'sample-project' + end + end + end + + describe '#to_human_reference' do + let(:owner) { create(:user, name: 'Gitlab') } + let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) } + let(:project) { create(:empty_project, name: 'Sample project', namespace: namespace) } + + context 'when nil argument' do + it 'returns nil' do + expect(project.to_human_reference).to be_nil + end + end + + context 'when same project argument' do + it 'returns nil' do + expect(project.to_human_reference(project)).to be_nil + end + end + + context 'when cross namespace project argument' do + let(:another_namespace_project) { create(:empty_project, name: 'another-project') } + + it 'returns complete name with namespace of the project' do + expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project' + end + end + + context 'when same namespace / cross-project argument' do + let(:another_project) { create(:empty_project, namespace: namespace) } + + it 'returns name of the project' do + expect(project.to_human_reference(another_project)).to eq 'Sample project' + end end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index f62f6bacbaa..79d2843e122 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -33,16 +33,31 @@ describe Snippet, models: true do end describe '#to_reference' do - let(:project) { create(:empty_project) } - let(:snippet) { create(:snippet, project: project) } + context 'when snippet belongs to a project' do + let(:project) { build(:empty_project, name: 'sample-project') } + let(:snippet) { build(:snippet, id: 1, project: project) } + + it 'returns a String reference to the object' do + expect(snippet.to_reference).to eq "$1" + end - it 'returns a String reference to the object' do - expect(snippet.to_reference).to eq "$#{snippet.id}" + it 'supports a cross-project reference' do + another_project = build(:project, name: 'another-project', namespace: project.namespace) + expect(snippet.to_reference(another_project)).to eq "sample-project$1" + end end - it 'supports a cross-project reference' do - cross = double('project') - expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}" + context 'when snippet does not belong to a project' do + let(:snippet) { build(:snippet, id: 1, project: nil) } + + it 'returns a String reference to the object' do + expect(snippet.to_reference).to eq "$1" + end + + it 'still returns shortest reference when project arg present' do + another_project = build(:project, name: 'another-project') + expect(snippet.to_reference(another_project)).to eq "$1" + end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index b49e4f3a8bc..eeab9827d99 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -111,14 +111,36 @@ describe ProjectPolicy, models: true do context 'guests' do let(:current_user) { guest } + let(:reporter_public_build_permissions) do + reporter_permissions - [:read_build, :read_pipeline] + end + it do is_expected.to include(*guest_permissions) - is_expected.not_to include(*reporter_permissions) + is_expected.not_to include(*reporter_public_build_permissions) is_expected.not_to include(*team_member_reporter_permissions) is_expected.not_to include(*developer_permissions) is_expected.not_to include(*master_permissions) is_expected.not_to include(*owner_permissions) end + + context 'public builds enabled' do + it do + is_expected.to include(*guest_permissions) + is_expected.to include(:read_build, :read_pipeline) + end + end + + context 'public builds disabled' do + before do + project.update(public_builds: false) + end + + it do + is_expected.to include(*guest_permissions) + is_expected.not_to include(:read_build, :read_pipeline) + end + end end context 'reporter' do diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 0ea991b18b8..7be7acebb19 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -5,7 +5,7 @@ describe API::Builds, api: true do let(:user) { create(:user) } let(:api_user) { user } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, creator_id: user.id, public_builds: false) } let!(:developer) { create(:project_member, :developer, user: user, project: project) } let(:reporter) { create(:project_member, :reporter, project: project) } let(:guest) { create(:project_member, :guest, project: project) } diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index f5e0fdcda2d..e0368e6001f 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe 'cycle analytics events' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, public_builds: false) } let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } describe 'GET /:namespace/:project/cycle_analytics/events/issues' do diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index c7de0d0c534..db196ed5751 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -189,7 +189,7 @@ describe Issues::MoveService, services: true do it 'rewrites references using a cross reference to old project' do expect(new_note.note) - .to eq "Note with reference to merge request #{old_project.to_reference}!1" + .to eq "Note with reference to merge request #{old_project.to_reference(new_project)}!1" end end @@ -217,7 +217,7 @@ describe Issues::MoveService, services: true do it 'rewrites referenced issues creating cross project reference' do expect(new_issue.description) - .to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}" + .to eq "Some description #{another_issue.to_reference(new_project)}" end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 07a9d8e1997..90b7e62bc6f 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -530,7 +530,7 @@ describe SystemNoteService, services: true do end it 'mentions referenced project' do - expect(subject.note).to include new_project.to_reference + expect(subject.note).to include new_project.path_with_namespace end end diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb index 739f9b63967..603ae52ed1e 100644 --- a/spec/workers/pipeline_notification_worker_spec.rb +++ b/spec/workers/pipeline_notification_worker_spec.rb @@ -11,7 +11,7 @@ describe PipelineNotificationWorker do status: status) end - let(:project) { create(:project) } + let(:project) { create(:project, public_builds: false) } let(:user) { create(:user) } let(:pusher) { user } let(:watcher) { pusher } |