diff --git a/CHANGELOG b/CHANGELOG index 2d57e013a9e11114de55695f58d6e2cc4a54b010..b0d08d972263c3cdc7e4c2a3ce212ca99f2d06ff 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,11 @@ v 8.5.0 (unreleased) - Update the ExternalIssue regex pattern (Blake Hitchcock) - Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson) - Optimized performance of finding issues to be closed by a merge request + - Add `avatar_url`, `description`, `git_ssh_url`, `git_http_url`, `path_with_namespace` + and `default_branch` in `project` in push, issue, merge-request and note webhooks data (Kirill Zaitsev) + - Deprecate the `ssh_url` in favor of `git_ssh_url` and `http_url` in favor of `git_http_url` + in `project` for push, issue, merge-request and note webhooks data (Kirill Zaitsev) + - Deprecate the `repository` key in push, issue, merge-request and note webhooks data, use `project` instead (Kirill Zaitsev) - API: Expose MergeRequest#merge_status (Andrei Dziahel) - Revert "Add IP check against DNSBLs at account sign-up" - Actually use the `skip_merges` option in Repository#commits (Tony Chu) @@ -49,6 +54,7 @@ v 8.5.0 (unreleased) - Fixed logo animation on Safari (Roman Rott) - Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg) - In seach autocomplete show only groups and projects you are member of + - Don't process cross-reference notes from forks - Fix: init.d script not working on OS X - Faster snippet search - Title for milestones should be unique (Zeger-Jan van de Weg) @@ -56,6 +62,8 @@ v 8.5.0 (unreleased) - Replaces "Create merge request" link with one to the "Merge Request" when one exists - Fix CI builds badge, add a new link to builds badge, deprecate the old one - Fix broken link to project in build notification emails + - Ability to see and sort on vote count from Issues and MR lists + - Fix builds scheduler when first build in stage was allowed to fail v 8.4.4 - Update omniauth-saml gem to 1.4.2 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index d2b13eb644d68d6a2962e3c6288f03ea32854725..ef5e4454454da88572cbefa4d65ef2254b9a5fe6 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.6.4 +0.6.5 diff --git a/Gemfile b/Gemfile index a1c57af4baca9ad98e40c3ed3e281129d4a11119..6dcc9b0dd809244c1869d2362d09de50c8a142be 100644 --- a/Gemfile +++ b/Gemfile @@ -112,7 +112,7 @@ gem 'diffy', '~> 3.0.3' # Application server group :unicorn do - gem "unicorn", '~> 4.8.2' + gem "unicorn", '~> 4.9.0' gem 'unicorn-worker-killer', '~> 0.4.2' end diff --git a/Gemfile.lock b/Gemfile.lock index 718285e166586515f0c50d07495bd78b34c02a5c..cebd79843b152a0fccaa2242493cb683ea76b4bd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,7 +50,7 @@ GEM after_commit_queue (1.3.0) activerecord (>= 3.0) akismet (2.0.0) - allocations (1.0.3) + allocations (1.0.4) annotate (2.6.10) activerecord (>= 3.2, <= 4.3) rake (~> 10.4) @@ -833,7 +833,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.1) - unicorn (4.8.3) + unicorn (4.9.0) kgio (~> 2.6) rack raindrops (~> 0.7) @@ -1034,7 +1034,7 @@ DEPENDENCIES uglifier (~> 2.7.2) underscore-rails (~> 1.8.0) unf (~> 0.1.4) - unicorn (~> 4.8.2) + unicorn (~> 4.9.0) unicorn-worker-killer (~> 0.4.2) version_sorter (~> 2.0.0) virtus (~> 1.0.1) diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee index d17b11234188127ab4e00afd366a36110ce45a36..e52b73f94f6b2308385aefd578ad7e5e071b807d 100644 --- a/app/assets/javascripts/issuable_context.js.coffee +++ b/app/assets/javascripts/issuable_context.js.coffee @@ -15,3 +15,5 @@ class @IssuableContext block.find('.selectbox').show() block.find('.value').hide() block.find('.js-select2').select2("open") + + $(".right-sidebar").niceScroll() diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 8d9a0aae5689d0ada250b967d8ee3bc18960b7fe..12cef6f8ea1c7a6bcbba402b110d655cb66e5d2d 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -117,4 +117,4 @@ body { &.ui_violet { @include gitlab-theme(#9988CC, $theme-violet, #443366, #332255); } -} +} \ No newline at end of file diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 529a43548c80b4b9dec44a0686db48b2e76a1b9f..d93b6ee6733f146c06280d03364e766fd82be961 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -12,6 +12,14 @@ .identifier { color: #5c5d5e; } + + .issue_created_ago, .author_link { + white-space: nowrap; + } + + .issue-meta { + margin-left: 65px + } } .detail-page-description { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 9d5dc42b6cc2685ca7941517b65822959dbc0232..ef62f069dc289d9521940d7d2fd2e248b8b3c270 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -80,6 +80,10 @@ display: inline-block; } + .select2-container span { + margin-top: 0; + } + .issuable-count { } @@ -88,6 +92,10 @@ margin-left: 20px; border-left: 1px solid $border-gray-light; padding-left: 10px; + + &:hover { + color: $gray-darkest; + } } } @@ -192,6 +200,10 @@ .btn { background: $gray-normal; border: 1px solid $border-gray-normal; + &:hover { + background: $gray-dark; + border: 1px solid $border-gray-dark; + } } &.right-sidebar-collapsed { @@ -223,6 +235,19 @@ display: block; margin-top: 0; } + + .btn-clipboard { + border: none; + + &:hover { + background: transparent; + } + + i { + color: #999999; + } + } + } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 8694bd654a745485ab8add2044b2e40448d49417..1cc853dd4f582d1936e77d35774b1893e102c135 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -24,7 +24,7 @@ display: inline-block; } - .issue-no-comments { + .issue-no-comments, .issue-no-votes { opacity: 0.5; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index f033ff15f881194de18202f67a7d5024d4aac43e..6b497cd56edd45a1bb4ffb4b703df2225dfbe7fe 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -163,7 +163,7 @@ display: inline-block; } - .merge-request-no-comments { + .merge-request-no-comments, .merge-request-no-votes { opacity: 0.5; } } @@ -236,4 +236,4 @@ } } } -} \ No newline at end of file +} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 48b1f95acb9a100b8f721252ee044c3fda74666f..2c329b60a19f376bcafe2d7a296f98eea1ac2a77 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -164,7 +164,7 @@ class ApplicationController < ActionController::Base end def git_not_found! - render html: "errors/git_not_found", layout: "errors", status: 404 + render "errors/git_not_found.html", layout: "errors", status: 404 end def method_missing(method_sym, *arguments, &block) diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 21f4d9f44ecf2270dbc83871ab473ffe350db96c..36951b9137264f403b6ba3cc178853266087a683 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -11,8 +11,6 @@ class Projects::CommitController < Projects::ApplicationController before_action :define_show_vars, only: [:show, :builds] def show - return git_not_found! unless @commit - apply_diff_view_cookie! @line_notes = commit.notes.inline @@ -68,6 +66,8 @@ class Projects::CommitController < Projects::ApplicationController end def define_show_vars + return git_not_found! unless commit + if params[:w].to_i == 1 @diffs = commit.diffs({ ignore_whitespace_change: true }) else diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index 07f355c35b1437dec6a33e2098e0c28e59b90cbc..196996f1752459654d0bf7e67484ad500938f14b 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -3,6 +3,7 @@ class Projects::ImportsController < Projects::ApplicationController before_action :authorize_admin_project! before_action :require_no_repo, only: [:new, :create] before_action :redirect_if_progress, only: [:new, :create] + before_action :redirect_if_no_import, only: :show def new end @@ -63,14 +64,19 @@ class Projects::ImportsController < Projects::ApplicationController def require_no_repo if @project.repository_exists? - redirect_to(namespace_project_path(@project.namespace, @project)) + redirect_to namespace_project_path(@project.namespace, @project) end end def redirect_if_progress if @project.import_in_progress? - redirect_to namespace_project_import_path(@project.namespace, @project) && - return + redirect_to namespace_project_import_path(@project.namespace, @project) + end + end + + def redirect_if_no_import + if @project.repository_exists? && @project.no_import? + redirect_to namespace_project_path(@project.namespace, @project) end end end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index ba9aea1c165335c4f1bb898409794e64963500bf..5c7614cfbaf4557928e9de20bd431eea12aa0207 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -11,7 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController end def archive - render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute + RepositoryArchiveCacheWorker.perform_async + headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format])) + head :ok rescue => ex logger.error("#{self.class.name}: #{ex}") return git_not_found! diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 02357e2f23eee9a9ee669c014da3ff2de4da139e..ecefa9b006df1ed2cd4b7a06dee4125509708f52 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -280,76 +280,6 @@ module ApplicationHelper end end - def issuable_link_next(project,issuable) - if project.nil? - nil - elsif current_controller?(:issues) - namespace_project_issue_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid)) - elsif current_controller?(:merge_requests) - namespace_project_merge_request_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid)) - end - end - - def issuable_link_prev(project,issuable) - if project.nil? - nil - elsif current_controller?(:issues) - namespace_project_issue_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid)) - elsif current_controller?(:merge_requests) - namespace_project_merge_request_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid)) - end - end - - def issuable_count(entity, project) - if project.nil? - 0 - elsif current_controller?(:issues) - project.issues.send(entity).count - elsif current_controller?(:merge_requests) - project.merge_requests.send(entity).count - end - end - - def next_issuable_for(project, id) - if project.nil? - nil - elsif current_controller?(:issues) - project.issues.where("id > ?", id).last - elsif current_controller?(:merge_requests) - project.merge_requests.where("id > ?", id).last - end - end - - def has_next_issuable?(project, id) - if project.nil? - nil - elsif current_controller?(:issues) - project.issues.where("id > ?", id).last - elsif current_controller?(:merge_requests) - project.merge_requests.where("id > ?", id).last - end - end - - def prev_issuable_for(project, id) - if project.nil? - nil - elsif current_controller?(:issues) - project.issues.where("id < ?", id).first - elsif current_controller?(:merge_requests) - project.merge_requests.where("id < ?", id).first - end - end - - def has_prev_issuable?(project, id) - if project.nil? - nil - elsif current_controller?(:issues) - project.issues.where("id < ?", id).first - elsif current_controller?(:merge_requests) - project.merge_requests.where("id < ?", id).first - end - end - def state_filters_text_for(entity, project) titles = { opened: "Open" diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index f9bacc8ba45ee102dc82c7c139e7fcdb5335fc16..6a3ab3ea40a444ad06b5830945e48c6ceadd54dc 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -69,7 +69,7 @@ module DiffHelper end def line_comments - @line_comments ||= @line_notes.select(&:active?).group_by(&:line_code) + @line_comments ||= @line_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code) end def organize_comments(type_left, type_right, line_code_left, line_code_right) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..91a3aa371ef541042d35c18a926d1151bd8408ae --- /dev/null +++ b/app/helpers/issuables_helper.rb @@ -0,0 +1,37 @@ +module IssuablesHelper + + def sidebar_gutter_toggle_icon + sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right') + end + + def sidebar_gutter_collapsed_class + "right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}" + end + + def issuables_count(issuable) + base_issuable_scope(issuable).maximum(:iid) + end + + def next_issuable_for(issuable) + base_issuable_scope(issuable).where('iid > ?', issuable.iid).last + end + + def prev_issuable_for(issuable) + base_issuable_scope(issuable).where('iid < ?', issuable.iid).first + end + + private + + def sidebar_gutter_collapsed? + cookies[:collapsed_gutter] == 'true' + end + + def base_issuable_scope(issuable) + issuable.project.send(issuable.class.table_name).send(issuable_state_scope(issuable)) + end + + def issuable_state_scope(issuable) + issuable.open? ? :opened : :closed + end + +end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 75f2ed5e0541fb2b37e709ee932ab37d2cba19a6..29cb753e62cf1dcc8049ea2d974c514fb3b3cf68 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -3,18 +3,6 @@ module NavHelper cookies[:collapsed_nav] == 'true' end - def sidebar_gutter_collapsed_class - if cookies[:collapsed_gutter] == 'true' - "right-sidebar-collapsed" - else - "right-sidebar-expanded" - end - end - - def sidebar_gutter_collapsed? - cookies[:collapsed_gutter] == 'true' - end - def nav_sidebar_class if nav_menu_collapsed? "sidebar-collapsed" @@ -32,9 +20,9 @@ module NavHelper end def page_gutter_class - if current_path?('merge_requests#show') || - current_path?('merge_requests#diffs') || - current_path?('merge_requests#commits') || + if current_path?('merge_requests#show') || + current_path?('merge_requests#diffs') || + current_path?('merge_requests#commits') || current_path?('issues#show') if cookies[:collapsed_gutter] == 'true' "page-gutter right-sidebar-collapsed" diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 241179b0212b4522bae07a594c0d6e1d62e25326..f9026b887dadf819c6779742ebbf47f9974a1d9a 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -11,6 +11,8 @@ module SortingHelper sort_value_largest_repo => sort_title_largest_repo, sort_value_recently_signin => sort_title_recently_signin, sort_value_oldest_signin => sort_title_oldest_signin, + sort_value_downvotes => sort_title_downvotes, + sort_value_upvotes => sort_title_upvotes } end @@ -54,6 +56,14 @@ module SortingHelper 'Oldest sign in' end + def sort_title_downvotes + 'Least popular' + end + + def sort_title_upvotes + 'Most popular' + end + def sort_value_oldest_updated 'updated_asc' end @@ -93,4 +103,12 @@ module SortingHelper def sort_value_oldest_signin 'oldest_sign_in' end + + def sort_value_downvotes + 'downvotes_desc' + end + + def sort_value_upvotes + 'upvotes_desc' + end end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index cf6aa592e2a77c009cbd0e68e6d3e2c6431d768e..e5f089fb8a0976a5e638e488c4df59c1f11b7814 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -69,10 +69,35 @@ module Issuable case method.to_s when 'milestone_due_asc' then order_milestone_due_asc when 'milestone_due_desc' then order_milestone_due_desc + when 'downvotes_desc' then order_downvotes_desc + when 'upvotes_desc' then order_upvotes_desc else order_by(method) end end + + def order_downvotes_desc + order_votes_desc('thumbsdown') + end + + def order_upvotes_desc + order_votes_desc('thumbsup') + end + + def order_votes_desc(award_emoji_name) + issuable_table = self.arel_table + note_table = Note.arel_table + + join_clause = issuable_table.join(note_table, Arel::Nodes::OuterJoin).on( + note_table[:noteable_id].eq(issuable_table[:id]).and( + note_table[:noteable_type].eq(self.name).and( + note_table[:is_award].eq(true).and(note_table[:note].eq(award_emoji_name)) + ) + ) + ).join_sources + + joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC") + end end def today? @@ -129,13 +154,10 @@ module Issuable hook_data = { object_kind: self.class.name.underscore, user: user.hook_attrs, - repository: { - name: project.name, - url: project.url_to_repo, - description: project.description, - homepage: project.web_url - }, - object_attributes: hook_attrs + project: project.hook_attrs, + object_attributes: hook_attrs, + # DEPRECATED + repository: project.hook_attrs.slice(:name, :url, :description, :homepage) } hook_data.merge!(assignee: assignee.hook_attrs) if assignee diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 1be8061e53d1c7547954802a8f85a07887b03882..ea2b0e075f6d5d3ca2be846de86d467eac1f7448 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -137,7 +137,7 @@ class MergeRequest < ActiveRecord::Base scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) } scope :of_projects, ->(ids) { where(target_project_id: ids) } - scope :opened, -> { with_state(:opened) } + scope :opened, -> { with_states(:opened, :reopened) } scope :merged, -> { with_state(:merged) } scope :closed, -> { with_state(:closed) } scope :closed_and_merged, -> { with_states(:closed, :merged) } diff --git a/app/models/project.rb b/app/models/project.rb index f11c6d7c6be5557eb333b6127a112b7c2d3086ce..95ad88c76ae0b71e3d7ad99d508da0ffd29c204e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -342,7 +342,7 @@ class Project < ActiveRecord::Base end def repository - @repository ||= Repository.new(path_with_namespace, nil, self) + @repository ||= Repository.new(path_with_namespace, self) end def commit(id = 'HEAD') @@ -382,6 +382,10 @@ class Project < ActiveRecord::Base external_import? || forked? end + def no_import? + import_status == 'none' + end + def external_import? import_url.present? end @@ -738,11 +742,20 @@ class Project < ActiveRecord::Base def hook_attrs { name: name, - ssh_url: ssh_url_to_repo, - http_url: http_url_to_repo, + description: description, web_url: web_url, + avatar_url: avatar_url, + git_ssh_url: ssh_url_to_repo, + git_http_url: http_url_to_repo, namespace: namespace.name, - visibility_level: visibility_level + visibility_level: visibility_level, + path_with_namespace: path_with_namespace, + default_branch: default_branch, + # Backward compatibility + homepage: web_url, + url: url_to_repo, + ssh_url: ssh_url_to_repo, + http_url: http_url_to_repo } end diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index 3d7e8bbee61a304bf66dddc64569a60c77786fa4..e76d9eca2abadba4bc03d97652440f359628004c 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -112,7 +112,7 @@ class PushoverService < Service priority: priority, title: "#{project.name_with_namespace}", message: message, - url: data[:repository][:homepage], + url: data[:project][:web_url], url_title: "See project #{project.name_with_namespace}" } diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 9f380a382cb40f0acaf855d659ebd034903b26a2..9629c7e1bb932ee48c27865c719697db53526aab 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -136,7 +136,7 @@ class ProjectTeam end def human_max_access(user_id) - Gitlab::Access.options.key max_member_access(user_id) + Gitlab::Access.options_with_owner.key(max_member_access(user_id)) end # This method assumes project and group members are eager loaded for optimal diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index c847eba8d1c9b340fef154c1c4391f7f81f8d7bf..c96e6f0b8ea3f6b954495690620fa520d65e6700 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -123,7 +123,7 @@ class ProjectWiki end def repository - Repository.new(path_with_namespace, default_branch, @project) + Repository.new(path_with_namespace, @project) end def default_branch diff --git a/app/models/repository.rb b/app/models/repository.rb index 7f0047a002e0dc8a3101cbdbb4c887d719177823..73aa67d46eccda329f4981303411b02d7d7dde8c 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -15,7 +15,7 @@ class Repository Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) end - def initialize(path_with_namespace, default_branch = nil, project = nil) + def initialize(path_with_namespace, project) @path_with_namespace = path_with_namespace @project = project end @@ -23,13 +23,11 @@ class Repository def raw_repository return nil unless path_with_namespace - @raw_repository ||= begin - repo = Gitlab::Git::Repository.new(path_to_repo) - repo.autocrlf = :input - repo - rescue Gitlab::Git::Repository::NoRepository - nil - end + @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo) + end + + def update_autocrlf_option + raw_repository.autocrlf = :input if raw_repository.autocrlf != :input end # Return absolute path to repository @@ -40,7 +38,12 @@ class Repository end def exists? - raw_repository + return false unless raw_repository + + raw_repository.rugged + true + rescue Gitlab::Git::Repository::NoRepository + false end def empty? @@ -67,7 +70,7 @@ class Repository end def commit(id = 'HEAD') - return nil unless raw_repository + return nil unless exists? commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Commit.new(commit, @project) if commit commit @@ -238,6 +241,15 @@ class Repository expire_branch_cache(branch_name) end + # Expires _all_ caches, including those that would normally only be expired + # under specific conditions. + def expire_all_caches! + expire_cache + expire_root_ref_cache + expire_emptiness_caches + expire_has_visible_content_cache + end + def expire_branch_cache(branch_name = nil) # When we push to the root branch we have to flush the cache for all other # branches as their statistics are based on the commits relative to the @@ -258,6 +270,14 @@ class Repository @root_ref = nil end + # Expires the cache(s) used to determine if a repository is empty or not. + def expire_emptiness_caches + cache.expire(:empty?) + @empty = nil + + expire_has_visible_content_cache + end + def expire_has_visible_content_cache cache.expire(:has_visible_content?) @has_visible_content = nil @@ -611,6 +631,8 @@ class Repository end def merge_base(first_commit_id, second_commit_id) + first_commit_id = commit(first_commit_id).try(:id) || first_commit_id + second_commit_id = commit(second_commit_id).try(:id) || second_commit_id rugged.merge_base(first_commit_id, second_commit_id) rescue Rugged::ReferenceError nil @@ -674,6 +696,8 @@ class Repository end def commit_with_hooks(current_user, branch) + update_autocrlf_option + oldrev = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + branch was_empty = empty? diff --git a/app/services/archive_repository_service.rb b/app/services/archive_repository_service.rb deleted file mode 100644 index 2160bf13e6d281cbc073e3e827d4bdee8d7558f3..0000000000000000000000000000000000000000 --- a/app/services/archive_repository_service.rb +++ /dev/null @@ -1,23 +0,0 @@ -class ArchiveRepositoryService - attr_reader :project, :ref, :format - - def initialize(project, ref, format) - format ||= 'tar.gz' - @project, @ref, @format = project, ref, format.downcase - end - - def execute(options = {}) - RepositoryArchiveCacheWorker.perform_async - - metadata = project.repository.archive_metadata(ref, storage_path, format) - raise "Repository or ref not found" if metadata.empty? - - metadata - end - - private - - def storage_path - Gitlab.config.gitlab.repository_downloads_path - end -end diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index ad901f2da5ddb731ea8fc196392f44eae294c62c..002f7ba12785aca3a496af1a3c9e8a4f90455526 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -34,6 +34,7 @@ module Ci build = commit.builds.create!(build_attrs) build.execute_hooks + build end end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index e3bf14966c84e011ff59ba161a36eef2ea8161ba..a1711d234ff6d27f8af973ef5567e16815e5cea5 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -1,10 +1,10 @@ -class GitPushService - attr_accessor :project, :user, :push_data, :push_commits +class GitPushService < BaseService + attr_accessor :push_data, :push_commits include Gitlab::CurrentSettings include Gitlab::Access # This method will be called after each git update - # and only if the provided user and project is present in GitLab. + # and only if the provided user and project are present in GitLab. # # All callbacks for post receive action should be placed here. # @@ -15,67 +15,67 @@ class GitPushService # 4. Executes the project's web hooks # 5. Executes the project's services # - def execute(project, user, oldrev, newrev, ref) - @project, @user = project, user - - branch_name = Gitlab::Git.ref_name(ref) - - project.repository.expire_cache(branch_name) - - if push_remove_branch?(ref, newrev) - project.repository.expire_has_visible_content_cache + def execute + @project.repository.expire_cache(branch_name) + if push_remove_branch? + @project.repository.expire_has_visible_content_cache @push_commits = [] - elsif push_to_new_branch?(ref, oldrev) - project.repository.expire_has_visible_content_cache + elsif push_to_new_branch? + @project.repository.expire_has_visible_content_cache # Re-find the pushed commits. - if is_default_branch?(ref) + if is_default_branch? # Initial push to the default branch. Take the full history of that branch as "newly pushed". - @push_commits = project.repository.commits(newrev) - - # Ensure HEAD points to the default branch in case it is not master - project.change_head(branch_name) - - # Set protection on the default branch if configured - if (current_application_settings.default_branch_protection != PROTECTION_NONE) - developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false - project.protected_branches.create({ name: project.default_branch, developers_can_push: developers_can_push }) - end + process_default_branch else # Use the pushed commits that aren't reachable by the default branch # as a heuristic. This may include more commits than are actually pushed, but # that shouldn't matter because we check for existing cross-references later. - @push_commits = project.repository.commits_between(project.default_branch, newrev) + @push_commits = @project.repository.commits_between(@project.default_branch, params[:newrev]) # don't process commits for the initial push to the default branch - process_commit_messages(ref) + process_commit_messages end - elsif push_to_existing_branch?(ref, oldrev) + elsif push_to_existing_branch? # Collect data for this git push - @push_commits = project.repository.commits_between(oldrev, newrev) - process_commit_messages(ref) + @push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev]) + process_commit_messages end - # Update merge requests that may be affected by this push. A new branch # could cause the last commit of a merge request to change. - project.update_merge_requests(oldrev, newrev, ref, @user) + update_merge_requests + end - @push_data = build_push_data(oldrev, newrev, ref) + protected - EventCreateService.new.push(project, user, @push_data) - project.execute_hooks(@push_data.dup, :push_hooks) - project.execute_services(@push_data.dup, :push_hooks) - CreateCommitBuildsService.new.execute(project, @user, @push_data) - ProjectCacheWorker.perform_async(project.id) + def update_merge_requests + @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user) + + EventCreateService.new.push(@project, current_user, build_push_data) + @project.execute_hooks(build_push_data.dup, :push_hooks) + @project.execute_services(build_push_data.dup, :push_hooks) + CreateCommitBuildsService.new.execute(@project, current_user, build_push_data) + ProjectCacheWorker.perform_async(@project.id) end - protected + def process_default_branch + @push_commits = project.repository.commits(params[:newrev]) + + # Ensure HEAD points to the default branch in case it is not master + project.change_head(branch_name) + + # Set protection on the default branch if configured + if (current_application_settings.default_branch_protection != PROTECTION_NONE) + developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false + @project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push }) + end + end # Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched, # close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables. - def process_commit_messages(ref) - is_default_branch = is_default_branch?(ref) + def process_commit_messages + is_default_branch = is_default_branch? authors = Hash.new do |hash, commit| email = commit.author_email @@ -94,7 +94,7 @@ class GitPushService # Close issues if these commits were pushed to the project's default branch and the commit message matches the # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to # a different branch. - closed_issues = commit.closes_issues(user) + closed_issues = commit.closes_issues(current_user) closed_issues.each do |issue| Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit) end @@ -104,34 +104,38 @@ class GitPushService end end - def build_push_data(oldrev, newrev, ref) - Gitlab::PushDataBuilder. - build(project, user, oldrev, newrev, ref, push_commits) + def build_push_data + @push_data ||= Gitlab::PushDataBuilder. + build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits) end - def push_to_existing_branch?(ref, oldrev) + def push_to_existing_branch? # Return if this is not a push to a branch (e.g. new commits) - Gitlab::Git.branch_ref?(ref) && !Gitlab::Git.blank_ref?(oldrev) + Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev]) end - def push_to_new_branch?(ref, oldrev) - Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(oldrev) + def push_to_new_branch? + Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:oldrev]) end - def push_remove_branch?(ref, newrev) - Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(newrev) + def push_remove_branch? + Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:newrev]) end - def push_to_branch?(ref) - Gitlab::Git.branch_ref?(ref) + def push_to_branch? + Gitlab::Git.branch_ref?(params[:ref]) end - def is_default_branch?(ref) - Gitlab::Git.branch_ref?(ref) && - (Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?) + def is_default_branch? + Gitlab::Git.branch_ref?(params[:ref]) && + (Gitlab::Git.ref_name(params[:ref]) == project.default_branch || project.default_branch.nil?) end def commit_user(commit) - commit.author || user + commit.author || current_user + end + + def branch_name + @branch_name ||= Gitlab::Git.ref_name(params[:ref]) end end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 294157b4f0e4edbeaf3c231e0c8e7e72d0e84358..f4dcb142850452cf51b39fa53c69bb1d9a4da5e8 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -16,11 +16,15 @@ module Projects return false unless can?(current_user, :remove_project, project) project.team.truncate - project.repository.expire_cache unless project.empty_repo? repo_path = project.path_with_namespace wiki_path = repo_path + '.wiki' + # Flush the cache for both repositories. This has to be done _before_ + # removing the physical repositories as some expiration code depends on + # Git data (e.g. a list of branch names). + flush_caches(project, wiki_path) + Project.transaction do project.destroy! @@ -70,5 +74,13 @@ module Projects def removal_path(path) "#{path}+#{project.id}#{DELETED_FLAG}" end + + def flush_caches(project, wiki_path) + project.repository.expire_all_caches! if project.repository.exists? + + wiki_repo = Repository.new(wiki_path, project) + + wiki_repo.expire_all_caches! if wiki_repo.exists? + end end end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 1083bcec05414e3f5664f30c2d58f07362dac75c..edced010811b9b9a7fe5a4579751a0d1755d8a09 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -274,12 +274,15 @@ class SystemNoteService # Check if a cross reference to a noteable from a mentioner already exists # # This method is used to prevent multiple notes being created for a mention - # when a issue is updated, for example. + # when a issue is updated, for example. The method also calls notes_for_mentioner + # to check if the mentioner is a commit, and return matches only on commit hash + # instead of project + commit, to avoid repeated mentions from forks. # # noteable - Noteable object being referenced # mentioner - Mentionable object # # Returns Boolean + def self.cross_reference_exists?(noteable, mentioner) # Initial scope should be system notes of this noteable type notes = Note.system.where(noteable_type: noteable.class) @@ -291,14 +294,20 @@ class SystemNoteService notes = notes.where(noteable_id: noteable.id) end - gfm_reference = mentioner.gfm_reference(noteable.project) - notes = notes.where(note: cross_reference_note_content(gfm_reference)) - - notes.count > 0 + notes_for_mentioner(mentioner, noteable, notes).count > 0 end private + def self.notes_for_mentioner(mentioner, noteable, notes) + if mentioner.is_a?(Commit) + notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}") + else + gfm_reference = mentioner.gfm_reference(noteable.project) + notes.where(note: cross_reference_note_content(gfm_reference)) + end + end + def self.create_note(args = {}) Note.create(args.merge(system: true)) end diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index 4e7d66397276efd4090e82acea5fa1944d35ec57..c3efa7727b1c2dc5b412c2f3165e37afaa57bd2e 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -11,7 +11,7 @@ %br - if current_user.can_create_project? You can create up to - %strong= pluralize(current_user.projects_limit, "project") + "." + %strong= pluralize(number_with_delimiter(current_user.projects_limit), "project") + "." - else If you are added to a project, it will be displayed here. @@ -44,7 +44,7 @@ .dashboard-intro-text %p.slead There are - %strong= publicish_project_count + %strong= number_with_delimiter(publicish_project_count) public projects on this server. %br Public projects are an easy way to allow everyone to have read-only access. diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 2c5b8dc4356abc8e57c9735abf8a2db638e2277f..f3bfe0a18b059781d9460ec65af9a87aee196164 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -36,7 +36,7 @@ = render "download", blob: blob - elsif blob.text? - if blob_svg?(blob) - = render "image", blob: sanitize_svg(blob) + = render "image", blob: blob - else = render "text", blob: blob - elsif blob.image? diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml index 51fa91b08e48a1045dc375a6e0ccf841221782f6..113dba5d83264714895175c54cf9088364d93258 100644 --- a/app/views/projects/blob/_image.html.haml +++ b/app/views/projects/blob/_image.html.haml @@ -1,2 +1,9 @@ .file-content.image_file - %img{ src: namespace_project_raw_path(@project.namespace, @project, @id)} + - if blob_svg?(blob) + - # We need to scrub SVG but we cannot do so in the RawController: it would + - # be wrong/strange if RawController modified the data. + - blob.load_all_data!(@repository) + - blob = sanitize_svg(blob) + %img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"} + - else + %img{src: namespace_project_raw_path(@project.namespace, @project, @id)} diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml index 058b71b21f5173efd9c37bd02377dd691d5e7af4..752e92e2e6bd4c97aeaa40b67b61b70afe7dd677 100644 --- a/app/views/projects/diffs/_image.html.haml +++ b/app/views/projects/diffs/_image.html.haml @@ -1,9 +1,11 @@ - diff = diff_file.diff +- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path)) +- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path)) - if diff.renamed_file || diff.new_file || diff.deleted_file .image %span.wrap .frame{class: image_diff_class(diff)} - %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} + %img{src: diff.deleted_file ? old_file_raw_path : file_raw_path} %p.image-info= "#{number_to_human_size file.size}" - else .image @@ -11,7 +13,7 @@ %span.wrap .frame.deleted %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))} - %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} + %img{src: old_file_raw_path} %p.image-info.hide %span.meta-filesize= "#{number_to_human_size old_file.size}" | @@ -23,7 +25,7 @@ %span.wrap .frame.added %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))} - %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} + %img{src: file_raw_path} %p.image-info.hide %span.meta-filesize= "#{number_to_human_size file.size}" | @@ -36,10 +38,10 @@ %div.swipe.view.hide .swipe-frame .frame.deleted - %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} + %img{src: old_file_raw_path} .swipe-wrap .frame.added - %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} + %img{src: file_raw_path} %span.swipe-bar %span.top-handle %span.bottom-handle @@ -47,9 +49,9 @@ %div.onion-skin.view.hide .onion-skin-frame .frame.deleted - %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} + %img{src: old_file_raw_path} .frame.added - %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} + %img{src: file_raw_path} .controls .transparent .drag-track diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index fdcb698747146c7cf9c41a67a3a37128dca7b0f7..042f660077e7fe1f005a2f57815031b7243a3e58 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -119,13 +119,13 @@ .col-sm-offset-2.col-sm-10 %p Get recent application code using the following command: .radio - = f.label :build_allow_git_fetch do + = f.label :build_allow_git_fetch_false do = f.radio_button :build_allow_git_fetch, 'false' %strong git clone %br %span.descr Slower but makes sure you have a clean dir before every build .radio - = f.label :build_allow_git_fetch do + = f.label :build_allow_git_fetch_true do = f.radio_button :build_allow_git_fetch, 'true' %strong git fetch %br diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index f9cf4910df3e11c7f5ed55c32c158c9d28168e85..5b0edcfa978e70eecb524a029f7744100620df88 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -15,6 +15,25 @@ %li = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name") + - upvotes, downvotes = issue.upvotes, issue.downvotes + - if upvotes > 0 || downvotes > 0 + %li + = icon('thumbs-up') + = upvotes + - else + %li{ class: 'issue-no-votes' } + = icon('thumbs-up') + = upvotes + + - if upvotes > 0 || downvotes > 0 + %li + = icon('thumbs-down') + = downvotes + - else + %li{ class: 'issue-no-votes' } + = icon('thumbs-down') + = downvotes + - note_count = issue.notes.user.count - if note_count > 0 %li diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index fe977fd700cbe14ff5251f1c74c3805b6b84bd4e..69a0e2a0c4dcdfac4e34853b0283f4f5d4795df5 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -6,16 +6,6 @@ .issue .detail-page-header - .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} Closed - .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} Open - %span.identifier - Issue ##{@issue.iid} - %span.creator - · - opened by #{link_to_member(@project, @issue.author, size: 24)} - · - = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago') - .pull-right - if can?(current_user, :create_issue, @project) = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New Issue', id: 'new_issue_link' do @@ -29,6 +19,19 @@ = icon('pencil-square-o') Edit + .pull-left + .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} Closed + .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} Open + + .issue-meta + %span.identifier + Issue ##{@issue.iid} + %span.creator + · + by #{link_to_member(@project, @issue.author, size: 24)} + · + = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago') + .issue-details.issuable-details .detail-page-description.content-block %h2.title diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml index a54733883b443698fbaea6c6552d469a6c540431..986d8c220dbbd7ee4530c233949368921c34bdd1 100644 --- a/app/views/projects/issues/update.js.haml +++ b/app/views/projects/issues/update.js.haml @@ -1,3 +1,3 @@ $('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}"; $('aside.right-sidebar').effect('highlight'); -new Issue(); \ No newline at end of file +new IssuableContext(); diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index e25bf917b435f6cf26e2cf1a7346cc0b380ae2b1..b230b3a01102e007913c0eb7daa6458f8acab7c6 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -24,6 +24,25 @@ %li = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name") + - upvotes, downvotes = merge_request.upvotes, merge_request.downvotes + - if upvotes > 0 || downvotes > 0 + %li + = icon('thumbs-up') + = upvotes + - else + %li{ class: 'merge-request-no-votes' } + = icon('thumbs-up') + = upvotes + + - if upvotes > 0 || downvotes > 0 + %li + = icon('thumbs-down') + = downvotes + - else + %li{ class: 'merge-request-no-votes' } + = icon('thumbs-down') + = downvotes + - note_count = merge_request.mr_and_commit_notes.user.count - if note_count > 0 %li diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml index ce5157d69a24026ccfe3de43d9b91f43c1ce42d4..9cce5660e1c509c8dfbcce51614f6a42869bf122 100644 --- a/app/views/projects/merge_requests/update.js.haml +++ b/app/views/projects/merge_requests/update.js.haml @@ -1,3 +1,3 @@ -$('aside.right-sidebar')[0].outerHTML= "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}"; -$('aside.right-sidebar').effect('highlight') -merge_request = new MergeRequest(); +$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}"; +$('aside.right-sidebar').effect('highlight'); +new IssuableContext(); diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index f09ab25276da1d33263420e4ed8fca6e2c6d9758..e3a6a5a68b681a84ea62e7113579f72caef58b50 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -20,3 +20,7 @@ = sort_title_milestone_soon = link_to page_filter_path(sort: sort_value_milestone_later) do = sort_title_milestone_later + = link_to page_filter_path(sort: sort_value_upvotes) do + = sort_title_upvotes + = link_to page_filter_path(sort: sort_value_downvotes) do + = sort_title_downvotes diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index ae96a45453fa896807dc8c71507ce179c1d9a315..a45775f36b51388a49490496e5fe6d71e24f3d66 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -4,21 +4,18 @@ %span.issuable-count.pull-left = issuable.iid of - = issuable_count(:all, @project) + = issuables_count(issuable) %span.pull-right %a.gutter-toggle{href: '#'} - - if sidebar_gutter_collapsed? - = icon('angle-double-left') - - else - = icon('angle-double-right') + = sidebar_gutter_toggle_icon .issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'} - - if has_prev_issuable?(@project, issuable.id) - = link_to 'Prev', issuable_link_prev(@project, issuable), class: 'btn btn-default prev-btn' + - if prev_issuable = prev_issuable_for(issuable) + = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn' - else %a.btn.btn-default.disabled{href: '#'} Prev - - if has_next_issuable?(@project, issuable.id) - = link_to 'Next', issuable_link_next(@project, issuable), class: 'btn btn-default next-btn' + - if next_issuable = next_issuable_for(issuable) + = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn' - else %a.btn.btn-default.disabled{href: '#'} Next @@ -50,7 +47,7 @@ .block.milestone .sidebar-collapsed-icon - = icon('balance-scale') + = icon('clock-o') %span - if issuable.milestone = issuable.milestone.title @@ -118,7 +115,7 @@ - project_ref = cross_project_reference(@project, issuable) .block.project-reference .sidebar-collapsed-icon - = icon('clipboard') + = clipboard_button(clipboard_text: project_ref) .title .cross-project-reference %span diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 00bf9dcd2d583b638f02478b95d5acb450532877..97db5b1d41e3ecc8b7842acd846701305e6f8e26 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -4,7 +4,7 @@ - ci = false unless local_assigns[:ci] == true - skip_namespace = false unless local_assigns[:skip_namespace] == true - css_class = '' unless local_assigns[:css_class] -- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true +- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit - cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2'] diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 994b8e8ed38b015c36489f55198f2b4c74935704..14d7813412e35a69da8cb540461777155c8cb04f 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -38,7 +38,7 @@ class PostReceive if Gitlab::Git.tag_ref?(ref) GitTagPushService.new.execute(project, @user, oldrev, newrev, ref) else - GitPushService.new.execute(project, @user, oldrev, newrev, ref) + GitPushService.new(project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute end end end diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index 2f991c523390af1ab894a067b9a6f3ccd8921cd4..2572b9d6d983a0d840990cb23d05fceee7b67fb0 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -27,6 +27,7 @@ class RepositoryForkWorker return end + project.repository.expire_emptiness_caches project.import_finish end end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index e295a9ddd1425e8895cb20ad6c7f77b7e837e5fb..0b6f746e1185d90ad7c8e80c6eb288ce0e56dc1c 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -18,6 +18,7 @@ class RepositoryImportWorker return end + project.repository.expire_emptiness_caches project.import_finish end end diff --git a/config/application.rb b/config/application.rb index 1e9ec74cdbfa1c865d0f2463cdcd64557438754c..0d596ed22f56ef646b08784ad11f89a227d176b0 100644 --- a/config/application.rb +++ b/config/application.rb @@ -6,6 +6,8 @@ I18n.config.enforce_available_locales = false Bundler.require(:default, Rails.env) module Gitlab + REDIS_CACHE_NAMESPACE = 'cache:gitlab' + class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers @@ -89,7 +91,7 @@ module Gitlab redis_config_hash[:path] = redis_uri.path end - redis_config_hash[:namespace] = 'cache:gitlab' + redis_config_hash[:namespace] = REDIS_CACHE_NAMESPACE redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever config.cache_store = :redis_store, redis_config_hash diff --git a/config/initializers/2_app.rb b/config/initializers/2_app.rb index 35b150c9929bb656e21d89861c0e081d61174ec8..bd74f90e7d205f67894960d194e20091da4d3ae3 100644 --- a/config/initializers/2_app.rb +++ b/config/initializers/2_app.rb @@ -3,6 +3,6 @@ module Gitlab Settings end - VERSION = File.read(Rails.root.join("VERSION")).strip - REVISION = Gitlab::Popen.popen(%W(#{config.git.bin_path} log --pretty=format:%h -n 1)).first.chomp + VERSION = File.read(Rails.root.join("VERSION")).strip.freeze + REVISION = Gitlab::Popen.popen(%W(#{config.git.bin_path} log --pretty=format:%h -n 1)).first.chomp.freeze end diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index d0630b9fa0789ba6daf52511475dd843685cea22..e87899b2d5c59628a3b42d6fe9d0415bbc459678 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -14,6 +14,7 @@ if Rails.env.production? if sentry_enabled Raven.configure do |config| config.dsn = current_application_settings.sentry_dsn + config.release = Gitlab::REVISION end end end diff --git a/config/sidekiq.yml.example b/config/sidekiq.yml.example index c691db67c6c62d86d22659e64d3180ead9d272c1..714bc06cb2417077da463a909d365d11e5abaa74 100644 --- a/config/sidekiq.yml.example +++ b/config/sidekiq.yml.example @@ -1,2 +1,2 @@ --- +--- :concurrency: 5 \ No newline at end of file diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 018d1898594b9d454745d78f579d0a2f848c67da..9e89e6e395ecb33d933d45accd9aafb23382ac1f 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -16,7 +16,7 @@ The API_TOKEN will take the Secure Variable value: `SECURE`. ### Predefined variables (Environment Variables) | Variable | Runner | Description | -|-------------------------|-------------| +|-------------------------|-----|--------| | **CI** | 0.4 | Mark that build is executed in CI environment | | **GITLAB_CI** | all | Mark that build is executed in GitLab CI environment | | **CI_SERVER** | all | Mark that build is executed in CI environment | diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 194c8171bb9cb1e7968d0bbbc9711e3edb27f42a..461a545c4740ccab3524f662593d273e40cdd21c 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -428,8 +428,30 @@ artifacts: - binaries/ ``` -The artifacts will be send after a successful build success to GitLab, and will -be accessible in the GitLab UI to download. +You may want to create artifacts only for tagged releases to avoid filling the +build server storage with temporary build artifacts. + +Create artifacts only for tags (`default-job` will not create artifacts): + +```yaml +default-job: + script: + - mvn test -U + except: + - tags + +release-job: + script: + - mvn package -U + artifacts: + paths: + - target/*.war + only: + - tags +``` + +The artifacts will be sent to GitLab after a successful build and will +be available for download in the GitLab UI. ### cache diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 4e108c17871892255031ea98350bfcbb72ff542b..28dedf3978cd8fb41c72a06fd6114d7ddcbbc9d6 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -15,7 +15,7 @@ or inconsistencies and guard for that. Try to make as little assumptions as poss about the state of the database. Please don't depend on GitLab specific code since it can change in future versions. -If needed copy-paste GitLab code into the migration to make make it forward compatible. +If needed copy-paste GitLab code into the migration to make it forward compatible. ## Comments in the migration diff --git a/doc/install/installation.md b/doc/install/installation.md index 7d3f9d0a2ed67b5bda47863446f880e9530359c4..7f16e6ff5c4345de3470f7bcea8d13e6b1d55fbb 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -182,25 +182,20 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ## 6. Redis -As of this writing, most Debian/Ubuntu distributions ship with Redis 2.2 or -2.4. GitLab requires at least Redis 2.8. +GitLab requires at least Redis 2.8. -Ubuntu users [can use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server) -to install a recent version of Redis. - -The following instructions cover building and installing Redis from scratch: +If you are using Debian 8 or Ubuntu 14.04 and up, then you can simply install +Redis 2.8 with: ```sh -# Build Redis -wget http://download.redis.io/releases/redis-2.8.23.tar.gz -tar xzf redis-2.8.23.tar.gz -cd redis-2.8.23 -make +sudo apt-get install redis-server +``` -# Install Redis -cd utils -sudo ./install_server.sh +If you are using Debian 7 or Ubuntu 12.04, follow the special documentation +on [an alternate Redis installation](redis.md). Once done, follow the rest of +the guide here. +``` # Configure redis to use sockets sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig @@ -224,7 +219,7 @@ if [ -d /etc/tmpfiles.d ]; then fi # Activate the changes to redis.conf -sudo service redis_6379 start +sudo service redis-server restart # Add git to the redis group sudo usermod -aG redis git @@ -358,7 +353,7 @@ GitLab Shell is an SSH access and repository management software developed speci cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout 0.6.4 + sudo -u git -H git checkout 0.6.5 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/install/redis.md b/doc/install/redis.md new file mode 100644 index 0000000000000000000000000000000000000000..4075e6283d0fd7a9aaf9ea00149e0a3c85decb12 --- /dev/null +++ b/doc/install/redis.md @@ -0,0 +1,60 @@ +# Install Redis on old distributions + +GitLab requires at least Redis 2.8. The following guide is for Debian 7 and +Ubuntu 12.04. If you are using Debian 8 or Ubuntu 14.04 and up, follow the +[installation guide](installation.md). + +## Install Redis 2.8 in Debian 7 + +Redis 2.8 is included in the Debian Wheezy [backports] repository. + +1. Edit `/etc/apt/sources.list` and add the following line: + + ``` + deb http://http.debian.net/debian wheezy-backports main + ``` + +1. Update the repositories: + + ``` + sudo apt-get update + ``` + +1. Install `redis-server`: + + ``` + sudo apt-get -t wheezy-backports install redis-server + ``` + +1. Follow the rest of the [installation guide](installation.md). + +## Install Redis 2.8 in Ubuntu 12.04 + +We will [use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server) +to install a recent version of Redis. + +1. Install the PPA repository: + + ``` + sudo add-apt-repository ppa:chris-lea/redis-server + ``` + + Your system will now fetch the PPA's key. This enables your Ubuntu system to + verify that the packages in the PPA have not been interfered with since they + were built. + +1. Update the repositories: + + ``` + sudo apt-get update + ``` + +1. Install `redis-server`: + + ``` + sudo apt-get install redis-server + ``` + +1. Follow the rest of the [installation guide](installation.md). + +[backports]: http://backports.debian.org/Instructions/ "Debian backports website" diff --git a/doc/update/8.3-to-8.4.md b/doc/update/8.3-to-8.4.md index 616b1f58b65618410b9e4a134e6f13f39414e224..269deec7a9c59413e36a8b01a71d0985ae1acfc7 100644 --- a/doc/update/8.3-to-8.4.md +++ b/doc/update/8.3-to-8.4.md @@ -81,27 +81,6 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y git diff origin/8-3-stable:config/gitlab.yml.example origin/8-4-stable:config/gitlab.yml.example ``` -#### Nginx configuration - -GitLab 8.3 introduced major changes in the NGINX configuration. Ensure you're -still up-to-date with the latest changes: - -```sh -# For HTTPS configurations -git diff origin/8-3-stable:lib/support/nginx/gitlab-ssl origin/8-4-stable:lib/support/nginx/gitlab-ssl - -# For HTTP configurations -git diff origin/8-3-stable:lib/support/nginx/gitlab origin/8-4-stable:lib/support/nginx/gitlab -``` - -If you are using Apache instead of NGINX please see the updated [Apache templates]. -Also note that because Apache does not support upstreams behind Unix sockets you -will need to let gitlab-workhorse listen on a TCP port. You can do this -via [/etc/default/gitlab]. - -[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache -[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/lib/support/init.d/gitlab.default.example#L34 - #### Init script We updated the init script for GitLab in order to set a specific PATH for gitlab-workhorse. diff --git a/doc/update/8.4-to-8.5.md b/doc/update/8.4-to-8.5.md index 42b26439848a2b8e19039bb05346bf8718269700..408a17ac348a28fc06ebf4f9fb874efb1a76307c 100644 --- a/doc/update/8.4-to-8.5.md +++ b/doc/update/8.4-to-8.5.md @@ -82,6 +82,32 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y git diff origin/8-4-stable:config/gitlab.yml.example origin/8-5-stable:config/gitlab.yml.example ``` +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +# For HTTPS configurations +git diff origin/8-4-stable:lib/support/nginx/gitlab-ssl origin/8-5-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/8-4-stable:lib/support/nginx/gitlab origin/8-5-stable:lib/support/nginx/gitlab +``` + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-5-stable/lib/support/init.d/gitlab.default.example#L37 + +#### Init script + +Ensure you're still up-to-date with the latest init script changes: + + sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + ### 8. Start application sudo service gitlab start diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index c556597225c9ddaac75e1af6fd19f7d9794f5561..b82306bd1dabb45689dcc9e6328f0036f20c0388 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -1,5 +1,12 @@ # Web hooks +_**Note:** +Starting from GitLab 8.5:_ + +- _the `repository` key is deprecated in favor of the `project` key_ +- _the `project.ssh_url` key is deprecated in favor of the `project.git_ssh_url` key_ +- _the `project.http_url` key is deprecated in favor of the `project.git_http_url` key_ + Project web hooks allow you to trigger an URL if new code is pushed or a new issue is created. You can configure web hooks to listen for specific events like pushes, issues or merge requests. GitLab will send a POST request with data to the web hook URL. @@ -37,8 +44,25 @@ X-Gitlab-Event: Push Hook "user_id": 4, "user_name": "John Smith", "user_email": "john@example.com", + "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80", "project_id": 15, - "repository": { + "project":{ + "name":"Diaspora", + "description":"", + "web_url":"http://example.com/mike/diaspora", + "avatar_url":null, + "git_ssh_url":"git@example.com:mike/diaspora.git", + "git_http_url":"http://example.com/mike/diaspora.git", + "namespace":"Mike", + "visibility_level":0, + "path_with_namespace":"mike/diaspora", + "default_branch":"master", + "homepage":"http://example.com/mike/diaspora", + "url":"git@example.com:mike/diasporadiaspora.git", + "ssh_url":"git@example.com:mike/diaspora.git", + "http_url":"http://example.com/mike/diaspora.git" + }, + "repository":{ "name": "Diaspora", "url": "git@example.com:mike/diasporadiaspora.git", "description": "", @@ -100,8 +124,25 @@ X-Gitlab-Event: Tag Push Hook "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7", "user_id": 1, "user_name": "John Smith", + "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80", "project_id": 1, - "repository": { + "project":{ + "name":"Example", + "description":"", + "web_url":"http://example.com/jsmith/example", + "avatar_url":null, + "git_ssh_url":"git@example.com:jsmith/example.git", + "git_http_url":"http://example.com/jsmith/example.git", + "namespace":"Jsmith", + "visibility_level":0, + "path_with_namespace":"jsmith/example", + "default_branch":"master", + "homepage":"http://example.com/jsmith/example", + "url":"git@example.com:jsmith/example.git", + "ssh_url":"git@example.com:jsmith/example.git", + "http_url":"http://example.com/jsmith/example.git" + }, + "repository":{ "name": "jsmith", "url": "ssh://git@example.com/jsmith/example.git", "description": "", @@ -135,7 +176,23 @@ X-Gitlab-Event: Issue Hook "username": "root", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" }, - "repository": { + "project":{ + "name":"Gitlab Test", + "description":"Aut reprehenderit ut est.", + "web_url":"http://example.com/gitlabhq/gitlab-test", + "avatar_url":null, + "git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git", + "git_http_url":"http://example.com/gitlabhq/gitlab-test.git", + "namespace":"GitlabHQ", + "visibility_level":20, + "path_with_namespace":"gitlabhq/gitlab-test", + "default_branch":"master", + "homepage":"http://example.com/gitlabhq/gitlab-test", + "url":"http://example.com/gitlabhq/gitlab-test.git", + "ssh_url":"git@example.com:gitlabhq/gitlab-test.git", + "http_url":"http://example.com/gitlabhq/gitlab-test.git" + }, + "repository":{ "name": "Gitlab Test", "url": "http://example.com/gitlabhq/gitlab-test.git", "description": "Aut reprehenderit ut est.", @@ -197,9 +254,25 @@ X-Gitlab-Event: Note Hook "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" }, "project_id": 5, - "repository": { + "project":{ + "name":"Gitlab Test", + "description":"Aut reprehenderit ut est.", + "web_url":"http://example.com/gitlabhq/gitlab-test", + "avatar_url":null, + "git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git", + "git_http_url":"http://example.com/gitlabhq/gitlab-test.git", + "namespace":"GitlabHQ", + "visibility_level":20, + "path_with_namespace":"gitlabhq/gitlab-test", + "default_branch":"master", + "homepage":"http://example.com/gitlabhq/gitlab-test", + "url":"http://example.com/gitlabhq/gitlab-test.git", + "ssh_url":"git@example.com:gitlabhq/gitlab-test.git", + "http_url":"http://example.com/gitlabhq/gitlab-test.git" + }, + "repository":{ "name": "Gitlab Test", - "url": "http://localhost/gitlab-org/gitlab-test.git", + "url": "http://example.com/gitlab-org/gitlab-test.git", "description": "Aut reprehenderit ut est.", "homepage": "http://example.com/gitlab-org/gitlab-test" }, @@ -260,9 +333,25 @@ X-Gitlab-Event: Note Hook "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" }, "project_id": 5, - "repository": { + "project":{ + "name":"Gitlab Test", + "description":"Aut reprehenderit ut est.", + "web_url":"http://example.com/gitlab-org/gitlab-test", + "avatar_url":null, + "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git", + "git_http_url":"http://example.com/gitlab-org/gitlab-test.git", + "namespace":"Gitlab Org", + "visibility_level":10, + "path_with_namespace":"gitlab-org/gitlab-test", + "default_branch":"master", + "homepage":"http://example.com/gitlab-org/gitlab-test", + "url":"http://example.com/gitlab-org/gitlab-test.git", + "ssh_url":"git@example.com:gitlab-org/gitlab-test.git", + "http_url":"http://example.com/gitlab-org/gitlab-test.git" + }, + "repository":{ "name": "Gitlab Test", - "url": "http://example.com/gitlab-org/gitlab-test.git", + "url": "http://localhost/gitlab-org/gitlab-test.git", "description": "Aut reprehenderit ut est.", "homepage": "http://example.com/gitlab-org/gitlab-test" }, @@ -300,21 +389,37 @@ X-Gitlab-Event: Note Hook "description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.", "position": 0, "locked_at": null, - "source": { - "name": "Gitlab Test", - "ssh_url": "git@example.com:gitlab-org/gitlab-test.git", - "http_url": "http://example.com/gitlab-org/gitlab-test.git", - "web_url": "http://example.com/gitlab-org/gitlab-test", - "namespace": "Gitlab Org", - "visibility_level": 10 + "source":{ + "name":"Gitlab Test", + "description":"Aut reprehenderit ut est.", + "web_url":"http://example.com/gitlab-org/gitlab-test", + "avatar_url":null, + "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git", + "git_http_url":"http://example.com/gitlab-org/gitlab-test.git", + "namespace":"Gitlab Org", + "visibility_level":10, + "path_with_namespace":"gitlab-org/gitlab-test", + "default_branch":"master", + "homepage":"http://example.com/gitlab-org/gitlab-test", + "url":"http://example.com/gitlab-org/gitlab-test.git", + "ssh_url":"git@example.com:gitlab-org/gitlab-test.git", + "http_url":"http://example.com/gitlab-org/gitlab-test.git" }, "target": { - "name": "Gitlab Test", - "ssh_url": "git@example.com:gitlab-org/gitlab-test.git", - "http_url": "http://example.com/gitlab-org/gitlab-test.git", - "web_url": "http://example.com/gitlab-org/gitlab-test", - "namespace": "Gitlab Org", - "visibility_level": 10 + "name":"Gitlab Test", + "description":"Aut reprehenderit ut est.", + "web_url":"http://example.com/gitlab-org/gitlab-test", + "avatar_url":null, + "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git", + "git_http_url":"http://example.com/gitlab-org/gitlab-test.git", + "namespace":"Gitlab Org", + "visibility_level":10, + "path_with_namespace":"gitlab-org/gitlab-test", + "default_branch":"master", + "homepage":"http://example.com/gitlab-org/gitlab-test", + "url":"http://example.com/gitlab-org/gitlab-test.git", + "ssh_url":"git@example.com:gitlab-org/gitlab-test.git", + "http_url":"http://example.com/gitlab-org/gitlab-test.git" }, "last_commit": { "id": "562e173be03b8ff2efb05345d12df18815438a4b", @@ -355,11 +460,27 @@ X-Gitlab-Event: Note Hook "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" }, "project_id": 5, - "repository": { - "name": "Gitlab Test", - "url": "http://example.com/gitlab-org/gitlab-test.git", - "description": "Aut reprehenderit ut est.", - "homepage": "http://example.com/gitlab-org/gitlab-test" + "project":{ + "name":"Gitlab Test", + "description":"Aut reprehenderit ut est.", + "web_url":"http://example.com/gitlab-org/gitlab-test", + "avatar_url":null, + "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git", + "git_http_url":"http://example.com/gitlab-org/gitlab-test.git", + "namespace":"Gitlab Org", + "visibility_level":10, + "path_with_namespace":"gitlab-org/gitlab-test", + "default_branch":"master", + "homepage":"http://example.com/gitlab-org/gitlab-test", + "url":"http://example.com/gitlab-org/gitlab-test.git", + "ssh_url":"git@example.com:gitlab-org/gitlab-test.git", + "http_url":"http://example.com/gitlab-org/gitlab-test.git" + }, + "repository":{ + "name":"diaspora", + "url":"git@example.com:mike/diasporadiaspora.git", + "description":"", + "homepage":"http://example.com/mike/diaspora" }, "object_attributes": { "id": 1241, @@ -397,7 +518,6 @@ X-Gitlab-Event: Note Hook ### Comment on code snippet - **Request header**: ``` @@ -415,11 +535,27 @@ X-Gitlab-Event: Note Hook "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" }, "project_id": 5, - "repository": { - "name": "Gitlab Test", - "url": "http://example.com/gitlab-org/gitlab-test.git", - "description": "Aut reprehenderit ut est.", - "homepage": "http://example.com/gitlab-org/gitlab-test" + "project":{ + "name":"Gitlab Test", + "description":"Aut reprehenderit ut est.", + "web_url":"http://example.com/gitlab-org/gitlab-test", + "avatar_url":null, + "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git", + "git_http_url":"http://example.com/gitlab-org/gitlab-test.git", + "namespace":"Gitlab Org", + "visibility_level":10, + "path_with_namespace":"gitlab-org/gitlab-test", + "default_branch":"master", + "homepage":"http://example.com/gitlab-org/gitlab-test", + "url":"http://example.com/gitlab-org/gitlab-test.git", + "ssh_url":"git@example.com:gitlab-org/gitlab-test.git", + "http_url":"http://example.com/gitlab-org/gitlab-test.git" + }, + "repository":{ + "name":"Gitlab Test", + "url":"http://example.com/gitlab-org/gitlab-test.git", + "description":"Aut reprehenderit ut est.", + "homepage":"http://example.com/gitlab-org/gitlab-test" }, "object_attributes": { "id": 1245, @@ -491,21 +627,37 @@ X-Gitlab-Event: Merge Request Hook "target_project_id": 14, "iid": 1, "description": "", - "source": { - "name": "awesome_project", - "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git", - "http_url": "http://example.com/awesome_space/awesome_project.git", - "web_url": "http://example.com/awesome_space/awesome_project", - "visibility_level": 20, - "namespace": "awesome_space" + "source":{ + "name":"Awesome Project", + "description":"Aut reprehenderit ut est.", + "web_url":"http://example.com/awesome_space/awesome_project", + "avatar_url":null, + "git_ssh_url":"git@example.com:awesome_space/awesome_project.git", + "git_http_url":"http://example.com/awesome_space/awesome_project.git", + "namespace":"Awesome Space", + "visibility_level":20, + "path_with_namespace":"awesome_space/awesome_project", + "default_branch":"master", + "homepage":"http://example.com/awesome_space/awesome_project", + "url":"http://example.com/awesome_space/awesome_project.git", + "ssh_url":"git@example.com:awesome_space/awesome_project.git", + "http_url":"http://example.com/awesome_space/awesome_project.git" }, "target": { - "name": "awesome_project", - "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git", - "http_url": "http://example.com/awesome_space/awesome_project.git", - "web_url": "http://example.com/awesome_space/awesome_project", - "visibility_level": 20, - "namespace": "awesome_space" + "name":"Awesome Project", + "description":"Aut reprehenderit ut est.", + "web_url":"http://example.com/awesome_space/awesome_project", + "avatar_url":null, + "git_ssh_url":"git@example.com:awesome_space/awesome_project.git", + "git_http_url":"http://example.com/awesome_space/awesome_project.git", + "namespace":"Awesome Space", + "visibility_level":20, + "path_with_namespace":"awesome_space/awesome_project", + "default_branch":"master", + "homepage":"http://example.com/awesome_space/awesome_project", + "url":"http://example.com/awesome_space/awesome_project.git", + "ssh_url":"git@example.com:awesome_space/awesome_project.git", + "http_url":"http://example.com/awesome_space/awesome_project.git" }, "last_commit": { "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", diff --git a/features/project/fork.feature b/features/project/fork.feature index 12695204e4718a2458022592386e792f53a2e878..ca3f2771aa50a85bf63d5fa948e22eb0050f2cda 100644 --- a/features/project/fork.feature +++ b/features/project/fork.feature @@ -32,6 +32,13 @@ Feature: Project Fork And I visit the forks page of the "Shop" project Then I should see my fork on the list + Scenario: Viewing forks of a Project that has no repo + Given I click link "Fork" + When I fork to my namespace + And I make forked repo invalid + And I visit the forks page of the "Shop" project + Then I should see my fork on the list + Scenario: Viewing private forks of a Project Given There is an existent fork of the "Shop" project And I click link "Fork" diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index 0b3d03aa2a58fdcbbd2c7f9b9bff63ab3a3b319b..89af58dcef351c175a1e3ed527ec48b34360a910 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -25,9 +25,16 @@ Feature: Project Issues Scenario: I visit issue page Given I click link "Release 0.4" Then I should see issue "Release 0.4" + And I should see "1 of 2" in the sidebar + + Scenario: I navigate between issues + Given I click link "Release 0.4" + Then I click link "Next" in the sidebar + Then I should see issue "Tweet control" + And I should see "2 of 2" in the sidebar @javascript - Scenario: I visit issue page + Scenario: I filter by author Given I add a user to project "Shop" And I click "author" dropdown Then I see current user as the first user @@ -81,6 +88,16 @@ Feature: Project Issues And I visit dashboard merge requests page Then The list should be sorted by "Oldest updated" + @javascript + Scenario: Sort issues by upvotes/downvotes + Given project "Shop" have "Bugfix" open issue + And issue "Release 0.4" have 2 upvotes and 1 downvote + And issue "Tweet control" have 1 upvote and 2 downvotes + And I sort the list by "Most popular" + Then The list should be sorted by "Most popular" + And I sort the list by "Least popular" + Then The list should be sorted by "Least popular" + @javascript Scenario: I search issue Given I fill in issue search with "Re" diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index ca1ee6b3c2b8935475870b82de4e4887f330173c..495f25f28e74164feec2ce5f3c430fdf4a9a74c8 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -39,6 +39,7 @@ Feature: Project Merge Requests Scenario: I visit merge request page Given I click link "Bug NS-04" Then I should see merge request "Bug NS-04" + And I should see "1 of 1" in the sidebar Scenario: I close merge request page Given I click link "Bug NS-04" @@ -106,6 +107,17 @@ Feature: Project Merge Requests And I visit dashboard merge requests page Then The list should be sorted by "Oldest updated" + @javascript + Scenario: Sort merge requests by upvotes/downvotes + Given project "Shop" have "Bug NS-05" open merge request with diffs inside + And project "Shop" have "Bug NS-06" open merge request + And merge request "Bug NS-04" have 2 upvotes and 1 downvote + And merge request "Bug NS-06" have 1 upvote and 2 downvotes + And I sort the list by "Most popular" + Then The list should be sorted by "Most popular" + And I sort the list by "Least popular" + Then The list should be sorted by "Least popular" + @javascript Scenario: Visiting Merge Requests after commenting on diffs Given project "Shop" have "Bug NS-05" open merge request with diffs inside diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb index 5810276ced3eb88b31334ecdab5ef7e550b9ff61..527f7853da9f093a2ea9f1ddce752ec403f7a534 100644 --- a/features/steps/project/fork.rb +++ b/features/steps/project/fork.rb @@ -62,6 +62,12 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps end end + step 'I make forked repo invalid' do + project = @user.fork_of(@project) + project.path = 'test-crappy-path' + project.save! + end + step 'There is an existent fork of the "Shop" project' do user = create(:user, name: 'Mike') @forked_project = Projects::ForkService.new(@project, user).execute diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index 8b9aa6aabfad30cee664acabbe69cffbd3b0995e..93cf608cc624bf09b0d44d0454cc04c66107b776 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -46,6 +46,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'I have award added' do + sleep 0.2 + page.within '.awards' do expect(page).to have_selector '.award' expect(page.find('.award.active .counter')).to have_content '1' diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index d556b73f9fdeacb4711281836754f333268484b2..54d64bacc52860c531a12658d8d28a9670c66f10 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -54,6 +54,10 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps expect(page).to have_content "Release 0.4" end + step 'I should see issue "Tweet control"' do + expect(page).to have_content "Tweet control" + end + step 'I click link "New Issue"' do click_link "New Issue" end @@ -170,6 +174,13 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps author: project.users.first) end + step 'project "Shop" have "Bugfix" open issue' do + create(:issue, + title: "Bugfix", + project: project, + author: project.users.first) + end + step 'project "Shop" have "Release 0.3" closed issue' do create(:closed_issue, title: "Release 0.3", @@ -177,6 +188,56 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps author: project.users.first) end + step 'issue "Release 0.4" have 2 upvotes and 1 downvote' do + issue = Issue.find_by(title: 'Release 0.4') + create_list(:upvote_note, 2, project: project, noteable: issue) + create(:downvote_note, project: project, noteable: issue) + end + + step 'issue "Tweet control" have 1 upvote and 2 downvotes' do + issue = Issue.find_by(title: 'Tweet control') + create(:upvote_note, project: project, noteable: issue) + create_list(:downvote_note, 2, project: project, noteable: issue) + end + + step 'The list should be sorted by "Least popular"' do + page.within '.issues-list' do + page.within 'li.issue:nth-child(1)' do + expect(page).to have_content 'Tweet control' + expect(page).to have_content '1 2' + end + + page.within 'li.issue:nth-child(2)' do + expect(page).to have_content 'Release 0.4' + expect(page).to have_content '2 1' + end + + page.within 'li.issue:nth-child(3)' do + expect(page).to have_content 'Bugfix' + expect(page).to have_content '0 0' + end + end + end + + step 'The list should be sorted by "Most popular"' do + page.within '.issues-list' do + page.within 'li.issue:nth-child(1)' do + expect(page).to have_content 'Release 0.4' + expect(page).to have_content '2 1' + end + + page.within 'li.issue:nth-child(2)' do + expect(page).to have_content 'Tweet control' + expect(page).to have_content '1 2' + end + + page.within 'li.issue:nth-child(3)' do + expect(page).to have_content 'Bugfix' + expect(page).to have_content '0 0' + end + end + end + step 'empty project "Empty Project"' do create :empty_project, name: 'Empty Project', namespace: @user.namespace end @@ -301,4 +362,5 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps def filter_issue(text) fill_in 'issue_search', with: text end + end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 337893e6209fa4bf7f55fd29a3cef13c4967a0b8..2160d8645fdc9921834eb51c113259d7b6585d94 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -138,6 +138,56 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps author: project.users.first) end + step 'merge request "Bug NS-04" have 2 upvotes and 1 downvote' do + merge_request = MergeRequest.find_by(title: 'Bug NS-04') + create_list(:upvote_note, 2, project: project, noteable: merge_request) + create(:downvote_note, project: project, noteable: merge_request) + end + + step 'merge request "Bug NS-06" have 1 upvote and 2 downvotes' do + merge_request = MergeRequest.find_by(title: 'Bug NS-06') + create(:upvote_note, project: project, noteable: merge_request) + create_list(:downvote_note, 2, project: project, noteable: merge_request) + end + + step 'The list should be sorted by "Least popular"' do + page.within '.mr-list' do + page.within 'li.merge-request:nth-child(1)' do + expect(page).to have_content 'Bug NS-06' + expect(page).to have_content '1 2' + end + + page.within 'li.merge-request:nth-child(2)' do + expect(page).to have_content 'Bug NS-04' + expect(page).to have_content '2 1' + end + + page.within 'li.merge-request:nth-child(3)' do + expect(page).to have_content 'Bug NS-05' + expect(page).to have_content '0 0' + end + end + end + + step 'The list should be sorted by "Most popular"' do + page.within '.mr-list' do + page.within 'li.merge-request:nth-child(1)' do + expect(page).to have_content 'Bug NS-04' + expect(page).to have_content '2 1' + end + + page.within 'li.merge-request:nth-child(2)' do + expect(page).to have_content 'Bug NS-06' + expect(page).to have_content '1 2' + end + + page.within 'li.merge-request:nth-child(3)' do + expect(page).to have_content 'Bug NS-05' + expect(page).to have_content '0 0' + end + end + end + step 'I click on the Changes tab' do page.within '.merge-request-tabs' do click_link 'Changes' diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index 25c2b476f43b0ed21488b2435ea14a4832710edd..ae10c6069a93e16b21aa057ae83f78c68d6a43b8 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -113,12 +113,46 @@ module SharedIssuable end end + step 'I sort the list by "Least popular"' do + find('button.dropdown-toggle.btn').click + + page.within('ul.dropdown-menu.dropdown-menu-align-right li') do + click_link 'Least popular' + end + end + + step 'I sort the list by "Most popular"' do + find('button.dropdown-toggle.btn').click + + page.within('ul.dropdown-menu.dropdown-menu-align-right li') do + click_link 'Most popular' + end + end + step 'The list should be sorted by "Oldest updated"' do page.within('div.dropdown.inline.prepend-left-10') do expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated') end end + step 'I should see "1 of 1" in the sidebar' do + expect_sidebar_content('1 of 1') + end + + step 'I should see "1 of 2" in the sidebar' do + expect_sidebar_content('1 of 2') + end + + step 'I should see "2 of 2" in the sidebar' do + expect_sidebar_content('2 of 2') + end + + step 'I click link "Next" in the sidebar' do + page.within '.issuable-sidebar' do + click_link 'Next' + end + end + def create_issuable_for_project(project_name:, title:, type: :issue) project = Project.find_by(name: project_name) @@ -159,4 +193,10 @@ module SharedIssuable expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}") end + def expect_sidebar_content(content) + page.within '.issuable-sidebar' do + expect(page).to have_content content + end + end + end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index c95d2d2001d244564bcb7b75c88773079c8fd490..0d0f0d4616d3d8f29d341cae1e10faf64cdfac2f 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -98,11 +98,8 @@ module API authorize! :download_code, user_project begin - ArchiveRepositoryService.new( - user_project, - params[:sha], - params[:format] - ).execute + RepositoryArchiveCacheWorker.perform_async + header *Gitlab::Workhorse.send_git_archive(user_project, params[:sha], params[:format]) rescue not_found!('File') end diff --git a/lib/ci/status.rb b/lib/ci/status.rb index c02b3b8f3e4c6666051edbacae4e0a7620de187e..3fb1fe29494adc79d6780630ba82ae552b04dce5 100644 --- a/lib/ci/status.rb +++ b/lib/ci/status.rb @@ -1,11 +1,9 @@ module Ci class Status def self.get_status(statuses) - statuses.reject! { |status| status.try(&:allow_failure?) } - if statuses.none? 'skipped' - elsif statuses.all?(&:success?) + elsif statuses.all? { |status| status.success? || status.ignored? } 'success' elsif statuses.all?(&:pending?) 'pending' diff --git a/lib/gitlab/note_data_builder.rb b/lib/gitlab/note_data_builder.rb index ea6b0ee796dd86a8a01b2c3df3b24c6d501a268e..71cf6a0d886270600b571357493630e8c4ea2b81 100644 --- a/lib/gitlab/note_data_builder.rb +++ b/lib/gitlab/note_data_builder.rb @@ -53,13 +53,10 @@ module Gitlab object_kind: "note", user: user.hook_attrs, project_id: project.id, - repository: { - name: project.name, - url: project.url_to_repo, - description: project.description, - homepage: project.web_url, - }, - object_attributes: note.hook_attrs + project: project.hook_attrs, + object_attributes: note.hook_attrs, + # DEPRECATED + repository: project.hook_attrs.slice(:name, :url, :description, :homepage) } base_data[:object_attributes][:url] = diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb index 4f9cdef3869e5b53743467a97e83c0ef07174bb2..da1c15fef6193acc4e46a9fb14d70804947e2c4b 100644 --- a/lib/gitlab/push_data_builder.rb +++ b/lib/gitlab/push_data_builder.rb @@ -22,6 +22,8 @@ module Gitlab # } # def build(project, user, oldrev, newrev, ref, commits = [], message = nil) + commits = Array(commits) + # Total commits count commits_count = commits.size @@ -47,18 +49,14 @@ module Gitlab user_id: user.id, user_name: user.name, user_email: user.email, + user_avatar: user.avatar_url, project_id: project.id, - repository: { - name: project.name, - url: project.url_to_repo, - description: project.description, - homepage: project.web_url, - git_http_url: project.http_url_to_repo, - git_ssh_url: project.ssh_url_to_repo, - visibility_level: project.visibility_level - }, + project: project.hook_attrs, commits: commit_attrs, - total_commits_count: commits_count + total_commits_count: commits_count, + # DEPRECATED + repository: project.hook_attrs.slice(:name, :url, :description, :homepage, + :git_http_url, :git_ssh_url, :visibility_level) } data diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index a23120a41769cdb888d1bf49cb3d55974378ba06..c3ddd4c268070be05704c6ddafcff041d7a05b43 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -3,19 +3,38 @@ require 'json' module Gitlab class Workhorse + SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data' + class << self def send_git_blob(repository, blob) - params_hash = { + params = { 'RepoPath' => repository.path_to_repo, 'BlobId' => blob.id, } - params = Base64.urlsafe_encode64(JSON.dump(params_hash)) [ - 'Gitlab-Workhorse-Send-Data', - "git-blob:#{params}", + SEND_DATA_HEADER, + "git-blob:#{encode(params)}", ] end + + def send_git_archive(project, ref, format) + format ||= 'tar.gz' + format.downcase! + params = project.repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format) + raise "Repository or ref not found" if params.empty? + + [ + SEND_DATA_HEADER, + "git-archive:#{encode(params)}", + ] + end + + protected + + def encode(hash) + Base64.urlsafe_encode64(JSON.dump(hash)) + end end end end diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake index 1728dda72cf3e7657e66ba7dbe016666d3d3dae2..b262ea898d59a943af0ccafb3e2f11af322af47a 100644 --- a/lib/tasks/cache.rake +++ b/lib/tasks/cache.rake @@ -1,11 +1,22 @@ namespace :cache do + CLEAR_BATCH_SIZE = 1000 + REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan + desc "GitLab | Clear redis cache" task :clear => :environment do - # Hack into Rails.cache until https://github.com/redis-store/redis-store/pull/225 - # is accepted (I hope) and we can update the redis-store gem. redis_store = Rails.cache.instance_variable_get(:@data) - redis_store.keys.each_slice(1000) do |key_slice| - redis_store.del(*key_slice) + cursor = [REDIS_SCAN_START_STOP, []] + loop do + cursor = redis_store.scan( + cursor.first, + match: "#{Gitlab::REDIS_CACHE_NAMESPACE}*", + count: CLEAR_BATCH_SIZE + ) + + keys = cursor.last + redis_store.del(*keys) if keys.any? + + break if cursor.first == REDIS_SCAN_START_STOP end end end diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 54d95cd62a5b6b1d37b5d9080c48ef78e71a4507..81099cb8ba96ccfa07d9de33ca9dd27d48af80ba 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -16,7 +16,6 @@ namespace :gitlab do check_git_config check_database_config_exists - check_database_is_not_sqlite check_migrations_are_up check_orphaned_group_members check_gitlab_config_exists diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..438e776ec4b9b9808e8ca0b8ed663db6644ee959 --- /dev/null +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +describe Projects::CommitController do + describe 'GET show' do + let(:project) { create(:project) } + + before do + user = create(:user) + project.team << [user, :master] + + sign_in(user) + end + + context 'with valid id' do + it 'responds with 200' do + go id: project.commit.id + + expect(response).to be_ok + end + end + + context 'with invalid id' do + it 'responds with 404' do + go id: project.commit.id.reverse + + expect(response).to be_not_found + end + end + + def go(id:) + get :show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: id + end + end +end diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 85d1d1e052475c481f8114e8e48b066dd189502f..0147bd2b9538fad25d74a36b7548e240e76e93ad 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -104,6 +104,18 @@ describe Projects::ImportsController do end end end + + context 'when import never happened' do + before do + project.update_attribute(:import_status, :none) + end + + it 'redirects to namespace_project_path' do + get :show, namespace_id: project.namespace.to_param, project_id: project.to_param + + expect(response).to redirect_to namespace_project_path(project.namespace, project) + end + end end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 9450a389d814ee9b0dcba91d0b2615a6f9cb4e5e..e82fe26c7a62e701ca30de46d81fd0081ad17298 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -123,6 +123,40 @@ describe Projects::MergeRequestsController do end end + describe 'GET #index' do + def get_merge_requests + get :index, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + state: 'opened' + end + + context 'when filtering by opened state' do + + context 'with opened merge requests' do + it 'should list those merge requests' do + get_merge_requests + + expect(assigns(:merge_requests)).to include(merge_request) + end + end + + context 'with reopened merge requests' do + before do + merge_request.close! + merge_request.reopen! + end + + it 'should list those merge requests' do + get_merge_requests + + expect(assigns(:merge_requests)).to include(merge_request) + end + end + + end + end + describe 'GET diffs' do def go(format: 'html') get :diffs, diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 18a30033ed8ed73dd048fc155bd00cb22957d174..09ec4f18f9d2394dc2276e6fe1d603b121d12c1c 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -8,15 +8,10 @@ describe Projects::RepositoriesController do before do sign_in(user) project.team << [user, :developer] - - allow(ArchiveRepositoryService).to receive(:new).and_return(service) end - let(:service) { ArchiveRepositoryService.new(project, "master", "zip") } - - it "executes ArchiveRepositoryService" do - expect(ArchiveRepositoryService).to receive(:new).with(project, "master", "zip") - expect(service).to receive(:execute) + it "uses Gitlab::Workhorse" do + expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip") get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" end @@ -24,7 +19,7 @@ describe Projects::RepositoriesController do context "when the service raises an error" do before do - allow(service).to receive(:execute).and_raise("Archive failed") + allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed") end it "renders Not Found" do diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index c1b6ecd329ada0b4d966cfb6e2a539f88d446d2b..1243647a78dc0a199685df256bc6a337dd4923ce 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -16,10 +16,30 @@ FactoryGirl.define do commit factory: :ci_commit + trait :success do + status 'success' + end + + trait :failed do + status 'failed' + end + trait :canceled do status 'canceled' end + trait :running do + status 'running' + end + + trait :pending do + status 'pending' + end + + trait :allowed_to_fail do + allow_failure true + end + after(:build) do |build, evaluator| build.project = build.commit.project end diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 35a20adeef3bd01871a4bc2b684f95338310dd95..32c202891d83ce9c0db8a8814165c74ce4d7c460 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -34,6 +34,8 @@ FactoryGirl.define do factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff] factory :note_on_project_snippet, traits: [:on_project_snippet] factory :system_note, traits: [:system] + factory :downvote_note, traits: [:award, :downvote] + factory :upvote_note, traits: [:award, :upvote] trait :on_commit do project @@ -65,6 +67,18 @@ FactoryGirl.define do system true end + trait :award do + is_award true + end + + trait :downvote do + note "thumbsdown" + end + + trait :upvote do + note "thumbsup" + end + trait :with_attachment do attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") } end diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..cc4199dc7b67f89aa89b68f28cd15582bf678ef4 --- /dev/null +++ b/spec/lib/ci/status_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Ci::Status do + describe '.get_status' do + subject { described_class.get_status(builds) } + + context 'all builds successful' do + let(:builds) { Array.new(2) { create(:ci_build, :success) } } + it { is_expected.to eq 'success' } + end + + context 'at least one build failed' do + let(:builds) { [create(:ci_build, :success), create(:ci_build, :failed)] } + it { is_expected.to eq 'failed' } + end + + context 'at least one running' do + let(:builds) { [create(:ci_build, :success), create(:ci_build, :running)] } + it { is_expected.to eq 'running' } + end + + context 'at least one pending' do + let(:builds) { [create(:ci_build, :success), create(:ci_build, :pending)] } + it { is_expected.to eq 'running' } + end + + context 'build success and failed but allowed to fail' do + let(:builds) { [create(:ci_build, :success), create(:ci_build, :failed, :allowed_to_fail)] } + it { is_expected.to eq 'success' } + end + + context 'one build failed but allowed to fail' do + let(:builds) { [create(:ci_build, :failed, :allowed_to_fail)] } + it { is_expected.to eq 'success' } + end + end +end diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb index 691f36e6cb74eab260512ba516fe7077a36a1a24..da6526774437aa24bc4551ffce44db080520ab13 100644 --- a/spec/lib/gitlab/note_data_builder_spec.rb +++ b/spec/lib/gitlab/note_data_builder_spec.rb @@ -16,62 +16,80 @@ describe 'Gitlab::NoteDataBuilder', lib: true do end describe 'When asking for a note on commit' do - let(:note) { create(:note_on_commit) } + let(:note) { create(:note_on_commit, project: project) } it 'returns the note and commit-specific data' do expect(data).to have_key(:commit) end + + include_examples 'project hook data' + include_examples 'deprecated repository hook data' end describe 'When asking for a note on commit diff' do - let(:note) { create(:note_on_commit_diff) } + let(:note) { create(:note_on_commit_diff, project: project) } it 'returns the note and commit-specific data' do expect(data).to have_key(:commit) end + + include_examples 'project hook data' + include_examples 'deprecated repository hook data' end describe 'When asking for a note on issue' do let(:issue) { create(:issue, created_at: fixed_time, updated_at: fixed_time) } - let(:note) { create(:note_on_issue, noteable_id: issue.id) } + let(:note) { create(:note_on_issue, noteable_id: issue.id, project: project) } it 'returns the note and issue-specific data' do expect(data).to have_key(:issue) expect(data[:issue].except('updated_at')).to eq(issue.hook_attrs.except('updated_at')) expect(data[:issue]['updated_at']).to be > issue.hook_attrs['updated_at'] end + + include_examples 'project hook data' + include_examples 'deprecated repository hook data' end describe 'When asking for a note on merge request' do let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) } - let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) } + let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id, project: project) } it 'returns the note and merge request data' do expect(data).to have_key(:merge_request) expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at')) expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at'] end + + include_examples 'project hook data' + include_examples 'deprecated repository hook data' end describe 'When asking for a note on merge request diff' do let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) } - let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) } + let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id, project: project) } it 'returns the note and merge request diff data' do expect(data).to have_key(:merge_request) expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at')) expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at'] end + + include_examples 'project hook data' + include_examples 'deprecated repository hook data' end describe 'When asking for a note on project snippet' do let!(:snippet) { create(:project_snippet, created_at: fixed_time, updated_at: fixed_time) } - let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) } + let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id, project: project) } it 'returns the note and project snippet data' do expect(data).to have_key(:snippet) expect(data[:snippet].except('updated_at')).to eq(snippet.hook_attrs.except('updated_at')) expect(data[:snippet]['updated_at']).to be > snippet.hook_attrs['updated_at'] end + + include_examples 'project hook data' + include_examples 'deprecated repository hook data' end end diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb index 3ef61685398601a3f09c160fa2ac607631bb8e59..961022b9d12ebc85b65cc82d66776470bd19f1ff 100644 --- a/spec/lib/gitlab/push_data_builder_spec.rb +++ b/spec/lib/gitlab/push_data_builder_spec.rb @@ -1,34 +1,32 @@ require 'spec_helper' -describe 'Gitlab::PushDataBuilder', lib: true do +describe Gitlab::PushDataBuilder, lib: true do let(:project) { create(:project) } let(:user) { create(:user) } - describe :build_sample do - let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) } + describe '.build_sample' do + let(:data) { described_class.build_sample(project, user) } it { expect(data).to be_a(Hash) } it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } it { expect(data[:ref]).to eq('refs/heads/master') } it { expect(data[:commits].size).to eq(3) } - it { expect(data[:repository][:git_http_url]).to eq(project.http_url_to_repo) } - it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) } - it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) } it { expect(data[:total_commits_count]).to eq(3) } it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) } it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) } it { expect(data[:commits].first[:removed]).to eq([]) } + + include_examples 'project hook data' + include_examples 'deprecated repository hook data' end - describe :build do + describe '.build' do let(:data) do - Gitlab::PushDataBuilder.build(project, - user, - Gitlab::Git::BLANK_SHA, - '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b', - 'refs/tags/v1.1.0') + described_class.build(project, user, Gitlab::Git::BLANK_SHA, + '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b', + 'refs/tags/v1.1.0') end it { expect(data).to be_a(Hash) } @@ -38,5 +36,10 @@ describe 'Gitlab::PushDataBuilder', lib: true do it { expect(data[:ref]).to eq('refs/tags/v1.1.0') } it { expect(data[:commits]).to be_empty } it { expect(data[:total_commits_count]).to be_zero } + + it 'does not raise an error when given nil commits' do + expect { described_class.build(spy, spy, spy, spy, spy, nil) }. + not_to raise_error + end end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d940bf05061be03313c5a935161e2f9ca37ab567 --- /dev/null +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Gitlab::Workhorse, lib: true do + let(:project) { create(:project) } + let(:subject) { Gitlab::Workhorse } + + describe "#send_git_archive" do + context "when the repository doesn't have an archive file path" do + before do + allow(project.repository).to receive(:archive_metadata).and_return(Hash.new) + end + + it "raises an error" do + expect { subject.send_git_archive(project, "master", "zip") }.to raise_error(RuntimeError) + end + end + end +end diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index dfc0cc3be1cb7a4adc6257a5115525fe3d8ef38d..4dc309a42559f8515cd23ff7c2b545aa82160657 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -247,6 +247,35 @@ describe Ci::Commit, models: true do end end + + context 'custom stage with first job allowed to fail' do + let(:yaml) do + { + stages: ['clean', 'test'], + clean_job: { + stage: 'clean', + allow_failure: true, + script: 'BUILD', + }, + test_job: { + stage: 'test', + script: 'TEST', + }, + } + end + + before do + stub_ci_commit_yaml_file(YAML.dump(yaml)) + create_builds + end + + it 'properly schedules builds' do + expect(commit.builds.pluck(:status)).to contain_exactly('pending') + commit.builds.running_or_pending.each(&:drop) + expect(commit.builds.pluck(:status)).to contain_exactly('pending', 'failed') + end + end + context 'properly creates builds when "when" is defined' do let(:yaml) do { diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 8f09ff03a787e819bb454d16e9a2d08455db2dcc..600089802b2a4bed2e4eb4e8c49551845b2dfa93 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -69,27 +69,28 @@ describe Issue, "Issuable" do end describe "#to_hook_data" do - let(:hook_data) { issue.to_hook_data(user) } + let(:data) { issue.to_hook_data(user) } + let(:project) { issue.project } + it "returns correct hook data" do - expect(hook_data[:object_kind]).to eq("issue") - expect(hook_data[:user]).to eq(user.hook_attrs) - expect(hook_data[:repository][:name]).to eq(issue.project.name) - expect(hook_data[:repository][:url]).to eq(issue.project.url_to_repo) - expect(hook_data[:repository][:description]).to eq(issue.project.description) - expect(hook_data[:repository][:homepage]).to eq(issue.project.web_url) - expect(hook_data[:object_attributes]).to eq(issue.hook_attrs) - expect(hook_data).to_not have_key(:assignee) + expect(data[:object_kind]).to eq("issue") + expect(data[:user]).to eq(user.hook_attrs) + expect(data[:object_attributes]).to eq(issue.hook_attrs) + expect(data).to_not have_key(:assignee) end context "issue is assigned" do before { issue.update_attribute(:assignee, user) } it "returns correct hook data" do - expect(hook_data[:object_attributes]['assignee_id']).to eq(user.id) - expect(hook_data[:assignee]).to eq(user.hook_attrs) + expect(data[:object_attributes]['assignee_id']).to eq(user.id) + expect(data[:assignee]).to eq(user.hook_attrs) end end + + include_examples 'project hook data' + include_examples 'deprecated repository hook data' end describe '#card_attributes' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c61ddf01118f5b1fca0854b7fdc03219d31c9e3b..f35b48601ada4856029b9e186053f0ad2faabad5 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -254,13 +254,22 @@ describe MergeRequest, models: true do end describe "#hook_attrs" do + let(:attrs_hash) { subject.hook_attrs.to_h } + + [:source, :target].each do |key| + describe "#{key} key" do + include_examples 'project hook data', project_key: key do + let(:data) { attrs_hash } + let(:project) { subject.send("#{key}_project") } + end + end + end + it "has all the required keys" do - attrs = subject.hook_attrs - attrs = attrs.to_h - expect(attrs).to include(:source) - expect(attrs).to include(:target) - expect(attrs).to include(:last_commit) - expect(attrs).to include(:work_in_progress) + expect(attrs_hash).to include(:source) + expect(attrs_hash).to include(:target) + expect(attrs_hash).to include(:last_commit) + expect(attrs_hash).to include(:work_in_progress) end end diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 5cd5ae327bf2da88eda2d83de61e58828e2f39fe..7b63da005f0d06303498e7075c106abd30323b32 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -68,14 +68,24 @@ describe ProjectTeam, models: true do end describe "#human_max_access" do - it "return master role" do - user = create :user - group = create :group - group.add_users([user.id], GroupMember::MASTER) - project = create(:project, namespace: group) - project.team << [user, :guest] - - expect(project.team.human_max_access(user.id)).to eq("Master") + it 'returns Master role' do + user = create(:user) + group = create(:group) + group.add_master(user) + + project = build_stubbed(:empty_project, namespace: group) + + expect(project.team.human_max_access(user.id)).to eq 'Master' + end + + it 'returns Owner role' do + user = create(:user) + group = create(:group) + group.add_owner(user) + + project = build_stubbed(:empty_project, namespace: group) + + expect(project.team.human_max_access(user.id)).to eq 'Owner' end end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index e1ee43e64db1d0ed9cb3bc31d282acecd7fea414..ed91b62c534e00acb1badd8f2b842d23bc1102db 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -200,13 +200,22 @@ describe Repository, models: true do describe :commit_with_hooks do context 'when pre hooks were successful' do - it 'should run without errors' do - expect_any_instance_of(GitHooksService).to receive(:execute).and_return(true) + before do + expect_any_instance_of(GitHooksService).to receive(:execute). + and_return(true) + end + it 'should run without errors' do expect do repository.commit_with_hooks(user, 'feature') { sample_commit.id } end.not_to raise_error end + + it 'should ensure the autocrlf Git option is set to :input' do + expect(repository).to receive(:update_autocrlf_option) + + repository.commit_with_hooks(user, 'feature') { sample_commit.id } + end end context 'when pre hooks failed' do @@ -220,6 +229,25 @@ describe Repository, models: true do end end + describe '#exists?' do + it 'returns true when a repository exists' do + expect(repository.exists?).to eq(true) + end + + it 'returns false when a repository does not exist' do + expect(repository.raw_repository).to receive(:rugged). + and_raise(Gitlab::Git::Repository::NoRepository) + + expect(repository.exists?).to eq(false) + end + + it 'returns false when there is no namespace' do + allow(repository).to receive(:path_with_namespace).and_return(nil) + + expect(repository.exists?).to eq(false) + end + end + describe '#has_visible_content?' do subject { repository.has_visible_content? } @@ -249,6 +277,33 @@ describe Repository, models: true do end end + describe '#update_autocrlf_option' do + describe 'when autocrlf is not already set to :input' do + before do + repository.raw_repository.autocrlf = true + end + + it 'sets autocrlf to :input' do + repository.update_autocrlf_option + + expect(repository.raw_repository.autocrlf).to eq(:input) + end + end + + describe 'when autocrlf is already set to :input' do + before do + repository.raw_repository.autocrlf = :input + end + + it 'does nothing' do + expect(repository.raw_repository).to_not receive(:autocrlf=). + with(:input) + + repository.update_autocrlf_option + end + end + end + describe '#empty?' do let(:empty_repository) { create(:project_empty_repo).repository } @@ -355,6 +410,17 @@ describe Repository, models: true do end end + describe '#expire_emptiness_caches' do + let(:cache) { repository.send(:cache) } + + it 'expires the caches' do + expect(cache).to receive(:expire).with(:empty?) + expect(repository).to receive(:expire_has_visible_content_cache) + + repository.expire_emptiness_caches + end + end + describe :skip_merged_commit do subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } } diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 4911cdd9da666392af1b2fa2f29b32d27dc2f1c1..0ae63b0afec49b5e26f98ae7236930cd439ab4ef 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -4,6 +4,7 @@ require 'mime/types' describe API::API, api: true do include ApiHelpers include RepoHelpers + include WorkhorseHelpers let(:user) { create(:user) } let(:user2) { create(:user) } @@ -91,21 +92,27 @@ describe API::API, api: true do get api("/projects/#{project.id}/repository/archive", user) repo_name = project.repository.name.gsub("\.git", "") expect(response.status).to eq(200) - expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/) + type, params = workhorse_send_data + expect(type).to eq('git-archive') + expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/) end it "should get the archive.zip" do get api("/projects/#{project.id}/repository/archive.zip", user) repo_name = project.repository.name.gsub("\.git", "") expect(response.status).to eq(200) - expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/) + type, params = workhorse_send_data + expect(type).to eq('git-archive') + expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/) end it "should get the archive.tar.bz2" do get api("/projects/#{project.id}/repository/archive.tar.bz2", user) repo_name = project.repository.name.gsub("\.git", "") expect(response.status).to eq(200) - expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/) + type, params = workhorse_send_data + expect(type).to eq('git-archive') + expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/) end it "should return 404 for invalid sha" do diff --git a/spec/services/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb deleted file mode 100644 index bd871605c660851c82089705e2e7254da4897f16..0000000000000000000000000000000000000000 --- a/spec/services/archive_repository_service_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'spec_helper' - -describe ArchiveRepositoryService, services: true do - let(:project) { create(:project) } - subject { ArchiveRepositoryService.new(project, "master", "zip") } - - describe "#execute" do - it "cleans old archives" do - expect(RepositoryArchiveCacheWorker).to receive(:perform_async) - - subject.execute(timeout: 0.0) - end - - context "when the repository doesn't have an archive file path" do - before do - allow(project.repository).to receive(:archive_metadata).and_return(Hash.new) - end - - it "raises an error" do - expect { subject.execute(timeout: 0.0) }.to raise_error(RuntimeError) - end - end - - end -end diff --git a/spec/services/ci/create_builds_service_spec.rb b/spec/services/ci/create_builds_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..1fca362868601e4b1cd6375dd63358ce5d1f0aa9 --- /dev/null +++ b/spec/services/ci/create_builds_service_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Ci::CreateBuildsService, services: true do + let(:commit) { create(:ci_commit) } + let(:user) { create(:user) } + + describe '#execute' do + # Using stubbed .gitlab-ci.yml created in commit factory + # + + subject do + described_class.new.execute(commit, 'test', 'master', nil, user, nil, status) + end + + context 'next builds available' do + let(:status) { 'success' } + + it { is_expected.to be_an_instance_of Array } + it { is_expected.to all(be_an_instance_of Ci::Build) } + end + + context 'builds skipped' do + let(:status) { 'skipped' } + + it { is_expected.to be_empty } + end + end +end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index eb3a5fe43f5ff14d7c6de41c13e095d07affca5b..994585fb32c5899941a076aa8cd0866053293903 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -5,7 +5,6 @@ describe GitPushService, services: true do let(:user) { create :user } let(:project) { create :project } - let(:service) { GitPushService.new } before do @blankrev = Gitlab::Git::BLANK_SHA @@ -15,10 +14,17 @@ describe GitPushService, services: true do end describe 'Push branches' do + + let(:oldrev) { @oldrev } + let(:newrev) { @newrev } + + subject do + execute_service(project, user, oldrev, newrev, @ref ) + end + context 'new branch' do - subject do - service.execute(project, user, @blankrev, @newrev, @ref) - end + + let(:oldrev) { @blankrev } it { is_expected.to be_truthy } @@ -36,9 +42,6 @@ describe GitPushService, services: true do end context 'existing branch' do - subject do - service.execute(project, user, @oldrev, @newrev, @ref) - end it { is_expected.to be_truthy } @@ -50,9 +53,8 @@ describe GitPushService, services: true do end context 'rm branch' do - subject do - service.execute(project, user, @oldrev, @blankrev, @ref) - end + + let(:newrev) { @blankrev } it { is_expected.to be_truthy } @@ -72,7 +74,7 @@ describe GitPushService, services: true do describe "Git Push Data" do before do - service.execute(project, user, @oldrev, @newrev, @ref) + service = execute_service(project, user, @oldrev, @newrev, @ref ) @push_data = service.push_data @commit = project.commit(@newrev) end @@ -134,20 +136,21 @@ describe GitPushService, services: true do describe "Push Event" do before do - service.execute(project, user, @oldrev, @newrev, @ref) + service = execute_service(project, user, @oldrev, @newrev, @ref ) @event = Event.last + @push_data = service.push_data end it { expect(@event).not_to be_nil } it { expect(@event.project).to eq(project) } it { expect(@event.action).to eq(Event::PUSHED) } - it { expect(@event.data).to eq(service.push_data) } + it { expect(@event.data).to eq(@push_data) } context "Updates merge requests" do it "when pushing a new branch for the first time" do expect(project).to receive(:update_merge_requests). with(@blankrev, 'newrev', 'refs/heads/master', user) - service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') + execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) end end end @@ -158,7 +161,7 @@ describe GitPushService, services: true do expect(project).to receive(:execute_hooks) expect(project.default_branch).to eq("master") expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false }) - service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') + execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) end it "when pushing a branch for the first time with default branch protection disabled" do @@ -167,7 +170,7 @@ describe GitPushService, services: true do expect(project).to receive(:execute_hooks) expect(project.default_branch).to eq("master") expect(project.protected_branches).not_to receive(:create) - service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') + execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) end it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do @@ -176,12 +179,12 @@ describe GitPushService, services: true do expect(project).to receive(:execute_hooks) expect(project.default_branch).to eq("master") expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true }) - service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') + execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) end it "when pushing new commits to existing branch" do expect(project).to receive(:execute_hooks) - service.execute(project, user, 'oldrev', 'newrev', 'refs/heads/master') + execute_service(project, user, 'oldrev', 'newrev', 'refs/heads/master' ) end end end @@ -204,7 +207,7 @@ describe GitPushService, services: true do it "creates a note if a pushed commit mentions an issue" do expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author) - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) end it "only creates a cross-reference note if one doesn't already exist" do @@ -212,7 +215,7 @@ describe GitPushService, services: true do expect(SystemNoteService).not_to receive(:cross_reference).with(issue, commit, commit_author) - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) end it "defaults to the pushing user if the commit's author is not known" do @@ -222,7 +225,7 @@ describe GitPushService, services: true do ) expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, user) - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) end it "finds references in the first push to a non-default branch" do @@ -231,7 +234,7 @@ describe GitPushService, services: true do expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author) - service.execute(project, user, @blankrev, @newrev, 'refs/heads/other') + execute_service(project, user, @blankrev, @newrev, 'refs/heads/other' ) end end @@ -255,18 +258,18 @@ describe GitPushService, services: true do context "to default branches" do it "closes issues" do - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) expect(Issue.find(issue.id)).to be_closed end it "adds a note indicating that the issue is now closed" do expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit) - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) end it "doesn't create additional cross-reference notes" do expect(SystemNoteService).not_to receive(:cross_reference) - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) end it "doesn't close issues when external issue tracker is in use" do @@ -274,7 +277,7 @@ describe GitPushService, services: true do # The push still shouldn't create cross-reference notes. expect do - service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf') + execute_service(project, user, @oldrev, @newrev, 'refs/heads/hurf' ) end.not_to change { Note.where(project_id: project.id, system: true).count } end end @@ -287,11 +290,11 @@ describe GitPushService, services: true do it "creates cross-reference notes" do expect(SystemNoteService).to receive(:cross_reference).with(issue, closing_commit, commit_author) - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) end it "doesn't close issues" do - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) expect(Issue.find(issue.id)).to be_opened end end @@ -328,7 +331,7 @@ describe GitPushService, services: true do let(:message) { "this is some work.\n\nrelated to JIRA-1" } it "should initiate one api call to jira server to mention the issue" do - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) expect(WebMock).to have_requested(:post, jira_api_comment_url).with( body: /mentioned this issue in/ @@ -346,7 +349,7 @@ describe GitPushService, services: true do } }.to_json - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) expect(WebMock).to have_requested(:post, jira_api_transition_url).with( body: transition_body ).once @@ -357,7 +360,7 @@ describe GitPushService, services: true do body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]." }.to_json - service.execute(project, user, @oldrev, @newrev, @ref) + execute_service(project, user, @oldrev, @newrev, @ref ) expect(WebMock).to have_requested(:post, jira_api_comment_url).with( body: comment_body ).once @@ -376,7 +379,13 @@ describe GitPushService, services: true do end it 'push to first branch updates HEAD' do - service.execute(project, user, @blankrev, @newrev, new_ref) + execute_service(project, user, @blankrev, @newrev, new_ref ) end end + + def execute_service(project, user, oldrev, newrev, ref) + service = described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref ) + service.execute + service + end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index d3364a71022c9d953c59971ad4570dfeea2066b3..1bdc03af12d7baa18f6891ecc6e56b7a58be6b9b 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -424,6 +424,21 @@ describe SystemNoteService, services: true do to be_falsey end end + + context 'commit with cross-reference from fork' do + let(:author2) { create(:user) } + let(:forked_project) { Projects::ForkService.new(project, author2).execute } + let(:commit2) { forked_project.commit } + + before do + described_class.cross_reference(noteable, commit0, author2) + end + + it 'is true when a fork mentions an external issue' do + expect(described_class.cross_reference_exists?(noteable, commit2)). + to be true + end + end end include JiraServiceHelper diff --git a/spec/support/project_hook_data_shared_example.rb b/spec/support/project_hook_data_shared_example.rb new file mode 100644 index 0000000000000000000000000000000000000000..422083875d70ea7d12da17207928142196fca3cd --- /dev/null +++ b/spec/support/project_hook_data_shared_example.rb @@ -0,0 +1,27 @@ +RSpec.shared_examples 'project hook data' do |project_key: :project| + it 'contains project data' do + expect(data[project_key][:name]).to eq(project.name) + expect(data[project_key][:description]).to eq(project.description) + expect(data[project_key][:web_url]).to eq(project.web_url) + expect(data[project_key][:avatar_url]).to eq(project.avatar_url) + expect(data[project_key][:git_http_url]).to eq(project.http_url_to_repo) + expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo) + expect(data[project_key][:namespace]).to eq(project.namespace.name) + expect(data[project_key][:visibility_level]).to eq(project.visibility_level) + expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace) + expect(data[project_key][:default_branch]).to eq(project.default_branch) + expect(data[project_key][:homepage]).to eq(project.web_url) + expect(data[project_key][:url]).to eq(project.url_to_repo) + expect(data[project_key][:ssh_url]).to eq(project.ssh_url_to_repo) + expect(data[project_key][:http_url]).to eq(project.http_url_to_repo) + end +end + +RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project| + it 'contains deprecated repository data' do + expect(data[:repository][:name]).to eq(project.name) + expect(data[:repository][:description]).to eq(project.description) + expect(data[:repository][:url]).to eq(project.url_to_repo) + expect(data[:repository][:homepage]).to eq(project.web_url) + end +end diff --git a/spec/support/workhorse_helpers.rb b/spec/support/workhorse_helpers.rb new file mode 100644 index 0000000000000000000000000000000000000000..107b6e309240d8f1b7165d445fd9e3c45cd70582 --- /dev/null +++ b/spec/support/workhorse_helpers.rb @@ -0,0 +1,16 @@ +module WorkhorseHelpers + extend self + + def workhorse_send_data + @_workhorse_send_data ||= begin + header = response.headers[Gitlab::Workhorse::SEND_DATA_HEADER] + split_header = header.split(':') + type = split_header.shift + header = split_header.join(':') + [ + type, + JSON.parse(Base64.urlsafe_decode64(header)), + ] + end + end +end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index dae31992620a0b86de8f8a64352b65cb4c669e0b..172537474ee85bfc93ee415f4300e8558bd85faa 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -19,6 +19,18 @@ describe RepositoryForkWorker do fork_project.namespace.path) end + it 'flushes the empty caches' do + expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository). + with(project.path_with_namespace, fork_project.namespace.path). + and_return(true) + + expect_any_instance_of(Repository).to receive(:expire_emptiness_caches). + and_call_original + + subject.perform(project.id, project.path_with_namespace, + fork_project.namespace.path) + end + it "handles bad fork" do expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false) subject.perform( diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..6739063543b2016800b19476acbf2c613325672a --- /dev/null +++ b/spec/workers/repository_import_worker_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe RepositoryImportWorker do + let(:project) { create(:project) } + + subject { described_class.new } + + describe '#perform' do + it 'imports a project' do + expect_any_instance_of(Projects::ImportService).to receive(:execute). + and_return({ status: :ok }) + + expect_any_instance_of(Repository).to receive(:expire_emptiness_caches) + expect_any_instance_of(Project).to receive(:import_finish) + + subject.perform(project.id) + end + end +end