diff options
106 files changed, 1340 insertions, 1044 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c357c489f8..c7528029c89 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -229,7 +229,6 @@ rake ee_compat_check: <<: *exec only: - branches@gitlab-org/gitlab-ce - - branches@gitlab/gitlabhq except: - master - tags @@ -133,7 +133,7 @@ gem 'acts-as-taggable-on', '~> 4.0' # Background jobs gem 'sidekiq', '~> 4.2' -gem 'sidekiq-cron', '~> 0.4.0' +gem 'sidekiq-cron', '~> 0.4.4' gem 'redis-namespace', '~> 1.5.2' gem 'sidekiq-limit_fetch', '~> 3.4' @@ -309,6 +309,8 @@ group :development, :test do gem 'knapsack', '~> 1.11.0' gem 'activerecord_sane_schema_dumper', '0.2' + + gem 'stackprof', '~> 0.2.10' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index bf9702b2562..5a14ed6fede 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -650,10 +650,10 @@ GEM connection_pool (~> 2.2, >= 2.2.0) rack-protection (~> 1.5) redis (~> 3.2, >= 3.2.1) - sidekiq-cron (0.4.0) + sidekiq-cron (0.4.4) redis-namespace (>= 1.5.2) rufus-scheduler (>= 2.0.24) - sidekiq (>= 4.0.0) + sidekiq (>= 4.2.1) sidekiq-limit_fetch (3.4.0) sidekiq (>= 4) simplecov (0.12.0) @@ -691,6 +691,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + stackprof (0.2.10) state_machines (0.4.0) state_machines-activemodel (0.4.0) activemodel (>= 4.1, < 5.1) @@ -925,7 +926,7 @@ DEPENDENCIES sham_rack (~> 1.3.6) shoulda-matchers (~> 2.8.0) sidekiq (~> 4.2) - sidekiq-cron (~> 0.4.0) + sidekiq-cron (~> 0.4.4) sidekiq-limit_fetch (~> 3.4) simplecov (= 0.12.0) slack-notifier (~> 1.2.0) @@ -937,6 +938,7 @@ DEPENDENCIES spring-commands-teaspoon (~> 0.0.2) sprockets (~> 3.7.0) sprockets-es6 (~> 0.9.2) + stackprof (~> 0.2.10) state_machines-activerecord (~> 0.4.0) sys-filesystem (~> 1.1.6) teaspoon (~> 1.1.0) diff --git a/README.md b/README.md index c9bb5af6da2..61204630fd2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # GitLab [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) -[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) +[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 index b9675f50e31..0d85e1a4678 100644 --- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 +++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 @@ -10,10 +10,15 @@ }, template: ` <span class="total-time"> - <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template> - <template v-if="time.hours">{{ time.hours }} <span>hr</span></template> - <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template> - <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template> + <template v-if="Object.keys(time).length"> + <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template> + <template v-if="time.hours">{{ time.hours }} <span>hr</span></template> + <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template> + <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template> + </template> + <template v-else> + -- + </template> </span> `, }); diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 07f49cce3dc..7ead8a18c2a 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -23,6 +23,7 @@ window.gl = window.gl || {}; window.gl.environmentsList = window.gl.environmentsList || {}; + window.gl.environmentsList.timeagoInstance = new timeago(); // eslint-disable-line gl.environmentsList.EnvironmentItem = Vue.component('environment-item', { @@ -148,14 +149,25 @@ }, /** + * Verifies if the date to be shown is present. + * + * @returns {Boolean|Undefined} + */ + canShowDate() { + return this.model.last_deployment && + this.model.last_deployment.deployable && + this.model.last_deployment.deployable !== undefined; + }, + + /** * Human readable date. * * @returns {String} */ createdDate() { - const timeagoInstance = new timeago(); // eslint-disable-line - - return timeagoInstance.format(this.model.created_at); + return window.gl.environmentsList.timeagoInstance.format( + this.model.last_deployment.deployable.created_at, + ); }, /** @@ -453,7 +465,7 @@ <td> <span - v-if="!isFolder && model.last_deployment" + v-if="!isFolder && canShowDate" class="environment-created-date-timeago"> {{createdDate}} </span> diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 89fe13b7a45..10769b7fd4f 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -59,12 +59,13 @@ // Tweaked to commands to start without a space only if char before is a non-word character // https://github.com/ichord/At.js var _a, _y, regexp, match; + subtext = subtext.split(' ').pop(); flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); _a = decodeURI("%C3%80"); _y = decodeURI("%C3%BF"); - regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi'); + regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "(?!\\W)([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi'); match = regexp.exec(subtext); diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index ad0d387067f..c0dd1cb3667 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -80,6 +80,7 @@ border-radius: 0; border: none; height: auto; + width: 100%; margin: 0; align-self: center; } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 42087c91530..4bd7ff8fefd 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -56,16 +56,9 @@ .md-header { .nav-links { - .active { - a { - border-bottom-color: #000; - } - } - a { padding-top: 0; line-height: 19px; - border-bottom: 1px solid $border-color; &.btn.btn-xs { padding: 2px 5px; diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index a2787ede53c..1839ffa0976 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -94,6 +94,7 @@ &.active a { border-bottom: none; + color: $link-underline-blue; } a { @@ -103,7 +104,6 @@ &:hover, &:active, &:focus { - color: $black; border-bottom: none; } } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 773155fe80a..7aad99eee4e 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -132,7 +132,7 @@ display: none; } - .btn-clipboard { + .btn-clipboard:hover { color: $gl-gray; } } @@ -235,6 +235,10 @@ padding-bottom: 10px; color: #999; + &:hover { + color: $gl-gray; + } + span { display: block; margin-top: 0; @@ -244,15 +248,17 @@ display: none; } + .avatar:hover { + border-color: #999; + } + .btn-clipboard { border: none; + color: #999; &:hover { background: transparent; - } - - i { - color: #999; + color: $gl-gray; } } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e66c1f8d072..15ec8be831e 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -90,14 +90,14 @@ ul.notes { } &.system-note-commit-list { - max-height: 63px; + max-height: 70px; overflow: hidden; display: block; ul { - margin: 3px 0 3px 15px !important; + margin: 3px 0 3px 16px !important; - li { + .gfm-commit { font-family: $monospace_font; font-size: 12px; } @@ -172,6 +172,10 @@ ul.notes { &.timeline-entry { padding: 14px 10px; } + + .system-note { + padding: 0; + } } &.is-editting { diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 5c44637fdee..5f13353baa1 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -11,7 +11,7 @@ class AutocompleteController < ApplicationController @users = @users.reorder(:name) @users = @users.page(params[:page]) - if params[:todo_filter].present? + if params[:todo_filter].present? && current_user @users = @users.todo_authors(current_user.id, params[:todo_state_filter]) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a2225cc8343..f47df8b623b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -325,16 +325,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.update(merge_error: nil) if params[:merge_when_build_succeeds].present? - unless @merge_request.pipeline + unless @merge_request.head_pipeline @status = :failed return end - if @merge_request.pipeline.active? + if @merge_request.head_pipeline.active? MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) .execute(@merge_request) @status = :merge_when_build_succeeds - elsif @merge_request.pipeline.success? + elsif @merge_request.head_pipeline.success? # This can be triggered when a user clicks the auto merge button while # the tests finish at about the same time MergeWorker.perform_async(@merge_request.id, current_user.id, params) @@ -398,7 +398,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_status - pipeline = @merge_request.pipeline + pipeline = @merge_request.head_pipeline + if pipeline status = pipeline.status coverage = pipeline.try(:coverage) @@ -534,7 +535,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def define_widget_vars - @pipeline = @merge_request.pipeline + @pipeline = @merge_request.head_pipeline end def define_commit_vars @@ -563,7 +564,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_pipelines_vars @pipelines = @merge_request.all_pipelines - @pipeline = @merge_request.pipeline + @pipeline = @merge_request.head_pipeline @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0 end @@ -631,7 +632,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge_when_build_succeeds_active? params[:merge_when_build_succeeds].present? && - @merge_request.pipeline && @merge_request.pipeline.active? + @merge_request.head_pipeline && @merge_request.head_pipeline.active? end def build_merge_request diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4294a10e9e3..fabbf97d4db 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -317,7 +317,7 @@ module Ci def merge_requests @merge_requests ||= project.merge_requests .where(source_branch: self.ref) - .select { |merge_request| merge_request.pipeline.try(:id) == self.id } + .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id } end private diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb index 7fd0905ee81..9dd4d9c6f24 100644 --- a/app/models/concerns/protected_branch_access.rb +++ b/app/models/concerns/protected_branch_access.rb @@ -2,6 +2,9 @@ module ProtectedBranchAccess extend ActiveSupport::Concern included do + belongs_to :protected_branch + delegate :project, to: :protected_branch + scope :master, -> { where(access_level: Gitlab::Access::MASTER) } scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) } end @@ -9,4 +12,10 @@ module ProtectedBranchAccess def humanize self.class.human_access_levels[self.access_level] end + + def check_access(user) + return true if user.is_admin? + + project.team.max_member_access(user.id) >= access_level + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 38d8c15e6b0..64990f8134e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -678,7 +678,7 @@ class MergeRequest < ActiveRecord::Base def mergeable_ci_state? return true unless project.only_allow_merge_if_build_succeeds? - !pipeline || pipeline.success? || pipeline.skipped? + !head_pipeline || head_pipeline.success? || head_pipeline.skipped? end def environments @@ -774,10 +774,10 @@ class MergeRequest < ActiveRecord::Base commits.map(&:sha) end - def pipeline + def head_pipeline return unless diff_head_sha && source_project - @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) + @head_pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) end def all_pipelines diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index 806b3ccd275..771e3376613 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -1,9 +1,6 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base include ProtectedBranchAccess - belongs_to :protected_branch - delegate :project, to: :protected_branch - validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER] } @@ -13,10 +10,4 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base Gitlab::Access::DEVELOPER => "Developers + Masters" }.with_indifferent_access end - - def check_access(user) - return true if user.is_admin? - - project.team.max_member_access(user.id) >= access_level - end end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 92e9c51d883..14610cb42b7 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -1,9 +1,6 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base include ProtectedBranchAccess - belongs_to :protected_branch - delegate :project, to: :protected_branch - validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS] } @@ -18,8 +15,7 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base def check_access(user) return false if access_level == Gitlab::Access::NO_ACCESS - return true if user.is_admin? - project.team.max_member_access(user.id) >= access_level + super end end diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb index abefcd5cc02..a0db5b8f0f4 100644 --- a/app/serializers/analytics_build_entity.rb +++ b/app/serializers/analytics_build_entity.rb @@ -13,7 +13,7 @@ class AnalyticsBuildEntity < Grape::Entity end expose :duration, as: :total_time do |build| - distance_of_time_as_hash(build.duration.to_f) + build.duration ? distance_of_time_as_hash(build.duration.to_f) : {} end expose :branch do diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb index 918abba8d99..9607ad55a8b 100644 --- a/app/serializers/entity_date_helper.rb +++ b/app/serializers/entity_date_helper.rb @@ -2,6 +2,8 @@ module EntityDateHelper include ActionView::Helpers::DateHelper def interval_in_words(diff) + return 'Not started' unless diff + "#{distance_of_time_in_words(Time.now, diff)} ago" end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index cde856b0186..e3bc9847200 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -45,9 +45,15 @@ module Ci return error('No builds for this pipeline.') end - pipeline.save - pipeline.process! - pipeline + Ci::Pipeline.transaction do + pipeline.save + + Ci::CreatePipelineBuildsService + .new(project, current_user) + .execute(pipeline) + end + + pipeline.tap(&:process!) end private diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 8face432d97..2e028c44d8b 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -5,10 +5,7 @@ module Ci def execute(pipeline) @pipeline = pipeline - # This method will ensure that our pipeline does have all builds for all stages created - if created_builds.empty? - create_builds! - end + ensure_created_builds! # TODO, remove me in 9.0 new_builds = stage_indexes_of_created_builds.map do |index| @@ -22,10 +19,6 @@ module Ci private - def create_builds! - Ci::CreatePipelineBuildsService.new(project, current_user).execute(pipeline) - end - def process_stage(index) current_status = status_for_prior_stages(index) @@ -76,5 +69,18 @@ module Ci def created_builds pipeline.builds.created end + + # This method is DEPRECATED and should be removed in 9.0. + # + # We need it to maintain backwards compatibility with previous versions + # when builds were not created within one transaction with the pipeline. + # + def ensure_created_builds! + return if created_builds.any? + + Ci::CreatePipelineBuildsService + .new(project, current_user) + .execute(pipeline) + end end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 58f69a41e14..800fd39c424 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -55,7 +55,7 @@ module MergeRequests def pipeline_merge_requests(pipeline) merge_requests_for(pipeline.ref).each do |merge_request| - next unless pipeline == merge_request.pipeline + next unless pipeline == merge_request.head_pipeline yield merge_request end @@ -63,7 +63,7 @@ module MergeRequests def commit_status_merge_requests(commit_status) merge_requests_for(commit_status.ref).each do |merge_request| - pipeline = merge_request.pipeline + pipeline = merge_request.head_pipeline next unless pipeline next unless pipeline.sha == commit_status.sha diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index f7abad54286..48b0fd504f4 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -4,13 +4,13 @@ %ul.nav-links = nav_link(page: [dashboard_projects_path, root_path]) do = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do - Your Projects + Your projects = nav_link(page: starred_dashboard_projects_path) do = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do - Starred Projects + Starred projects = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do - Explore Projects + Explore projects .nav-controls = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml index c034bbe430e..8bddbef3562 100644 --- a/app/views/errors/access_denied.html.haml +++ b/app/views/errors/access_denied.html.haml @@ -1,6 +1,10 @@ -- page_title "Access Denied" -%h1 403 -%h3 Access Denied -%hr -%p You are not allowed to access this page. -%p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"} +- content_for(:title, 'Access Denied') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 403 +.container + %h3 Access Denied + %hr + %p You are not allowed to access this page. + %p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"} diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml index 90cfbebfcc6..064ff14ad2c 100644 --- a/app/views/errors/encoding.html.haml +++ b/app/views/errors/encoding.html.haml @@ -1,5 +1,9 @@ -- page_title "Encoding Error" -%h1 500 -%h3 Encoding Error -%hr -%p Page can't be loaded because of an encoding error. +- content_for(:title, 'Encoding Error') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 500 +.container + %h3 Encoding Error + %hr + %p Page can't be loaded because of an encoding error. diff --git a/app/views/errors/git_not_found.html.haml b/app/views/errors/git_not_found.html.haml index ff5d4cc1506..c5c12a410ac 100644 --- a/app/views/errors/git_not_found.html.haml +++ b/app/views/errors/git_not_found.html.haml @@ -1,7 +1,10 @@ -- page_title "Git Resource Not Found" -%h1 404 -%h3 Git Resource Not found -%hr -%p - Application can't get access to some branch or commit in your repository. It - may have been moved. +- content_for(:title, 'Git Resource Not Found') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 404 +.container + %h3 Git Resource Not found + %hr + %p Application can't get access to some branch or commit in your repository. It + may have been moved diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index 3756b98ebb2..50a54a93cb5 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,5 +1,9 @@ -- page_title "Not Found" -%h1 404 -%h3 The resource you were looking for doesn't exist. -%hr -%p You may have mistyped the address or the page may have moved. +- content_for(:title, 'Not Found') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 404 +.container + %h3 The resource you were looking for doesn't exist. + %hr + %p You may have mistyped the address or the page may have moved. diff --git a/app/views/errors/omniauth_error.html.haml b/app/views/errors/omniauth_error.html.haml index 3e70e98a24c..d91f1878cb6 100644 --- a/app/views/errors/omniauth_error.html.haml +++ b/app/views/errors/omniauth_error.html.haml @@ -1,9 +1,13 @@ -- page_title "Auth Error" -%h1 422 -%h3 Sign-in using #{@provider} auth failed -%hr -%p Sign-in failed because #{@error}. -%p There are couple of steps you can take: +- content_for(:title, 'Auth Error') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 422 +.container + %h3 Sign-in using #{@provider} auth failed + %hr + %p Sign-in failed because #{@error}. + %p There are couple of steps you can take: %ul %li Try logging in using your email diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml index 2fd4859c1c6..882fdf1317d 100644 --- a/app/views/invites/show.html.haml +++ b/app/views/invites/show.html.haml @@ -6,7 +6,7 @@ - if inviter = @member.created_by by = link_to inviter.name, user_url(inviter) - to join + to join - case @member.source - when Project - project = @member.source @@ -20,11 +20,18 @@ = link_to group.name, group_url(group) as #{@member.human_access}. -- if @member.source.users.include?(current_user) +- is_member = @member.source.users.include?(current_user) + +- if is_member %p However, you are already a member of this #{@member.source.is_a?(Group) ? "group" : "project"}. Sign in using a different account to accept the invitation. -- else + +- if @member.invite_email != current_user.email + %p + Note that this invitation was sent to #{mail_to @member.invite_email}, but you are signed in as #{link_to current_user.to_reference, user_url(current_user)} with email #{mail_to current_user.email}. + +- unless is_member .actions = link_to "Accept invitation", accept_invite_url(@token), method: :post, class: "btn btn-success" = link_to "Decline", decline_invite_url(@token), method: :post, class: "btn btn-danger prepend-left-10" diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index 7fbe065df00..a3b925f6afd 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -1,10 +1,59 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" - %body{class: "#{user_application_theme} application navless"} - = Gon::Base.render_data - = render "layouts/header/empty" - .container.navless-container - = render "layouts/flash" - .error-page - = yield + %head + %meta{:content => "width=device-width, initial-scale=1, maximum-scale=1", :name => "viewport"} + %title= yield(:title) + :css + body { + color: #666; + text-align: center; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: auto; + font-size: 14px; + } + + h1 { + font-size: 56px; + line-height: 100px; + font-weight: normal; + color: #456; + } + + h2 { + font-size: 24px; + color: #666; + line-height: 1.5em; + } + + h3 { + color: #456; + font-size: 20px; + font-weight: normal; + line-height: 28px; + } + + hr { + max-width: 800px; + margin: 18px auto; + border: 0; + border-top: 1px solid #EEE; + border-bottom: 1px solid white; + } + + img { + max-width: 40vw; + display: block; + margin: 40px auto; + } + + .container { + margin: auto 20px; + } + + ul { + margin: auto; + text-align: left; + display:inline-block; + } +%body + = yield diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index 747bfa554cb..d48923b422a 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -2,12 +2,12 @@ %h2.merge-requests-title = pluralize(@merge_requests.count, 'Related Merge Request') %ul.unstyled-list.related-merge-requests - - has_any_ci = @merge_requests.any?(&:pipeline) + - has_any_ci = @merge_requests.any?(&:head_pipeline) - @merge_requests.each do |merge_request| %li %span.merge-request-ci-status - - if merge_request.pipeline - = render_pipeline_status(merge_request.pipeline) + - if merge_request.head_pipeline + = render_pipeline_status(merge_request.head_pipeline) - elsif has_any_ci = icon('blank fw') %span.merge-request-id diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 9ffcc48eb80..fa189ae62d8 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -15,9 +15,9 @@ = icon('ban') CLOSED - - if merge_request.pipeline + - if merge_request.head_pipeline %li - = render_pipeline_status(merge_request.pipeline) + = render_pipeline_status(merge_request.head_pipeline) - if merge_request.open? && merge_request.broken? %li diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 608fdf1c5f5..a8918c85dde 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -14,7 +14,7 @@ ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", - ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}", + ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}", ci_message: { normal: "Build {{status}} for \"{{title}}\"", preparing: "{{status}} build for \"{{title}}\"" diff --git a/bin/rspec-stackprof b/bin/rspec-stackprof new file mode 100755 index 00000000000..df79feb201d --- /dev/null +++ b/bin/rspec-stackprof @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +require 'stackprof' +$:.unshift 'spec' +require 'rails_helper' + +filename = ARGV[0].split('/').last +interval = ENV.fetch('INTERVAL', 1000).to_i +limit = ENV.fetch('LIMIT', 20) +output_file = "tmp/#{filename}.dump" + +StackProf.run(mode: :wall, out: output_file, interval: interval) do + RSpec::Core::Runner.run(ARGV, $stderr, $stdout) +end + +system("stackprof #{output_file} --text --limit #{limit}") diff --git a/changelogs/unreleased/24844-environments-date.yml b/changelogs/unreleased/24844-environments-date.yml new file mode 100644 index 00000000000..2bc23d40a68 --- /dev/null +++ b/changelogs/unreleased/24844-environments-date.yml @@ -0,0 +1,4 @@ +--- +title: Fixes Environments displaying incorrect date since 8.14 upgrade +merge_request: +author: diff --git a/changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml b/changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml new file mode 100644 index 00000000000..7ddf0b46d4c --- /dev/null +++ b/changelogs/unreleased/24894-style-system-note-in-commit-discussion.yml @@ -0,0 +1,4 @@ +--- +title: Fixes system note style in commit discussion +merge_request: 7721 +author: diff --git a/changelogs/unreleased/24999-fix-project-avatar-alignment.yml b/changelogs/unreleased/24999-fix-project-avatar-alignment.yml new file mode 100644 index 00000000000..7af812e7359 --- /dev/null +++ b/changelogs/unreleased/24999-fix-project-avatar-alignment.yml @@ -0,0 +1,4 @@ +--- +title: Adjust the width of project avatars to fix alignment within their container +merge_request: +author: Ryan Harris diff --git a/changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml b/changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml new file mode 100644 index 00000000000..cc8b0e28277 --- /dev/null +++ b/changelogs/unreleased/25002-sentence-case-dashboard-tabs.yml @@ -0,0 +1,4 @@ +--- +title: Sentence cased the nav tab headers on the project dashboard page +merge_request: +author: Ryan Harris diff --git a/changelogs/unreleased/25011-hoverstates-for-collapsed-issue-merge-request-sidebar.yml b/changelogs/unreleased/25011-hoverstates-for-collapsed-issue-merge-request-sidebar.yml new file mode 100644 index 00000000000..2c3ba1dfe44 --- /dev/null +++ b/changelogs/unreleased/25011-hoverstates-for-collapsed-issue-merge-request-sidebar.yml @@ -0,0 +1,4 @@ +--- +title: Adds hoverstates for collapsed Issue/Merge Request sidebar +merge_request: !7777 +author: diff --git a/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml b/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml new file mode 100644 index 00000000000..862de7c5db1 --- /dev/null +++ b/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml @@ -0,0 +1,4 @@ +--- +title: Do not raise error in AutocompleteController#users when not authorized +merge_request: 7817 +author: Semyon Pupkov diff --git a/changelogs/unreleased/api-branch-status.yml b/changelogs/unreleased/api-branch-status.yml new file mode 100644 index 00000000000..c5763345a22 --- /dev/null +++ b/changelogs/unreleased/api-branch-status.yml @@ -0,0 +1,4 @@ +--- +title: 'API: Expose merge status for branch API' +merge_request: +author: Robert Schilling diff --git a/changelogs/unreleased/fix-ca-no-date.yml b/changelogs/unreleased/fix-ca-no-date.yml new file mode 100644 index 00000000000..6de4a56ac0d --- /dev/null +++ b/changelogs/unreleased/fix-ca-no-date.yml @@ -0,0 +1,4 @@ +--- +title: Fix for error thrown in cycle analytics events if build has not started +merge_request: +author: diff --git a/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml b/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml new file mode 100644 index 00000000000..e37841e80c3 --- /dev/null +++ b/changelogs/unreleased/fix-create-pipeline-with-builds-in-transaction.yml @@ -0,0 +1,4 @@ +--- +title: Create builds in transaction to avoid empty pipelines +merge_request: 7742 +author: diff --git a/changelogs/unreleased/improve-invite-accept-page.yml b/changelogs/unreleased/improve-invite-accept-page.yml new file mode 100644 index 00000000000..8a09a5ae42f --- /dev/null +++ b/changelogs/unreleased/improve-invite-accept-page.yml @@ -0,0 +1,4 @@ +--- +title: Add note to the invite page when the logged in user email is not the same as the invitation +merge_request: +author: diff --git a/changelogs/unreleased/issue_24363.yml b/changelogs/unreleased/issue_24363.yml new file mode 100644 index 00000000000..0298890b477 --- /dev/null +++ b/changelogs/unreleased/issue_24363.yml @@ -0,0 +1,4 @@ +--- +title: Fix appearance in error pages +merge_request: +author: Luis Alonso Chavez Armendariz diff --git a/changelogs/unreleased/sh-update-sidekiq-cron.yml b/changelogs/unreleased/sh-update-sidekiq-cron.yml new file mode 100644 index 00000000000..d79ba817a18 --- /dev/null +++ b/changelogs/unreleased/sh-update-sidekiq-cron.yml @@ -0,0 +1,4 @@ +--- +title: Update Sidekiq-cron to fix compatibility issues with Sidekiq 4.2.1 +merge_request: +author: diff --git a/db/migrate/20130319214458_create_forked_project_links.rb b/db/migrate/20130319214458_create_forked_project_links.rb index 66eb11a4b2b..065a5e08243 100644 --- a/db/migrate/20130319214458_create_forked_project_links.rb +++ b/db/migrate/20130319214458_create_forked_project_links.rb @@ -1,11 +1,13 @@ # rubocop:disable all class CreateForkedProjectLinks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :forked_project_links do |t| t.integer :forked_to_project_id, null: false t.integer :forked_from_project_id, null: false - t.timestamps + t.timestamps null: true end add_index :forked_project_links, :forked_to_project_id, unique: true end diff --git a/db/migrate/20130506090604_create_deploy_keys_projects.rb b/db/migrate/20130506090604_create_deploy_keys_projects.rb index 7d6662d358a..8b9662a27c3 100644 --- a/db/migrate/20130506090604_create_deploy_keys_projects.rb +++ b/db/migrate/20130506090604_create_deploy_keys_projects.rb @@ -1,11 +1,13 @@ # rubocop:disable all class CreateDeployKeysProjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :deploy_keys_projects do |t| t.integer :deploy_key_id, null: false t.integer :project_id, null: false - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20130617095603_create_users_groups.rb b/db/migrate/20130617095603_create_users_groups.rb index 45cff93fe4a..4ba7d0c9461 100644 --- a/db/migrate/20130617095603_create_users_groups.rb +++ b/db/migrate/20130617095603_create_users_groups.rb @@ -1,12 +1,14 @@ # rubocop:disable all class CreateUsersGroups < ActiveRecord::Migration + DOWNTIME = false + def change create_table :users_groups do |t| t.integer :group_access, null: false t.integer :group_id, null: false t.integer :user_id, null: false - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20130711063759_create_project_group_links.rb b/db/migrate/20130711063759_create_project_group_links.rb index bd9d40a50db..efccb2aa938 100644 --- a/db/migrate/20130711063759_create_project_group_links.rb +++ b/db/migrate/20130711063759_create_project_group_links.rb @@ -1,11 +1,13 @@ # rubocop:disable all class CreateProjectGroupLinks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :project_group_links do |t| t.integer :project_id, null: false t.integer :group_id, null: false - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20131112114325_create_broadcast_messages.rb b/db/migrate/20131112114325_create_broadcast_messages.rb index ce37a8e2708..ad2549e53af 100644 --- a/db/migrate/20131112114325_create_broadcast_messages.rb +++ b/db/migrate/20131112114325_create_broadcast_messages.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateBroadcastMessages < ActiveRecord::Migration + DOWNTIME = false + def change create_table :broadcast_messages do |t| t.text :message, null: false @@ -7,7 +9,7 @@ class CreateBroadcastMessages < ActiveRecord::Migration t.datetime :ends_at t.integer :alert_type - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb index 395c3edfc79..6c7a92b6950 100644 --- a/db/migrate/20140122112253_create_merge_request_diffs.rb +++ b/db/migrate/20140122112253_create_merge_request_diffs.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateMergeRequestDiffs < ActiveRecord::Migration + DOWNTIME = false + def up create_table :merge_request_diffs do |t| t.string :state, null: false, default: 'collected' @@ -7,7 +9,7 @@ class CreateMergeRequestDiffs < ActiveRecord::Migration t.text :st_diffs, null: true t.integer :merge_request_id, null: false - t.timestamps + t.timestamps null: true end if ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/ diff --git a/db/migrate/20140209025651_create_emails.rb b/db/migrate/20140209025651_create_emails.rb index 571beb19cdd..51886f8fc89 100644 --- a/db/migrate/20140209025651_create_emails.rb +++ b/db/migrate/20140209025651_create_emails.rb @@ -1,11 +1,13 @@ # rubocop:disable all class CreateEmails < ActiveRecord::Migration + DOWNTIME = false + def change create_table :emails do |t| t.integer :user_id, null: false t.string :email, null: false - - t.timestamps + + t.timestamps null: true end add_index :emails, :user_id diff --git a/db/migrate/20140625115202_create_users_star_projects.rb b/db/migrate/20140625115202_create_users_star_projects.rb index 32dd99e83be..d4f3fe5ac62 100644 --- a/db/migrate/20140625115202_create_users_star_projects.rb +++ b/db/migrate/20140625115202_create_users_star_projects.rb @@ -1,10 +1,12 @@ # rubocop:disable all class CreateUsersStarProjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :users_star_projects do |t| t.integer :project_id, null: false t.integer :user_id, null: false - t.timestamps + t.timestamps null: true end add_index :users_star_projects, :user_id add_index :users_star_projects, :project_id diff --git a/db/migrate/20140729134820_create_labels.rb b/db/migrate/20140729134820_create_labels.rb index df0f8cb9f03..66d20e741a6 100644 --- a/db/migrate/20140729134820_create_labels.rb +++ b/db/migrate/20140729134820_create_labels.rb @@ -1,12 +1,14 @@ # rubocop:disable all class CreateLabels < ActiveRecord::Migration + DOWNTIME = false + def change create_table :labels do |t| t.string :title t.string :color t.integer :project_id - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20140729140420_create_label_links.rb b/db/migrate/20140729140420_create_label_links.rb index fa5992605f8..dacd9f2e4b6 100644 --- a/db/migrate/20140729140420_create_label_links.rb +++ b/db/migrate/20140729140420_create_label_links.rb @@ -1,12 +1,14 @@ # rubocop:disable all class CreateLabelLinks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :label_links do |t| t.integer :label_id t.integer :target_id t.string :target_type - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20140914113604_add_members_table.rb b/db/migrate/20140914113604_add_members_table.rb index bc3c1bb61e4..0f76bb0ef79 100644 --- a/db/migrate/20140914113604_add_members_table.rb +++ b/db/migrate/20140914113604_add_members_table.rb @@ -1,5 +1,7 @@ # rubocop:disable all class AddMembersTable < ActiveRecord::Migration + DOWNTIME = false + def change create_table :members do |t| t.integer :access_level, null: false @@ -9,7 +11,7 @@ class AddMembersTable < ActiveRecord::Migration t.integer :notification_level, null: false t.string :type - t.timestamps + t.timestamps null: true end add_index :members, :type diff --git a/db/migrate/20140914173417_remove_old_member_tables.rb b/db/migrate/20140914173417_remove_old_member_tables.rb index aff8e94e5be..d2ab326ef1f 100644 --- a/db/migrate/20140914173417_remove_old_member_tables.rb +++ b/db/migrate/20140914173417_remove_old_member_tables.rb @@ -1,5 +1,7 @@ # rubocop:disable all class RemoveOldMemberTables < ActiveRecord::Migration + DOWNTIME = false + def up drop_table :users_groups drop_table :users_projects @@ -12,7 +14,7 @@ class RemoveOldMemberTables < ActiveRecord::Migration t.integer :user_id, null: false t.integer :notification_level, null: false, default: 3 - t.timestamps + t.timestamps null: true end create_table :users_projects do |t| @@ -21,7 +23,7 @@ class RemoveOldMemberTables < ActiveRecord::Migration t.integer :user_id, null: false t.integer :notification_level, null: false, default: 3 - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20141118150935_add_audit_event.rb b/db/migrate/20141118150935_add_audit_event.rb index 3884228456f..52d70b4a0ac 100644 --- a/db/migrate/20141118150935_add_audit_event.rb +++ b/db/migrate/20141118150935_add_audit_event.rb @@ -1,5 +1,7 @@ # rubocop:disable all class AddAuditEvent < ActiveRecord::Migration + DOWNTIME = false + def change create_table :audit_events do |t| t.integer :author_id, null: false @@ -13,7 +15,7 @@ class AddAuditEvent < ActiveRecord::Migration # Details for the event t.text :details - t.timestamps + t.timestamps null: true end add_index :audit_events, :author_id diff --git a/db/migrate/20141216155758_create_doorkeeper_tables.rb b/db/migrate/20141216155758_create_doorkeeper_tables.rb index b323ffe96f5..17e45a77291 100644 --- a/db/migrate/20141216155758_create_doorkeeper_tables.rb +++ b/db/migrate/20141216155758_create_doorkeeper_tables.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateDoorkeeperTables < ActiveRecord::Migration + DOWNTIME = false + def change create_table :oauth_applications do |t| t.string :name, null: false @@ -7,7 +9,7 @@ class CreateDoorkeeperTables < ActiveRecord::Migration t.string :secret, null: false t.text :redirect_uri, null: false t.string :scopes, null: false, default: '' - t.timestamps + t.timestamps null: true end add_index :oauth_applications, :uid, unique: true diff --git a/db/migrate/20150108073740_create_application_settings.rb b/db/migrate/20150108073740_create_application_settings.rb index dfa2f765357..0e4c66ca8c0 100644 --- a/db/migrate/20150108073740_create_application_settings.rb +++ b/db/migrate/20150108073740_create_application_settings.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateApplicationSettings < ActiveRecord::Migration + DOWNTIME = false + def change create_table :application_settings do |t| t.integer :default_projects_limit @@ -8,7 +10,7 @@ class CreateApplicationSettings < ActiveRecord::Migration t.boolean :gravatar_enabled t.text :sign_in_text - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20150313012111_create_subscriptions_table.rb b/db/migrate/20150313012111_create_subscriptions_table.rb index 8adb193b27f..a9a8435330d 100644 --- a/db/migrate/20150313012111_create_subscriptions_table.rb +++ b/db/migrate/20150313012111_create_subscriptions_table.rb @@ -1,15 +1,17 @@ # rubocop:disable all class CreateSubscriptionsTable < ActiveRecord::Migration + DOWNTIME = false + def change create_table :subscriptions do |t| t.integer :user_id t.references :subscribable, polymorphic: true t.boolean :subscribed - - t.timestamps + + t.timestamps null: true end - add_index :subscriptions, + add_index :subscriptions, [:subscribable_id, :subscribable_type, :user_id], unique: true, name: 'subscriptions_user_id_and_ref_fields' diff --git a/db/migrate/20150806104937_create_abuse_reports.rb b/db/migrate/20150806104937_create_abuse_reports.rb index 3c749b5d9a9..52aed9e1d1d 100644 --- a/db/migrate/20150806104937_create_abuse_reports.rb +++ b/db/migrate/20150806104937_create_abuse_reports.rb @@ -1,12 +1,14 @@ # rubocop:disable all class CreateAbuseReports < ActiveRecord::Migration + DOWNTIME = false + def change create_table :abuse_reports do |t| t.integer :reporter_id t.integer :user_id t.text :message - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20151103134857_create_lfs_objects.rb b/db/migrate/20151103134857_create_lfs_objects.rb index 745b52e2b24..db6fa27199b 100644 --- a/db/migrate/20151103134857_create_lfs_objects.rb +++ b/db/migrate/20151103134857_create_lfs_objects.rb @@ -1,11 +1,13 @@ # rubocop:disable all class CreateLfsObjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :lfs_objects do |t| t.string :oid, null: false, unique: true t.integer :size, null: false - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20151103134958_create_lfs_objects_projects.rb b/db/migrate/20151103134958_create_lfs_objects_projects.rb index 3178e85b899..5af1c39fd9c 100644 --- a/db/migrate/20151103134958_create_lfs_objects_projects.rb +++ b/db/migrate/20151103134958_create_lfs_objects_projects.rb @@ -1,11 +1,13 @@ # rubocop:disable all class CreateLfsObjectsProjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :lfs_objects_projects do |t| t.integer :lfs_object_id, null: false t.integer :project_id, null: false - t.timestamps + t.timestamps null: true end add_index :lfs_objects_projects, :project_id diff --git a/db/migrate/20151105094515_create_releases.rb b/db/migrate/20151105094515_create_releases.rb index 145b8db1486..34dd7a10942 100644 --- a/db/migrate/20151105094515_create_releases.rb +++ b/db/migrate/20151105094515_create_releases.rb @@ -1,12 +1,14 @@ # rubocop:disable all class CreateReleases < ActiveRecord::Migration + DOWNTIME = false + def change create_table :releases do |t| t.string :tag t.text :description t.integer :project_id - t.timestamps + t.timestamps null: true end add_index :releases, :project_id diff --git a/db/migrate/20160212123307_create_tasks.rb b/db/migrate/20160212123307_create_tasks.rb index 20573b01351..cd3ad0e4cd8 100644 --- a/db/migrate/20160212123307_create_tasks.rb +++ b/db/migrate/20160212123307_create_tasks.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateTasks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :tasks do |t| t.references :user, null: false, index: true @@ -9,7 +11,7 @@ class CreateTasks < ActiveRecord::Migration t.integer :action, null: false t.string :state, null: false, index: true - t.timestamps + t.timestamps null: true end end end diff --git a/db/migrate/20160416180807_add_award_emoji.rb b/db/migrate/20160416180807_add_award_emoji.rb index a3bee9b1bc6..0d252e5044e 100644 --- a/db/migrate/20160416180807_add_award_emoji.rb +++ b/db/migrate/20160416180807_add_award_emoji.rb @@ -1,12 +1,14 @@ # rubocop:disable all class AddAwardEmoji < ActiveRecord::Migration + DOWNTIME = false + def change create_table :award_emoji do |t| t.string :name t.references :user t.references :awardable, polymorphic: true - t.timestamps + t.timestamps null: true end add_index :award_emoji, :user_id diff --git a/db/migrate/20160831214002_create_project_features.rb b/db/migrate/20160831214002_create_project_features.rb index 2d76a015a08..343953826f0 100644 --- a/db/migrate/20160831214002_create_project_features.rb +++ b/db/migrate/20160831214002_create_project_features.rb @@ -10,7 +10,7 @@ class CreateProjectFeatures < ActiveRecord::Migration t.integer :snippets_access_level t.integer :builds_access_level - t.timestamps + t.timestamps null: true end end end diff --git a/doc/api/branches.md b/doc/api/branches.md index 07dfa5d4d7f..ffcfea41453 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -22,6 +22,7 @@ Example response: [ { "name": "master", + "merged": false, "protected": true, "developers_can_push": false, "developers_can_merge": false, @@ -65,6 +66,7 @@ Example response: ```json { "name": "master", + "merged": false, "protected": true, "developers_can_push": false, "developers_can_merge": false, @@ -123,6 +125,7 @@ Example response: ] }, "name": "master", + "merged": false, "protected": true, "developers_can_push": true, "developers_can_merge": true @@ -166,6 +169,7 @@ Example response: ] }, "name": "master", + "merged": false, "protected": false, "developers_can_push": false, "developers_can_merge": false @@ -206,6 +210,7 @@ Example response: ] }, "name": "newbranch", + "merged": false, "protected": false, "developers_can_push": false, "developers_can_merge": false diff --git a/doc/api/projects.md b/doc/api/projects.md index de57f91bb8e..132be644b59 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -626,6 +626,7 @@ Parameters: | `path` | string | no | Custom repository name for new project. By default generated based on name | | `default_branch` | string | no | `master` by default | | `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) | +| `default_branch` | string | no | `master` by default | | `description` | string | no | Short project description | | `issues_enabled` | boolean | no | Enable issues for this project | | `merge_requests_enabled` | boolean | no | Enable merge requests for this project | diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 1bc6a24e914..b8c9eb2c9a8 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -60,7 +60,7 @@ Parameters: - `file_path` (required) - Full path to new file. Ex. lib/class.rb - `branch_name` (required) - The name of branch -- `encoding` (optional) - 'text' or 'base64'. Text is default. +- `encoding` (optional) - Change encoding to 'base64'. Default is text. - `author_email` (optional) - Specify the commit author's email address - `author_name` (optional) - Specify the commit author's name - `content` (required) - File content @@ -89,7 +89,7 @@ Parameters: - `file_path` (required) - Full path to file. Ex. lib/class.rb - `branch_name` (required) - The name of branch -- `encoding` (optional) - 'text' or 'base64'. Text is default. +- `encoding` (optional) - Change encoding to 'base64'. Default is text. - `author_email` (optional) - Specify the commit author's email address - `author_name` (optional) - Specify the commit author's name - `content` (required) - New file content diff --git a/doc/ci/README.md b/doc/ci/README.md index 545cc72682d..db236ce2a66 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -21,6 +21,7 @@ - [CI services (linked docker containers)](services/README.md) - [CI/CD pipelines settings](../user/project/pipelines/settings.md) - [Review Apps](review_apps/index.md) +- [Git submodules](git_submodules.md) Using Git submodules in your CI jobs ## Breaking changes diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md new file mode 100644 index 00000000000..1d782200cca --- /dev/null +++ b/doc/ci/git_submodules.md @@ -0,0 +1,86 @@ +# Using Git submodules with GitLab CI + +> **Notes:** +- GitLab 8.12 introduced a new [CI build permissions model][newperms] and you + are encouraged to upgrade your GitLab instance if you haven't done already. + If you are **not** using GitLab 8.12 or higher, you would need to work your way + around submodules in order to access the sources of e.g., `gitlab.com/group/project` + with the use of [SSH keys](ssh_keys/README.md). +- With GitLab 8.12 onward, your permissions are used to evaluate what a CI build + can access. More information about how this system works can be found in the + [Build permissions model](../user/permissions.md#builds-permissions). +- The HTTP(S) Git protocol [must be enabled][gitpro] in your GitLab instance. + +## Configuring the `.gitmodules` file + +If dealing with [Git submodules][gitscm], your project will probably have a file +named `.gitmodules`. + +Let's consider the following example: + +1. Your project is located at `https://gitlab.com/secret-group/my-project`. +1. To checkout your sources you usually use an SSH address like + `git@gitlab.com:secret-group/my-project.git`. +1. Your project depends on `https://gitlab.com/group/project`, which you want + to include as a submodule. + +If you are using GitLab 8.12+ and your submodule is on the same GitLab server, +you must update your `.gitmodules` file to use **relative URLs**. +Since Git allows the usage of relative URLs for your `.gitmodules` configuration, +this easily allows you to use HTTP(S) for cloning all your CI builds and SSH +for all your local checkouts. The `.gitmodules` would look like: + +```ini +[submodule "project"] + path = project + url = ../../group/project.git +``` + +The above configuration will instruct Git to automatically deduce the URL that +should be used when cloning sources. Whether you use HTTP(S) or SSH, Git will use +that same channel and it will allow to make all your CI builds use HTTP(S) +(because GitLab CI only uses HTTP(S) for cloning your sources), and all your local +clones will continue using SSH. + +For all other submodules not located on the same GitLab server, use the full +HTTP(S) protocol URL: + +```ini +[submodule "project-x"] + path = project-x + url = https://gitserver.com/group/project-x.git +``` + +Once `.gitmodules` is correctly configured, you can move on to +[configuring your `.gitlab-ci.yml`](#using-git-submodules-in-your-ci-jobs). + +## Using Git submodules in your CI jobs + +There are a few steps you need to take in order to make submodules work +correctly with your CI builds: + +1. First, make sure you have used [relative URLs](#configuring-the-gitmodules-file) + for the submodules located in the same GitLab server. +1. Then, use `git submodule sync/update` in `before_script`: + + ```yaml + before_script: + - git submodule sync --recursive + - git submodule update --init --recursive + ``` + + `--recursive` should be used in either both or none (`sync/update`) depending on + whether you have recursive submodules. + +The rationale to set the `sync` and `update` in `before_script` is because of +the way Git submodules work. On a fresh Runner workspace, Git will set the +submodule URL including the token in `.git/config` +(or `.git/modules/<submodule>/config`) based on `.gitmodules` and the current +remote URL. On subsequent builds on the same Runner, `.git/config` is cached +and already contains a full URL for the submodule, corresponding to the previous +build, and to **a token from a previous build**. `sync` allows to force updating +the full URL. + +[gitpro]: ../user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols +[gitscm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules "Git submodules documentation" +[newperms]: ../user/project/new_ci_build_permissions_model.md diff --git a/doc/development/performance.md b/doc/development/performance.md index 8337c2d9cb3..5c43ae7b79a 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -101,6 +101,116 @@ In short: 5. If you must write a benchmark use the benchmark-ips Gem instead of Ruby's `Benchmark` module. +## Profiling + +By collecting snapshots of process state at regular intervals, profiling allows +you to see where time is spent in a process. The [StackProf](https://github.com/tmm1/stackprof) +gem is included in GitLab's development environment, allowing you to investigate +the behaviour of suspect code in detail. + +It's important to note that profiling an application *alters its performance*, +and will generally be done *in an unrepresentative environment*. In particular, +a method is not necessarily troublesome just because it is executed many times, +or takes a long time to execute. Profiles are tools you can use to better +understand what is happening in an application - using that information wisely +is up to you! + +Keeping that in mind, to create a profile, identify (or create) a spec that +exercises the troublesome code path, then run it using the `bin/rspec-stackprof` +helper, e.g.: + +``` +$ LIMIT=10 bin/rspec-stackprof spec/policies/project_policy_spec.rb +8/8 |====== 100 ======>| Time: 00:00:18 + +Finished in 18.19 seconds (files took 4.8 seconds to load) +8 examples, 0 failures + +================================== + Mode: wall(1000) + Samples: 17033 (5.59% miss rate) + GC: 1901 (11.16%) +================================== + TOTAL (pct) SAMPLES (pct) FRAME + 6000 (35.2%) 2566 (15.1%) Sprockets::Cache::FileStore#get + 2018 (11.8%) 888 (5.2%) ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_no_cache + 1338 (7.9%) 640 (3.8%) ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements#execute + 3125 (18.3%) 394 (2.3%) Sprockets::Cache::FileStore#safe_open + 913 (5.4%) 301 (1.8%) ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#exec_cache + 288 (1.7%) 288 (1.7%) ActiveRecord::Attribute#initialize + 246 (1.4%) 246 (1.4%) Sprockets::Cache::FileStore#safe_stat + 295 (1.7%) 193 (1.1%) block (2 levels) in class_attribute + 187 (1.1%) 187 (1.1%) block (4 levels) in class_attribute +``` + +You can limit the specs that are run by passing any arguments `rspec` would +normally take. + +The output is sorted by the `Samples` column by default. This is the number of +samples taken where the method is the one currently being executed. The `Total` +column shows the number of samples taken where the method, or any of the methods +it calls, were being executed. + +To create a graphical view of the call stack: + +```shell +$ stackprof tmp/project_policy_spec.rb.dump --graphviz > project_policy_spec.dot +$ dot -Tsvg project_policy_spec.dot > project_policy_spec.svg +``` + +To load the profile in [kcachegrind](https://kcachegrind.github.io/): + +``` +$ stackprof tmp/project_policy_spec.dump --callgrind > project_policy_spec.callgrind +$ kcachegrind project_policy_spec.callgrind # Linux +$ qcachegrind project_policy_spec.callgrind # Mac +``` + +It may be useful to zoom in on a specific method, e.g.: + +``` +$ stackprof tmp/project_policy_spec.rb.dump --method warm_asset_cache +TestEnv#warm_asset_cache (/Users/lupine/dev/gitlab.com/gitlab-org/gitlab-development-kit/gitlab/spec/support/test_env.rb:164) + samples: 0 self (0.0%) / 6288 total (36.9%) + callers: + 6288 ( 100.0%) block (2 levels) in <top (required)> + callees (6288 total): + 6288 ( 100.0%) Capybara::RackTest::Driver#visit + code: + | 164 | def warm_asset_cache + | 165 | return if warm_asset_cache? + | 166 | return unless defined?(Capybara) + | 167 | + 6288 (36.9%) | 168 | Capybara.current_session.driver.visit '/' + | 169 | end +$ stackprof tmp/project_policy_spec.rb.dump --method BasePolicy#abilities +BasePolicy#abilities (/Users/lupine/dev/gitlab.com/gitlab-org/gitlab-development-kit/gitlab/app/policies/base_policy.rb:79) + samples: 0 self (0.0%) / 50 total (0.3%) + callers: + 25 ( 50.0%) BasePolicy.abilities + 25 ( 50.0%) BasePolicy#collect_rules + callees (50 total): + 25 ( 50.0%) ProjectPolicy#rules + 25 ( 50.0%) BasePolicy#collect_rules + code: + | 79 | def abilities + | 80 | return RuleSet.empty if @user && @user.blocked? + | 81 | return anonymous_abilities if @user.nil? + 50 (0.3%) | 82 | collect_rules { rules } + | 83 | end +``` + +Since the profile includes the work done by the test suite as well as the +application code, these profiles can be used to investigate slow tests as well. +However, for smaller runs (like this example), this means that the cost of +setting up the test suite will tend to dominate. + +It's also possible to modify the application code in-place to output profiles +whenever a particular code path is triggered without going through the test +suite first. See the +[StackProf documentation](https://github.com/tmm1/stackprof/blob/master/README.md) +for details. + ## Importance of Changes When working on performance improvements, it's important to always ask yourself diff --git a/doc/user/permissions.md b/doc/user/permissions.md index cea78864df2..39fe2409a29 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -141,10 +141,9 @@ instance and project. In addition, all admins can use the admin interface under | See events in the system | | | | ✓ | | Admin interface | | | | ✓ | -### Build permissions - -> Changed in GitLab 8.12. +### Builds permissions +>**Note:** GitLab 8.12 has a completely redesigned build permissions system. Read all about the [new model and its implications][new-mod]. diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 60b7bec2ba7..4f12acb8398 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -34,9 +34,9 @@ as created be the pusher (local push or via the UI) and any build created in thi pipeline will have the permissions of the pusher. This allows us to make it really easy to evaluate the access for all projects -that have Git submodules or are using container images that the pusher would -have access too. **The permission is granted only for time that build is running. -The access is revoked after the build is finished.** +that have [Git submodules][gitsub] or are using container images that the pusher +would have access too. **The permission is granted only for time that build is +running. The access is revoked after the build is finished.** ## Types of users @@ -141,7 +141,7 @@ with GitLab 8.12. With the new build permissions model, there is now an easy way to access all dependent source code in a project. That way, we can: -1. Access a project's Git submodules +1. Access a project's [Git submodules][gitsub] 1. Access private container images 1. Access project's and submodule LFS objects @@ -179,108 +179,8 @@ As a user: ### Git submodules -> -It often happens that while working on one project, you need to use another -project from within it; perhaps it’s a library that a third party developed or -you’re developing a project separately and are using it in multiple parent -projects. -A common issue arises in these scenarios: you want to be able to treat the two -projects as separate yet still be able to use one from within the other. -> -_Excerpt from the [Git website][git-scm] about submodules._ - -If dealing with submodules, your project will probably have a file named -`.gitmodules`. And this is how it usually looks like: - -``` -[submodule "tools"] - path = tools - url = git@gitlab.com/group/tools.git -``` - -> **Note:** -If you are **not** using GitLab 8.12 or higher, you would need to work your way -around this issue in order to access the sources of `gitlab.com/group/tools` -(e.g., use [SSH keys](../ssh_keys/README.md)). -> -With GitLab 8.12 onward, your permissions are used to evaluate what a CI build -can access. More information about how this system works can be found in the -[Build permissions model](../../user/permissions.md#builds-permissions). - -To make use of the new changes, you have to update your `.gitmodules` file to -use a relative URL. - -Let's consider the following example: - -1. Your project is located at `https://gitlab.com/secret-group/my-project`. -1. To checkout your sources you usually use an SSH address like - `git@gitlab.com:secret-group/my-project.git`. -1. Your project depends on `https://gitlab.com/group/tools`. -1. You have the `.gitmodules` file with above content. - -Since Git allows the usage of relative URLs for your `.gitmodules` configuration, -this easily allows you to use HTTP for cloning all your CI builds and SSH -for all your local checkouts. - -For example, if you change the `url` of your `tools` dependency, from -`git@gitlab.com/group/tools.git` to `../../group/tools.git`, this will instruct -Git to automatically deduce the URL that should be used when cloning sources. -Whether you use HTTP or SSH, Git will use that same channel and it will allow -to make all your CI builds use HTTPS (because GitLab CI uses HTTPS for cloning -your sources), and all your local clones will continue using SSH. - -Given the above explanation, your `.gitmodules` file should eventually look -like this: - -``` -[submodule "tools"] - path = tools - url = ../../group/tools.git -``` - -However, you have to explicitly tell GitLab CI to clone your submodules as this -is not done automatically. You can achieve that by adding a `before_script` -section to your `.gitlab-ci.yml`: - -``` -before_script: - - git submodule update --init --recursive - -test: - script: - - run-my-tests -``` - -This will make GitLab CI initialize (fetch) and update (checkout) all your -submodules recursively. - -If Git does not use the newly added relative URLs but still uses your old URLs, -you might need to add `git submodule sync --recursive` to your `.gitlab-ci.yml`, -prior to running `git submodule update --init --recursive`. This transfers the -changes from your `.gitmodules` file into the `.git` folder, which is kept by -runners between runs. - -In case your environment or your Docker image doesn't have Git installed, -you have to either ask your Administrator or install the missing dependency -yourself: - -``` -# Debian / Ubuntu -before_script: - - apt-get update -y - - apt-get install -y git-core - - git submodule update --init --recursive - -# CentOS / RedHat -before_script: - - yum install git - - git submodule update --init --recursive - -# Alpine -before_script: - - apk add -U git - - git submodule update --init --recursive -``` +To properly configure submodules with GitLab CI, read the +[Git submodules documentation][gitsub]. ### Container Registry @@ -310,7 +210,7 @@ test: [build permissions]: ../permissions.md#builds-permissions [comment]: https://gitlab.com/gitlab-org/gitlab-ce/issues/22484#note_16648302 [ext]: ../permissions.md#external-users -[git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules +[gitsub]: ../../ci/git_submodules.md [https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols [triggers]: ../../ci/triggers/README.md [update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update diff --git a/lib/api/entities.rb b/lib/api/entities.rb index fdb19558c1c..d5dfb8d00be 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -141,8 +141,12 @@ module API options[:project].repository.commit(repo_branch.dereferenced_target) end + expose :merged do |repo_branch, options| + options[:project].repository.merged_to_root_ref?(repo_branch.name) + end + expose :protected do |repo_branch, options| - options[:project].protected_branch? repo_branch.name + options[:project].protected_branch?(repo_branch.name) end expose :developers_can_push do |repo_branch, options| diff --git a/lib/api/files.rb b/lib/api/files.rb index 96510e651a3..28f306e45f3 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -23,140 +23,107 @@ module API branch_name: attrs[:branch_name] } end + + params :simple_file_params do + requires :file_path, type: String, desc: 'The path to new file. Ex. lib/class.rb' + requires :branch_name, type: String, desc: 'The name of branch' + requires :commit_message, type: String, desc: 'Commit Message' + optional :author_email, type: String, desc: 'The email of the author' + optional :author_name, type: String, desc: 'The name of the author' + end + + params :extended_file_params do + use :simple_file_params + requires :content, type: String, desc: 'File content' + optional :encoding, type: String, values: %w[base64], desc: 'File encoding' + end end + params do + requires :id, type: String, desc: 'The project ID' + end resource :projects do - # Get file from repository - # File content is Base64 encoded - # - # Parameters: - # file_path (required) - The path to the file. Ex. lib/class.rb - # ref (required) - The name of branch, tag or commit - # - # Example Request: - # GET /projects/:id/repository/files - # - # Example response: - # { - # "file_name": "key.rb", - # "file_path": "app/models/key.rb", - # "size": 1476, - # "encoding": "base64", - # "content": "IyA9PSBTY2hlbWEgSW5mb3...", - # "ref": "master", - # "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83", - # "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50", - # "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - # } - # + desc 'Get a file from repository' + params do + requires :file_path, type: String, desc: 'The path to the file. Ex. lib/class.rb' + requires :ref, type: String, desc: 'The name of branch, tag, or commit' + end get ":id/repository/files" do authorize! :download_code, user_project - required_attributes! [:file_path, :ref] - attrs = attributes_for_keys [:file_path, :ref] - ref = attrs.delete(:ref) - file_path = attrs.delete(:file_path) - - commit = user_project.commit(ref) - not_found! 'Commit' unless commit + commit = user_project.commit(params[:ref]) + not_found!('Commit') unless commit repo = user_project.repository - blob = repo.blob_at(commit.sha, file_path) + blob = repo.blob_at(commit.sha, params[:file_path]) + not_found!('File') unless blob - if blob - blob.load_all_data!(repo) - status(200) + blob.load_all_data!(repo) + status(200) - { - file_name: blob.name, - file_path: blob.path, - size: blob.size, - encoding: "base64", - content: Base64.strict_encode64(blob.data), - ref: ref, - blob_id: blob.id, - commit_id: commit.id, - last_commit_id: repo.last_commit_for_path(commit.sha, file_path).id - } - else - not_found! 'File' - end + { + file_name: blob.name, + file_path: blob.path, + size: blob.size, + encoding: "base64", + content: Base64.strict_encode64(blob.data), + ref: params[:ref], + blob_id: blob.id, + commit_id: commit.id, + last_commit_id: repo.last_commit_for_path(commit.sha, params[:file_path]).id + } end - # Create new file in repository - # - # Parameters: - # file_path (required) - The path to new file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # POST /projects/:id/repository/files - # + desc 'Create new file in repository' + params do + use :extended_file_params + end post ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :content, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name] - result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::CreateService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(201) - commit_response(attrs) + commit_response(file_params) else render_api_error!(result[:message], 400) end end - # Update existing file in repository - # - # Parameters: - # file_path (optional) - The path to file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # PUT /projects/:id/repository/files - # + desc 'Update existing file in repository' + params do + use :extended_file_params + end put ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :content, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name] - result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::UpdateService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(200) - commit_response(attrs) + commit_response(file_params) else http_status = result[:http_status] || 400 render_api_error!(result[:message], http_status) end end - # Delete existing file in repository - # - # Parameters: - # file_path (optional) - The path to file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # DELETE /projects/:id/repository/files - # + desc 'Delete an existing file in repository' + params do + use :simple_file_params + end delete ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :commit_message, :author_email, :author_name] - result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::DeleteService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(200) - commit_response(attrs) + commit_response(file_params) else render_api_error!(result[:message], 400) end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 34d9c3c6932..cbafa952ef6 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -308,11 +308,6 @@ module API # Projects helpers def filter_projects(projects) - # If the archived parameter is passed, limit results accordingly - if params[:archived].present? - projects = projects.where(archived: to_boolean(params[:archived])) - end - if params[:search].present? projects = projects.search(params[:search]) end @@ -321,25 +316,8 @@ module API projects = projects.search_by_visibility(params[:visibility]) end - projects.reorder(project_order_by => project_sort) - end - - def project_order_by - order_fields = %w(id name path created_at updated_at last_activity_at) - - if order_fields.include?(params['order_by']) - params['order_by'] - else - 'created_at' - end - end - - def project_sort - if params["sort"] == 'asc' - :asc - else - :desc - end + projects = projects.where(archived: params[:archived]) + projects.reorder(params[:order_by] => params[:sort]) end # file helpers diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 90fa588b455..97baebc1d27 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -192,7 +192,7 @@ module API should_remove_source_branch: params[:should_remove_source_branch] } - if params[:merge_when_build_succeeds] && merge_request.pipeline && merge_request.pipeline.active? + if params[:merge_when_build_succeeds] && merge_request.head_pipeline && merge_request.head_pipeline.active? ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). execute(merge_request) else diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 2ea3c433ae2..8975b1a751c 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -1,293 +1,287 @@ module API # Projects API class Projects < Grape::API + include PaginationParams + before { authenticate! } - resource :projects, requirements: { id: /[^\/]+/ } do + helpers do + params :optional_params do + optional :description, type: String, desc: 'The description of the project' + optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled' + optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled' + optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled' + optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled' + optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled' + optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' + optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project' + optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project' + optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.' + optional :visibility_level, type: Integer, values: [ + Gitlab::VisibilityLevel::PRIVATE, + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC ], desc: 'Create a public project. The same as visibility_level = 20.' + optional :public_builds, type: Boolean, desc: 'Perform public builds' + optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' + optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' + optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' + end + + def map_public_to_visibility_level(attrs) + publik = attrs.delete(:public) + if !publik.nil? && !attrs[:visibility_level].present? + # Since setting the public attribute to private could mean either + # private or internal, use the more conservative option, private. + attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE + end + attrs + end + end + + resource :projects do helpers do - def map_public_to_visibility_level(attrs) - publik = attrs.delete(:public) - if publik.present? && !attrs[:visibility_level].present? - publik = to_boolean(publik) - # Since setting the public attribute to private could mean either - # private or internal, use the more conservative option, private. - attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE - end - attrs + params :sort_params do + optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], + default: 'created_at', desc: 'Return projects ordered by field' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return projects sorted in ascending and descending order' + end + + params :filter_params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + optional :visibility, type: String, values: %w[public internal private], + desc: 'Limit by visibility' + optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' + use :sort_params + end + + params :create_params do + optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.' + optional :import_url, type: String, desc: 'URL from which the project is imported' end end - # Get a projects list for authenticated user - # - # Example Request: - # GET /projects + desc 'Get a projects list for authenticated user' do + success Entities::BasicProjectDetails + end + params do + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + use :filter_params + use :pagination + end get do projects = current_user.authorized_projects projects = filter_projects(projects) - projects = paginate projects entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - present projects, with: entity, user: current_user + present paginate(projects), with: entity, user: current_user end - # Get a list of visible projects for authenticated user - # - # Example Request: - # GET /projects/visible + desc 'Get a list of visible projects for authenticated user' do + success Entities::BasicProjectDetails + end + params do + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + use :filter_params + use :pagination + end get '/visible' do projects = ProjectsFinder.new.execute(current_user) projects = filter_projects(projects) - projects = paginate projects entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - present projects, with: entity, user: current_user + present paginate(projects), with: entity, user: current_user end - # Get an owned projects list for authenticated user - # - # Example Request: - # GET /projects/owned + desc 'Get an owned projects list for authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/owned' do projects = current_user.owned_projects projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::ProjectWithAccess, user: current_user + + present paginate(projects), with: Entities::ProjectWithAccess, user: current_user end - # Gets starred project for the authenticated user - # - # Example Request: - # GET /projects/starred + desc 'Gets starred project for the authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/starred' do projects = current_user.viewable_starred_projects projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::Project, user: current_user + + present paginate(projects), with: Entities::Project, user: current_user end - # Get all projects for admin user - # - # Example Request: - # GET /projects/all + desc 'Get all projects for admin user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/all' do authenticated_as_admin! projects = Project.all projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::ProjectWithAccess, user: current_user + + present paginate(projects), with: Entities::ProjectWithAccess, user: current_user end - # Get a single project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id - get ":id" do - present user_project, with: Entities::ProjectWithAccess, user: current_user, - user_can_admin_project: can?(current_user, :admin_project, user_project) + desc 'Search for projects the current user has access to' do + success Entities::Project + end + params do + requires :query, type: String, desc: 'The project name to be searched' + use :sort_params + use :pagination end + get "/search/:query" do + search_service = Search::GlobalService.new(current_user, search: params[:query]).execute + projects = search_service.objects('projects', params[:page]) + projects = projects.reorder(params[:order_by] => params[:sort]) - # Get events for a single project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/events - get ":id/events" do - events = paginate user_project.events.recent - present events, with: Entities::Event - end - - # Create new project - # - # Parameters: - # name (required) - name for new project - # description (optional) - short project description - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # namespace_id (optional) - defaults to user namespace - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - 0 by default - # import_url (optional) - # public_builds (optional) - # lfs_enabled (optional) - # request_access_enabled (optional) - Allow users to request member access - # Example Request - # POST /projects + present paginate(projects), with: Entities::Project + end + + desc 'Create new project' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + optional :path, type: String, desc: 'The path of the repository' + use :optional_params + use :create_params + end post do - required_attributes! [:name] - attrs = attributes_for_keys [:builds_enabled, - :container_registry_enabled, - :description, - :import_url, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :namespace_id, - :only_allow_merge_if_build_succeeds, - :path, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) - @project = ::Projects::CreateService.new(current_user, attrs).execute - if @project.saved? - present @project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @project) + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + project = ::Projects::CreateService.new(current_user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) else - if @project.errors[:limit_reached].present? - error!(@project.errors[:limit_reached], 403) + if project.errors[:limit_reached].present? + error!(project.errors[:limit_reached], 403) end - render_validation_error!(@project) + render_validation_error!(project) end end - # Create new project for a specified user. Only available to admin users. - # - # Parameters: - # user_id (required) - The ID of a user - # name (required) - name for new project - # description (optional) - short project description - # default_branch (optional) - 'master' by default - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - # import_url (optional) - # public_builds (optional) - # lfs_enabled (optional) - # request_access_enabled (optional) - Allow users to request member access - # Example Request - # POST /projects/user/:user_id + desc 'Create new project for a specified user. Only available to admin users.' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + requires :user_id, type: Integer, desc: 'The ID of a user' + optional :default_branch, type: String, desc: 'The default branch of the project' + use :optional_params + use :create_params + end post "user/:user_id" do authenticated_as_admin! - user = User.find(params[:user_id]) - attrs = attributes_for_keys [:builds_enabled, - :default_branch, - :description, - :import_url, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :only_allow_merge_if_build_succeeds, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) - @project = ::Projects::CreateService.new(user, attrs).execute - if @project.saved? - present @project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @project) + user = User.find_by(id: params.delete(:user_id)) + not_found!('User') unless user + + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + project = ::Projects::CreateService.new(user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) else - render_validation_error!(@project) + render_validation_error!(project) end end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: { id: /[^\/]+/ } do + desc 'Get a single project' do + success Entities::ProjectWithAccess + end + get ":id" do + present user_project, with: Entities::ProjectWithAccess, user: current_user, + user_can_admin_project: can?(current_user, :admin_project, user_project) + end + + desc 'Get events for a single project' do + success Entities::Event + end + params do + use :pagination + end + get ":id/events" do + present paginate(user_project.events.recent), with: Entities::Event + end - # Fork new project for the current user or provided namespace. - # - # Parameters: - # id (required) - The ID of a project - # namespace (optional) - The ID or name of the namespace that the project will be forked into. - # Example Request - # POST /projects/fork/:id + desc 'Fork new project for the current user or provided namespace.' do + success Entities::Project + end + params do + optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' + end post 'fork/:id' do - attrs = {} - namespace_id = params[:namespace] + fork_params = declared_params(include_missing: false) + namespace_id = fork_params[:namespace] if namespace_id.present? - namespace = Namespace.find_by(id: namespace_id) || Namespace.find_by_path_or_name(namespace_id) + fork_params[:namespace] = if namespace_id =~ /^\d+$/ + Namespace.find_by(id: namespace_id) + else + Namespace.find_by_path_or_name(namespace_id) + end - unless namespace && can?(current_user, :create_projects, namespace) + unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace]) not_found!('Target Namespace') end - - attrs[:namespace] = namespace end - @forked_project = - ::Projects::ForkService.new(user_project, - current_user, - attrs).execute + forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute - if @forked_project.errors.any? - conflict!(@forked_project.errors.messages) + if forked_project.errors.any? + conflict!(forked_project.errors.messages) else - present @forked_project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @forked_project) + present forked_project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, forked_project) end end - # Update an existing project - # - # Parameters: - # id (required) - the id of a project - # name (optional) - name of a project - # path (optional) - path of a project - # description (optional) - short project description - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - visibility level of a project - # public_builds (optional) - # lfs_enabled (optional) - # Example Request - # PUT /projects/:id + desc 'Update an existing project' do + success Entities::Project + end + params do + optional :name, type: String, desc: 'The name of the project' + optional :default_branch, type: String, desc: 'The default branch of the project' + optional :path, type: String, desc: 'The path of the repository' + use :optional_params + at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled, + :wiki_enabled, :builds_enabled, :snippets_enabled, + :shared_runners_enabled, :container_registry_enabled, + :lfs_enabled, :public, :visibility_level, :public_builds, + :request_access_enabled, :only_allow_merge_if_build_succeeds, + :only_allow_merge_if_all_discussions_are_resolved, :path, + :default_branch + end put ':id' do - attrs = attributes_for_keys [:builds_enabled, - :container_registry_enabled, - :default_branch, - :description, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :only_allow_merge_if_build_succeeds, - :path, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) authorize_admin_project + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) authorize! :rename_project, user_project if attrs[:name].present? - if attrs[:visibility_level].present? - authorize! :change_visibility_level, user_project - end + authorize! :change_visibility_level, user_project if attrs[:visibility_level].present? - ::Projects::UpdateService.new(user_project, - current_user, attrs).execute + ::Projects::UpdateService.new(user_project, current_user, attrs).execute if user_project.errors.any? render_validation_error!(user_project) @@ -297,12 +291,9 @@ module API end end - # Archive project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # PUT /projects/:id/archive + desc 'Archive a project' do + success Entities::Project + end post ':id/archive' do authorize!(:archive_project, user_project) @@ -311,12 +302,9 @@ module API present user_project, with: Entities::Project end - # Unarchive project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # PUT /projects/:id/unarchive + desc 'Unarchive a project' do + success Entities::Project + end post ':id/unarchive' do authorize!(:archive_project, user_project) @@ -325,12 +313,9 @@ module API present user_project, with: Entities::Project end - # Star project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # POST /projects/:id/star + desc 'Star a project' do + success Entities::Project + end post ':id/star' do if current_user.starred?(user_project) not_modified! @@ -342,12 +327,9 @@ module API end end - # Unstar project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id/star + desc 'Unstar a project' do + success Entities::Project + end delete ':id/star' do if current_user.starred?(user_project) current_user.toggle_star(user_project) @@ -359,67 +341,51 @@ module API end end - # Remove project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id + desc 'Remove a project' delete ":id" do authorize! :remove_project, user_project ::Projects::DestroyService.new(user_project, current_user, {}).async_execute end - # Mark this project as forked from another - # - # Parameters: - # id: (required) - The ID of the project being marked as a fork - # forked_from_id: (required) - The ID of the project it was forked from - # Example Request: - # POST /projects/:id/fork/:forked_from_id + desc 'Mark this project as forked from another' + params do + requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from' + end post ":id/fork/:forked_from_id" do authenticated_as_admin! + forked_from_project = find_project!(params[:forked_from_id]) - unless forked_from_project.nil? - if user_project.forked_from_project.nil? - user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) - else - render_api_error!("Project already forked", 409) - end + not_found!("Source Project") unless forked_from_project + + if user_project.forked_from_project.nil? + user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) else - not_found!("Source Project") + render_api_error!("Project already forked", 409) end end - # Remove a forked_from relationship - # - # Parameters: - # id: (required) - The ID of the project being marked as a fork - # Example Request: - # DELETE /projects/:id/fork + desc 'Remove a forked_from relationship' delete ":id/fork" do authorize! :remove_fork_project, user_project + if user_project.forked? user_project.forked_project_link.destroy + else + not_modified! end end - # Share project with group - # - # Parameters: - # id (required) - The ID of a project - # group_id (required) - The ID of a group - # group_access (required) - Level of permissions for sharing - # expires_at (optional) - Share expiration date - # - # Example Request: - # POST /projects/:id/share + desc 'Share the project with a group' do + success Entities::ProjectGroupLink + end + params do + requires :group_id, type: Integer, desc: 'The ID of a group' + requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level' + optional :expires_at, type: Date, desc: 'Share expiration date' + end post ":id/share" do authorize! :admin_project, user_project - required_attributes! [:group_id, :group_access] - attrs = attributes_for_keys [:group_id, :group_access, :expires_at] - - group = Group.find_by_id(attrs[:group_id]) + group = Group.find_by_id(params[:group_id]) unless group && can?(current_user, :read_group, group) not_found!('Group') @@ -429,7 +395,7 @@ module API return render_api_error!("The project sharing with group is disabled", 400) end - link = user_project.project_group_links.new(attrs) + link = user_project.project_group_links.new(declared_params(include_missing: false)) if link.save present link, with: Entities::ProjectGroupLink @@ -451,40 +417,26 @@ module API no_content! end - # Upload a file - # - # Parameters: - # id: (required) - The ID of the project - # file: (required) - The file to be uploaded + desc 'Upload a file' + params do + requires :file, type: File, desc: 'The file to be uploaded' + end post ":id/uploads" do ::Projects::UploadService.new(user_project, params[:file]).execute end - # search for projects current_user has access to - # - # Parameters: - # query (required) - A string contained in the project name - # per_page (optional) - number of projects to return per page - # page (optional) - the page to retrieve - # Example Request: - # GET /projects/search/:query - get "/search/:query" do - search_service = Search::GlobalService.new(current_user, search: params[:query]).execute - projects = search_service.objects('projects', params[:page]) - projects = projects.reorder(project_order_by => project_sort) - - present paginate(projects), with: Entities::Project + desc 'Get the users list of a project' do + success Entities::UserBasic + end + params do + optional :search, type: String, desc: 'Return list of users matching the search criteria' + use :pagination end - - # Get a users list - # - # Example Request: - # GET /users get ':id/users' do - @users = User.where(id: user_project.team.users.map(&:id)) - @users = @users.search(params[:search]) if params[:search].present? - @users = paginate @users - present @users, with: Entities::UserBasic + users = User.where(id: user_project.team.users.map(&:id)) + users = users.search(params[:search]) if params[:search].present? + + present paginate(users), with: Entities::UserBasic end end end diff --git a/public/404.html b/public/404.html index 92b7f4da0b9..11b29d09a82 100644 --- a/public/404.html +++ b/public/404.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ </head> <body> + <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" + alt="GitLab Logo" /> <h1> - <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br /> 404 </h1> <div class="container"> diff --git a/public/422.html b/public/422.html index f625f8a33b7..9bd7cb4b7c8 100644 --- a/public/422.html +++ b/public/422.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ </head> <body> + <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" + alt="GitLab Logo" /> <h1> - <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br /> 422 </h1> <div class="container"> diff --git a/public/500.html b/public/500.html index d76c66ba92a..f92e8839f8d 100644 --- a/public/500.html +++ b/public/500.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ </head> <body> + <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" + alt="GitLab Logo" /> <h1> - <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br /> 500 </h1> <div class="container"> diff --git a/public/502.html b/public/502.html index 1a3c7efc769..c2be4f130a9 100644 --- a/public/502.html +++ b/public/502.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ </head> <body> + <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" + alt="GitLab Logo" /> <h1> - <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br /> 502 </h1> <div class="container"> diff --git a/public/503.html b/public/503.html index c1c4e3ffdb8..8850ffce362 100644 --- a/public/503.html +++ b/public/503.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ </head> <body> + <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" + alt="GitLab Logo" /> <h1> - <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br /> 503 </h1> <div class="container"> diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index d9a86346c81..ea2fd90a9b0 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -4,7 +4,7 @@ describe AutocompleteController do let!(:project) { create(:project) } let!(:user) { create(:user) } - context 'users and members' do + context 'GET users' do let!(:user2) { create(:user) } let!(:non_member) { create(:user) } @@ -144,6 +144,15 @@ describe AutocompleteController do it { expect(body).to be_kind_of(Array) } it { expect(body.size).to eq 0 } end + + describe 'GET #users with todo filter' do + it 'gives an array of users' do + get :users, todo_filter: true + + expect(response.status).to eq 200 + expect(body).to be_kind_of(Array) + end + end end context 'author of issuable included' do @@ -180,7 +189,7 @@ describe AutocompleteController do end end - context 'projects' do + context 'GET projects' do let(:authorized_project) { create(:project) } let(:authorized_search_project) { create(:project, name: 'rugged') } diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index ac2a1ba5dff..1735791f644 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -7,26 +7,30 @@ FactoryGirl.define do project factory: :empty_project factory :ci_pipeline_without_jobs do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { YAML.dump({}) } + after(:build) do |pipeline| + allow(pipeline).to receive(:ci_yaml_file) { YAML.dump({}) } end end factory :ci_pipeline_with_one_job do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" } }) } - end - end - - factory :ci_pipeline_with_two_job do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" }, spinach: { script: "ls" } }) } + after(:build) do |pipeline| + allow(pipeline).to receive(:ci_yaml_file) do + YAML.dump({ rspec: { script: "ls" } }) + end end end factory :ci_pipeline do - after(:build) do |commit| - allow(commit).to receive(:ci_yaml_file) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) } + transient { config nil } + + after(:build) do |pipeline, evaluator| + allow(pipeline).to receive(:ci_yaml_file) do + if evaluator.config + YAML.dump(evaluator.config) + else + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end + end end end end diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index d14a1158b67..a6b841c0210 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -73,7 +73,7 @@ describe 'Profile > Preferences', feature: true do expect(page.current_path).to eq starred_dashboard_projects_path end - click_link 'Your Projects' + click_link 'Your projects' expect(page).not_to have_content("You don't have starred projects yet") expect(page.current_path).to eq dashboard_projects_path diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index 35fcef7a712..87cce32d6c6 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Internal Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :internal) } + let(:group) { create(:group, :internal) } let(:project) { create(:project, :internal, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be internal" do @@ -34,75 +21,75 @@ describe 'Internal Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 75a93342628..1d6b3e77c22 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Private Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :private) } + let(:group) { create(:group, :private) } let(:project) { create(:project, :private, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be private" do @@ -34,75 +21,75 @@ describe 'Private Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index 6c5ee93970b..d7d76177269 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Public Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :public) } + let(:group) { create(:group, :public) } let(:project) { create(:project, :public, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be public" do @@ -34,75 +21,75 @@ describe 'Public Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6 index 14e90a9dd1b..5d7c6b2411d 100644 --- a/spec/javascripts/environments/environment_item_spec.js.es6 +++ b/spec/javascripts/environments/environment_item_spec.js.es6 @@ -1,4 +1,5 @@ //= require vue +//= require timeago //= require environments/components/environment_item describe('Environment item', () => { @@ -109,6 +110,8 @@ describe('Environment item', () => { name: 'deploy', build_path: '/root/ci-folders/builds/1279', retry_path: '/root/ci-folders/builds/1279/retry', + created_at: '2016-11-29T18:11:58.430Z', + updated_at: '2016-11-29T18:11:58.430Z', }, manual_actions: [ { @@ -149,6 +152,17 @@ describe('Environment item', () => { ).toContain('#'); }); + it('should render last deployment date', () => { + const timeagoInstance = new timeago(); // eslint-disable-line + const formatedDate = timeagoInstance.format( + environment.last_deployment.deployable.created_at, + ); + + expect( + component.$el.querySelector('.environment-created-date-timeago').textContent, + ).toContain(formatedDate); + }); + describe('With user information', () => { it('should render user avatar with link to profile', () => { expect( diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 58ccd056328..26034cb1c7b 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -570,7 +570,7 @@ describe MergeRequest, models: true do end end - describe '#pipeline' do + describe '#head_pipeline' do describe 'when the source project exists' do it 'returns the latest pipeline' do pipeline = double(:ci_pipeline, ref: 'master') @@ -581,7 +581,7 @@ describe MergeRequest, models: true do with('master', '123abc'). and_return(pipeline) - expect(subject.pipeline).to eq(pipeline) + expect(subject.head_pipeline).to eq(pipeline) end end @@ -589,7 +589,7 @@ describe MergeRequest, models: true do it 'returns nil' do allow(subject).to receive(:source_project).and_return(nil) - expect(subject.pipeline).to be_nil + expect(subject.head_pipeline).to be_nil end end end @@ -857,7 +857,7 @@ describe MergeRequest, models: true do context 'and a failed pipeline is associated' do before do pipeline.update(status: 'failed') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_falsey } @@ -866,7 +866,7 @@ describe MergeRequest, models: true do context 'and a successful pipeline is associated' do before do pipeline.update(status: 'success') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -875,7 +875,7 @@ describe MergeRequest, models: true do context 'and a skipped pipeline is associated' do before do pipeline.update(status: 'skipped') - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -883,7 +883,7 @@ describe MergeRequest, models: true do context 'when no pipeline is associated' do before do - allow(subject).to receive(:pipeline) { nil } + allow(subject).to receive(:head_pipeline) { nil } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -896,7 +896,7 @@ describe MergeRequest, models: true do context 'and a failed pipeline is associated' do before do pipeline.statuses << create(:commit_status, status: 'failed', project: project) - allow(subject).to receive(:pipeline) { pipeline } + allow(subject).to receive(:head_pipeline) { pipeline } end it { expect(subject.mergeable_ci_state?).to be_truthy } @@ -904,7 +904,7 @@ describe MergeRequest, models: true do context 'when no pipeline is associated' do before do - allow(subject).to receive(:pipeline) { nil } + allow(subject).to receive(:head_pipeline) { nil } end it { expect(subject.mergeable_ci_state?).to be_truthy } diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index fe6b875b997..28fbae18de1 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -31,11 +31,22 @@ describe API::API, api: true do expect(json_response['name']).to eq(branch_name) expect(json_response['commit']['id']).to eq(branch_sha) + expect(json_response['merged']).to eq(false) expect(json_response['protected']).to eq(false) expect(json_response['developers_can_push']).to eq(false) expect(json_response['developers_can_merge']).to eq(false) end + context 'on a merged branch' do + it "returns the branch information for a single branch" do + get api("/projects/#{project.id}/repository/branches/merge-test", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq('merge-test') + expect(json_response['merged']).to eq(true) + end + end + it "returns a 403 error if guest" do get api("/projects/#{project.id}/repository/branches", user2) expect(response).to have_http_status(403) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 3ecf3eea5f5..edc985b765b 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -466,7 +466,7 @@ describe API::API, api: true do end it "enables merge when build succeeds if the ci is active" do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) + allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow(pipeline).to receive(:active?).and_return(true) put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e53ee2a4e76..482e81b29a6 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -415,16 +415,7 @@ describe API::API, api: true do not_to change { Project.count } expect(response).to have_http_status(400) - expect(json_response['message']['name']).to eq([ - 'can\'t be blank', - 'is too short (minimum is 0 characters)', - Gitlab::Regex.project_name_regex_message - ]) - expect(json_response['message']['path']).to eq([ - 'can\'t be blank', - 'is too short (minimum is 0 characters)', - Gitlab::Regex.send(:project_path_regex_message) - ]) + expect(json_response['error']).to eq('name is missing') end it 'assigns attributes to project' do @@ -438,6 +429,7 @@ describe API::API, api: true do post api("/projects/user/#{user.id}", admin), project + expect(response).to have_http_status(201) project.each_pair do |k, v| next if %i[has_external_issue_tracker path].include?(k) expect(json_response[k.to_s]).to eq(v) @@ -447,6 +439,8 @@ describe API::API, api: true do it 'sets a project as public' do project = attributes_for(:project, :public) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_truthy expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end @@ -454,6 +448,8 @@ describe API::API, api: true do it 'sets a project as public using :public' do project = attributes_for(:project, { public: true }) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_truthy expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end @@ -461,6 +457,8 @@ describe API::API, api: true do it 'sets a project as internal' do project = attributes_for(:project, :internal) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end @@ -468,6 +466,7 @@ describe API::API, api: true do it 'sets a project as internal overriding :public' do project = attributes_for(:project, :internal, { public: true }) post api("/projects/user/#{user.id}", admin), project + expect(response).to have_http_status(201) expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end @@ -848,7 +847,7 @@ describe API::API, api: true do it 'is idempotent if not forked' do expect(project_fork_target.forked_from_project).to be_nil delete api("/projects/#{project_fork_target.id}/fork", admin) - expect(response).to have_http_status(200) + expect(response).to have_http_status(304) expect(project_fork_target.reload.forked_from_project).to be_nil end end @@ -865,7 +864,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at end.to change { ProjectGroupLink.count }.by(1) - expect(response.status).to eq 201 + expect(response).to have_http_status(201) expect(json_response['group_id']).to eq(group.id) expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER) expect(json_response['expires_at']).to eq(expires_at.to_s) @@ -873,18 +872,18 @@ describe API::API, api: true do it "returns a 400 error when group id is not given" do post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it "returns a 400 error when access level is not given" do post api("/projects/#{project.id}/share", user), group_id: group.id - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it "returns a 400 error when sharing is disabled" do project.namespace.update(share_with_group_lock: true) post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it 'returns a 404 error when user cannot read group' do @@ -892,19 +891,20 @@ describe API::API, api: true do post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 404 + expect(response).to have_http_status(404) end it 'returns a 404 error when group does not exist' do post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 404 + expect(response).to have_http_status(404) end - it "returns a 409 error when wrong params passed" do + it "returns a 400 error when wrong params passed" do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 - expect(response.status).to eq 409 - expect(json_response['message']).to eq 'Group access is not included in the list' + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq 'group_access does not have a valid value' end end @@ -1017,7 +1017,6 @@ describe API::API, api: true do it 'updates visibility_level from public to private' do project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC }) - project_param = { public: false } put api("/projects/#{project3.id}", user), project_param expect(response).to have_http_status(200) diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 705dbb7d1c0..5c90fd9bad9 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -40,7 +40,7 @@ describe 'cycle analytics events' do expect(json_response['events']).not_to be_empty - first_mr_iid = MergeRequest.order(created_at: :desc).pluck(:iid).first.to_s + first_mr_iid = project.merge_requests.order(id: :desc).pluck(:iid).first.to_s expect(json_response['events'].first['iid']).to eq(first_mr_iid) end diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb index c0b7e86b17c..6b33fe66a63 100644 --- a/spec/serializers/analytics_build_entity_spec.rb +++ b/spec/serializers/analytics_build_entity_spec.rb @@ -7,7 +7,9 @@ describe AnalyticsBuildEntity do context 'build with an author' do let(:user) { create(:user) } - let(:build) { create(:ci_build, author: user, started_at: 2.hours.ago, finished_at: 1.hour.ago) } + let(:started_at) { 2.hours.ago } + let(:finished_at) { 1.hour.ago } + let(:build) { create(:ci_build, author: user, started_at: started_at, finished_at: finished_at) } subject { entity.as_json } @@ -31,5 +33,54 @@ describe AnalyticsBuildEntity do it 'contains the duration' do expect(subject[:total_time]).to eq(hours: 1 ) end + + context 'no started at or finished at date' do + let(:started_at) { nil } + let(:finished_at) { nil } + + it 'does not blow up' do + expect{ subject[:date] }.not_to raise_error + end + + it 'shows the right message' do + expect(subject[:date]).to eq('Not started') + end + + it 'shows the right total time' do + expect(subject[:total_time]).to eq({}) + end + end + + context 'no started at date' do + let(:started_at) { nil } + + it 'does not blow up' do + expect{ subject[:date] }.not_to raise_error + end + + it 'shows the right message' do + expect(subject[:date]).to eq('Not started') + end + + it 'shows the right total time' do + expect(subject[:total_time]).to eq({}) + end + end + + context 'no finished at date' do + let(:finished_at) { nil } + + it 'does not blow up' do + expect{ subject[:date] }.not_to raise_error + end + + it 'shows the right message' do + expect(subject[:date]).to eq('about 2 hours ago') + end + + it 'shows the right total time' do + expect(subject[:total_time]).to eq({ hours: 2 }) + end + end end end diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index ff113efd916..ebb11166964 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -1,31 +1,10 @@ require 'spec_helper' describe Ci::ProcessPipelineService, services: true do - let(:pipeline) { create(:ci_pipeline, ref: 'master') } + let(:pipeline) { create(:ci_empty_pipeline, ref: 'master') } let(:user) { create(:user) } - let(:config) { nil } - - before do - allow(pipeline).to receive(:ci_yaml_file).and_return(config) - end describe '#execute' do - def all_builds - pipeline.builds - end - - def builds - all_builds.where.not(status: [:created, :skipped]) - end - - def process_pipeline - described_class.new(pipeline.project, user).execute(pipeline) - end - - def succeed_pending - builds.pending.update_all(status: 'success') - end - context 'start queuing next builds' do before do create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage_idx: 0) @@ -223,10 +202,6 @@ describe Ci::ProcessPipelineService, services: true do pipeline.builds.running_or_pending.each(&:success) expect(manual_actions).to be_many # production and clear cache end - - def manual_actions - pipeline.manual_actions - end end end @@ -282,15 +257,6 @@ describe Ci::ProcessPipelineService, services: true do expect(builds.map(&:status)).to eq(%w[success skipped pending]) end end - - def create_build(name, stage_idx, when_value = nil) - create(:ci_build, - :created, - pipeline: pipeline, - name: name, - stage_idx: stage_idx, - when: when_value) - end end context 'when failed build in the middle stage is retried' do @@ -327,65 +293,92 @@ describe Ci::ProcessPipelineService, services: true do end end - context 'creates a builds from .gitlab-ci.yml' do - let(:config) do - YAML.dump({ - rspec: { - stage: 'test', - script: 'rspec' - }, - rubocop: { - stage: 'test', - script: 'rubocop' - }, - deploy: { - stage: 'deploy', - script: 'deploy' - } - }) + context 'when there are builds that are not created yet' do + let(:pipeline) do + create(:ci_pipeline, config: config) end - # Using stubbed .gitlab-ci.yml created in commit factory - # + let(:config) do + { rspec: { stage: 'test', script: 'rspec' }, + deploy: { stage: 'deploy', script: 'rsync' } } + end before do - stub_ci_pipeline_yaml_file(config) create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage: 'build', stage_idx: 0) create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage: 'build', stage_idx: 0) end - it 'when processing a pipeline' do - # Currently we have two builds with state created + it 'processes the pipeline' do + # Currently we have five builds with state created + # expect(builds.count).to eq(0) expect(all_builds.count).to eq(2) - # Create builds will mark the created as pending - expect(process_pipeline).to be_truthy + # Process builds service will enqueue builds from the first stage. + # + process_pipeline + expect(builds.count).to eq(2) expect(all_builds.count).to eq(2) - # When we builds succeed we will create a rest of pipeline from .gitlab-ci.yml - # We will have 2 succeeded, 2 pending (from stage test), total 5 (one more build from deploy) + # When builds succeed we will enqueue remaining builds. + # + # We will have 2 succeeded, 1 pending (from stage test), total 4 (two + # additional build from `.gitlab-ci.yml`). + # succeed_pending - expect(process_pipeline).to be_truthy + process_pipeline + expect(builds.success.count).to eq(2) - expect(builds.pending.count).to eq(2) - expect(all_builds.count).to eq(5) + expect(builds.pending.count).to eq(1) + expect(all_builds.count).to eq(4) - # When we succeed the 2 pending from stage test, - # We will queue a deploy stage, no new builds will be created + # When pending build succeeds in stage test, we enqueue deploy stage. + # succeed_pending - expect(process_pipeline).to be_truthy + process_pipeline + expect(builds.pending.count).to eq(1) - expect(builds.success.count).to eq(4) - expect(all_builds.count).to eq(5) + expect(builds.success.count).to eq(3) + expect(all_builds.count).to eq(4) - # When we succeed last pending build, we will have a total of 5 succeeded builds, no new builds will be created + # When the last one succeeds we have 4 successful builds. + # succeed_pending - expect(process_pipeline).to be_falsey - expect(builds.success.count).to eq(5) - expect(all_builds.count).to eq(5) + process_pipeline + + expect(builds.success.count).to eq(4) + expect(all_builds.count).to eq(4) end end end + + def all_builds + pipeline.builds + end + + def builds + all_builds.where.not(status: [:created, :skipped]) + end + + def process_pipeline + described_class.new(pipeline.project, user).execute(pipeline) + end + + def succeed_pending + builds.pending.update_all(status: 'success') + end + + def manual_actions + pipeline.manual_actions + end + + def create_build(name, stage_idx, when_value = nil) + create(:ci_build, + :created, + pipeline: pipeline, + name: name, + stage_idx: stage_idx, + when: when_value) + end end diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb index a44312dd363..bb7830c7eea 100644 --- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb +++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb @@ -20,13 +20,19 @@ describe MergeRequests::AddTodoWhenBuildFailsService do let(:todo_service) { TodoService.new } let(:merge_request) do - create(:merge_request, merge_user: user, source_branch: 'master', - target_branch: 'feature', source_project: project, target_project: project, + create(:merge_request, merge_user: user, + source_branch: 'master', + target_branch: 'feature', + source_project: project, + target_project: project, state: 'opened') end before do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) + allow_any_instance_of(MergeRequest) + .to receive(:head_pipeline) + .and_return(pipeline) + allow(service).to receive(:todo_service).and_return(todo_service) end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index c0164138713..963d9573ac4 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -21,7 +21,10 @@ describe MergeRequests::MergeWhenBuildSucceedsService do context 'first time enabling' do before do - allow(merge_request).to receive(:pipeline).and_return(pipeline) + allow(merge_request) + .to receive(:head_pipeline) + .and_return(pipeline) + service.execute(merge_request) end @@ -43,8 +46,12 @@ describe MergeRequests::MergeWhenBuildSucceedsService do let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } before do - allow(mr_merge_if_green_enabled).to receive(:pipeline).and_return(pipeline) - allow(mr_merge_if_green_enabled).to receive(:mergeable?).and_return(true) + allow(mr_merge_if_green_enabled).to receive(:head_pipeline) + .and_return(pipeline) + + allow(mr_merge_if_green_enabled).to receive(:mergeable?) + .and_return(true) + allow(pipeline).to receive(:success?).and_return(true) end @@ -138,9 +145,12 @@ describe MergeRequests::MergeWhenBuildSucceedsService do before do # This behavior of MergeRequest: we instantiate a new object - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_wrap_original do - Ci::Pipeline.find(pipeline.id) - end + # + allow_any_instance_of(MergeRequest) + .to receive(:head_pipeline) + .and_wrap_original do + Ci::Pipeline.find(pipeline.id) + end end it "doesn't merge if any of stages failed" do diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb index 691d7e05f57..ceddb656596 100644 --- a/spec/support/matchers/access_matchers.rb +++ b/spec/support/matchers/access_matchers.rb @@ -7,7 +7,7 @@ module AccessMatchers extend RSpec::Matchers::DSL include Warden::Test::Helpers - def emulate_user(user, project = nil) + def emulate_user(user, membership = nil) case user when :user login_as(create(:user)) @@ -19,16 +19,17 @@ module AccessMatchers login_as(create(:user, external: true)) when User login_as(user) - when :owner - raise ArgumentError, "cannot emulate owner without project" unless project - - login_as(project.owner) - when *Gitlab::Access.sym_options.keys - raise ArgumentError, "cannot emulate user #{user} without project" unless project + when *Gitlab::Access.sym_options_with_owner.keys + raise ArgumentError, "cannot emulate #{user} without membership parent" unless membership role = user - user = create(:user) - project.public_send(:"add_#{role}", user) + + if role == :owner && membership.owner + user = membership.owner + else + user = create(:user) + membership.public_send(:"add_#{role}", user) + end login_as(user) else @@ -47,14 +48,14 @@ module AccessMatchers matcher :be_allowed_for do |user| match do |url| - emulate_user(user, @project) + emulate_user(user, @membership) visit(url) status_code != 404 && current_path != new_user_session_path end - chain :of do |project| - @project = project + chain :of do |membership| + @membership = membership end description { description_for(user, 'allowed') } @@ -62,14 +63,14 @@ module AccessMatchers matcher :be_denied_for do |user| match do |url| - emulate_user(user, @project) + emulate_user(user, @membership) visit(url) status_code == 404 || current_path == new_user_session_path end - chain :of do |project| - @project = project + chain :of do |membership| + @membership = membership end description { description_for(user, 'denied') } |