diff --git a/CHANGELOG b/CHANGELOG index d53429a5d44c9198c446edf2e352d1c97cd9b0b1..77348e3d38285bb372bda48e2e0d73534284fba6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,24 +1,33 @@ Please view this file on the master branch, on stable branches it's out of date. v 7.11.0 (unreleased) + - Get Gitorious importer to work again. - Fix clone URL field and X11 Primary selection (Dmitry Medvinsky) - Ignore invalid lines in .gitmodules - Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu) - Redirect to sign in page after signing out. + - Fix "Hello @username." references not working by no longer allowing usernames to end in period. - - Add "Reply quoting selected text" shortcut key (`r`) - Fix bug causing `@whatever` inside an issue's first code block to be picked up as a user mention. - Fix bug causing `@whatever` inside an inline code snippet (backtick-style) to be picked up as a user mention. + - Added GitLab Event header for project hooks - + - Show Atom feed buttons everywhere where applicable. + - Add project activity atom feed. + - Don't crash when an MR from a fork has a cross-reference comment from the target project on of its commits. + - Include commit comments in MR from a forked project. - - - - - + - Add default project and snippet visibility settings to the admin web UI. + - Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu) - Move snippets UI to fluid layout - Improve UI for sidebar. Increase separation between navigation and content - Improve new project command options (Ben Bodenmiller) - Prevent sending empty messages to HipChat (Chulki Lee) + - Improve UI for mobile phones on dashboard and project pages + - Add room notification and message color option for HipChat -v 7.10.0 (unreleased) +v 7.10.0 - Ignore submodules that are defined in .gitmodules but are checked in as directories. - Allow projects to be imported from Google Code. - Remove access control for uploaded images to fix broken images in emails (Hannes Rosenögger) diff --git a/Gemfile b/Gemfile index 238337d7731ba0d71264eae527835e91b7ae1bf6..27abaf3f5d3ee39fed49a77fe30d93152a9c9d67 100644 --- a/Gemfile +++ b/Gemfile @@ -159,7 +159,7 @@ gem "slack-notifier", "~> 1.0.0" gem 'asana', '~> 0.0.6' # d3 -gem "d3_rails", "~> 3.1.4" +gem 'd3_rails', '~> 3.5.5' #cal-heatmap gem "cal-heatmap-rails", "~> 0.0.1" @@ -189,7 +189,7 @@ gem 'turbolinks' gem 'jquery-turbolinks' gem 'select2-rails' -gem 'jquery-atwho-rails', "~> 0.3.3" +gem 'jquery-atwho-rails', '~> 1.0.0' gem "jquery-rails" gem "jquery-ui-rails" gem "jquery-scrollto-rails" @@ -224,14 +224,13 @@ end group :development, :test do gem 'coveralls', require: false gem 'rubocop', '0.28.0', require: false - # gem 'rails-dev-tweaks' gem 'spinach-rails' gem "rspec-rails", '2.99' - gem "capybara", '~> 2.2.1' + gem 'capybara', '~> 2.2.1' + gem 'capybara-screenshot', '~> 1.0.0' gem "pry-rails" gem "awesome_print" gem "database_cleaner" - gem "launchy" gem 'factory_girl_rails' # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) diff --git a/Gemfile.lock b/Gemfile.lock index 9d17cd1128270ef62f16f9336975ada0f8ecce57..79797f98030c7e5af32ee974aa7d7b47a72b3827 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -85,6 +85,9 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) + capybara-screenshot (1.0.9) + capybara (>= 1.0, < 3) + launchy carrierwave (0.9.0) activemodel (>= 3.2.0) activesupport (>= 3.2.0) @@ -116,7 +119,7 @@ GEM crack (0.4.1) safe_yaml (~> 0.9.0) creole (0.3.8) - d3_rails (3.1.10) + d3_rails (3.5.5) railties (>= 3.1.0) daemons (1.1.9) database_cleaner (1.3.0) @@ -298,7 +301,7 @@ GEM phantomjs (>= 1.9) railties (>= 3.2.0) sprockets-rails - jquery-atwho-rails (0.3.3) + jquery-atwho-rails (1.0.1) jquery-rails (3.1.0) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) @@ -677,13 +680,14 @@ DEPENDENCIES byebug cal-heatmap-rails (~> 0.0.1) capybara (~> 2.2.1) + capybara-screenshot (~> 1.0.0) carrierwave charlock_holmes coffee-rails colored coveralls creole (~> 0.3.6) - d3_rails (~> 3.1.4) + d3_rails (~> 3.5.5) database_cleaner default_value_for (~> 3.0.0) devise (= 3.2.4) @@ -720,13 +724,12 @@ DEPENDENCIES httparty jasmine (~> 2.2.0) jasmine-rails - jquery-atwho-rails (~> 0.3.3) + jquery-atwho-rails (~> 1.0.0) jquery-rails jquery-scrollto-rails jquery-turbolinks jquery-ui-rails kaminari (~> 0.15.1) - launchy letter_opener minitest (~> 5.3.0) mousetrap-rails diff --git a/README.md b/README.md index 852d9443609b14324fbd9601cd1498519f5b99cd..f3330dfe86a4d9939d8eaf9c157ac0b9a82dcd1d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +## Canonical source + +The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible. + +The source of GitLab Enterprise Edition is [hosted on GitLab.com](https://gitlab.com/subscribers/gitlab-ee) and acessible only to [subscribers](https://about.gitlab.com/subscription/). + #  GitLab ## Subscriber onboarding information @@ -43,10 +49,6 @@ There are two editions of GitLab. *GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users. To get access to the EE and support please [become a subscriber](https://about.gitlab.com/pricing/). -## Canonical source - -- The source of GitLab Enterprise Edition is [hosted on GitLab.com](https://dev.gitlab.org/gitlab/gitlab-ee/) and acessible only to [subscribers](https://about.gitlab.com/subscription/). - ## Code status - [](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) diff --git a/VERSION b/VERSION index 990b853f77b274b63a2edffb892c226066838e16..6aed29f5601ace782c81501d2815e3cfd1b0db33 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.10.0.pre-ee +7.11.0.pre-ee diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 020c103dbc525414c56c05a793ec7b0ff4df5e64..bb9da1470180b51828cc5f3241533d126eb2a55b 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -132,10 +132,17 @@ $ -> ), 1 # Initialize tooltips - $('.has_tooltip').tooltip() - - # Bottom tooltip - $('.has_bottom_tooltip').tooltip(placement: 'bottom') + $('body').tooltip({ + selector: '.has_tooltip, [data-toggle="tooltip"], .page-sidebar-collapsed .nav-sidebar a' + placement: (_, el) -> + $el = $(el) + if $el.attr('id') == 'js-shortcuts-home' + # Place the logo tooltip on the right when collapsed, bottom when expanded + $el.parents('header').hasClass('header-collapsed') and 'right' or 'bottom' + else + # Otherwise use the data-placement attribute like normal + $el.data('placement') + }) # Form submitter $('.trigger-submit').on 'change', -> diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 00d56ae5b4b0de2d24c87d34e4b072c9a3ab56c8..4eb3f3c03f310a9a50276709ae0b639154b37791 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -2,19 +2,19 @@ window.GitLab ?= {} GitLab.GfmAutoComplete = - # private_token: '' dataSource: '' + # Emoji Emoji: - template: '<li data-value="${insert}">${name} <img alt="${name}" height="20" src="${image}" width="20" /></li>' + template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>' # Team Members Members: - template: '<li data-value="${username}">${username} <small>${name}</small></li>' + template: '<li>${username} <small>${name}</small></li>' # Issues and MergeRequests Issues: - template: '<li data-value="${id}"><small>${id}</small> ${title} </li>' + template: '<li><small>${id}</small> ${title}</li>' # Add GFM auto-completion to all input fields, that accept GFM input. setup: -> @@ -23,45 +23,46 @@ GitLab.GfmAutoComplete = # Emoji input.atwho at: ':' - tpl: @Emoji.template - callbacks: - before_save: (emojis) => - $.map emojis, (em) => name: em.name, insert: em.name+ ':', image: em.path + displayTpl: @Emoji.template + insertTpl: ':${name}:' # Team Members input.atwho at: '@' - tpl: @Members.template - search_key: 'search' + displayTpl: @Members.template + insertTpl: '${atwho-at}${username}' + searchKey: 'search' callbacks: - before_save: (members) => - $.map members, (m) => name: m.name, username: m.username, search: "#{m.username} #{m.name}" + beforeSave: (members) -> + $.map members, (m) -> name: m.name, username: m.username, search: "#{m.username} #{m.name}" input.atwho at: '#' alias: 'issues' - search_key: 'search' - tpl: @Issues.template + searchKey: 'search' + displayTpl: @Issues.template + insertTpl: '${atwho-at}${id}' callbacks: - before_save: (issues) -> + beforeSave: (issues) -> $.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}" input.atwho at: '!' alias: 'mergerequests' - search_key: 'search' - tpl: @Issues.template + searchKey: 'search' + displayTpl: @Issues.template + insertTpl: '${atwho-at}${id}' callbacks: - before_save: (merges) -> + beforeSave: (merges) -> $.map merges, (m) -> id: m.iid, title: sanitize(m.title), search: "#{m.iid} #{m.title}" - input.one "focus", => + input.one 'focus', => $.getJSON(@dataSource).done (data) -> # load members - input.atwho 'load', "@", data.members + input.atwho 'load', '@', data.members # load issues - input.atwho 'load', "issues", data.issues + input.atwho 'load', 'issues', data.issues # load merge requests - input.atwho 'load', "mergerequests", data.mergerequests + input.atwho 'load', 'mergerequests', data.mergerequests # load emojis - input.atwho 'load', ":", data.emojis + input.atwho 'load', ':', data.emojis diff --git a/app/assets/stylesheets/base/gl_bootstrap.scss b/app/assets/stylesheets/base/gl_bootstrap.scss index 62a3eade5c73a4c54eaf297e1722d54eb0d61323..427f333423c6271bb4a4abba5e211375aa00ebaf 100644 --- a/app/assets/stylesheets/base/gl_bootstrap.scss +++ b/app/assets/stylesheets/base/gl_bootstrap.scss @@ -117,7 +117,7 @@ color: #888; text-shadow: 0 1px 1px #fff; } - i[class~="fa"] { + i.fa { line-height: 14px; } } diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss index feff931156cb7c55c7891fedeaf02664c5e3d61f..b8b163a42b286c25c449820c6d68577419105d97 100644 --- a/app/assets/stylesheets/base/mixins.scss +++ b/app/assets/stylesheets/base/mixins.scss @@ -106,7 +106,6 @@ p > code { font-size: inherit; font-weight: inherit; - color: #555; } li { diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss index 9558f241b7c7e96c3f93547dc4571ed38e3cb386..869e586839ba22c565cdaeb5ad81f644a6797182 100644 --- a/app/assets/stylesheets/generic/issue_box.scss +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -6,7 +6,7 @@ .issue-box { display: inline-block; - padding: 7px 13px; + padding: 4px 13px; font-weight: normal; margin-right: 5px; diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss index 71a1fc4493f4cccf0213b2a3e4697100b1586a2b..c9bbacd7348efe6936cd6c754582afc7712c6ed8 100644 --- a/app/assets/stylesheets/generic/mobile.scss +++ b/app/assets/stylesheets/generic/mobile.scss @@ -4,6 +4,11 @@ margin-top: 20px; } + .container-fluid { + padding-left: 5px; + padding-right: 5px; + } + .nav.nav-tabs > li > a { padding: 10px; font-size: 12px; @@ -27,6 +32,34 @@ .project-home-links { display: none; } + + .project-avatar { + display: none; + } + + .project-home-panel { + padding-left: 0 !important; + + .project-home-row { + .project-home-desc { + margin-right: 0 !important; + float: none !important; + } + + .project-repo-buttons { + position: static; + margin-top: 15px; + width: 100%; + float: none; + text-align: left; + } + } + } + + .navbar-inner .title { + margin-left: 6px !important; + max-width: 70% !important; + } } @media (max-width: $screen-sm-max) { diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index cd86a9be8b2753f2f472fa79e29d771eb4f1b752..3572f33e91fff979bd34a08c25bfc48d55cea041 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -25,17 +25,8 @@ display: inline-block; } - .issue-actions { - display: none; - position: absolute; - top: 10px; - right: 15px; - } - - &:hover { - .issue-actions { - display: block; - } + .issue-no-comments { + opacity: 0.5; } } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 8abd4207beb15f41f2761f07f36edb7d3871520e..3165396a94d212034db1f3d2ba56465574b4644e 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -91,11 +91,16 @@ .merge-request-info { color: #999; font-size: 13px; - - .merge-request-labels { - display: inline-block; - } } + + } + + .merge-request-labels { + display: inline-block; + } + + .merge-request-no-comments { + opacity: 0.5; } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index facd7e193146ad66f4783f58cc9147924eb46ef2..97b19deb3edcc67019e26fc59850235fc9e29b48 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -136,7 +136,7 @@ ul.notes { display: none; float: right; - [class~="fa"] { + i.fa { font-size: 16px; line-height: 16px; vertical-align: middle; diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 6e19f97ce060750c14e8812fbd23cfd4f0a6a4d7..657d70d726222c1d082cfbf63357dabb93d5281a 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -40,6 +40,8 @@ def application_setting_params :home_page_url, :help_text, :max_attachment_size, + :default_project_visibility, + :default_snippet_visibility, restricted_visibility_levels: [] ) end diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index 0a463239d7496d7e27f8d7b1eb998ad1ab95df4f..690096bdbcf5ff05ae66d0ad4e61afcf61556313 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -33,7 +33,7 @@ def test owner_name: "Someone", owner_email: "example@gitlabhq.com" } - @hook.execute(data) + @hook.execute(data, 'system_hooks') redirect_to :back end diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index c1fdcd7fab66ef698154f668b731792276e7cbda..a62170662e12c34070374c69f6396a616b82aeeb 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -40,15 +40,6 @@ def service def application_services_params params.permit(:id, - service: [ - :title, :token, :type, :active, :api_key, :subdomain, - :room, :recipients, :project_url, :webhook, - :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, - :build_key, :server, :teamcity_url, :build_type, - :description, :issues_url, :new_issue_url, :restrict_to_branch, - :send_from_committer_email, :disable_diffs, - :push_events, :tag_push_events, :note_events, :issues_events, - :merge_requests_events - ]) + service: Projects::ServicesController::ALLOWED_PARAMS) end end diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb index 6067a87ee04dbc019e4b013956ffcc8d2bb33e8e..c121d2de7cb65c4732e769355b0018826c28dd25 100644 --- a/app/controllers/import/gitorious_controller.rb +++ b/app/controllers/import/gitorious_controller.rb @@ -6,7 +6,7 @@ def new def callback session[:gitorious_repos] = params[:repos] - redirect_to status_import_gitorious_url + redirect_to status_import_gitorious_path end def status diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 8a1b7899be3d0172aeb088de81f8b7f71ef21e45..78d42d695b68e7404a7e5b42f7dba8369b82286f 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -10,11 +10,11 @@ class Projects::CommitController < Projects::ApplicationController def show return git_not_found! unless @commit - @line_notes = commit.notes(@project).inline + @line_notes = commit.notes.inline @diffs = @commit.diffs @note = @project.build_commit_note(commit) - @notes_count = commit.notes(@project).count - @notes = commit.notes(@project).not_inline.fresh + @notes_count = commit.notes.count + @notes = commit.notes.not_inline.fresh @noteable = @commit @comments_allowed = @reply_allowed = true @comments_target = { @@ -36,6 +36,6 @@ def branches end def commit - @commit ||= @project.repository.commit(params[:id]) + @commit ||= @project.commit(params[:id]) end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ab75f2ddb7351f5dd22007de9e6787134d24dcd2..41b4e55a598f2e270797b578d9cd0d1553e8932c 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -146,7 +146,7 @@ def branch_from def branch_to @target_project = selected_target_project - @commit = @target_project.repository.commit(params[:ref]) if params[:ref].present? + @commit = @target_project.commit(params[:ref]) if params[:ref].present? end def update_branches diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 2c3209def7ce0080bf480bd2366b02022d109eed..3f9b578793efde167156f62edc40806d6440e71e 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -1,4 +1,14 @@ class Projects::ServicesController < Projects::ApplicationController + ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :subdomain, + :room, :recipients, :project_url, :webhook, + :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, + :build_key, :server, :teamcity_url, :build_type, + :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, + :colorize_messages, :channels, + :push_events, :issues_events, :merge_requests_events, :tag_push_events, + :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, + :jira_issue_transition_id, :notify, :color] + # Authorize before_action :authorize_admin_project! before_action :service, only: [:edit, :update, :test] @@ -45,16 +55,6 @@ def service end def service_params - params.require(:service).permit( - :title, :token, :type, :active, :api_key, :subdomain, - :room, :recipients, :project_url, :webhook, - :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, - :build_key, :server, :teamcity_url, :build_type, - :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, - :colorize_messages, :channels, - :push_events, :issues_events, :merge_requests_events, :tag_push_events, - :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, - :jira_issue_transition_id - ) + params.require(:service).permit(ALLOWED_PARAMS) end end diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index a35adeac6f039eae1ff22b755621747d27768383..e2d0b0d945908c9746c7a94e4de8422b98ea675a 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -1,11 +1,8 @@ class Projects::UploadsController < Projects::ApplicationController layout 'project' - # We want to skip these filters for only the `show` action if `image?` is true, - # but `skip_before_action` doesn't work with both `only` and `if`, so we accomplish the same like this. - skipped_filters = [:authenticate_user!, :reject_blocked!, :project, :repository] - skip_before_action *skipped_filters, only: [:show] - before_action *skipped_filters, only: [:show], unless: :image? + skip_before_action :authenticate_user!, :reject_blocked!, :project, + :repository, if: -> { action_name == 'show' && image? } def create link_to_file = ::Projects::UploadService.new(project, params[:file]). diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 0be3fdb058b57a08e2c9c45dd09d975c68259e3b..936a014fb9ed0117f5a323f3c8e7b75e17b9315e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -66,8 +66,6 @@ def show return end - limit = (params[:limit] || 20).to_i - @show_star = !(current_user && current_user.starred?(@project)) respond_to do |format| @@ -85,11 +83,14 @@ def show end format.json do - @events = @project.events.recent - @events = event_filter.apply_filter(@events).with_associations - @events = @events.limit(limit).offset(params[:offset] || 0) + load_events pager_json('events/_events', @events.count) end + + format.atom do + load_events + render layout: false + end end end @@ -167,6 +168,13 @@ def user_layout current_user ? 'projects' : 'public_projects' end + def load_events + @events = @project.events.recent + @events = event_filter.apply_filter(@events).with_associations + limit = (params[:limit] || 20).to_i + @events = @events.limit(limit).offset(params[:offset] || 0) + end + def project_params params.require(:project).permit( :name, :path, :description, :issues_tracker, :tag_list, diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a6844b2a47b84221d4ad5d724a798ecef05e3cd2..6e86400a4f6e4c3bd9a5d4db8bf3508b5a01688a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -318,4 +318,18 @@ def path_to_key(key, admin = false) profile_key_path(key) end end + + def state_filters_text_for(entity, project) + entity_title = entity.to_s.humanize + + count = + if project.nil? + "" + elsif current_controller?(:issues) + " (#{project.issues.send(entity).count})" + elsif current_controller?(:merge_requests) + " (#{project.merge_requests.send(entity).count})" + end + "#{entity_title}#{count}" + end end diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 0d573e72a80aa93807d869b1436b5fdd070eda13..66a1383d61b380b6fe974ca2a380de7b4eea0b89 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -10,7 +10,21 @@ def visibility_level_color(level) end end - def visibility_level_description(level) + # Return the description for the +level+ argument. + # + # +level+ One of the Gitlab::VisibilityLevel constants + # +form_model+ Either a model object (Project, Snippet, etc.) or the name of + # a Project or Snippet class. + def visibility_level_description(level, form_model) + case form_model.is_a?(String) ? form_model : form_model.class.name + when 'PersonalSnippet', 'ProjectSnippet', 'Snippet' + snippet_visibility_level_description(level) + when 'Project' + project_visibility_level_description(level) + end + end + + def project_visibility_level_description(level) capture_haml do haml_tag :span do case level @@ -64,4 +78,12 @@ def restricted_visibility_levels(show_all = false) return [] if current_user.is_admin? && !show_all current_application_settings.restricted_visibility_levels || [] end + + def default_project_visibility + current_application_settings.default_project_visibility + end + + def default_snippet_visibility + current_application_settings.default_snippet_visibility + end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 0dbb2939bb30c33702d54cd8c4225c955908bc84..9cb7077e59d37d0317b6b995ca4bd347ede0b2d5 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -79,7 +79,7 @@ def repository_push_email(project_id, recipient, author_id: nil, @disable_diffs = disable_diffs if @compare - @commits = Commit.decorate(compare.commits) + @commits = Commit.decorate(compare.commits, @project) @diffs = compare.diffs end @@ -101,8 +101,8 @@ def repository_push_email(project_id, recipient, author_id: nil, if @commits.length > 1 @target_url = namespace_project_compare_url(@project.namespace, @project, - from: Commit.new(@compare.base), - to: Commit.new(@compare.head)) + from: Commit.new(@compare.base, @project), + to: Commit.new(@compare.head, @project)) @subject << "Deleted " if @reverse_compare @subject << "#{@commits.length} commits: #{@commits.first.title}" else diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 97a486c952b85597bdce1b2ce1c931c00090cf69..9a90a8e4673ff41dc092728ec661abdb249a6452 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -18,6 +18,8 @@ # help_text :text # restricted_visibility_levels :text # max_attachment_size :integer default(10) +# default_project_visibility :integer +# default_snippet_visibility :integer # class ApplicationSetting < ActiveRecord::Base @@ -52,7 +54,9 @@ def self.create_from_defaults gravatar_enabled: Settings.gravatar['enabled'], sign_in_text: Settings.extra['sign_in_text'], restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], - max_attachment_size: Settings.gitlab['max_attachment_size'] + max_attachment_size: Settings.gitlab['max_attachment_size'], + default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], + default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'] ) end diff --git a/app/models/commit.rb b/app/models/commit.rb index 1cabc060c2affd7afd6ca56864a7aa74b5d76183..be5a118bfecfe7a1ae53180b52285dc1f2ab91da 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -3,8 +3,12 @@ class Commit include StaticModel extend ActiveModel::Naming include Mentionable + include Participable attr_mentionable :safe_message + participant :author, :committer, :notes, :mentioned_users + + attr_accessor :project # Safe amount of changes (files and lines) in one commit to render # Used to prevent 500 error on huge commits by suppressing diff @@ -18,12 +22,12 @@ class Commit DIFF_HARD_LIMIT_LINES = 50000 unless defined?(DIFF_HARD_LIMIT_LINES) class << self - def decorate(commits) + def decorate(commits, project) commits.map do |commit| if commit.kind_of?(Commit) commit else - self.new(commit) + self.new(commit, project) end end end @@ -41,10 +45,11 @@ def truncate_sha(sha) attr_accessor :raw - def initialize(raw_commit) + def initialize(raw_commit, project) raise "Nil as raw commit passed" unless raw_commit @raw = raw_commit + @project = project end def id @@ -100,7 +105,7 @@ def description? description.present? end - def hook_attrs(project) + def hook_attrs path_with_namespace = project.path_with_namespace { @@ -117,7 +122,7 @@ def hook_attrs(project) # Discover issues should be closed when this commit is pushed to a project's # default branch. - def closes_issues(project, current_user = self.committer) + def closes_issues(current_user = self.committer) Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message) end @@ -134,22 +139,7 @@ def committer User.find_for_commit(committer_email, committer_name) end - def participants(project, current_user = nil) - users = [] - users << author - users << committer - - users.push *self.mentioned_users(current_user, project) - - notes(project).each do |note| - users << note.author - users.push *note.mentioned_users(current_user, project) - end - - users.uniq - end - - def notes(project) + def notes project.notes.for_commit_id(self.id) end @@ -169,6 +159,6 @@ def short_id end def parents - @parents ||= Commit.decorate(super) + @parents ||= Commit.decorate(super, project) end end diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb new file mode 100644 index 0000000000000000000000000000000000000000..e64561982645ca4fc8ca0c7a757570a0b5359b5b --- /dev/null +++ b/app/models/commit_range.rb @@ -0,0 +1,106 @@ +# CommitRange makes it easier to work with commit ranges +# +# Examples: +# +# range = CommitRange.new('f3f85602...e86e1013') +# range.exclude_start? # => false +# range.reference_title # => "Commits f3f85602 through e86e1013" +# range.to_s # => "f3f85602...e86e1013" +# +# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae') +# range.exclude_start? # => true +# range.reference_title # => "Commits f3f85602^ through e86e1013" +# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"} +# range.to_s # => "f3f85602..e86e1013" +# +# # Assuming `project` is a Project with a repository containing both commits: +# range.project = project +# range.valid_commits? # => true +# +class CommitRange + include ActiveModel::Conversion + + attr_reader :sha_from, :notation, :sha_to + + # Optional Project model + attr_accessor :project + + # See `exclude_start?` + attr_reader :exclude_start + + # The beginning and ending SHA sums can be between 6 and 40 hex characters, + # and the range selection can be double- or triple-dot. + PATTERN = /\h{6,40}\.{2,3}\h{6,40}/ + + # Initialize a CommitRange + # + # range_string - The String commit range. + # project - An optional Project model. + # + # Raises ArgumentError if `range_string` does not match `PATTERN`. + def initialize(range_string, project = nil) + range_string.strip! + + unless range_string.match(/\A#{PATTERN}\z/) + raise ArgumentError, "invalid CommitRange string format: #{range_string}" + end + + @exclude_start = !range_string.include?('...') + @sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2) + + @project = project + end + + def inspect + %(#<#{self.class}:#{object_id} #{to_s}>) + end + + def to_s + "#{sha_from[0..7]}#{notation}#{sha_to[0..7]}" + end + + # Returns a String for use in a link's title attribute + def reference_title + "Commits #{suffixed_sha_from} through #{sha_to}" + end + + # Return a Hash of parameters for passing to a URL helper + # + # See `namespace_project_compare_url` + def to_param + { from: suffixed_sha_from, to: sha_to } + end + + def exclude_start? + exclude_start + end + + # Check if both the starting and ending commit IDs exist in a project's + # repository + # + # project - An optional Project to check (default: `project`) + def valid_commits?(project = project) + return nil unless project.present? + return false unless project.valid_repo? + + commit_from.present? && commit_to.present? + end + + def persisted? + true + end + + def commit_from + @commit_from ||= project.repository.commit(suffixed_sha_from) + end + + def commit_to + @commit_to ||= project.repository.commit(sha_to) + end + + private + + def suffixed_sha_from + sha_from + (exclude_start? ? '^' : '') + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index a21d9bdfe8aae4a74fdaf82e88a3d81fc8a5f141..97846b06d72a3b98074c8194cafe41fc357bb6ac 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -7,6 +7,7 @@ module Issuable extend ActiveSupport::Concern include Mentionable + include Participable included do belongs_to :author, class_name: "User" @@ -45,6 +46,7 @@ module Issuable prefix: true attr_mentionable :title, :description + participant :author, :assignee, :notes, :mentioned_users end module ClassMethods @@ -117,22 +119,6 @@ def votes_count upvotes + downvotes end - # Return all users participating on the discussion - def participants(current_user = self.author) - users = [] - users << author - users << assignee if is_assigned? - - users.push *self.mentioned_users(current_user) - - notes.each do |note| - users << note.author - users.push *note.mentioned_users(current_user) - end - - users.uniq - end - def subscribed?(user) subscription = subscriptions.find_by_user_id(user.id) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index aad5e51479381e4ee1da386eaecc26bb00c9592a..3ef3e8b67d879ed9c2e0a9ab8941f9c62d735745 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -64,7 +64,7 @@ def references(p = project, current_user = self.author, text = mentionable_text) def create_cross_references!(p = project, a = author, without = []) refs = references(p) - without refs.each do |ref| - Note.create_cross_reference_note(ref, local_reference, a, p) + Note.create_cross_reference_note(ref, local_reference, a) end end diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb new file mode 100644 index 0000000000000000000000000000000000000000..7a5e4876ff24cbe785c8c31c1d743a8bcad9aaa0 --- /dev/null +++ b/app/models/concerns/participable.rb @@ -0,0 +1,65 @@ +# == Participable concern +# +# Contains functionality related to objects that can have participants, such as +# an author, an assignee and people mentioned in its description or comments. +# +# Used by Issue, Note, MergeRequest, Snippet and Commit. +# +# Usage: +# +# class Issue < ActiveRecord::Base +# include Participable +# +# # ... +# +# participant :author, :assignee, :mentioned_users, :notes +# end +# +# issue = Issue.last +# users = issue.participants +# # `users` will contain the issue's author, its assignee, +# # all users returned by its #mentioned_users method, +# # as well as all participants to all of the issue's notes, +# # since Note implements Participable as well. +# +module Participable + extend ActiveSupport::Concern + + module ClassMethods + def participant(*attrs) + participant_attrs.concat(attrs.map(&:to_s)) + end + + def participant_attrs + @participant_attrs ||= [] + end + end + + def participants(current_user = self.author) + self.class.participant_attrs.flat_map do |attr| + meth = method(attr) + + value = + if meth.arity == 1 + meth.call(current_user) + else + meth.call + end + + participants_for(value, current_user) + end.compact.uniq + end + + private + + def participants_for(value, current_user = nil) + case value + when User + [value] + when Enumerable, ActiveRecord::Relation + value.flat_map { |v| participants_for(v, current_user) } + when Participable + value.participants(current_user) + end + end +end diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 315d96af1b9fa04bd8de97d44cff861fc81429d8..e9fd441352d85535c0f341f2c18069428da15c83 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -30,12 +30,15 @@ class WebHook < ActiveRecord::Base validates :url, presence: true, format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" } - def execute(data) + def execute(data, hook_name) parsed_url = URI.parse(url) if parsed_url.userinfo.blank? WebHook.post(url, body: data.to_json, - headers: { "Content-Type" => "application/json" }, + headers: { + "Content-Type" => "application/json", + "X-Gitlab-Event" => hook_name.singularize.titleize + }, verify: false) else post_url = url.gsub("#{parsed_url.userinfo}@", "") @@ -45,7 +48,10 @@ def execute(data) } WebHook.post(post_url, body: data.to_json, - headers: { "Content-Type" => "application/json" }, + headers: { + "Content-Type" => "application/json", + "X-Gitlab-Event" => hook_name.singularize.titleize + }, verify: false, basic_auth: auth) end @@ -54,7 +60,7 @@ def execute(data) false end - def async_execute(data) - Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data) + def async_execute(data, hook_name) + Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data, hook_name) end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b92834e5e3f2bc91cf0b646a13ff4c0860329de0..fab3fb259f2881647b8a1a690578e49c68a6c012 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -216,10 +216,13 @@ def mr_and_commit_notes commits_for_notes_limit = 100 commit_ids = commits.last(commits_for_notes_limit).map(&:id) - project.notes.where( - "(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", + Note.where( + "(project_id = :target_project_id AND noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR" + + "(project_id = :source_project_id AND noteable_type = 'Commit' AND commit_id IN (:commit_ids))", mr_id: id, - commit_ids: commit_ids + commit_ids: commit_ids, + target_project_id: target_project_id, + source_project_id: source_project_id ) end @@ -245,7 +248,7 @@ def hook_attrs } unless last_commit.nil? - attrs.merge!(last_commit: last_commit.hook_attrs(source_project)) + attrs.merge!(last_commit: last_commit.hook_attrs) end attributes.merge!(attrs) @@ -262,7 +265,7 @@ def project # Return the set of issues that will be closed if this merge request is accepted. def closes_issues(current_user = self.author) if target_branch == project.default_branch - issues = commits.flat_map { |c| c.closes_issues(project, current_user) } + issues = commits.flat_map { |c| c.closes_issues(current_user) } issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user). closed_by_message(description)) issues.uniq.sort_by(&:id) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index acac1ca4cf7ca2e3ccabbf70e754f6402d730e19..df1c2b78758225484d2e73c5615a386c784ee266 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -67,7 +67,7 @@ def dump_commits(commits) end def load_commits(array) - array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash)) } + array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) } end def dump_diffs(diffs) @@ -88,7 +88,7 @@ def unmerged_commits commits = compare_result.commits if commits.present? - commits = Commit.decorate(commits). + commits = Commit.decorate(commits, merge_request.source_project). sort_by(&:created_at). reverse end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index e1de114375e63ce012d3d5eb93d57bf99a30322a..211dfa76b811f34ffe9f8763d4f7b185d057d95a 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -60,15 +60,24 @@ def search(query) def clean_path(path) path = path.dup + # Get the email username by removing everything after an `@` sign. path.gsub!(/@.*\z/, "") + # Usernames can't end in .git, so remove it. path.gsub!(/\.git\z/, "") + # Remove dashes at the start of the username. path.gsub!(/\A-+/, "") + # Remove periods at the end of the username. path.gsub!(/\.+\z/, "") + # Remove everything that's not in the list of allowed characters. path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") + # Users with the great usernames of "." or ".." would end up with a blank username. + # Work around that by setting their username to "blank", followed by a counter. + path = "blank" if path.blank? + counter = 0 base = path - while Namespace.by_path(path).present? + while Namespace.find_by_path_or_name(path) counter += 1 path = "#{base}#{counter}" end diff --git a/app/models/note.rb b/app/models/note.rb index 8df55237dc5bf008c8fb65724f7d78660d7eed2b..c06dc1579fbad9a0fd468c770459c91197f97b9d 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -23,10 +23,12 @@ class Note < ActiveRecord::Base include Mentionable include Gitlab::CurrentSettings + include Participable default_value_for :system, false attr_mentionable :note + participant :author, :mentioned_users belongs_to :project belongs_to :noteable, polymorphic: true @@ -77,11 +79,11 @@ def create_status_change_note(noteable, project, author, status, source) # +mentioner+'s description or an associated Note. # Create a system Note associated with +noteable+ with a GFM back-reference # to +mentioner+. - def create_cross_reference_note(noteable, mentioner, author, project) - gfm_reference = mentioner_gfm_ref(noteable, mentioner, project) + def create_cross_reference_note(noteable, mentioner, author) + gfm_reference = mentioner_gfm_ref(noteable, mentioner) note_options = { - project: project, + project: noteable.project, author: author, note: cross_reference_note_content(gfm_reference), system: true @@ -241,7 +243,7 @@ def cross_reference_disallowed?(noteable, mentioner) # Determine whether or not a cross-reference note already exists. def cross_reference_exists?(noteable, mentioner) - gfm_reference = mentioner_gfm_ref(noteable, mentioner) + gfm_reference = mentioner_gfm_ref(noteable, mentioner, true) notes = if noteable.is_a?(Commit) where(commit_id: noteable.id, noteable_type: 'Commit') else @@ -274,43 +276,19 @@ def cross_reference_note_pattern(gfm_reference) # Prepend the mentioner's namespaced project path to the GFM reference for # cross-project references. For same-project references, return the # unmodified GFM reference. - def mentioner_gfm_ref(noteable, mentioner, project = nil) - if mentioner.is_a?(Commit) - if project.nil? - return mentioner.gfm_reference.sub('commit ', 'commit %') - else - mentioning_project = project - end - else - mentioning_project = mentioner.project - end - - noteable_project_id = noteable_project_id(noteable, mentioning_project) - - full_gfm_reference(mentioning_project, noteable_project_id, mentioner) - end - - # Return the ID of the project that +noteable+ belongs to, or nil if - # +noteable+ is a commit and is not part of the project that owns - # +mentioner+. - def noteable_project_id(noteable, mentioning_project) - if noteable.is_a?(Commit) - if mentioning_project.repository.commit(noteable.id) - # The noteable commit belongs to the mentioner's project - mentioning_project.id - else - nil - end - else - noteable.project.id + def mentioner_gfm_ref(noteable, mentioner, cross_reference = false) + if mentioner.is_a?(Commit) && cross_reference + return mentioner.gfm_reference.sub('commit ', 'commit %') end + + full_gfm_reference(mentioner.project, noteable.project, mentioner) end # Return the +mentioner+ GFM reference. If the mentioner and noteable # projects are not the same, add the mentioning project's path to the # returned value. - def full_gfm_reference(mentioning_project, noteable_project_id, mentioner) - if mentioning_project.id == noteable_project_id + def full_gfm_reference(mentioning_project, noteable_project, mentioner) + if mentioning_project == noteable_project mentioner.gfm_reference else if mentioner.is_a?(Commit) @@ -517,7 +495,7 @@ def for_project_snippet? # override to return commits, which are not active record def noteable if for_commit? - project.repository.commit(commit_id) + project.commit(commit_id) else super end diff --git a/app/models/project.rb b/app/models/project.rb index 46713511e53285c16ea50760998b648cdff9f28e..e6a9cd0c3e4aae2530feba74f9dcc244db9676cb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -259,7 +259,11 @@ def team end def repository - @repository ||= Repository.new(path_with_namespace) + @repository ||= Repository.new(path_with_namespace, nil, self) + end + + def commit(id = 'HEAD') + repository.commit(id) end def saved? @@ -492,7 +496,7 @@ def path_with_namespace def execute_hooks(data, hooks_scope = :push_hooks) hooks.send(hooks_scope).each do |hook| - hook.async_execute(data) + hook.async_execute(data, hooks_scope.to_s) end if group group.hooks.send(hooks_scope).each do |hook| @@ -708,11 +712,21 @@ def group_ldap_synced? end def create_repository - if gitlab_shell.add_repository(path_with_namespace) - true + if forked? + if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path) + ensure_satellite_exists + true + else + errors.add(:base, 'Failed to fork repository') + false + end else - errors.add(:base, 'Failed to create repository') - false + if gitlab_shell.add_repository(path_with_namespace) + true + else + errors.add(:base, 'Failed to create repository') + false + end end end diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 07520eab5d13164793824b186ecb693adb4314cd..2fa5f0ce71c275c21f56baaa6a594c27f9ed5849 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -20,7 +20,7 @@ class HipchatService < Service MAX_COMMITS = 3 - prop_accessor :token, :room, :server + prop_accessor :token, :room, :server, :notify, :color validates :token, presence: true, if: :activated? def title @@ -39,6 +39,8 @@ def fields [ { type: 'text', name: 'token', placeholder: 'Room token' }, { type: 'text', name: 'room', placeholder: 'Room name or ID' }, + { type: 'checkbox', name: 'notify' }, + { type: 'select', name: 'color', choices: ['yellow', 'red', 'green', 'purple', 'gray', 'random'] }, { type: 'text', name: 'server', placeholder: 'Leave blank for default. https://hipchat.example.com' } ] @@ -52,7 +54,7 @@ def execute(data) return unless supported_events.include?(data[:object_kind]) message = create_message(data) return unless message.present? - gate[room].send('GitLab', message) + gate[room].send('GitLab', message, message_options) end private @@ -63,6 +65,10 @@ def gate @gate ||= HipChat::Client.new(token, options) end + def message_options + { notify: notify.present? && notify == '1', color: color || 'yellow' } + end + def create_message(data) object_kind = data[:object_kind] diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 772c868d9cd0088307f7bab85d8166878a528a1c..0706a1ca0d12d485de81e6052e3f7e71fa38b7c5 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -112,7 +112,7 @@ def search_files(query) end def repository - Repository.new(path_with_namespace, default_branch) + Repository.new(path_with_namespace, default_branch, @project) end def default_branch diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 97207ba12726c4cd6179f6e623d5a447ff0de39f..8ebd790a89e1be20e12c4a6764f1cc4709a4441e 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -18,6 +18,6 @@ class ProtectedBranch < ActiveRecord::Base validates :project, presence: true def commit - project.repository.commit(self.name) + project.commit(self.name) end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 263a436d5210469c5566684a5fbb2b7a52129f7c..1b8c74028d9aa1f6d27bab3717f972a488566d3b 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1,11 +1,12 @@ class Repository include Gitlab::ShellAdapter - attr_accessor :raw_repository, :path_with_namespace + attr_accessor :raw_repository, :path_with_namespace, :project - def initialize(path_with_namespace, default_branch = nil) + def initialize(path_with_namespace, default_branch = nil, project = nil) @path_with_namespace = path_with_namespace @raw_repository = Gitlab::Git::Repository.new(path_to_repo) if path_with_namespace + @project = project rescue Gitlab::Git::Repository::NoRepository nil end @@ -28,7 +29,7 @@ def empty? def commit(id = 'HEAD') return nil unless raw_repository commit = Gitlab::Git::Commit.find(raw_repository, id) - commit = Commit.new(commit) if commit + commit = Commit.new(commit, @project) if commit commit rescue Rugged::OdbError nil @@ -42,13 +43,13 @@ def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false) limit: limit, offset: offset, ) - commits = Commit.decorate(commits) if commits.present? + commits = Commit.decorate(commits, @project) if commits.present? commits end def commits_between(from, to) commits = Gitlab::Git::Commit.between(raw_repository, from, to) - commits = Commit.decorate(commits) if commits.present? + commits = Commit.decorate(commits, @project) if commits.present? commits end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index c11c28805ebdd4a4c4cf5298451e95c02613daa0..d2af26539b6444627fb30f43ab13053d9ba2e54a 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -19,6 +19,7 @@ class Snippet < ActiveRecord::Base include Sortable include Linguist::BlobHelper include Gitlab::VisibilityLevel + include Participable default_value_for :visibility_level, Snippet::PRIVATE @@ -47,6 +48,8 @@ class Snippet < ActiveRecord::Base scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } + participant :author, :notes + def self.content_types [ ".rb", ".py", ".pl", ".scala", ".c", ".cpp", ".java", @@ -87,18 +90,6 @@ def visibility_level_field visibility_level end - def participants(current_user = self.author) - users = [] - users << author - - notes.each do |note| - users << note.author - users.push *note.mentioned_users(current_user) - end - - users.uniq - end - class << self def search(query) where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%") diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb index 25f9e2032464665f4137b817f6ac2c12dc3cab96..1a7318048b3323ce8a32327cb7638c2d3e4570df 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -38,7 +38,7 @@ def success(branch) end def create_push_data(project, user, tag) - commits = [project.repository.commit(tag.target)].compact + commits = [project.commit(tag.target)].compact Gitlab::PushDataBuilder. build(project, user, Gitlab::Git::BLANK_SHA, tag.target, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", commits, tag.message) end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 25e5dbe619816490da25bda219ddb5e84b19f80f..e8274b5d367b077185a6a66567e450490f6bd33e 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -70,7 +70,7 @@ def process_commit_messages(ref) # 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. - issues_to_close = commit.closes_issues(project, user) + issues_to_close = commit.closes_issues(user) # Load commit author only if needed. # For push with 1k commits it prevents 900+ requests in database @@ -98,7 +98,7 @@ def process_commit_messages(ref) author ||= commit_user(commit) refs.each do |r| - Note.create_cross_reference_note(r, commit, author, project) + Note.create_cross_reference_note(r, commit, author) end end end diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index bf203bbd69272c1f80103403ae87b6a7937abb02..075a6118da2054f285c90720f87dc4b392a04faa 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -25,7 +25,7 @@ def build_push_data(oldrev, newrev, ref) tag_name = Gitlab::Git.ref_name(ref) tag = project.repository.find_tag(tag_name) if tag && tag.target == newrev - commit = project.repository.commit(tag.target) + commit = project.commit(tag.target) commits = [commit].compact message = tag.message end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 493b4bc1e415dfee106c4b492deecd460b2357ae..02503f0872768e68394fa045e40aac009911d04b 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -29,7 +29,7 @@ def execute # At this point we decide if merge request can be created # If we have at least one commit to merge -> creation allowed if commits.present? - merge_request.compare_commits = Commit.decorate(commits) + merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project) merge_request.can_be_created = true merge_request.compare_failed = false diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index e969061f229d8a8ee6ce266cdf9a88a50c79af45..d19a6c2eca32bca8121117f0fc123c2e34af1c4e 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -15,7 +15,7 @@ def execute # Create a cross-reference note if this Note contains GFM that names an # issue, merge request, or commit. note.references.each do |mentioned| - Note.create_cross_reference_note(mentioned, note.noteable, note.author, note.project) + Note.create_cross_reference_note(mentioned, note.noteable, note.author) end execute_hooks(note) diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 63431b82471ee5e16a9b1d1283dccc85d6ac957a..45a0db761ec4b5783394141f3892658e35d72ebc 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -13,8 +13,7 @@ def execute # Create a cross-reference note if this Note contains GFM that # names an issue, merge request, or commit. note.references.each do |mentioned| - Note.create_cross_reference_note(mentioned, note.noteable, - note.author, note.project) + Note.create_cross_reference_note(mentioned, note.noteable, note.author) end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index c7e45a2c2c705ab1addf21ef552c16f4d3f9405f..0d7ffbeebd9a68b045b77a8b62d6893fd51a266a 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -129,9 +129,7 @@ def new_note(note) # Add all users participating in the thread (author, assignee, comment authors) participants = - if target.is_a?(Commit) - target.participants(note.project, note.author) - elsif target.respond_to?(:participants) + if target.respond_to?(:participants) target.participants(note.author) else note.mentioned_users diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index d798d3c3093eda1f4c6233405136dfc31cb91be5..66d61a5ef5b11462c635301dd510d9e01286efb1 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -5,6 +5,8 @@ def initialize(user, params) end def execute + forked_from_project_id = params.delete(:forked_from_project_id) + @project = Project.new(params) # Make sure that the user is allowed to use the specified visibility @@ -45,10 +47,14 @@ def execute @project.creator = current_user + if forked_from_project_id + @project.build_forked_project_link(forked_from_project_id: forked_from_project_id) + end + Project.transaction do @project.save - unless @project.import? + if @project.persisted? && !@project.import? unless @project.create_repository raise 'Failed to create repository' end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 1e4deb6ed39babde42725d703f7206fd2c14011a..50f208b11d15c396c3ecb1f253e6e28d7e52245f 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -1,66 +1,28 @@ module Projects class ForkService < BaseService - include Gitlab::ShellAdapter - def execute - @from_project = @project - - project_params = { - visibility_level: @from_project.visibility_level, - description: @from_project.description, + new_params = { + forked_from_project_id: @project.id, + visibility_level: @project.visibility_level, + description: @project.description, + name: @project.name, + path: @project.path, + namespace_id: @params[:namespace].try(:id) || current_user.namespace.id } - project = Project.new(project_params) - project.name = @from_project.name - project.path = @from_project.path - project.creator = @current_user - if @from_project.avatar.present? && @from_project.avatar.image? - project.avatar = @from_project.avatar - end - - if namespace = @params[:namespace] - project.namespace = namespace - else - project.namespace = @current_user.namespace + if @project.avatar.present? && @project.avatar.image? + new_params[:avatar] = @project.avatar end - unless @current_user.can?(:create_projects, project.namespace) - project.errors.add(:namespace, 'insufficient access rights') - return project - end - - # If the project cannot save, we do not want to trigger the project destroy - # as this can have the side effect of deleting a repo attached to an existing - # project with the same name and namespace - if project.valid? - begin - Project.transaction do - #First save the DB entries as they can be rolled back if the repo fork fails - project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id) - if project.save - project.team << [@current_user, :master, @current_user] - end - - #Now fork the repo - unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path) - raise 'forking failed in gitlab-shell' - end - - project.ensure_satellite_exists - end + new_project = CreateService.new(current_user, new_params).execute - if @from_project.gitlab_ci? - ForkRegistrationWorker.perform_async(@from_project.id, project.id, @current_user.private_token) - end - rescue => ex - project.errors.add(:base, 'Fork transaction failed.') - project.destroy + if new_project.persisted? + if @project.gitlab_ci? + ForkRegistrationWorker.perform_async(@project.id, new_project.id, @current_user.private_token) end - else - project.errors.add(:base, 'Invalid fork destination') end - project + new_project end end end diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index c2d8f48f6e420ee0b55e3f8671d036a24052e4d2..b91590a1a9019af31e5149c7c64a712846457088 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -13,21 +13,19 @@ def execute(note_type, note_id) end def participants_in(type, id) - users = + target = case type when "Issue" - issue = project.issues.find_by_iid(id) - issue.participants(current_user) if issue + project.issues.find_by_iid(id) when "MergeRequest" - merge_request = project.merge_requests.find_by_iid(id) - merge_request.participants(current_user) if merge_request + project.merge_requests.find_by_iid(id) when "Commit" - commit = project.repository.commit(id) - commit.participants(project, current_user) if commit + project.commit(id) end + + return [] unless target - return [] unless users - + users = target.participants(current_user) sorted(users) end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index c5d0b08845ba79d1c3131f47d3b8a6ba10e9d0c6..60235b6be2ab1abe4e51058d721e02671860f9b6 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -7,12 +7,12 @@ def execute_hooks_for(model, event) def execute_hooks(data) SystemHook.all.each do |sh| - async_execute_hook sh, data + async_execute_hook(sh, data, 'system_hooks') end end - def async_execute_hook(hook, data) - Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data) + def async_execute_hook(hook, data, hook_name) + Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data, hook_name) end def build_event_data(model, event) diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb index ab661984fc9ca15257c717b6f4a8b2cf30ed19f6..5b447909def4a2579e4aae7ebce12af92eeff5c4 100644 --- a/app/services/test_hook_service.rb +++ b/app/services/test_hook_service.rb @@ -1,7 +1,7 @@ class TestHookService def execute(hook, current_user) data = Gitlab::PushDataBuilder.build_sample(project(hook), current_user) - hook.execute(data) + hook.execute(data, 'push_hooks') end private diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index de64e2e522297ef0928beafe83fb817f86ba3ed5..3f41504b9dbb5036652f51c5e72545f7689daccc 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -42,6 +42,14 @@ = f.label :default_branch_protection, class: 'control-label col-sm-2' .col-sm-10 = f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control' + .form-group + = f.label :default_project_visibility, class: 'control-label col-sm-2' + .col-sm-10 + = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: 'Project') + .form-group + = f.label :default_snippet_visibility, class: 'control-label col-sm-2' + .col-sm-10 + = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: 'Snippet') .form-group = f.label :restricted_visibility_levels, class: 'control-label col-sm-2' .col-sm-10 diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index c1fc1602d0a2a4bea19efffb85df499d9836c229..ba49013d834f15aa3a5b9c92c1181a587faf9582 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -1,4 +1,14 @@ -= render "events/event_last_push", event: @last_push -= render 'shared/event_filter' +.hidden-xs + = render "events/event_last_push", event: @last_push + + - if current_user + %ul.nav.nav-pills.event_filter.pull-right + %li.pull-right + = link_to dashboard_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do + %i.fa.fa-rss + Activity Feed + + = render 'shared/event_filter' + %hr .content_list = spinner diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index 72e9e361dc3d4f984b8cae48ff87e34baddf4650..6e88fc9be409c9c0041be659ba640be100fe2bef 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -1,9 +1,9 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{current_user.name} issues" - xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" - xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html" - xml.id issues_dashboard_url(private_token: current_user.private_token) + xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html" + xml.id issues_dashboard_url xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index db19a46cb263dd075e79dbcd6dc1e62e4396d5a7..62cc80a30dc866d706c7468df14a816c63e5cb2b 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,3 +1,7 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues") + %h3.page-title Issues @@ -6,5 +10,11 @@ %hr .append-bottom-20 + .pull-right + - if current_user + .hidden-xs.pull-left + = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do + %i.fa.fa-rss + = render 'shared/issuable_filter' = render 'shared/issues' diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder index da631ecb33e00f01a6f08002da08d78b8f47464c..71edb73cd8a2d93221f44ded6bef637d9e48963c 100644 --- a/app/views/dashboard/show.atom.builder +++ b/app/views/dashboard/show.atom.builder @@ -1,9 +1,9 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do - xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}" - xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml" + xml.title "Activity" + xml.link href: dashboard_url(format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" xml.link href: dashboard_url, rel: "alternate", type: "text/html" - xml.id projects_url + xml.id dashboard_url xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index fa8946011b741331df864f866420150af171f20c..2754a4894da89dbb99617ea61a7be2eaa0e775c9 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -1,3 +1,7 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity") + - if @projects.any? .dashboard.row %section.activities.col-md-8 diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml index 552525f4a0783257f881e7dcd125ccdfc986301c..c2577a249824d165272c23eb4d9d3ccf891c89f1 100644 --- a/app/views/events/event/_created_project.html.haml +++ b/app/views/events/event/_created_project.html.haml @@ -18,7 +18,7 @@ %a.twitter-share-button{ | href: "https://twitter.com/share", | "data-url" => event.project.web_url, | - "data-text" => "I just #{event.project.imported? ? "imported" : "created"} a new project in GitLab! GitLab is version control on your server.", | + "data-text" => "I just #{event.action_name} a new project on GitLab! GitLab is version control on your server.", | "data-size" => "medium", | "data-related" => "gitlab", | "data-hashtags" => "gitlab", | diff --git a/app/views/groups/_settings_nav.html.haml b/app/views/groups/_settings_nav.html.haml index 6c4a7572cece0753187a8164a324a287eef8e780..b660e7e7512bd428feeff16df94510c6837bdcb8 100644 --- a/app/views/groups/_settings_nav.html.haml +++ b/app/views/groups/_settings_nav.html.haml @@ -1,12 +1,12 @@ %ul.sidebar-subnav = nav_link(path: 'groups#edit') do - = link_to edit_group_path(@group), title: 'Group' do - %i.fa.fa-pencil-square-o + = link_to edit_group_path(@group), title: 'Group', data: {placement: 'right'} do + = icon('pencil-square-o') %span Group = nav_link(path: 'groups#projects') do - = link_to projects_group_path(@group), title: 'Projects' do - %i.fa.fa-folder + = link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do + = icon('folder') %span Projects - if ldap_enabled? diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder index 240001967f36a8e55664e6c7d99da2c95634f2db..66fe7e25871ab3c55b4c730b5258b8a8015421b6 100644 --- a/app/views/groups/issues.atom.builder +++ b/app/views/groups/issues.atom.builder @@ -1,9 +1,9 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{@user.name} issues" - xml.link :href => issues_dashboard_url(:atom, :private_token => @user.private_token), :rel => "self", :type => "application/atom+xml" - xml.link :href => issues_dashboard_url(:private_token => @user.private_token), :rel => "alternate", :type => "text/html" - xml.id issues_dashboard_url(:private_token => @user.private_token) + xml.link href: issues_dashboard_url(format: :atom, private_token: @user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html" + xml.id issues_dashboard_url xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 6c0d89c4e7cbf7e67aecb1fd8333a7526c50de86..cf0da2da46696b37a2f34f9c0750c94a69bb5d15 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,3 +1,7 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues") + %h3.page-title Issues @@ -10,5 +14,11 @@ %hr .append-bottom-20 + .pull-right + - if current_user + .hidden-xs.pull-left + = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do + %i.fa.fa-rss + = render 'shared/issuable_filter' = render 'shared/issues' diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index c78bd1bd2637f2412f6f1f90a346ea86fe495e57..b52e78faaa3037da5d5c7fd5bca9eb415ac8a8d7 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -1,9 +1,9 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do - xml.title "Group feed - #{@group.name}" - xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml" - xml.link href: group_path(@group), rel: "alternate", type: "text/html" - xml.id projects_url + xml.title "#{@group.name} activity" + xml.link href: group_url(@group, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: group_url(@group), rel: "alternate", type: "text/html" + xml.id group_url(@group) xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index a4660851da9c5b91473e0fcd4d837cbd46d2e435..872a498463e851172c058826b3357b00e0b17428 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,3 +1,7 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") + .dashboard .header-with-avatar.clearfix = image_tag group_icon(@group), class: "avatar group-avatar s90" @@ -11,9 +15,20 @@ %hr .row %section.activities.col-md-8 - - if current_user - = render "events/event_last_push", event: @last_push - = render 'shared/event_filter' + .hidden-xs + - if current_user + = render "events/event_last_push", event: @last_push + + - if current_user + %ul.nav.nav-pills.event_filter.pull-right + %li + = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do + %i.fa.fa-rss + Activity Feed + + = render 'shared/event_filter' + %hr + .content_list = spinner %aside.side.col-md-4 diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 03830e7ec781330284ff6402a9298415e66eec87..fd4165d6b642ef426041816cc41cb2d0c0a50bd1 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -5,6 +5,7 @@ %title = "#{title} | " if defined?(title) GitLab + = favicon_link_tag 'favicon.ico' = stylesheet_link_tag "application", :media => "all" = stylesheet_link_tag "print", :media => "print" @@ -14,16 +15,8 @@ %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} %meta{name: 'theme-color', content: '#474D57'} + = yield(:meta_tags) + = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') = render 'layouts/bootlint' if Rails.env.development? - - -# Atom feed - - if current_user - - if controller_name == 'projects' && action_name == 'index' - = auto_discovery_link_tag :atom, projects_url(:atom, private_token: current_user.private_token), title: "Dashboard feed" - - if @project && !@project.new_record? - - if current_controller?(:tree, :commits) - = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}") - - if current_controller?(:issues) - = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 281ce31fe9c1ed9d5a6f9676c289201435c6e1b7..8d4c7b39b19b0c0656d22aaf4bb37c01c8b07ccd 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -2,48 +2,47 @@ .navbar-inner .container %div.app_logo - = link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do + = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do = brand_header_logo %h3 GitLab %h1.title= title - %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} + %button.navbar-toggle{type: 'button', data: {target: '.navbar-collapse', toggle: 'collapse'}} %span.sr-only Toggle navigation - %i.fa.fa-bars + = icon('bars') .navbar-collapse.collapse %ul.nav.navbar-nav %li.hidden-sm.hidden-xs - = render "layouts/search" + = render 'layouts/search' %li.visible-sm.visible-xs - = link_to search_path, title: "Search", class: 'has_bottom_tooltip', 'data-original-title' => 'Search area' do - %i.fa.fa-search + = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('search') %li - = link_to help_path, title: 'Help', class: 'has_bottom_tooltip', - 'data-original-title' => 'Help' do - %i.fa.fa-question-circle + = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('question-circle') %li - = link_to explore_root_path, title: "Explore", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do - %i.fa.fa-globe + = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('globe') %li - = link_to user_snippets_path(current_user), title: "Your snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'Your snippets' do - %i.fa.fa-clipboard + = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('clipboard') - if current_user.is_admin? %li - = link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do - %i.fa.fa-cogs + = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('cogs') - if current_user.can_create_project? %li - = link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do - %i.fa.fa-plus + = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('plus') %li - = link_to profile_path, title: "Profile settings", class: 'has_bottom_tooltip', 'data-original-title' => 'Profile settings"' do - %i.fa.fa-user + = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('user') %li - = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Sign out", class: 'has_bottom_tooltip', 'data-original-title' => 'Sign out' do - %i.fa.fa-sign-out + = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('sign-out') %li.hidden-xs - = link_to current_user, class: "profile-pic has_bottom_tooltip", id: 'profile-pic', 'data-original-title' => 'Your profile' do + = link_to current_user, class: 'profile-pic', id: 'profile-pic', data: {toggle: 'tooltip', placement: 'bottom'} do = image_tag avatar_icon(current_user.email, 60), alt: 'User activity' = render 'shared/outdated_browser' diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 1c164800b0e686d335484fcafa254620d8f436b1..0fa2ec9824dc44f2be60197f0d1d008d4810a217 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -15,7 +15,3 @@ = yield = yield :embedded_scripts - -:coffeescript - $('.page-sidebar-collapsed .nav-sidebar a').tooltip placement: "right" - diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index dc90d29c66ea65fd60b547898dc8427ca79b7098..fda6880163d8cf3d7ce7cac49fb07f7aa784b617 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -5,37 +5,37 @@ %span Overview = nav_link(controller: :projects) do - = link_to admin_namespaces_projects_path, title: 'Projects' do + = link_to admin_namespaces_projects_path, title: 'Projects', data: {placement: 'right'} do = icon('cube fw') %span Projects = nav_link(controller: :users) do - = link_to admin_users_path, title: 'Users' do + = link_to admin_users_path, title: 'Users', data: {placement: 'right'} do = icon('user fw') %span Users = nav_link(controller: :groups) do - = link_to admin_groups_path, title: 'Groups' do + = link_to admin_groups_path, title: 'Groups', data: {placement: 'right'} do = icon('group fw') %span Groups = nav_link(controller: :deploy_keys) do - = link_to admin_deploy_keys_path, title: 'Deploy Keys' do + = link_to admin_deploy_keys_path, title: 'Deploy Keys', data: {placement: 'right'} do = icon('key fw') %span Deploy Keys = nav_link(controller: :logs) do - = link_to admin_logs_path, title: 'Logs' do + = link_to admin_logs_path, title: 'Logs', data: {placement: 'right'} do = icon('file-text fw') %span Logs = nav_link(controller: :broadcast_messages) do - = link_to admin_broadcast_messages_path, title: 'Broadcast Messages' do + = link_to admin_broadcast_messages_path, title: 'Broadcast Messages', data: {placement: 'right'} do = icon('bullhorn fw') %span Messages = nav_link(controller: :hooks) do - = link_to admin_hooks_path, title: 'Hooks' do + = link_to admin_hooks_path, title: 'Hooks', data: {placement: 'right'} do = icon('external-link fw') %span Hooks @@ -45,7 +45,7 @@ %span Git Hooks = nav_link(controller: :background_jobs) do - = link_to admin_background_jobs_path, title: 'Background Jobs' do + = link_to admin_background_jobs_path, title: 'Background Jobs', data: {placement: 'right'} do = icon('cog fw') %span Background Jobs @@ -56,19 +56,19 @@ Appearance = nav_link(controller: :applications) do - = link_to admin_applications_path, title: 'Applications' do + = link_to admin_applications_path, title: 'Applications', data: {placement: 'right'} do = icon('cloud fw') %span Applications = nav_link(controller: :services) do - = link_to admin_application_settings_services_path, title: 'Service Templates' do + = link_to admin_application_settings_services_path, title: 'Service Templates', data: {placement: 'right'} do = icon('copy fw') %span Service Templates = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do - = link_to admin_application_settings_path, title: 'Settings' do + = link_to admin_application_settings_path, title: 'Settings', data: {placement: 'right'} do = icon('cogs fw') %span Settings diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index c5997e44370bcdbaa2a01b038dcf756812261dc9..d46dba4a240544f0f38876643eab2b10fa5b3a36 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,38 +1,38 @@ %ul.nav.nav-sidebar = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do - = link_to root_path, title: 'Home', class: 'shortcuts-activity' do + = link_to root_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do = icon('dashboard fw') %span Your Projects = nav_link(path: 'projects#starred') do - = link_to starred_dashboard_projects_path, title: 'Starred Projects' do + = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do = icon('star fw') %span Starred Projects = nav_link(controller: :groups) do - = link_to dashboard_groups_path, title: 'Groups' do + = link_to dashboard_groups_path, title: 'Groups', data: {placement: 'right'} do = icon('group fw') %span Groups = nav_link(controller: :milestones) do - = link_to dashboard_milestones_path, title: 'Milestones' do + = link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do = icon('clock-o fw') %span Milestones = nav_link(path: 'dashboard#issues') do - = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do + = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do = icon('exclamation-circle fw') %span Issues %span.count= current_user.assigned_issues.opened.count = nav_link(path: 'dashboard#merge_requests') do - = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do + = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do = icon('tasks fw') %span Merge Requests %span.count= current_user.assigned_merge_requests.opened.count = nav_link(controller: :help) do - = link_to help_path, title: 'Help' do + = link_to help_path, title: 'Help', data: {placement: 'right'} do = icon('question-circle fw') %span Help diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml index 38d84cd9b51f00dfd22a70b1339b56e4996c0c2d..66870e84ceb1470f97b960e440542c60a8cd472c 100644 --- a/app/views/layouts/nav/_explore.html.haml +++ b/app/views/layouts/nav/_explore.html.haml @@ -1,18 +1,18 @@ %ul.nav.nav-sidebar = nav_link(path: 'projects#trending') do - = link_to explore_root_path do + = link_to explore_root_path, title: 'Trending Projects', data: {placement: 'right'} do = icon('comments fw') %span Trending Projects = nav_link(path: 'projects#starred') do - = link_to starred_explore_projects_path do + = link_to starred_explore_projects_path, title: 'Most-starred Projects', data: {placement: 'right'} do = icon('star fw') - %span Most Starred Projects + %span Most-starred Projects = nav_link(path: 'projects#index') do - = link_to explore_projects_path do + = link_to explore_projects_path, title: 'All Projects', data: {placement: 'right'} do = icon('bookmark fw') %span All Projects = nav_link(controller: :groups) do - = link_to explore_groups_path do + = link_to explore_groups_path, title: 'All Groups', data: {placement: 'right'} do = icon('group fw') %span All Groups diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 7cce9ffe3d516e13c58bac0885f136847e54cfad..74a8526dbd7f10a62c7729755fb94a618936f737 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,38 +1,38 @@ %ul.nav.nav-sidebar = nav_link(path: 'groups#show', html_options: {class: 'home'}) do - = link_to group_path(@group), title: "Home" do + = link_to group_path(@group), title: 'Home', data: {placement: 'right'} do = icon('dashboard fw') %span Activity - if current_user = nav_link(controller: [:group, :milestones]) do - = link_to group_milestones_path(@group), title: 'Milestones' do + = link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do = icon('clock-o fw') %span Milestones = nav_link(path: 'groups#issues') do - = link_to issues_group_path(@group), title: 'Issues' do + = link_to issues_group_path(@group), title: 'Issues', data: {placement: 'right'} do = icon('exclamation-circle fw') %span Issues - if current_user %span.count= Issue.opened.of_group(@group).count = nav_link(path: 'groups#merge_requests') do - = link_to merge_requests_group_path(@group), title: 'Merge Requests' do + = link_to merge_requests_group_path(@group), title: 'Merge Requests', data: {placement: 'right'} do = icon('tasks fw') %span Merge Requests - if current_user %span.count= MergeRequest.opened.of_group(@group).count = nav_link(controller: [:group_members]) do - = link_to group_group_members_path(@group), title: 'Members' do + = link_to group_group_members_path(@group), title: 'Members', data: {placement: 'right'} do = icon('users fw') %span Members - if can?(current_user, :admin_group, @group) = nav_link(html_options: { class: "#{"active" if group_settings_page?} separate-item" }) do - = link_to edit_group_path(@group), title: 'Settings', class: "tab no-highlight" do + = link_to edit_group_path(@group), title: 'Settings', class: 'tab no-highlight', data: {placement: 'right'} do = icon ('cogs fw') %span Settings diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 9f2932c79cd0e53b0c4753143feddd5d7b9e3040..31d8ed3ed8639b1b065f0033d0606cc14649363b 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,50 +1,50 @@ %ul.nav.nav-sidebar = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do - = link_to profile_path, title: "Profile" do + = link_to profile_path, title: 'Profile', data: {placement: 'right'} do = icon('user fw') %span Profile = nav_link(controller: :accounts) do - = link_to profile_account_path, title: 'Account' do + = link_to profile_account_path, title: 'Account', data: {placement: 'right'} do = icon('gear fw') %span Account = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do - = link_to applications_profile_path, title: 'Applications' do + = link_to applications_profile_path, title: 'Applications', data: {placement: 'right'} do = icon('cloud fw') %span Applications = nav_link(controller: :emails) do - = link_to profile_emails_path, title: 'Emails' do + = link_to profile_emails_path, title: 'Emails', data: {placement: 'right'} do = icon('envelope-o fw') %span Emails %span.count= current_user.emails.count + 1 - unless current_user.ldap_user? = nav_link(controller: :passwords) do - = link_to edit_profile_password_path, title: 'Password' do + = link_to edit_profile_password_path, title: 'Password', data: {placement: 'right'} do = icon('lock fw') %span Password = nav_link(controller: :notifications) do - = link_to profile_notifications_path, title: 'Notifications' do + = link_to profile_notifications_path, title: 'Notifications', data: {placement: 'right'} do = icon('inbox fw') %span Notifications = nav_link(controller: :keys) do - = link_to profile_keys_path, title: 'SSH Keys' do + = link_to profile_keys_path, title: 'SSH Keys', data: {placement: 'right'} do = icon('key fw') %span SSH Keys %span.count= current_user.keys.count = nav_link(path: 'profiles#design') do - = link_to design_profile_path, title: 'Design' do + = link_to design_profile_path, title: 'Design', data: {placement: 'right'} do = icon('image fw') %span Design = nav_link(path: 'profiles#history') do - = link_to history_profile_path, title: 'History' do + = link_to history_profile_path, title: 'History', data: {placement: 'right'} do = icon('history fw') %span History diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index a2a9d8a340b96d6fbef47e21dfddbb71ebb2421b..01b3d70194fcb8225dfc969bf1c718099ba549a5 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,7 +1,7 @@ %ul.project-navigation.nav.nav-sidebar - if @project_settings_nav = nav_link do - = link_to project_path(@project), title: 'Back to project', class: "" do + = link_to project_path(@project), title: 'Back to project', data: {placement: 'right'} do = icon('caret-square-o-left fw') %span Back to project @@ -11,49 +11,49 @@ = render 'projects/settings_nav' - else - = nav_link(path: 'projects#show', html_options: {class: "home"}) do - = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do + = nav_link(path: 'projects#show', html_options: {class: 'home'}) do + = link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do = icon('dashboard fw') %span Project - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do - = link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree' do + = link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do = icon('files-o fw') %span Files - if project_nav_tab? :commits = nav_link(controller: %w(commit commits compare repositories tags branches)) do - = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits' do + = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do = icon('history fw') %span Commits - if project_nav_tab? :network = nav_link(controller: %w(network)) do - = link_to namespace_project_network_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network' do + = link_to namespace_project_network_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do = icon('code-fork fw') %span Network - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do - = link_to namespace_project_graph_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs' do + = link_to namespace_project_graph_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs', data: {placement: 'right'} do = icon('area-chart fw') %span Graphs - if project_nav_tab? :milestones = nav_link(controller: :milestones) do - = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do + = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones', data: {placement: 'right'} do = icon('clock-o fw') %span Milestones - if project_nav_tab? :issues = nav_link(controller: :issues) do - = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do + = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do = icon('exclamation-circle fw') %span Issues @@ -62,7 +62,7 @@ - if project_nav_tab? :merge_requests = nav_link(controller: :merge_requests) do - = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do + = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do = icon('tasks fw') %span Merge Requests @@ -70,28 +70,28 @@ - if project_nav_tab? :labels = nav_link(controller: :labels) do - = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do + = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels', data: {placement: 'right'} do = icon('tags fw') %span Labels - if project_nav_tab? :wiki = nav_link(controller: :wikis) do - = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do + = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki', data: {placement: 'right'} do = icon('book fw') %span Wiki - if project_nav_tab? :snippets = nav_link(controller: :snippets) do - = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do + = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} do = icon('file-text-o fw') %span Snippets - if project_nav_tab? :settings = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do - = link_to edit_project_path(@project), title: 'Settings', class: "stat-tab tab no-highlight" do + = link_to edit_project_path(@project), title: 'Settings', class: 'stat-tab tab no-highlight', data: {placement: 'right'} do = icon('cogs fw') %span Settings diff --git a/app/views/layouts/nav/_snippets.html.haml b/app/views/layouts/nav/_snippets.html.haml index edd05f2dd818fed95847fb108956c78caa92221e..0de3a9e5bb77ac87b798cbd293c5c88fbf33a27b 100644 --- a/app/views/layouts/nav/_snippets.html.haml +++ b/app/views/layouts/nav/_snippets.html.haml @@ -1,11 +1,11 @@ %ul.nav.nav-sidebar = nav_link(path: user_snippets_path(current_user), html_options: {class: 'home'}) do - = link_to user_snippets_path(current_user), title: 'Your snippets' do + = link_to user_snippets_path(current_user), title: 'Your snippets', data: {placement: 'right'} do = icon('dashboard fw') %span Your Snippets = nav_link(path: snippets_path) do - = link_to snippets_path, title: 'Discover snippets' do + = link_to snippets_path, title: 'Discover snippets', data: {placement: 'right'} do = icon('globe fw') %span Discover Snippets diff --git a/app/views/profiles/update.js.erb b/app/views/profiles/update.js.erb index e664ac2a52ab81cd0cb1f128664ff86909bb8ab3..db37619136d32a0ce7585e40a0ef52ab747dc14c 100644 --- a/app/views/profiles/update.js.erb +++ b/app/views/profiles/update.js.erb @@ -1,9 +1,3 @@ // Remove body class for any previous theme, re-add current one -$('body').removeClass('ui_basic ui_mars ui_modern ui_gray ui_color light_theme dark_theme') +$('body').removeClass('<%= Gitlab::Theme.body_classes %>') $('body').addClass('<%= app_theme %> <%= theme_type %>') - -// Re-render the header to reflect the new theme -$('header').html('<%= escape_javascript(render("layouts/head_panel", title: "Profile")) %>') - -// Re-initialize header tooltips -$('.has_bottom_tooltip').tooltip({placement: 'bottom'}) diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml index b45af073a7d415eebd2a6eaac2fc72ac5508f067..299c8ad713dc6ada0dd390aa7ac9770a06b14fc9 100644 --- a/app/views/projects/_settings_nav.html.haml +++ b/app/views/projects/_settings_nav.html.haml @@ -1,12 +1,12 @@ %ul.project-settings-nav.sidebar-subnav = nav_link(path: 'projects#edit') do - = link_to edit_project_path(@project), title: 'Project', class: "stat-tab tab " do - %i.fa.fa-pencil-square-o + = link_to edit_project_path(@project), title: 'Project', class: 'stat-tab tab', data: {placement: 'right'} do + = icon('pencil-square-o') %span Project = nav_link(controller: [:project_members, :teams]) do - = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: "team-tab tab" do - %i.fa.fa-users + = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do + = icon('users') %span Members = nav_link(controller: :group_links) do @@ -15,13 +15,13 @@ %span Groups = nav_link(controller: :deploy_keys) do - = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do - %i.fa.fa-key + = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys', data: {placement: 'right'} do + = icon('key') %span Deploy Keys = nav_link(controller: :hooks) do - = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks' do - %i.fa.fa-link + = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks', data: {placement: 'right'} do + = icon('link') %span Web Hooks = nav_link(controller: :git_hooks) do @@ -30,13 +30,13 @@ %span Git Hooks = nav_link(controller: :services) do - = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do - %i.fa.fa-cogs + = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services', data: {placement: 'right'} do + = icon('cogs') %span Services = nav_link(controller: :protected_branches) do - = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do - %i.fa.fa-lock + = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches', data: {placement: 'right'} do + = icon('lock') %span Protected branches = nav_link(controller: :audit_events) do diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml deleted file mode 100644 index 42c8e685224c022894f724ce9140d50d6be4273e..0000000000000000000000000000000000000000 --- a/app/views/projects/_visibility_level.html.haml +++ /dev/null @@ -1,27 +0,0 @@ -.form-group.project-visibility-level-holder - = f.label :visibility_level, class: 'control-label' do - Visibility Level - = link_to "(?)", help_page_path("public_access", "public_access") - .col-sm-10 - - if can_change_visibility_level - - Gitlab::VisibilityLevel.values.each do |level| - .radio - - restricted = restricted_visibility_levels.include?(level) - = label :project_visibility_level, level do - = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted - = visibility_level_icon(level) - .option-title - = visibility_level_label(level) - .option-descr - = visibility_level_description(level) - - unless restricted_visibility_levels.empty? - .col-sm-10 - %span.info - Some visibility level settings have been restricted by the administrator. - - else - .col-sm-10 - %span.info - = visibility_level_icon(visibility_level) - %strong - = visibility_level_label(visibility_level) - .light= visibility_level_description(visibility_level) diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index e6a859fea8fbe96445adf1327777199c9c570c1f..89dd68d647185a0da034e66b57b89f5d389a0168 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -12,7 +12,7 @@ .file-content.blame.highlight %table - @blame.each do |commit, lines, since| - - commit = Commit.new(commit) + - commit = Commit.new(commit, @project) %tr %td.blame-commit %span.commit diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 8e1aaa4d051c0da760105c4f819825ac0f4b6a0b..083fca9b6582fbeeff93ec3f9060e3dca1b678fe 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -12,7 +12,7 @@ - if @note_counts - note_count = @note_counts.fetch(commit.id, 0) - else - - notes = commit.notes(project) + - notes = commit.notes - note_count = notes.user.count - if note_count > 0 diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml index 2ee7d73bd20950eb79eca087882a905d5b1e2540..ce60fbdf032128249361b358b92fa6119491abc9 100644 --- a/app/views/projects/commits/_commit_list.html.haml +++ b/app/views/projects/commits/_commit_list.html.haml @@ -3,9 +3,9 @@ Commits (#{@commits.count}) - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE %ul.well-list - - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit| + - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE), @project).each do |commit| = render "projects/commits/inline_commit", commit: commit, project: @project %li.warning-row.unstyled other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues. - else - %ul.well-list= render Commit.decorate(@commits), project: @project + %ul.well-list= render Commit.decorate(@commits, @project), project: @project diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder index 9211de72b1b49bad03734dc430e09337fdf488f5..01edd9447ce2a9ef1da2e8288efb876d7a3dbb85 100644 --- a/app/views/projects/commits/show.atom.builder +++ b/app/views/projects/commits/show.atom.builder @@ -1,18 +1,18 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do - xml.title "Recent commits to #{@project.name}:#{@ref}" - xml.link :href => namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => namespace_project_commits_url(@project.namespace, @project, @ref), :rel => "alternate", :type => "text/html" + xml.title "#{@project.name}:#{@ref} commits" + xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref), rel: "alternate", type: "text/html" xml.id namespace_project_commits_url(@project.namespace, @project, @ref) xml.updated @commits.first.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") if @commits.any? @commits.each do |commit| xml.entry do - xml.id namespace_project_commit_url(@project.namespace, @project, :id => commit.id) - xml.link :href => namespace_project_commit_url(@project.namespace, @project, :id => commit.id) - xml.title truncate(commit.title, :length => 80) + xml.id namespace_project_commit_url(@project.namespace, @project, id: commit.id) + xml.link href: namespace_project_commit_url(@project.namespace, @project, id: commit.id) + xml.title truncate(commit.title, length: 80) xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(commit.author_email) + xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(commit.author_email) xml.author do |author| xml.name commit.author_name xml.email commit.author_email diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 7ea855e1a4e7b863c1c14f6ba2735cf8d17f1fa0..fb1012deb74a69c0d5bc1c4084a29e921af5fa78 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -1,3 +1,7 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") + = render "head" .tree-ref-holder diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f9437c89dacb33766ea3302f06c9154f1729fb83..168dd53e169a5ea413840eaf097c7b4a2ce028d9 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -29,7 +29,7 @@ .col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'}) - = render "visibility_level", f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project) + = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project), form_model: @project .form-group = f.label :tag_list, "Tags", class: 'control-label' diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 998e74d12cf8539b7fd7a5e9597da79de5c04cb5..ef36d1f95470c1a8e0710d3e7b540abaca17f58b 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -6,24 +6,34 @@ .issue-title %span.str-truncated = link_to_gfm issue.title, issue_path(issue), class: "row_title" + .issue-labels + - issue.labels.each do |label| + = link_to namespace_project_issues_path(issue.project.namespace, issue.project, label_name: label.name) do + = render_colored_label(label) .pull-right.light - if issue.closed? %span CLOSED + - if issue.assignee + = link_to_member(@project, issue.assignee, name: false) - note_count = issue.notes.user.count - if note_count > 0 %span %i.fa.fa-comments = note_count + - else + + %span.issue-no-comments + %i.fa.fa-comments + = 0 .issue-info - = link_to "##{issue.iid}", issue_path(issue), class: "light" - - if issue.assignee - assigned to #{link_to_member(@project, issue.assignee)} + = "##{issue.iid} opened #{time_ago_with_tooltip(issue.created_at, 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe - if issue.votes_count > 0 = render 'votes/votes_inline', votable: issue - if issue.milestone + %span %i.fa.fa-clock-o = issue.milestone.title @@ -33,20 +43,3 @@ .pull-right.issue-updated-at %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')} - - .issue-labels - - issue.labels.each do |label| - = link_to namespace_project_issues_path(issue.project.namespace, issue.project, label_name: label.name) do - = render_colored_label(label) - - .issue-actions - - if can? current_user, :modify_issue, issue - - if issue.closed? - = link_to 'Reopen', issue_path(issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-sm btn-grouped reopen_issue btn-reopen", remote: true - - else - = link_to 'Close', issue_path(issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-sm btn-grouped close_issue btn-close", remote: true - = link_to edit_namespace_project_issue_path(issue.project.namespace, issue.project, issue), class: "btn btn-sm edit-issue-link btn-grouped" do - %i.fa.fa-pencil-square-o - Edit - - diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder index 126f2c07faaa68192fc4b4a09dc8586eced6c497..5fa8fbdf8935629c0732b5b95ad6216cc4dd3421 100644 --- a/app/views/projects/issues/index.atom.builder +++ b/app/views/projects/issues/index.atom.builder @@ -1,8 +1,8 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{@project.name} issues" - xml.link :href => namespace_project_issues_url(@project.namespace, @project, :atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => namespace_project_issues_url(@project.namespace, @project), :rel => "alternate", :type => "text/html" + xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html" xml.id namespace_project_issues_url(@project.namespace, @project) xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index d3c7ae24a752f4b3298e9951c94de9bf67511b9f..c2522816f3b1f808dbb02338a2767b6507b9bca9 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,3 +1,7 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") + .append-bottom-10 .pull-right .pull-left diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 4f30d1e69f79fd0525bfb4a290bffc15ad19f011..5d5a23b5409268722665f56d2656c9fbd441dee9 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -2,6 +2,10 @@ .merge-request-title %span.str-truncated = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title" + .merge-request-labels + - merge_request.labels.each do |label| + = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, label_name: label.name) do + = render_colored_label(label) .pull-right.light - if merge_request.merged? %span @@ -17,20 +21,26 @@ %i.fa.fa-code-fork %span= merge_request.target_branch - note_count = merge_request.mr_and_commit_notes.user.count + - if merge_request.assignee + + = link_to_member(merge_request.source_project, merge_request.assignee, name: false) - if note_count > 0 %span %i.fa.fa-comments = note_count + - else + + %span.merge-request-no-comments + %i.fa.fa-comments + = 0 + .merge-request-info - = link_to "##{merge_request.iid}", merge_request_path(merge_request), class: "light" - - if merge_request.assignee - assigned to #{link_to_member(merge_request.source_project, merge_request.assignee)} - - else - Unassigned + = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe - if merge_request.votes_count > 0 = render 'votes/votes_inline', votable: merge_request - if merge_request.milestone_id? + %span %i.fa.fa-clock-o = merge_request.milestone.title @@ -38,11 +48,5 @@ %span.task-status = merge_request.task_status - .pull-right.hidden-xs %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')} - - .merge-request-labels - - merge_request.labels.each do |label| - = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, label_name: label.name) do - = render_colored_label(label) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index a06c85b4251261b4934628cd4bac968224c9d966..47c69f89a97888c6ee3dde97a3977c9e868de584 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -93,7 +93,7 @@ %span.light (optional) .col-sm-10 = f.text_area :description, placeholder: "Awesome project", class: "form-control", rows: 3, maxlength: 250, tabindex: 3 - = render "visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true + = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project .form-actions = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder new file mode 100644 index 0000000000000000000000000000000000000000..bb713dcafa52cc2f4011e9c6742274a0050d1727 --- /dev/null +++ b/app/views/projects/show.atom.builder @@ -0,0 +1,12 @@ +xml.instruct! +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do + xml.title "#{@project.name} activity" + xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html" + xml.id namespace_project_url(@project.namespace, @project) + xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? + + @events.each do |event| + event_to_atom(xml, event) + end +end diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 4464c51744aa77199b331d8effc9293ffbc8027a..1787caa243d4a1226a13b59271122b8449020bcf 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,9 +1,12 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "#{@project.name} activity") + - if current_user && can?(current_user, :download_code, @project) = render 'shared/no_ssh' = render 'shared/no_password' = render "home_panel" - %ul.nav.nav-tabs %li.active = link_to '#tab-activity', 'data-toggle' => 'tab' do @@ -13,11 +16,11 @@ = link_to '#tab-readme', 'data-toggle' => 'tab' do Readme - if @repository.changelog - %li + %li.hidden-xs = link_to changelog_url(@project) do Changelog - if @repository.contribution_guide - %li + %li.hidden-xs = link_to contribution_guide_url(@project) do Contribution guide - if @repository.license @@ -38,8 +41,18 @@ = link_to '#aside', class: 'show-aside' do %i.fa.fa-angle-left %section.col-md-9 - = render "events/event_last_push", event: @last_push - = render 'shared/event_filter' + .hidden-xs + = render "events/event_last_push", event: @last_push + + - if current_user + %ul.nav.nav-pills.event_filter.pull-right + %li + = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do + %i.fa.fa-rss + Activity Feed + + = render 'shared/event_filter' + %hr .content_list = spinner %aside.col-md-3.project-side diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml index 2d4d5d030ab3ec37c7601b4dc9d114bfec25621f..7baddebde455944aebc557848ab5f25409b2ef87 100644 --- a/app/views/projects/snippets/edit.html.haml +++ b/app/views/projects/snippets/edit.html.haml @@ -1,4 +1,4 @@ %h3.page-title Edit snippet %hr -= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet) += render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet), visibility_level: @snippet.visibility_level diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml index bb659dba0cf1cb21504f6796f835193055565305..5efe662665ee72824a0ef2e9d8904161491cfe51 100644 --- a/app/views/projects/snippets/new.html.haml +++ b/app/views/projects/snippets/new.html.haml @@ -1,4 +1,4 @@ %h3.page-title New snippet %hr -= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet) += render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet), visibility_level: default_snippet_visibility diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index feca145369752b8ad9e3758288dd7a33986d1e83..a8a580944e1d7c0fea60a60f3413141463c107e6 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,3 +1,7 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") + .tree-ref-holder = render 'shared/ref_switcher', destination: 'tree', path: @path diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index d07a9e2b92468860f9b77d3345b27dc6fff5e97c..334db60690d7fd4f1c6d2d5cace134a9be789093 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -3,17 +3,3 @@ = event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.team, 'Team' - - - if current_user - - if current_controller?(:dashboard) - %li.pull-right - = link_to dashboard_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do - %i.fa.fa-rss - News Feed - - - if current_controller?(:groups) - %li.pull-right - = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do - %i.fa.fa-rss - News Feed -%hr diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml index 83f5a3a8015ef276467f55072924b803f2b888e6..f9eb2dcfa282a18eec598a2b7186a435102623b5 100644 --- a/app/views/shared/_issuable_filter.html.haml +++ b/app/views/shared/_issuable_filter.html.haml @@ -4,15 +4,15 @@ %li{class: ("active" if params[:state] == 'opened')} = link_to page_filter_path(state: 'opened') do %i.fa.fa-exclamation-circle - Open + #{state_filters_text_for(:opened, @project)} %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed') do %i.fa.fa-check-circle - Closed + #{state_filters_text_for(:closed, @project)} %li{class: ("active" if params[:state] == 'all')} = link_to page_filter_path(state: 'all') do %i.fa.fa-compass - All + #{state_filters_text_for(:all, @project)} .issues-details-filters = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_id, :label_name]), method: :get, class: 'filter-form' do diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..1c6ec198d3d460425f73702aa7cbb92eeb303623 --- /dev/null +++ b/app/views/shared/_visibility_level.html.haml @@ -0,0 +1,14 @@ +.form-group.project-visibility-level-holder + = f.label :visibility_level, class: 'control-label' do + Visibility Level + = link_to "(?)", help_page_path("public_access", "public_access") + .col-sm-10 + - if can_change_visibility_level + = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) + - else + .col-sm-10 + %span.info + = visibility_level_icon(visibility_level) + %strong + = visibility_level_label(visibility_level) + .light= visibility_level_description(visibility_level, form_model) diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..b07c4d20f12cade024feb47c21e063a6e54dfec1 --- /dev/null +++ b/app/views/shared/_visibility_radios.html.haml @@ -0,0 +1,14 @@ +- Gitlab::VisibilityLevel.values.each do |level| + .radio + - restricted = restricted_visibility_levels.include?(level) + = label model_method, level do + = form.radio_button model_method, level, checked: (selected_level == level), disabled: restricted + = visibility_level_icon(level) + .option-title + = visibility_level_label(level) + .option-descr + = visibility_level_description(level, form_model) +- unless restricted_visibility_levels.empty? + .col-sm-10 + %span.info + Some visibility level settings have been restricted by the administrator. diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 4e0663ea2080b087a3226124394d2fddf324925a..6783587bda948e82f70a0adb00b3795529498f83 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -10,7 +10,7 @@ = f.label :title, class: 'control-label' .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true - = render "shared/snippets/visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true + = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet .form-group .file-editor diff --git a/app/views/shared/snippets/_visibility_level.html.haml b/app/views/shared/snippets/_visibility_level.html.haml deleted file mode 100644 index 9acff18e450f2302e69440df80273a5b73c18ad6..0000000000000000000000000000000000000000 --- a/app/views/shared/snippets/_visibility_level.html.haml +++ /dev/null @@ -1,27 +0,0 @@ -.form-group.project-visibility-level-holder - = f.label :visibility_level, class: 'control-label' do - Visibility Level - = link_to "(?)", help_page_path("public_access", "public_access") - .col-sm-10 - - if can_change_visibility_level - - Gitlab::VisibilityLevel.values.each do |level| - .radio - - restricted = restricted_visibility_levels.include?(level) - = f.radio_button :visibility_level, level, disabled: restricted - = label "#{dom_class(@snippet)}_visibility_level", level do - = visibility_level_icon(level) - .option-title - = visibility_level_label(level) - .option-descr - = snippet_visibility_level_description(level) - - unless restricted_visibility_levels.empty? - .col-sm-10 - %span.info - Some visibility level settings have been restricted by the administrator. - - else - .col-sm-10 - %span.info - = visibility_level_icon(visibility_level) - %strong - = visibility_level_label(visibility_level) - .light= visibility_level_description(visibility_level) diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml index 7042d07d5e8bbacf6ba445639e92ecf83a722480..30aa174edfb3329b039c4da52dabf4982ba9e4c9 100644 --- a/app/views/snippets/edit.html.haml +++ b/app/views/snippets/edit.html.haml @@ -1,4 +1,4 @@ %h3.page-title Edit snippet %hr -= render "shared/snippets/form", url: snippet_path(@snippet) += render 'shared/snippets/form', url: snippet_path(@snippet), visibility_level: @snippet.visibility_level diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml index 694d70583173a69cc826c54d853bf2b51f1a9d8a..77cfd9af335ab8afde5915e4ecc64a130d547e35 100644 --- a/app/views/snippets/new.html.haml +++ b/app/views/snippets/new.html.haml @@ -1,4 +1,4 @@ %h3.page-title New snippet %hr -= render "shared/snippets/form", url: snippets_path(@snippet) += render "shared/snippets/form", url: snippets_path(@snippet), visibility_level: default_snippet_visibility diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder index 8fe30b23635fd58e414485a3a3510c36b135effe..50232dc7186054179c5a5477948e86507079819c 100644 --- a/app/views/users/show.atom.builder +++ b/app/views/users/show.atom.builder @@ -1,9 +1,9 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do - xml.title "Activity feed for #{@user.name}" + xml.title "#{@user.name} activity" xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml" xml.link href: user_url(@user), rel: "alternate", type: "text/html" - xml.id projects_url + xml.id user_url(@user) xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 9dd8cb0738c55bb681aca50603a100e6369d82e2..15a3f741e6cd990c575a60b6c91535ea4df28274 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,3 +1,6 @@ += content_for :meta_tags do + = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") + .row = link_to '#aside', class: 'show-aside' do %i.fa.fa-angle-left diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index 8b50f423984f8f452b0c8ada50d6250143a6d7fb..84a54656df25d7c5982c7361138e889c5cbfff5f 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -137,8 +137,7 @@ def send_one_commit(project, hook_attrs, repo_name, branch) end def commit_from_id(project, id) - commit = Gitlab::Git::Commit.find(project.repository, id) - Commit.new(commit) + project.commit(id) end def files_count(commit) diff --git a/app/workers/project_web_hook_worker.rb b/app/workers/project_web_hook_worker.rb index 73085c046bd6ab50ab3183a7178ab196939f3ad9..fb87896528889c245185fb95adb1761612bea26a 100644 --- a/app/workers/project_web_hook_worker.rb +++ b/app/workers/project_web_hook_worker.rb @@ -3,8 +3,8 @@ class ProjectWebHookWorker sidekiq_options queue: :project_web_hook - def perform(hook_id, data) + def perform(hook_id, data, hook_name) data = data.with_indifferent_access - WebHook.find(hook_id).execute(data) + WebHook.find(hook_id).execute(data, hook_name) end end diff --git a/app/workers/system_hook_worker.rb b/app/workers/system_hook_worker.rb index 3ebc62b7e7a5a890d01f65b492b14ba6b76d60aa..a122c274763ad0ce41118de0499d11595379802f 100644 --- a/app/workers/system_hook_worker.rb +++ b/app/workers/system_hook_worker.rb @@ -3,7 +3,7 @@ class SystemHookWorker sidekiq_options queue: :system_hook - def perform(hook_id, data) - SystemHook.find(hook_id).execute data + def perform(hook_id, data, hook_name) + SystemHook.find(hook_id).execute(data, hook_name) end end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 66f13a329c518c348015a9b743a4f6a6e1d4885d..84271f2c30a0a2d13d39e57d2896bce84eb0bb68 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -76,7 +76,6 @@ production: &base merge_requests: true wiki: true snippets: false - visibility_level: "private" # can be "private" | "internal" | "public" ## Webhook settings # Number of seconds to wait for HTTP response after sending webhook HTTP POST request (default: 10) diff --git a/config/routes.rb b/config/routes.rb index 843dfe2b29e3cdcd5a2603951d25484203125360..81d8cac1022bb58b212961bccde2f1608241c20a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -252,7 +252,7 @@ constraints: { username: /.*/ } get '/u/:username' => 'users#show', as: :user, - constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } + constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } # # Dashboard Area @@ -279,7 +279,7 @@ # # Groups Area # - resources :groups, constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } do + resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } do member do get :issues get :merge_requests @@ -328,7 +328,7 @@ # Project Area # resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+/ }, except: + resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }, except: [:new, :create, :index], path: "/") do member do put :transfer diff --git a/db/migrate/20150421120000_remove_periods_at_ends_of_usernames.rb b/db/migrate/20150421120000_remove_periods_at_ends_of_usernames.rb new file mode 100644 index 0000000000000000000000000000000000000000..3057ea3c68c22f89141948d475662b2df4a90ca8 --- /dev/null +++ b/db/migrate/20150421120000_remove_periods_at_ends_of_usernames.rb @@ -0,0 +1,88 @@ +class RemovePeriodsAtEndsOfUsernames < ActiveRecord::Migration + include Gitlab::ShellAdapter + + class Namespace < ActiveRecord::Base + class << self + def find_by_path_or_name(path) + find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase) + end + + def clean_path(path) + path = path.dup + # Get the email username by removing everything after an `@` sign. + path.gsub!(/@.*\z/, "") + # Usernames can't end in .git, so remove it. + path.gsub!(/\.git\z/, "") + # Remove dashes at the start of the username. + path.gsub!(/\A-+/, "") + # Remove periods at the end of the username. + path.gsub!(/\.+\z/, "") + # Remove everything that's not in the list of allowed characters. + path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") + + # Users with the great usernames of "." or ".." would end up with a blank username. + # Work around that by setting their username to "blank", followed by a counter. + path = "blank" if path.blank? + + counter = 0 + base = path + while Namespace.find_by_path_or_name(path) + counter += 1 + path = "#{base}#{counter}" + end + + path + end + end + end + + def up + changed_paths = {} + + select_all("SELECT id, username FROM users WHERE username LIKE '%.'").each do |user| + username_was = user["username"] + username = Namespace.clean_path(username_was) + changed_paths[username_was] = username + + username = quote_string(username) + execute "UPDATE users SET username = '#{username}' WHERE id = #{user["id"]}" + execute "UPDATE namespaces SET path = '#{username}', name = '#{username}' WHERE type IS NULL AND owner_id = #{user["id"]}" + end + + select_all("SELECT id, path FROM namespaces WHERE type = 'Group' AND path LIKE '%.'").each do |group| + path_was = group["path"] + path = Namespace.clean_path(path_was) + changed_paths[path_was] = path + + path = quote_string(path) + execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{group["id"]}" + end + + changed_paths.each do |path_was, path| + # Don't attempt to move if original path only contains periods. + next if path_was =~ /\A\.+\z/ + + if gitlab_shell.mv_namespace(path_was, path) + # If repositories moved successfully we need to remove old satellites + # and send update instructions to users. + # However we cannot allow rollback since we moved namespace dir + # So we basically we mute exceptions in next actions + begin + gitlab_shell.rm_satellites(path_was) + # We cannot send update instructions since models and mailers + # can't safely be used from migrations as they may be written for + # later versions of the database. + # send_update_instructions + rescue + # Returning false does not rollback after_* transaction but gives + # us information about failing some of tasks + false + end + else + # if we cannot move namespace directory we should rollback + # db changes in order to prevent out of sync between db and fs + raise Exception.new('namespace directory cannot be moved') + end + end + end +end diff --git a/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb b/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..9b0f13f3fa7e62929b4d2f57b1148647a96b3a4a --- /dev/null +++ b/db/migrate/20150423033240_add_default_project_visibililty_to_application_settings.rb @@ -0,0 +1,7 @@ +class AddDefaultProjectVisibililtyToApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :default_project_visibility, :integer + visibility = Settings.gitlab.default_projects_features['visibility_level'] + execute("update application_settings set default_project_visibility = #{visibility}") + end +end diff --git a/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb b/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..51237354d9f7fc39490f2a47f4c73582e40f6b32 --- /dev/null +++ b/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb @@ -0,0 +1,7 @@ +class AddDefaultSnippetVisibilityToAppSettings < ActiveRecord::Migration + def change + add_column :application_settings, :default_snippet_visibility, :integer + visibility = Settings.gitlab.default_projects_features['visibility_level'] + execute("update application_settings set default_snippet_visibility = #{visibility}") + end +end diff --git a/db/schema.rb b/db/schema.rb index 7e101ca142291a0a48301c4d992abb41aa4d2174..6728c211abc7da5db60b7b4542c7404bcfda32a4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150417122318) do +ActiveRecord::Schema.define(version: 20150425173433) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -41,6 +41,8 @@ t.text "help_text" t.text "restricted_visibility_levels" t.integer "max_attachment_size", default: 10, null: false + t.integer "default_project_visibility" + t.integer "default_snippet_visibility" end create_table "audit_events", force: true do |t| diff --git a/doc/integration/gitlab_buttons_in_gmail.md b/doc/integration/gitlab_buttons_in_gmail.md index a9885cef1092ce710ffb7981dd7c810b11329903..e35bb8ba6936da6a25087b8da671d5742218b5f7 100644 --- a/doc/integration/gitlab_buttons_in_gmail.md +++ b/doc/integration/gitlab_buttons_in_gmail.md @@ -7,11 +7,11 @@ If correctly setup, emails that require an action will be marked in Gmail.  To get this functioning, you need to be registered with Google. -[See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google) +[See how to register with Google in this document.](https://developers.google.com/gmail/markup/registering-with-google) -To aid the registering with google, GitLab offers a rake task that will send an email to google whitelisting email address from your GitLab server. +To aid the registering with Google, GitLab offers a rake task that will send an email to Google whitelisting email address from your GitLab server. -To check what would be sent to the google email address, run the rake task: +To check what would be sent to the Google email address, run the rake task: ```bash bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production @@ -19,7 +19,7 @@ bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production **This will not send the email but give you the output of how the mail will look.** -Copy the output of the rake task to [google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate". +Copy the output of the rake task to [Google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate". If you receive "No errors detected" message from the tester you can send the email using: diff --git a/features/admin/settings.feature b/features/admin/settings.feature index 89e85125daf256df3ad8cfaeac736a1d5c550ef8..5a5d2afeeb0b6bf13eb325fb8daff2687e20386e 100644 --- a/features/admin/settings.feature +++ b/features/admin/settings.feature @@ -19,6 +19,9 @@ Feature: Admin Settings Scenario: Change Slack Service Template settings When I click on "Service Templates" And I click on "Slack" service + And I fill out Slack settings Then I check all events and submit form And I should see service template settings saved + Then I click on "Slack" service And I should see all checkboxes checked + And I should see Slack settings saved diff --git a/features/project/team_management.feature b/features/project/team_management.feature index 2ecaa5d694f7542a5280253be9a587f6479f25b2..e5de4d819f0df8ab73526a64ea75183ed10f623a 100644 --- a/features/project/team_management.feature +++ b/features/project/team_management.feature @@ -3,13 +3,13 @@ Feature: Project Team Management Given I sign in as a user And I own project "Shop" And gitlab user "Mike" - And gitlab user "Sam" - And "Sam" is "Shop" developer + And gitlab user "Dmitriy" + And "Dmitriy" is "Shop" developer And I visit project "Shop" team page Scenario: See all team members Then I should be able to see myself in team - And I should see "Sam" in team list + And I should see "Dmitriy" in team list @javascript Scenario: Add user to project @@ -25,14 +25,14 @@ Feature: Project Team Management @javascript Scenario: Update user access - Given I should see "Sam" in team list as "Developer" - And I change "Sam" role to "Reporter" - And I should see "Sam" in team list as "Reporter" + Given I should see "Dmitriy" in team list as "Developer" + And I change "Dmitriy" role to "Reporter" + And I should see "Dmitriy" in team list as "Reporter" Scenario: Cancel team member - Given I click cancel link for "Sam" + Given I click cancel link for "Dmitriy" Then I visit project "Shop" team page - And I should not see "Sam" in team list + And I should not see "Dmitriy" in team list Scenario: Import team from another project Given I own project "Website" diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb index 4f6604c2995e6b38a268b30341095588cf6c1deb..39a4849836505ae31861f292381a97cd1d73f0e3 100644 --- a/features/steps/admin/settings.rb +++ b/features/steps/admin/settings.rb @@ -44,10 +44,15 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps page.check('Comments') page.check('Issues events') page.check('Merge Request events') - fill_in 'Webhook', with: "http://localhost" click_on 'Save' end + step 'I fill out Slack settings' do + fill_in 'Webhook', with: 'http://localhost' + fill_in 'Username', with: 'test_user' + fill_in 'Channel', with: '#test_channel' + end + step 'I should see service template settings saved' do page.should have_content 'Application settings saved successfully' end @@ -58,6 +63,12 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps end end + step 'I should see Slack settings saved' do + find_field('Webhook').value.should eq 'http://localhost' + find_field('Username').value.should eq 'test_user' + find_field('Channel').value.should eq '#test_channel' + end + def help_text 'For help related to GitLab contact Marc Smith at marc@smith.example or find him in office 42.' end diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index 5e588ceb780c7f4196ad2702a0adbf6b81370662..93456a81ecf7838006a3db256463713555c3fa6d 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -4,7 +4,9 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps include SharedProject step 'I click "New project" link' do - click_link "New project" + within('.content') do + click_link "New project" + end end step 'I see "New project" page' do diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index 57b727f837ed4d007f881f9c7dc551a16d3c7077..30b1934b3630df173da4e4d74279f84e4e39e968 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -18,7 +18,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps step 'I see commits atom feed' do commit = @project.repository.commit response_headers['Content-Type'].should have_content("application/atom+xml") - body.should have_selector("title", text: "Recent commits to #{@project.name}") + body.should have_selector("title", text: "#{@project.name}:master commits") body.should have_selector("author email", text: commit.author_email) body.should have_selector("entry summary", text: commit.description[0..10]) end diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index 1f2e863f8ca86dbb73e29796faa9033e4b5c2856..e01f712c18c3a4291745e02591a768448c273a0a 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -9,8 +9,8 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps page.should have_content(@user.username) end - step 'I should see "Sam" in team list' do - user = User.find_by(name: "Sam") + step 'I should see "Dmitriy" in team list' do + user = User.find_by(name: "Dmitriy") page.should have_content(user.name) page.should have_content(user.username) end @@ -51,15 +51,15 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps end end - step 'I should see "Sam" in team list as "Developer"' do + step 'I should see "Dmitriy" in team list as "Developer"' do within ".access-developer" do - page.should have_content('Sam') + page.should have_content('Dmitriy') end end - step 'I change "Sam" role to "Reporter"' do + step 'I change "Dmitriy" role to "Reporter"' do project = Project.find_by(name: "Shop") - user = User.find_by(name: 'Sam') + user = User.find_by(name: 'Dmitriy') project_member = project.project_members.find_by(user_id: user.id) within "#project_member_#{project_member.id}" do click_button "Edit access level" @@ -68,9 +68,9 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps end end - step 'I should see "Sam" in team list as "Reporter"' do + step 'I should see "Dmitriy" in team list as "Reporter"' do within ".access-reporter" do - page.should have_content('Sam') + page.should have_content('Dmitriy') end end @@ -78,8 +78,8 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps click_link "Remove from team" end - step 'I should not see "Sam" in team list' do - user = User.find_by(name: "Sam") + step 'I should not see "Dmitriy" in team list' do + user = User.find_by(name: "Dmitriy") page.should_not have_content(user.name) page.should_not have_content(user.username) end @@ -88,12 +88,12 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps create(:user, name: "Mike") end - step 'gitlab user "Sam"' do - create(:user, name: "Sam") + step 'gitlab user "Dmitriy"' do + create(:user, name: "Dmitriy") end - step '"Sam" is "Shop" developer' do - user = User.find_by(name: "Sam") + step '"Dmitriy" is "Shop" developer' do + user = User.find_by(name: "Dmitriy") project = Project.find_by(name: "Shop") project.team << [user, :developer] end @@ -119,9 +119,9 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps click_button 'Import' end - step 'I click cancel link for "Sam"' do + step 'I click cancel link for "Dmitriy"' do project = Project.find_by(name: "Shop") - user = User.find_by(name: 'Sam') + user = User.find_by(name: 'Dmitriy') project_member = project.project_members.find_by(user_id: user.id) within "#project_member_#{project_member.id}" do click_link('Remove user from team') diff --git a/features/support/capybara.rb b/features/support/capybara.rb new file mode 100644 index 0000000000000000000000000000000000000000..31dbf0feb2fc57897ad34de32426bc7e8ae65e31 --- /dev/null +++ b/features/support/capybara.rb @@ -0,0 +1,24 @@ +require 'spinach/capybara' +require 'capybara/poltergeist' + +# Give CI some extra time +timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 10 + +Capybara.javascript_driver = :poltergeist +Capybara.register_driver :poltergeist do |app| + Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout) +end + +Spinach.hooks.on_tag("javascript") do + Capybara.current_driver = Capybara.javascript_driver +end + +Capybara.default_wait_time = timeout +Capybara.ignore_hidden_elements = false + +unless ENV['CI'] || ENV['CI_SERVER'] + require 'capybara-screenshot/spinach' + + # Keep only the screenshots generated from the last failing test suite + Capybara::Screenshot.prune_strategy = :keep_last_run +end diff --git a/features/support/db_cleaner.rb b/features/support/db_cleaner.rb new file mode 100644 index 0000000000000000000000000000000000000000..1ab308cfa556f4c640c5b4a51b00ae6d5a571aed --- /dev/null +++ b/features/support/db_cleaner.rb @@ -0,0 +1,11 @@ +require 'database_cleaner' + +DatabaseCleaner.strategy = :truncation + +Spinach.hooks.before_scenario do + DatabaseCleaner.start +end + +Spinach.hooks.after_scenario do + DatabaseCleaner.clean +end diff --git a/features/support/env.rb b/features/support/env.rb index be17065ccfd728ddb1c1b171dd75fa901e003e0d..f34302721ed0cc3d13ed373203d6196817f18c51 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -11,40 +11,18 @@ require './config/environment' require 'rspec' require 'rspec/expectations' -require 'database_cleaner' -require 'spinach/capybara' require 'sidekiq/testing/inline' +require_relative 'capybara' +require_relative 'db_cleaner' + %w(select2_helper test_env repo_helpers).each do |f| require Rails.root.join('spec', 'support', f) end -Dir["#{Rails.root}/features/steps/shared/*.rb"].each {|file| require file} +Dir["#{Rails.root}/features/steps/shared/*.rb"].each { |file| require file } WebMock.allow_net_connect! -# -# JS driver -# -require 'capybara/poltergeist' -Capybara.javascript_driver = :poltergeist -Capybara.register_driver :poltergeist do |app| - Capybara::Poltergeist::Driver.new(app, js_errors: false, timeout: 90) -end -Spinach.hooks.on_tag("javascript") do - ::Capybara.current_driver = ::Capybara.javascript_driver -end -Capybara.default_wait_time = 60 -Capybara.ignore_hidden_elements = false - -DatabaseCleaner.strategy = :truncation - -Spinach.hooks.before_scenario do - DatabaseCleaner.start -end - -Spinach.hooks.after_scenario do - DatabaseCleaner.clean -end Spinach.hooks.before_run do include RSpec::Mocks::ExampleMethods diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 0de4e720ffe00ca8c8af9b0746e234b7d0ac3902..23270b1c0f46529a1482eea5507009a5c9757870 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -32,7 +32,7 @@ class Commits < Grape::API # GET /projects/:id/repository/commits/:sha get ":id/repository/commits/:sha" do sha = params[:sha] - commit = user_project.repository.commit(sha) + commit = user_project.commit(sha) not_found! "Commit" unless commit present commit, with: Entities::RepoCommitDetail end @@ -46,7 +46,7 @@ class Commits < Grape::API # GET /projects/:id/repository/commits/:sha/diff get ":id/repository/commits/:sha/diff" do sha = params[:sha] - commit = user_project.repository.commit(sha) + commit = user_project.commit(sha) not_found! "Commit" unless commit commit.diffs end @@ -60,7 +60,7 @@ class Commits < Grape::API # GET /projects/:id/repository/commits/:sha/comments get ':id/repository/commits/:sha/comments' do sha = params[:sha] - commit = user_project.repository.commit(sha) + commit = user_project.commit(sha) not_found! 'Commit' unless commit notes = Note.where(commit_id: commit.id) present paginate(notes), with: Entities::CommitNote @@ -81,7 +81,7 @@ class Commits < Grape::API required_attributes! [:note] sha = params[:sha] - commit = user_project.repository.commit(sha) + commit = user_project.commit(sha) not_found! 'Commit' unless commit opts = { note: params[:note], diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 75f2f5fa2d0f76cc19d3cd7cd5f0d50733348596..fa9aab290011d6cbc1341bc0e6a9fcea157f7310 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -269,11 +269,11 @@ class Label < Grape::Entity class Compare < Grape::Entity expose :commit, using: Entities::RepoCommit do |compare, options| - Commit.decorate(compare.commits).last + Commit.decorate(compare.commits, nil).last end expose :commits, using: Entities::RepoCommit do |compare, options| - Commit.decorate(compare.commits) + Commit.decorate(compare.commits, nil) end expose :diffs, using: Entities::RepoDiff do |compare, options| diff --git a/lib/api/files.rb b/lib/api/files.rb index 3176ef0e256297e489176aea1a6d92123cbcb60a..e0ea6d7dd1dcedd191c63cdca3582f532973513b 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -34,7 +34,7 @@ class Files < Grape::API ref = attrs.delete(:ref) file_path = attrs.delete(:file_path) - commit = user_project.repository.commit(ref) + commit = user_project.commit(ref) not_found! 'Commit' unless commit blob = user_project.repository.blob_at(commit.sha, file_path) diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 1fbf3dca3c6faff82f943b72b7b2c1aada99330f..2d96c9666d2a907119fecdfcf8948a19e16a0478 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -62,7 +62,7 @@ def handle_project_member_errors(errors) ref = params[:ref_name] || user_project.try(:default_branch) || 'master' path = params[:path] || nil - commit = user_project.repository.commit(ref) + commit = user_project.commit(ref) not_found!('Tree') unless commit tree = user_project.repository.tree(commit.id, path) diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 518964db50d3739a5472771b9f1f3a8e0ab9b1e4..22b8f90dc5c6b95fc5fc0bdf05c2f744df19f7d9 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -47,7 +47,7 @@ class SystemHooks < Grape::API owner_name: "Someone", owner_email: "example@gitlabhq.com" } - @hook.execute(data) + @hook.execute(data, 'system_hooks') data end diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index 3fd0823df063662b5f05ead5446e6c4897200ba5..45bb904ed7afacefd386483c0a37c74887e458d6 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -17,7 +17,7 @@ def timestamps events = Event.reorder(nil).contributions.where(author_id: user.id). where("created_at > ?", date_from).where(project_id: projects). group('date(created_at)'). - select('date(created_at), count(id) as total_amount'). + select('date(created_at) as date, count(id) as total_amount'). map(&:attributes) dates = (1.year.ago.to_date..(Date.today + 1.day)).to_a diff --git a/lib/gitlab/gitorious_import/client.rb b/lib/gitlab/gitorious_import/client.rb index 8cdc3d4afae73a9434b72b3547393eb35de255a0..1fa89dba448727912843b0adeb3badcdb3325f06 100644 --- a/lib/gitlab/gitorious_import/client.rb +++ b/lib/gitlab/gitorious_import/client.rb @@ -14,7 +14,7 @@ def authorize_url(redirect_uri) end def repos - @repos ||= repo_names.map { |full_name| Repository.new(full_name) } + @repos ||= repo_names.map { |full_name| GitoriousImport::Repository.new(full_name) } end def repo(id) diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb index 6e4de197eeb3b31bbe6ba44c143decea3b811eaf..3e5d728f3bce43ecc99e37db99895f17ee7c0f74 100644 --- a/lib/gitlab/identifier.rb +++ b/lib/gitlab/identifier.rb @@ -5,7 +5,7 @@ module Identifier def identify(identifier, project, newrev) if identifier.blank? # Local push from gitlab - email = project.repository.commit(newrev).author_email rescue nil + email = project.commit(newrev).author_email rescue nil User.find_by(email: email) if email elsif identifier =~ /\Auser-\d+\Z/ diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb index 1128c1bed7a3eecf47590966ee7e0156f04218a3..8764f7e474f4111730b227172c6b06f69c0aea29 100644 --- a/lib/gitlab/markdown/commit_range_reference_filter.rb +++ b/lib/gitlab/markdown/commit_range_reference_filter.rb @@ -32,11 +32,8 @@ def initialize(*args) # Pattern used to extract commit range references from text # - # The beginning and ending SHA1 sums can be between 6 and 40 hex - # characters, and the range selection can be double- or triple-dot. - # # This pattern supports cross-project references. - COMMIT_RANGE_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit_range>\h{6,40}\.{2,3}\h{6,40})/ + COMMIT_RANGE_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit_range>#{CommitRange::PATTERN})/ def call replace_text_nodes_matching(COMMIT_RANGE_PATTERN) do |content| @@ -53,52 +50,34 @@ def call # links have `gfm` and `gfm-commit_range` class names attached for # styling. def commit_range_link_filter(text) - self.class.references_in(text) do |match, commit_range, project_ref| + self.class.references_in(text) do |match, id, project_ref| project = self.project_from_ref(project_ref) - from_id, to_id = split_commit_range(commit_range) + range = CommitRange.new(id, project) + + if range.valid_commits? + push_result(:commit_range, range) - if valid_range?(project, from_id, to_id) - url = url_for_commit_range(project, from_id, to_id) + url = url_for_commit_range(project, range) - title = "Commits #{from_id} through #{to_id}" + title = range.reference_title klass = reference_class(:commit_range) project_ref += '@' if project_ref %(<a href="#{url}" title="#{title}" - class="#{klass}">#{project_ref}#{commit_range}</a>) + class="#{klass}">#{project_ref}#{range}</a>) else match end end end - def split_commit_range(range) - from_id, to_id = range.split(/\.{2,3}/, 2) - from_id << "^" if range !~ /\.{3}/ - - [from_id, to_id] - end - - def commit(id) - unless @commit_map[id] - @commit_map[id] = project.repository.commit(id) - end - - @commit_map[id] - end - - def valid_range?(project, from_id, to_id) - project && project.valid_repo? && commit(from_id) && commit(to_id) - end - - def url_for_commit_range(project, from_id, to_id) + def url_for_commit_range(project, range) h = Rails.application.routes.url_helpers h.namespace_project_compare_url(project.namespace, project, - from: from_id, to: to_id, - only_path: context[:only_path]) + range.to_param.merge(only_path: context[:only_path])) end end end diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb index 745de6402cf1ac77c8f3ee5a5375e7ff01012cab..b20b29f5d0ca07b7736242cf8659d926c9c16b00 100644 --- a/lib/gitlab/markdown/commit_reference_filter.rb +++ b/lib/gitlab/markdown/commit_reference_filter.rb @@ -48,6 +48,8 @@ def commit_link_filter(text) project = self.project_from_ref(project_ref) if commit = commit_from_ref(project, commit_ref) + push_result(:commit, commit) + url = url_for_commit(project, commit) title = escape_once(commit.link_title) @@ -57,7 +59,7 @@ def commit_link_filter(text) %(<a href="#{url}" title="#{title}" - class="#{klass}">#{project_ref}#{commit_ref}</a>) + class="#{klass}">#{project_ref}#{commit.short_id}</a>) else match end @@ -66,7 +68,7 @@ def commit_link_filter(text) def commit_from_ref(project, commit_ref) if project && project.valid_repo? - project.repository.commit(commit_ref) + project.commit(commit_ref) end end diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb index c9267cc3e9d90c46bd41df347d5fa3f8967d70f4..4b360369d3768f5905c05fd41aa9b275e9706f6f 100644 --- a/lib/gitlab/markdown/issue_reference_filter.rb +++ b/lib/gitlab/markdown/issue_reference_filter.rb @@ -48,6 +48,9 @@ def issue_link_filter(text) project = self.project_from_ref(project_ref) if project && project.issue_exists?(issue) + # FIXME (rspeicher): Law of Demeter + push_result(:issue, project.issues.where(iid: issue).first) + url = url_for_issue(issue, project, only_path: context[:only_path]) title = escape_once("Issue: #{title_for_issue(issue, project)}") diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index 4c21192c0d3cb4ba2cacd4228fc9084e10f97606..a357f28458d5269a0bf8ef6061f004137a69d798 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -52,11 +52,13 @@ def label_link_filter(text) params = label_params(id, name) if label = project.labels.find_by(params) - url = url_for_label(project, label) + push_result(:label, label) + url = url_for_label(project, label) klass = reference_class(:label) - %(<a href="#{url}" class="#{klass}">#{render_colored_label(label)}</a>) + %(<a href="#{url}" + class="#{klass}">#{render_colored_label(label)}</a>) else match end diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index 40239523cdaa33188a4c436b36b84a7ab0d0b2f3..7c28fe112efcd0b3a8cb3bf9c811b7bb6e1ec50a 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -48,6 +48,8 @@ def merge_request_link_filter(text) project = self.project_from_ref(project_ref) if project && merge_request = project.merge_requests.find_by(iid: id) + push_result(:merge_request, merge_request) + title = escape_once("Merge Request: #{merge_request.title}") klass = reference_class(:merge_request) diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb index ef4aa408a7e57b8ddd1c7423ca1718538f148506..a4303d96befe2bbedc85564ac33334b568323e41 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -12,7 +12,15 @@ module Markdown # :reference_class - Custom CSS class added to reference links. # :only_path - Generate path-only links. # + # Results: + # :references - A Hash of references that were found and replaced. class ReferenceFilter < HTML::Pipeline::Filter + def initialize(*args) + super + + result[:references] = Hash.new { |hash, type| hash[type] = [] } + end + def escape_once(html) ERB::Util.html_escape_once(html) end @@ -29,6 +37,16 @@ def project context[:project] end + # Add a reference to the pipeline's result Hash + # + # type - Singular Symbol reference type (e.g., :issue, :user, etc.) + # values - One or more Objects to add + def push_result(type, *values) + return if values.empty? + + result[:references][type].push(*values) + end + def reference_class(type) "gfm gfm-#{type} #{context[:reference_class]}".strip end diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb index ada67de992b575a6c543f497ffcf3bd53b7ac56b..64a0a2696f7587867fc4d06024695d64223d252e 100644 --- a/lib/gitlab/markdown/snippet_reference_filter.rb +++ b/lib/gitlab/markdown/snippet_reference_filter.rb @@ -48,6 +48,8 @@ def snippet_link_filter(text) project = self.project_from_ref(project_ref) if project && snippet = project.snippets.find_by(id: id) + push_result(:snippet, snippet) + title = escape_once("Snippet: #{snippet.title}") klass = reference_class(:snippet) diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb index 5fc8ed55fe26f114459f52e1ce95f3746b9a1190..28ec041b1d49735855f5380853474a393e6693af 100644 --- a/lib/gitlab/markdown/user_reference_filter.rb +++ b/lib/gitlab/markdown/user_reference_filter.rb @@ -38,27 +38,11 @@ def call # Returns a String with `@user` references replaced with links. All links # have `gfm` and `gfm-project_member` class names attached for styling. def user_link_filter(text) - project = context[:project] - - self.class.references_in(text) do |match, user| - klass = reference_class(:project_member) - - if user == 'all' - url = link_to_all(project) - - %(<a href="#{url}" class="#{klass}">@#{user}</a>) - elsif namespace = Namespace.find_by(path: user) - if namespace.is_a?(Group) - if user_can_reference_group?(namespace) - url = group_url(user, only_path: context[:only_path]) - %(<a href="#{url}" class="#{klass}">@#{user}</a>) - else - match - end - else - url = user_url(user, only_path: context[:only_path]) - %(<a href="#{url}" class="#{klass}">@#{user}</a>) - end + self.class.references_in(text) do |match, username| + if username == 'all' + link_to_all + elsif namespace = Namespace.find_by(path: username) + link_to_namespace(namespace) || match else match end @@ -71,17 +55,46 @@ def urls Rails.application.routes.url_helpers end - def group_url(*args) - urls.group_url(*args) + def link_class + reference_class(:project_member) + end + + def link_to_all + project = context[:project] + + # FIXME (rspeicher): Law of Demeter + push_result(:user, *project.team.members.flatten) + + url = urls.namespace_project_url(project.namespace, project, + only_path: context[:only_path]) + + %(<a href="#{url}" class="#{link_class}">@all</a>) + end + + def link_to_namespace(namespace) + if namespace.is_a?(Group) + link_to_group(namespace.path, namespace) + else + link_to_user(namespace.path, namespace) + end end - def user_url(*args) - urls.user_url(*args) + def link_to_group(group, namespace) + return unless user_can_reference_group?(namespace) + + push_result(:user, *namespace.users) + + url = urls.group_url(group, only_path: context[:only_path]) + + %(<a href="#{url}" class="#{link_class}">@#{group}</a>) end - def link_to_all(project) - urls.namespace_project_url(project.namespace, project, - only_path: context[:only_path]) + def link_to_user(user, namespace) + push_result(:user, namespace.owner) + + url = urls.user_url(user, only_path: context[:only_path]) + + %(<a href="#{url}" class="#{link_class}">@#{user}</a>) end def user_can_reference_group?(group) diff --git a/lib/gitlab/note_data_builder.rb b/lib/gitlab/note_data_builder.rb index 644dec45dca6db0eec39da424f2d443c2a2abc27..ea6b0ee796dd86a8a01b2c3df3b24c6d501a268e 100644 --- a/lib/gitlab/note_data_builder.rb +++ b/lib/gitlab/note_data_builder.rb @@ -69,8 +69,8 @@ def build_base_data(project, user, note) def build_data_for_commit(project, user, note) # commit_id is the SHA hash - commit = project.repository.commit(note.commit_id) - commit.hook_attrs(project) + commit = project.commit(note.commit_id) + commit.hook_attrs end end end diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb index f8da452e4c07cf676294419fa044679095020cbe..f97784f5abb7e88495bbe9b9c44f8eeb201e93a6 100644 --- a/lib/gitlab/push_data_builder.rb +++ b/lib/gitlab/push_data_builder.rb @@ -30,9 +30,7 @@ def build(project, user, oldrev, newrev, ref, commits = [], message = nil) # For performance purposes maximum 20 latest commits # will be passed as post receive hook data. - commit_attrs = commits_limited.map do |commit| - commit.hook_attrs(project) - end + commit_attrs = commits_limited.map(&:hook_attrs) type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push" # Hash to be passed as post_receive_data diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 13b6f508e4f508176c24bcccb1b9154e12503c3a..1856872fb5ff57d67dee2e51b9868dd2943a8424 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -8,153 +8,80 @@ def initialize(project, current_user = nil) @current_user = current_user end - def can?(user, action, subject) - Ability.abilities.allowed?(user, action, subject) - end - def analyze(text) - text = text.dup - - # Remove preformatted/code blocks so that references are not included - text.gsub!(/^```.*?^```/m, '') - text.gsub!(/[^`]`[^`]*?`[^`]/, '') - - @references = Hash.new { |hash, type| hash[type] = [] } - parse_references(text) + @_text = text.dup end - # Given a valid project, resolve the extracted identifiers of the requested type to - # model objects. - def users - references[:user].uniq.map do |project, identifier| - if identifier == "all" - project.team.members.flatten - elsif namespace = Namespace.find_by(path: identifier) - if namespace.is_a?(Group) - namespace.users if can?(current_user, :read_group, namespace) - else - namespace.owner - end - end - end.flatten.compact.uniq + result = pipeline_result(:user) + result.uniq end def labels - references[:label].uniq.map do |project, identifier| - project.labels.where(id: identifier).first - end.compact.uniq + result = pipeline_result(:label) + result.uniq end def issues - references[:issue].uniq.map do |project, identifier| - if project.default_issues_tracker? - project.issues.where(iid: identifier).first - elsif project.jira_tracker? - JiraIssue.new(identifier, project) - end - end.compact.uniq + # TODO (rspeicher): What about external issues? + #EE code +#<<<<<<< HEAD + #references[:issue].uniq.map do |project, identifier| + #if project.default_issues_tracker? + #project.issues.where(iid: identifier).first + #elsif project.jira_tracker? + #JiraIssue.new(identifier, project) + #end + #end.compact.uniq +#======= + + result = pipeline_result(:issue) + result.uniq end def merge_requests - references[:merge_request].uniq.map do |project, identifier| - project.merge_requests.where(iid: identifier).first - end.compact.uniq + result = pipeline_result(:merge_request) + result.uniq end def snippets - references[:snippet].uniq.map do |project, identifier| - project.snippets.where(id: identifier).first - end.compact.uniq + result = pipeline_result(:snippet) + result.uniq end def commits - references[:commit].uniq.map do |project, identifier| - repo = project.repository - repo.commit(identifier) if repo - end.compact.uniq + result = pipeline_result(:commit) + result.uniq end def commit_ranges - references[:commit_range].uniq.map do |project, identifier| - repo = project.repository - if repo - from_id, to_id = identifier.split(/\.{2,3}/, 2) - [repo.commit(from_id), repo.commit(to_id)] - end - end.compact.uniq + result = pipeline_result(:commit_range) + result.uniq end private - NAME_STR = Gitlab::Regex::NAMESPACE_REGEX_STR - PROJ_STR = "(?<project>#{NAME_STR}/#{NAME_STR})" - - REFERENCE_PATTERN = %r{ - (?<prefix>\W)? # Prefix - ( # Reference - @(?<user>#{NAME_STR}) # User name - |~(?<label>\d+) # Label ID - |(?<issue>([A-Z\-]+-)\d+) # JIRA Issue ID - |#{PROJ_STR}?\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID - |#{PROJ_STR}?!(?<merge_request>\d+) # MR ID - |\$(?<snippet>\d+) # Snippet ID - |(#{PROJ_STR}@)?(?<commit_range>[\h]{6,40}\.{2,3}[\h]{6,40}) # Commit range - |(#{PROJ_STR}@)?(?<commit>[\h]{6,40}) # Commit ID - ) - (?<suffix>\W)? # Suffix - }x.freeze - - TYPES = %i(user issue label merge_request snippet commit commit_range).freeze - - def parse_references(text, project = @project) - # parse reference links - text.gsub!(REFERENCE_PATTERN) do |match| - type = TYPES.detect { |t| $~[t].present? } - - actual_project = project - project_prefix = nil - project_path = $LAST_MATCH_INFO[:project] - if project_path - actual_project = ::Project.find_with_namespace(project_path) - actual_project = nil unless can?(current_user, :read_project, actual_project) - project_prefix = project_path - end - - parse_result($LAST_MATCH_INFO, type, - actual_project, project_prefix) || match - end - end - - # Called from #parse_references. Attempts to build a gitlab reference - # link. Returns nil if +type+ is nil, if the match string is an HTML - # entity, if the reference is invalid, or if the matched text includes an - # invalid project path. - def parse_result(match_info, type, project, project_prefix) - prefix = match_info[:prefix] - suffix = match_info[:suffix] - - return nil if html_entity?(prefix, suffix) || type.nil? - return nil if project.nil? && !project_prefix.nil? - - identifier = match_info[type] - ref_link = reference_link(type, identifier, project, project_prefix) - - if ref_link - "#{prefix}#{ref_link}#{suffix}" - else - nil - end - end - - # Return true if the +prefix+ and +suffix+ indicate that the matched string - # is an HTML entity like & - def html_entity?(prefix, suffix) - prefix && suffix && prefix[0] == '&' && suffix[-1] == ';' - end - - def reference_link(type, identifier, project, _) - references[type] << [project, identifier] + # Instantiate and call HTML::Pipeline with a single reference filter type, + # returning the result + # + # filter_type - Symbol reference type (e.g., :commit, :issue, etc.) + # + # Returns the results Array for the requested filter type + def pipeline_result(filter_type) + klass = filter_type.to_s.camelize + 'ReferenceFilter' + filter = "Gitlab::Markdown::#{klass}".constantize + + context = { + project: project, + current_user: current_user, + # We don't actually care about the links generated + only_path: true + } + + pipeline = HTML::Pipeline.new([filter], context) + result = pipeline.call(@_text) + + result[:references][filter_type] end end end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 9aeed5e69399c27cd0e88362d194029042090bae..0571574aa4fdc5e538835b398046e4723c5cfe34 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -2,7 +2,7 @@ module Gitlab module Regex extend self - NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*)'.freeze + NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])'.freeze def namespace_regex @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze @@ -10,7 +10,7 @@ def namespace_regex def namespace_regex_message "can contain only letters, digits, '_', '-' and '.'. " \ - "Cannot start with '-'." \ + "Cannot start with '-' or end in '.'." \ end diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb index 43093c7d27ebbf804adef24176611b0a31ed6075..f0e61aa2e81ad6b8c94652f7b8b55c814e04fc48 100644 --- a/lib/gitlab/theme.rb +++ b/lib/gitlab/theme.rb @@ -7,33 +7,44 @@ class Theme COLOR = 5 unless const_defined?(:COLOR) BLUE = 6 unless const_defined?(:BLUE) - def self.css_class_by_id(id) - themes = { - BASIC => "ui_basic", - MARS => "ui_mars", - MODERN => "ui_modern", - GRAY => "ui_gray", - COLOR => "ui_color", - BLUE => "ui_blue" + def self.classes + @classes ||= { + BASIC => 'ui_basic', + MARS => 'ui_mars', + MODERN => 'ui_modern', + GRAY => 'ui_gray', + COLOR => 'ui_color', + BLUE => 'ui_blue' } + end + def self.css_class_by_id(id) id ||= Gitlab.config.gitlab.default_theme - - themes[id] + classes[id] end - def self.type_css_class_by_id(id) - types = { + def self.types + @types ||= { BASIC => 'light_theme', MARS => 'dark_theme', MODERN => 'dark_theme', GRAY => 'dark_theme', - COLOR => 'dark_theme' + COLOR => 'dark_theme', + BLUE => 'light_theme' } + end + def self.type_css_class_by_id(id) id ||= Gitlab.config.gitlab.default_theme - types[id] end + + # Convenience method to get a space-separated String of all the theme + # classes that mighty be applied to the `body` element + # + # Returns a String + def self.body_classes + (classes.values + types.values).uniq.join(' ') + end end end diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 04a2eb12db0f5a8a6c3e1060814f414e20e57c7d..1a6303b6c8252285826cf8f829f3b373d14e21c5 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -282,7 +282,8 @@ namespace :gitlab do def check_redis_version print "Redis version >= 2.0.0? ... " - if run_and_match(%W(redis-cli --version), /redis-cli 2.\d.\d/) + redis_version = run(%W(redis-cli --version)) + if redis_version.try(:match, /redis-cli 2.\d.\d/) || redis_version.try(:match, /redis-cli 3.\d.\d/) puts "yes".green else puts "no".red diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb index 3394a1f863fa1f12477a2ea0466c186e14120a12..2cfa399a047ac0de4161a429f6479f22799dc343 100644 --- a/spec/controllers/commit_controller_spec.rb +++ b/spec/controllers/commit_controller_spec.rb @@ -3,7 +3,7 @@ describe Projects::CommitController do let(:project) { create(:project) } let(:user) { create(:user) } - let(:commit) { project.repository.commit("master") } + let(:commit) { project.commit("master") } before do sign_in(user) diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb index fca1a06eb88d3d0ec2dd397a5e95cf9aec723ef6..133beba7b98e96f96b678a2b26aafb97a37932b7 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -14,7 +14,7 @@ Commit.any_instance.stub(title: "fix ##{issue.iid}\n\nask @#{fred.username} for details") end - let(:commit) { project.repository.commit } + let(:commit) { project.commit } before do login_as :user diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index e5f33d5a25acba8971b86f6cdbe7240875a43898..e217f0739d2919d07e8163a4cd1f7b2aa9448c1c 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -21,7 +21,7 @@ end before do - visit namespace_project_issues_path(project.namespace, project) + visit edit_namespace_project_issue_path(project.namespace, project, issue) click_link "Edit" end diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 5bd09793b11dac1209da4d095263dc0b728d1181..95719b4b49f474c70941cc381baa8f9be48923d1 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -4,7 +4,7 @@ include RepoHelpers let(:project) { create(:project) } - let(:commit) { project.repository.commit(sample_commit.id) } + let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff) } diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 64f130e4ae48b26d65c68552a345031f45194248..e309dbb6a2f9a6429b8b35aaec9de3ac6802f7b2 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -6,7 +6,7 @@ let!(:project) { create(:project) } let(:user) { create(:user, username: 'gfm') } - let(:commit) { project.repository.commit } + let(:commit) { project.commit } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:snippet) { create(:project_snippet, project: project) } diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb index 8271e00f41b24f03669224eaf86cc7d2653efa71..2013b3e4c2a84160ecff0385f4944acb3dee52e1 100644 --- a/spec/helpers/tree_helper_spec.rb +++ b/spec/helpers/tree_helper_spec.rb @@ -6,7 +6,7 @@ before { @repository = project.repository - @commit = project.repository.commit("e56497bb") + @commit = project.commit("e56497bb") } context "on a directory containing more than one file/directory" do diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..3840e64981fabada491d16dd5152091464c3b2e2 --- /dev/null +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe VisibilityLevelHelper do + include Haml::Helpers + + before :all do + init_haml_helpers + end + + let(:project) { create(:project) } + + describe 'visibility_level_description' do + shared_examples 'a visibility level description' do + let(:desc) do + visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, + form_model) + end + + let(:expected_class) do + class_name = case form_model.class.name + when 'String' + form_model + else + form_model.class.name + end + + class_name.match(/(project|snippet)$/i)[0] + end + + it 'should refer to the correct class' do + expect(desc).to match(/#{expected_class}/i) + end + end + + context 'form_model argument is a String' do + context 'model object is a personal snippet' do + it_behaves_like 'a visibility level description' do + let(:form_model) { 'PersonalSnippet' } + end + end + + context 'model object is a project snippet' do + it_behaves_like 'a visibility level description' do + let(:form_model) { 'ProjectSnippet' } + end + end + + context 'model object is a project' do + it_behaves_like 'a visibility level description' do + let(:form_model) { 'Project' } + end + end + end + + context 'form_model argument is a model object' do + context 'model object is a personal snippet' do + it_behaves_like 'a visibility level description' do + let(:form_model) { create(:personal_snippet) } + end + end + + context 'model object is a project snippet' do + it_behaves_like 'a visibility level description' do + let(:form_model) { create(:project_snippet, project: project) } + end + end + + context 'model object is a project' do + it_behaves_like 'a visibility level description' do + let(:form_model) { project } + end + end + end + end +end diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 40eb45e37cafd9348b73200e5893312c01889672..8b7946f3117269ec0edefea673971b5473f39985 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -4,7 +4,7 @@ include RepoHelpers let(:project) { create(:project) } - let(:commit) { project.repository.commit(sample_commit.id) } + let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff) } diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb index 918f6d0ead4f11b408d30f4fb06918558cdcdd30..4d5d1431683f8995c348494f8b180d77a4429a01 100644 --- a/spec/lib/gitlab/diff/parser_spec.rb +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -4,7 +4,7 @@ include RepoHelpers let(:project) { create(:project) } - let(:commit) { project.repository.commit(sample_commit.id) } + let(:commit) { project.commit(sample_commit.id) } let(:diff) { commit.diffs.first } let(:parser) { Gitlab::Diff::Parser.new } diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index 5ebdc8926e203b9c2698046a160a89426bff6550..7274cb309a0a5fb9cced19f7d463cd7de3df5b4a 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -5,8 +5,8 @@ module Gitlab::Markdown include ReferenceFilterSpecHelper let(:project) { create(:project) } - let(:commit1) { project.repository.commit } - let(:commit2) { project.repository.commit("HEAD~2") } + let(:commit1) { project.commit } + let(:commit2) { project.commit("HEAD~2") } it 'requires project context' do expect { described_class.call('Commit Range 1c002d..d200c1', {}) }. @@ -42,13 +42,17 @@ module Gitlab::Markdown reference = "#{commit1.short_id}...#{commit2.id}" reference2 = "#{commit1.id}...#{commit2.short_id}" - expect(filter("See #{reference}").css('a').first.text).to eq reference - expect(filter("See #{reference2}").css('a').first.text).to eq reference2 + exp = commit1.short_id + '...' + commit2.short_id + + expect(filter("See #{reference}").css('a').first.text).to eq exp + expect(filter("See #{reference2}").css('a').first.text).to eq exp end it 'links with adjacent text' do doc = filter("See (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + + exp = Regexp.escape("#{commit1.short_id}...#{commit2.short_id}") + expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) end it 'ignores invalid commit IDs' do @@ -81,13 +85,18 @@ module Gitlab::Markdown expect(link).not_to match %r(https?://) expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true) end + + it 'adds to the results hash' do + result = pipeline_result("See #{reference}") + expect(result[:references][:commit_range]).not_to be_empty + end end context 'cross-project reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, namespace: namespace) } - let(:commit1) { project.repository.commit } - let(:commit2) { project.repository.commit("HEAD~2") } + let(:commit1) { project.commit } + let(:commit2) { project.commit("HEAD~2") } let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" } context 'when user can access reference' do @@ -102,7 +111,9 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + + exp = Regexp.escape("#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}") + expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) end it 'ignores invalid commit IDs on the referenced project' do @@ -112,6 +123,11 @@ module Gitlab::Markdown exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id}...#{commit2.id.reverse}" expect(filter(act).to_html).to eq exp end + + it 'adds to the results hash' do + result = pipeline_result("See #{reference}") + expect(result[:references][:commit_range]).not_to be_empty + end end context 'when user cannot access reference' do diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb index 71fd2db2c5899e8722120f26ae2cef74b6a4ceee..cc32a4fcf030f6fb6bef269d7587a3be150bd998 100644 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb @@ -5,7 +5,7 @@ module Gitlab::Markdown include ReferenceFilterSpecHelper let(:project) { create(:project) } - let(:commit) { project.repository.commit } + let(:commit) { project.commit } it 'requires project context' do expect { described_class.call('Commit 1c002d', {}) }. @@ -27,15 +27,23 @@ module Gitlab::Markdown it "links to a valid reference of #{size} characters" do doc = filter("See #{reference[0...size]}") - expect(doc.css('a').first.text).to eq reference[0...size] + expect(doc.css('a').first.text).to eq commit.short_id expect(doc.css('a').first.attr('href')). to eq urls.namespace_project_commit_url(project.namespace, project, reference) end end + it 'always uses the short ID as the link text' do + doc = filter("See #{commit.id}") + expect(doc.text).to eq "See #{commit.short_id}" + + doc = filter("See #{commit.id[0...6]}") + expect(doc.text).to eq "See #{commit.short_id}" + end + it 'links with adjacent text' do doc = filter("See (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/) end it 'ignores invalid commit IDs' do @@ -55,7 +63,7 @@ module Gitlab::Markdown allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="}) doc = filter("See #{reference}") - expect(doc.text).to eq "See #{commit.id}" + expect(doc.text).to eq "See #{commit.short_id}" end it 'includes default classes' do @@ -75,12 +83,17 @@ module Gitlab::Markdown expect(link).not_to match %r(https?://) expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true) end + + it 'adds to the results hash' do + result = pipeline_result("See #{reference}") + expect(result[:references][:commit]).not_to be_empty + end end context 'cross-project reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, namespace: namespace) } - let(:commit) { project.repository.commit } + let(:commit) { project.commit } let(:reference) { "#{project2.path_with_namespace}@#{commit.id}" } context 'when user can access reference' do @@ -95,13 +108,20 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) + + exp = Regexp.escape(project2.path_with_namespace) + expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/) end it 'ignores invalid commit IDs on the referenced project' do exp = act = "Committed #{project2.path_with_namespace}##{commit.id.reverse}" expect(filter(act).to_html).to eq exp end + + it 'adds to the results hash' do + result = pipeline_result("See #{reference}") + expect(result[:references][:commit]).not_to be_empty + end end context 'when user cannot access reference' do diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb index f95b37d69544422117aaadc35e2b50111d1ca6be..393bf32e19610c632fd310b53af2e8f4e7920902 100644 --- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb @@ -34,7 +34,7 @@ def helper end it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = filter("Fixed #{reference}") expect(doc.css('a').first.attr('href')). to eq helper.url_for_issue(issue.iid, project) @@ -81,6 +81,11 @@ def helper expect(link).not_to match %r(https?://) expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true) end + + it 'adds to the results hash' do + result = pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end end context 'cross-project reference' do @@ -117,6 +122,11 @@ def helper expect(filter(act).to_html).to eq exp end + + it 'adds to the results hash' do + result = pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end end context 'when user cannot access reference' do diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb index c84e568e17269417d42fa28421c0f1f368f7934c..9f898837466c88cc7e7bff38c2fb23e563d2a289 100644 --- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb @@ -39,6 +39,11 @@ module Gitlab::Markdown expect(link).to eq urls.namespace_project_issues_url(project.namespace, project, label_name: label.name, only_path: true) end + it 'adds to the results hash' do + result = pipeline_result("Label #{reference}") + expect(result[:references][:label]).to eq [label] + end + describe 'label span element' do it 'includes default classes' do doc = filter("Label #{reference}") diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb index 0f66442269b7dba839a6f0add9005ababbb5981d..d6e745114f288aa63f5dad883a74924286014b8b 100644 --- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb @@ -69,6 +69,11 @@ module Gitlab::Markdown expect(link).not_to match %r(https?://) expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true) end + + it 'adds to the results hash' do + result = pipeline_result("Merge #{reference}") + expect(result[:references][:merge_request]).to eq [merge] + end end context 'cross-project reference' do @@ -98,6 +103,11 @@ module Gitlab::Markdown expect(filter(act).to_html).to eq exp end + + it 'adds to the results hash' do + result = pipeline_result("Merge #{reference}") + expect(result[:references][:merge_request]).to eq [merge] + end end context 'when user cannot access reference' do diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb index 79533a90b558d2e8f7797252d632e61530d8e98f..a4b331157afe5a8d9ef229eb5ad60d8cbd3bebbf 100644 --- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb @@ -68,6 +68,11 @@ module Gitlab::Markdown expect(link).not_to match %r(https?://) expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true) end + + it 'adds to the results hash' do + result = pipeline_result("Snippet #{reference}") + expect(result[:references][:snippet]).to eq [snippet] + end end context 'cross-project reference' do @@ -96,6 +101,11 @@ module Gitlab::Markdown expect(filter(act).to_html).to eq exp end + + it 'adds to the results hash' do + result = pipeline_result("Snippet #{reference}") + expect(result[:references][:snippet]).to eq [snippet] + end end context 'when user cannot access reference' do diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb index a5eb927072e5f5a79baf989efdfaf7455f7eb400..922502ada334f92f75c43b4ad3499445a633c625 100644 --- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb @@ -24,9 +24,29 @@ module Gitlab::Markdown end end + context 'mentioning @all' do + before do + project.team << [project.creator, :developer] + end + + it 'supports a special @all mention' do + doc = filter("Hey @all") + expect(doc.css('a').length).to eq 1 + expect(doc.css('a').first.attr('href')) + .to eq urls.namespace_project_url(project.namespace, project) + end + + it 'adds to the results hash' do + result = pipeline_result('Hey @all') + expect(result[:references][:user]).to eq [project.creator] + end + end + context 'mentioning a user' do + let(:reference) { "@#{user.username}" } + it 'links to a User' do - doc = filter("Hey @#{user.username}") + doc = filter("Hey #{reference}") expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) end @@ -45,22 +65,45 @@ module Gitlab::Markdown doc = filter("Hey @#{user.username}") expect(doc.css('a').length).to eq 1 end + + it 'adds to the results hash' do + result = pipeline_result("Hey #{reference}") + expect(result[:references][:user]).to eq [user] + end end context 'mentioning a group' do let(:group) { create(:group) } let(:user) { create(:user) } - it 'links to a Group that the current user can read' do - group.add_user(user, Gitlab::Access::DEVELOPER) + let(:reference) { "@#{group.name}" } + + context 'that the current user can read' do + before do + group.add_user(user, Gitlab::Access::DEVELOPER) + end - doc = filter("Hey @#{group.name}", current_user: user) - expect(doc.css('a').first.attr('href')).to eq urls.group_url(group) + it 'links to the Group' do + doc = filter("Hey #{reference}", current_user: user) + expect(doc.css('a').first.attr('href')).to eq urls.group_url(group) + end + + it 'adds to the results hash' do + result = pipeline_result("Hey #{reference}", current_user: user) + expect(result[:references][:user]).to eq group.users + end end - it 'ignores references to a Group that the current user cannot read' do - doc = filter("Hey @#{group.name}", current_user: user) - expect(doc.to_html).to eq "Hey @#{group.name}" + context 'that the current user cannot read' do + it 'ignores references to the Group' do + doc = filter("Hey #{reference}", current_user: user) + expect(doc.to_html).to eq "Hey #{reference}" + end + + it 'does not add to the results hash' do + result = pipeline_result("Hey #{reference}", current_user: user) + expect(result[:references][:user]).to eq [] + end end end @@ -70,13 +113,6 @@ module Gitlab::Markdown expect(doc.to_html).to match(/\(<a.+>@#{user.username}<\/a>\.\)/) end - it 'supports a special @all mention' do - doc = filter("Hey @all") - expect(doc.css('a').length).to eq 1 - expect(doc.css('a').first.attr('href')) - .to eq urls.namespace_project_url(project.namespace, project) - end - it 'includes default classes' do doc = filter("Hey @#{user.username}") expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member' diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index a33b5a6d22088e8e31c49d253dec203b5bfc7c36..77afa7d1ebe0c47fdbbfb267699804bf9976e31f 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -4,80 +4,6 @@ let(:project) { create(:project) } subject { Gitlab::ReferenceExtractor.new(project, project.creator) } - it 'extracts username references' do - subject.analyze('this contains a @user reference') - expect(subject.references[:user]).to eq([[project, 'user']]) - end - - it 'extracts issue references' do - subject.analyze('this one talks about issue #1234') - expect(subject.references[:issue]).to eq([[project, '1234']]) - end - - it 'extracts JIRA issue references' do - subject.analyze('this one talks about issue JIRA-1234') - expect(subject.references[:issue]).to eq([[project, 'JIRA-1234']]) - end - - it 'extracts merge request references' do - subject.analyze("and here's !43, a merge request") - expect(subject.references[:merge_request]).to eq([[project, '43']]) - end - - it 'extracts snippet ids' do - subject.analyze('snippets like $12 get extracted as well') - expect(subject.references[:snippet]).to eq([[project, '12']]) - end - - it 'extracts commit shas' do - subject.analyze('commit shas 98cf0ae3 are pulled out as Strings') - expect(subject.references[:commit]).to eq([[project, '98cf0ae3']]) - end - - it 'extracts commit ranges' do - subject.analyze('here you go, a commit range: 98cf0ae3...98cf0ae4') - expect(subject.references[:commit_range]).to eq([[project, '98cf0ae3...98cf0ae4']]) - end - - it 'extracts multiple references and preserves their order' do - subject.analyze('@me and @you both care about this') - expect(subject.references[:user]).to eq([ - [project, 'me'], - [project, 'you'] - ]) - end - - it 'leaves the original note unmodified' do - text = 'issue #123 is just the worst, @user' - subject.analyze(text) - expect(text).to eq('issue #123 is just the worst, @user') - end - - it 'extracts no references for <pre>..</pre> blocks' do - subject.analyze("<pre>def puts '#1 issue'\nend\n</pre>```") - expect(subject.issues).to be_blank - end - - it 'extracts no references for <code>..</code> blocks' do - subject.analyze("<code>def puts '!1 request'\nend\n</code>```") - expect(subject.merge_requests).to be_blank - end - - it 'extracts no references for code blocks with language' do - subject.analyze("this code:\n```ruby\ndef puts '#1 issue'\nend\n```") - expect(subject.issues).to be_blank - end - - it 'extracts issue references for invalid code blocks' do - subject.analyze('test: ```this one talks about issue #1234```') - expect(subject.references[:issue]).to eq([[project, '1234']]) - end - - it 'handles all possible kinds of references' do - accessors = described_class::TYPES.map { |t| "#{t}s".to_sym } - expect(subject).to respond_to(*accessors) - end - it 'accesses valid user objects' do @u_foo = create(:user, username: 'foo') @u_bar = create(:user, username: 'bar') @@ -135,7 +61,7 @@ end it 'accesses valid commits' do - commit = project.repository.commit('master') + commit = project.commit('master') subject.analyze("this references commits #{commit.sha[0..6]} and 012345") extracted = subject.commits @@ -145,16 +71,16 @@ end it 'accesses valid commit ranges' do - commit = project.repository.commit('master') - earlier_commit = project.repository.commit('master~2') + commit = project.commit('master') + earlier_commit = project.commit('master~2') subject.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}") + extracted = subject.commit_ranges expect(extracted.size).to eq(1) - expect(extracted[0][0].sha).to eq(earlier_commit.sha) - expect(extracted[0][0].message).to eq(earlier_commit.message) - expect(extracted[0][1].sha).to eq(commit.sha) - expect(extracted[0][1].message).to eq(commit.message) + expect(extracted.first).to be_kind_of(CommitRange) + expect(extracted.first.commit_from).to eq earlier_commit + expect(extracted.first.commit_to).to eq commit end context 'with a project with an underscore' do diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index e4900e4761a1a0530718c4cdf935d4210623783b..faa7ea7abe37bdd0ca95f0ba7bb8fdd8d328453b 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -466,7 +466,7 @@ end describe 'on a commit' do - let(:commit) { project.repository.commit } + let(:commit) { project.commit } before(:each) { allow(note).to receive(:noteable).and_return(commit) } @@ -670,8 +670,8 @@ let(:example_site_path) { root_path } let(:user) { create(:user) } let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) } - let(:commits) { Commit.decorate(compare.commits) } - let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base), to: Commit.new(compare.head)) } + let(:commits) { Commit.decorate(compare.commits, nil) } + let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) } let(:send_from_committer_email) { false } subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, send_from_committer_email: send_from_committer_email) } @@ -774,7 +774,7 @@ let(:example_site_path) { root_path } let(:user) { create(:user) } let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) } - let(:commits) { Commit.decorate(compare.commits) } + let(:commits) { Commit.decorate(compare.commits, nil) } let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) } subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) } diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..31ee3e99cad6eccbecb79e94406571944ffcf6b0 --- /dev/null +++ b/spec/models/commit_range_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' + +describe CommitRange do + let(:sha_from) { 'f3f85602' } + let(:sha_to) { 'e86e1013' } + + let(:range) { described_class.new("#{sha_from}...#{sha_to}") } + let(:range2) { described_class.new("#{sha_from}..#{sha_to}") } + + it 'raises ArgumentError when given an invalid range string' do + expect { described_class.new("Foo") }.to raise_error + end + + describe '#to_s' do + it 'is correct for three-dot syntax' do + expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}" + end + + it 'is correct for two-dot syntax' do + expect(range2.to_s).to eq "#{sha_from[0..7]}..#{sha_to[0..7]}" + end + end + + describe '#reference_title' do + it 'returns the correct String for three-dot ranges' do + expect(range.reference_title).to eq "Commits #{sha_from} through #{sha_to}" + end + + it 'returns the correct String for two-dot ranges' do + expect(range2.reference_title).to eq "Commits #{sha_from}^ through #{sha_to}" + end + end + + describe '#to_param' do + it 'includes the correct keys' do + expect(range.to_param.keys).to eq %i(from to) + end + + it 'includes the correct values for a three-dot range' do + expect(range.to_param).to eq({from: sha_from, to: sha_to}) + end + + it 'includes the correct values for a two-dot range' do + expect(range2.to_param).to eq({from: sha_from + '^', to: sha_to}) + end + end + + describe '#exclude_start?' do + it 'is false for three-dot ranges' do + expect(range.exclude_start?).to eq false + end + + it 'is true for two-dot ranges' do + expect(range2.exclude_start?).to eq true + end + end + + describe '#valid_commits?' do + context 'without a project' do + it 'returns nil' do + expect(range.valid_commits?).to be_nil + end + end + + it 'accepts an optional project argument' do + project1 = double('project1').as_null_object + project2 = double('project2').as_null_object + + # project1 gets assigned through the accessor, but ignored when not given + # as an argument to `valid_commits?` + expect(project1).not_to receive(:present?) + range.project = project1 + + # project2 gets passed to `valid_commits?` + expect(project2).to receive(:present?).and_return(false) + + range.valid_commits?(project2) + end + + context 'with a project' do + let(:project) { double('project', repository: double('repository')) } + + context 'with a valid repo' do + before do + expect(project).to receive(:valid_repo?).and_return(true) + range.project = project + end + + it 'is false when `sha_from` is invalid' do + expect(project.repository).to receive(:commit).with(sha_from).and_return(false) + expect(project.repository).not_to receive(:commit).with(sha_to) + expect(range).not_to be_valid_commits + end + + it 'is false when `sha_to` is invalid' do + expect(project.repository).to receive(:commit).with(sha_from).and_return(true) + expect(project.repository).to receive(:commit).with(sha_to).and_return(false) + expect(range).not_to be_valid_commits + end + + it 'is true when both `sha_from` and `sha_to` are valid' do + expect(project.repository).to receive(:commit).with(sha_from).and_return(true) + expect(project.repository).to receive(:commit).with(sha_to).and_return(true) + expect(range).to be_valid_commits + end + end + + context 'without a valid repo' do + before do + expect(project).to receive(:valid_repo?).and_return(false) + range.project = project + end + + it 'returns false' do + expect(range).not_to be_valid_commits + end + end + end + end +end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 506bc3339b63d6f7ee3702d50d44881db0734bc4..ad2ac143d972c99e996d0e4475c79cde45423f1d 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -2,7 +2,7 @@ describe Commit do let(:project) { create :project } - let(:commit) { project.repository.commit } + let(:commit) { project.commit } describe '#title' do it "returns no_commit_message when safe_message is blank" do @@ -58,13 +58,13 @@ it 'detects issues that this commit is marked as closing' do commit.stub(safe_message: "Fixes ##{issue.iid}") - expect(commit.closes_issues(project)).to eq([issue]) + expect(commit.closes_issues).to eq([issue]) end it 'does not detect issues from other projects' do ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}" commit.stub(safe_message: "Fixes #{ext_ref}") - expect(commit.closes_issues(project)).to be_empty + expect(commit.closes_issues).to be_empty end end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 67ec9193ad7db5daa111c11a1fbcfca249bd1567..14f873e6b5a924c643f1adc16e4cd73f789a3ba9 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -52,22 +52,26 @@ end it "POSTs to the web hook URL" do - @project_hook.execute(@data) - expect(WebMock).to have_requested(:post, @project_hook.url).once + @project_hook.execute(@data, 'push_hooks') + expect(WebMock).to have_requested(:post, @project_hook.url). + with(headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook'}). + once end it "POSTs the data as JSON" do json = @data.to_json - @project_hook.execute(@data) - expect(WebMock).to have_requested(:post, @project_hook.url).with(body: json).once + @project_hook.execute(@data, 'push_hooks') + expect(WebMock).to have_requested(:post, @project_hook.url). + with(headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook'}). + once end it "catches exceptions" do expect(WebHook).to receive(:post).and_raise("Some HTTP Post error") expect { - @project_hook.execute(@data) + @project_hook.execute(@data, 'push_hooks') }.to raise_error end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 96a6dea638c0bf52bf14a59e82257adae4691997..9800a20f8435df52f3c6a6b60b5e748cae7f9fb2 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -331,16 +331,17 @@ let(:author) { create(:user) } let(:issue) { create(:issue, project: project) } let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) } - let(:commit) { project.repository.commit } let(:jira_issue) { JiraIssue.new("JIRA-1", project)} let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? } + let(:commit) { project.commit } # Test all of {issue, merge request, commit} in both the referenced and referencing # roles, to ensure that the correct information can be inferred from any argument. context 'issue from a merge request' do + subject { Note.create_cross_reference_note(issue, mergereq, author) } + context 'in default issue tracker' do - subject { Note.create_cross_reference_note(issue, mergereq, author, project) } it { is_expected.to be_valid } @@ -366,6 +367,7 @@ end end + context 'in JIRA issue tracker' do before do jira_service_settings @@ -385,10 +387,9 @@ end context 'issue from a commit' do - context 'in default issue tracker' do - subject { Note.create_cross_reference_note(issue, commit, author, project) } - + subject { Note.create_cross_reference_note(issue, commit, author) } + context 'in default issue tracker' do it { is_expected.to be_valid } describe '#noteable' do @@ -456,7 +457,7 @@ end context 'merge request from an issue' do - subject { Note.create_cross_reference_note(mergereq, issue, author, project) } + subject { Note.create_cross_reference_note(mergereq, issue, author) } it { is_expected.to be_valid } @@ -477,7 +478,7 @@ end context 'commit from a merge request' do - subject { Note.create_cross_reference_note(commit, mergereq, author, project) } + subject { Note.create_cross_reference_note(commit, mergereq, author) } it { is_expected.to be_valid } @@ -498,13 +499,13 @@ end context 'commit contained in a merge request' do - subject { Note.create_cross_reference_note(mergereq.commits.first, mergereq, author, project) } + subject { Note.create_cross_reference_note(mergereq.commits.first, mergereq, author) } it { is_expected.to be_nil } end context 'commit from issue' do - subject { Note.create_cross_reference_note(commit, issue, author, project) } + subject { Note.create_cross_reference_note(commit, issue, author) } it { is_expected.to be_valid } @@ -531,7 +532,7 @@ context 'commit from commit' do let(:parent_commit) { commit.parents.first } - subject { Note.create_cross_reference_note(commit, parent_commit, author, project) } + subject { Note.create_cross_reference_note(commit, parent_commit, author) } it { is_expected.to be_valid } @@ -561,11 +562,11 @@ let(:project) { create :project } let(:author) { create :user } let(:issue) { create :issue } - let(:commit0) { project.repository.commit } - let(:commit1) { project.repository.commit('HEAD~2') } + let(:commit0) { project.commit } + let(:commit1) { project.commit('HEAD~2') } before do - Note.create_cross_reference_note(issue, commit0, author, project) + Note.create_cross_reference_note(issue, commit0, author) end it 'detects if a mentionable has already been mentioned' do @@ -578,7 +579,7 @@ context 'commit on commit' do before do - Note.create_cross_reference_note(commit0, commit1, author, project) + Note.create_cross_reference_note(commit0, commit1, author) end it { expect(Note.cross_reference_exists?(commit0, commit1)).to be_truthy } @@ -606,7 +607,7 @@ let(:author) { create :user } let(:issue0) { create :issue, project: project } let(:issue1) { create :issue, project: second_project } - let!(:note) { Note.create_cross_reference_note(issue0, issue1, author, project) } + let!(:note) { Note.create_cross_reference_note(issue0, issue1, author) } it 'detects if a mentionable has already been mentioned' do expect(Note.cross_reference_exists?(issue0, issue1)).to be_truthy @@ -641,7 +642,7 @@ end it 'should identify cross-reference notes as system notes' do - @note = Note.create_cross_reference_note(issue, other, author, project) + @note = Note.create_cross_reference_note(issue, other, author) expect(@note).to be_system end diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 8ab847e64323f06c127ba3ecf8949590d86caff4..348f83c56ad5a20221d7b166e07d79a82a05fec5 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -213,5 +213,21 @@ "<pre>snippet note</pre>") end end + + context "#message_options" do + it "should be set to the defaults" do + expect(hipchat.send(:message_options)).to eq({notify: false, color: 'yellow'}) + end + + it "should set notfiy to true" do + hipchat.stub(notify: '1') + expect(hipchat.send(:message_options)).to eq({notify: true, color: 'yellow'}) + end + + it "should set the color" do + hipchat.stub(color: 'red') + expect(hipchat.send(:message_options)).to eq({notify: false, color: 'red'}) + end + end end end diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb index fb3ff552c8d412440c4b4b6263d748cecb08ec77..7a784796031a63c3092e976f1e2e9a290f532675 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -50,7 +50,6 @@ it 'should fail if forked project exists in the user namespace' do post api("/projects/fork/#{project.id}", user) expect(response.status).to eq(409) - expect(json_response['message']['base']).to eq(['Invalid fork destination']) expect(json_response['message']['name']).to eq(['has already been taken']) expect(json_response['message']['path']).to eq(['has already been taken']) end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index ba29498659a320ef5572149dd91914905dfc7811..f2939a4afc587a1f54fb3fc1290059a16ee13f83 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -45,7 +45,7 @@ before do service.execute(project, user, @oldrev, @newrev, @ref) @push_data = service.push_data - @commit = project.repository.commit(@newrev) + @commit = project.commit(@newrev) end subject { @push_data } @@ -152,7 +152,7 @@ describe "cross-reference notes" do let(:issue) { create :issue, project: project } let(:commit_author) { create :user } - let(:commit) { project.repository.commit } + let(:commit) { project.commit } before do commit.stub({ @@ -165,22 +165,22 @@ end it "creates a note if a pushed commit mentions an issue" do - expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author, project) + expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author) service.execute(project, user, @oldrev, @newrev, @ref) end it "only creates a cross-reference note if one doesn't already exist" do - Note.create_cross_reference_note(issue, commit, user, project) + Note.create_cross_reference_note(issue, commit, user) - expect(Note).not_to receive(:create_cross_reference_note).with(issue, commit, commit_author, project) + expect(Note).not_to receive(:create_cross_reference_note).with(issue, commit, commit_author) service.execute(project, user, @oldrev, @newrev, @ref) end it "defaults to the pushing user if the commit's author is not known" do commit.stub(author_name: 'unknown name', author_email: 'unknown@email.com') - expect(Note).to receive(:create_cross_reference_note).with(issue, commit, user, project) + expect(Note).to receive(:create_cross_reference_note).with(issue, commit, user) service.execute(project, user, @oldrev, @newrev, @ref) end @@ -189,7 +189,7 @@ allow(project.repository).to receive(:commits_between).with(@blankrev, @newrev).and_return([]) allow(project.repository).to receive(:commits_between).with("master", @newrev).and_return([commit]) - expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author, project) + expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author) service.execute(project, user, @blankrev, @newrev, 'refs/heads/other') end @@ -199,7 +199,7 @@ let(:issue) { create :issue, project: project } let(:other_issue) { create :issue, project: project } let(:commit_author) { create :user } - let(:closing_commit) { project.repository.commit } + let(:closing_commit) { project.commit } context "for default gitlab issue tracker" do before do diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb index a050fdf6c0eb1ac58e08a7243c49d2c76c45db22..76f69b396e03efb3185f631333a9a3da7acedcc4 100644 --- a/spec/services/git_tag_push_service_spec.rb +++ b/spec/services/git_tag_push_service_spec.rb @@ -19,7 +19,7 @@ @push_data = service.push_data @tag_name = Gitlab::Git.ref_name(@ref) @tag = project.repository.find_tag(@tag_name) - @commit = project.repository.commit(@tag.target) + @commit = project.commit(@tag.target) end subject { @push_data } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 6d2bc41c2b90342aaab01c096d626e6b67f1ca2f..2a54b2e920acdd6fc6bc5f25280bb0452d534d94 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -57,7 +57,7 @@ end it 'filters out "mentioned in" notes' do - mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author, issue.project) + mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author) expect(Notify).not_to receive(:note_issue_email) notification.new_note(mentioned_note) @@ -128,7 +128,7 @@ def should_not_email(user_id) end it 'filters out "mentioned in" notes' do - mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author, issue.project) + mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author) expect(Notify).not_to receive(:note_issue_email) notification.new_note(mentioned_note) diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index c9025bdf133087619ec0999f79e543db7d51b97e..f158ac87e2b724eacbb1bd587cbd3600c0560fd7 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -27,7 +27,7 @@ it "fails due to transaction failure" do @to_project = fork_project(@from_project, @to_user, false) expect(@to_project.errors).not_to be_empty - expect(@to_project.errors[:base]).to include("Fork transaction failed.") + expect(@to_project.errors[:base]).to include("Failed to fork repository") end end @@ -36,8 +36,8 @@ @existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) @to_project = fork_project(@from_project, @to_user) expect(@existing_project.persisted?).to be_truthy - expect(@to_project.errors[:base]).to include("Invalid fork destination") - expect(@to_project.errors[:base]).not_to include("Fork transaction failed.") + expect(@to_project.errors[:name]).to eq(['has already been taken']) + expect(@to_project.errors[:path]).to eq(['has already been taken']) end end @@ -81,7 +81,7 @@ context 'fork project for group when user not owner' do it 'group developer should fail to fork project into the group' do to_project = fork_project(@project, @developer, true, @opts) - expect(to_project.errors[:namespace]).to eq(['insufficient access rights']) + expect(to_project.errors[:namespace]).to eq(['is not valid']) end end @@ -91,7 +91,6 @@ namespace: @group) to_project = fork_project(@project, @group_owner, true, @opts) expect(existing_project.persisted?).to be_truthy - expect(to_project.errors[:base]).to eq(['Invalid fork destination']) expect(to_project.errors[:name]).to eq(['has already been taken']) expect(to_project.errors[:path]).to eq(['has already been taken']) end @@ -99,10 +98,7 @@ end def fork_project(from_project, user, fork_success = true, params = {}) - context = Projects::ForkService.new(from_project, user, params) - shell = double('gitlab_shell') - shell.stub(fork_repository: fork_success) - context.stub(gitlab_shell: shell) - context.execute + allow_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(fork_success) + Projects::ForkService.new(from_project, user, params).execute end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 53ccaa4fd6791833f2adcbaf028d579031bc2a39..8fe51cf4add38ef91311761efdd97b9dd94c73d0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,19 +10,13 @@ ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' -require 'capybara/rails' -require 'capybara/rspec' require 'webmock/rspec' require 'email_spec' require 'sidekiq/testing/inline' -require 'capybara/poltergeist' - -Capybara.javascript_driver = :poltergeist -Capybara.default_wait_time = 10 # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. -Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} +Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } WebMock.disable_net_connect!(allow_localhost: true) diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb new file mode 100644 index 0000000000000000000000000000000000000000..fed1ab6ee33f743b181ab69ab8a74056b5020978 --- /dev/null +++ b/spec/support/capybara.rb @@ -0,0 +1,21 @@ +require 'capybara/rails' +require 'capybara/rspec' +require 'capybara/poltergeist' + +# Give CI some extra time +timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 10 + +Capybara.javascript_driver = :poltergeist +Capybara.register_driver :poltergeist do |app| + Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout) +end + +Capybara.default_wait_time = timeout +Capybara.ignore_hidden_elements = true + +unless ENV['CI'] || ENV['CI_SERVER'] + require 'capybara-screenshot/rspec' + + # Keep only the screenshots generated from the last failing test suite + Capybara::Screenshot.prune_strategy = :keep_last_run +end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index 25ec5cbe6f2a043d58af338897837e8af3500af0..53fb654555397953f1907fbf510207a31bbf9161 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -83,14 +83,14 @@ def common_mentionable_setup mentioned_objects.each do |referenced| expect(Note).to receive(:create_cross_reference_note). - with(referenced, subject.local_reference, author, project) + with(referenced, subject.local_reference, author) end subject.create_cross_references!(project, author) end it 'detects existing cross-references' do - Note.create_cross_reference_note(mentioned_issue, subject.local_reference, author, project) + Note.create_cross_reference_note(mentioned_issue, subject.local_reference, author) expect(subject).to have_mentioned(mentioned_issue) expect(subject).not_to have_mentioned(mentioned_mr) @@ -107,6 +107,8 @@ def common_mentionable_setup end it 'creates new cross-reference notes when the mentionable text is edited' do + subject.save + cross = ext_proj.path_with_namespace new_text = <<-MSG @@ -132,10 +134,9 @@ def common_mentionable_setup # These two issues are new and should receive reference notes new_issues.each do |newref| expect(Note).to receive(:create_cross_reference_note). - with(newref, subject.local_reference, author, project) + with(newref, subject.local_reference, author) end - subject.save set_mentionable_text.call(new_text) subject.notice_added_references(project, author) end diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/reference_filter_spec_helper.rb index bcee5715cad051658e518e7ff590799ee5598a50..41253811490e0872423b0c91bd3071d7736342f4 100644 --- a/spec/support/reference_filter_spec_helper.rb +++ b/spec/support/reference_filter_spec_helper.rb @@ -35,6 +35,20 @@ def filter(html, contexts = {}) described_class.call(html, contexts) end + # Run text through HTML::Pipeline with the current filter and return the + # result Hash + # + # body - String text to run through the pipeline + # contexts - Hash context for the filter. (default: {project: project}) + # + # Returns the Hash of the pipeline result + def pipeline_result(body, contexts = {}) + contexts.reverse_merge!(project: project) + + pipeline = HTML::Pipeline.new([described_class], contexts) + pipeline.call(body) + end + def allow_cross_reference! allow_any_instance_of(described_class). to receive(:user_can_reference_project?).and_return(true)