diff options
91 files changed, 1029 insertions, 629 deletions
diff --git a/CHANGELOG b/CHANGELOG index 92ae6767ea2..33cf1c4378e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,12 +1,14 @@ 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. - + - Fix broken file browsing with relative submodule in personal projects (Stan Hu) - 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. @@ -15,16 +17,18 @@ v 7.11.0 (unreleased) - 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. - - - - + - Fix adding new group members from admin area + - 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 + - Allow to use non-ASCII letters and dashes in project and namespace name. (Jakub Jirutka) -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) @@ -208,6 +208,7 @@ group :development do gem "letter_opener" gem 'quiet_assets', '~> 1.0.1' gem 'rack-mini-profiler', require: false + gem 'rerun', '~> 0.10.0' # Better errors handler gem 'better_errors' @@ -223,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 f0f8601a760..676a5197900 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -47,7 +47,7 @@ GEM astrolabe (1.3.0) parser (>= 2.2.0.pre.3, < 3.0) attr_required (1.0.0) - autoprefixer-rails (5.1.6) + autoprefixer-rails (5.1.11) execjs json awesome_print (1.2.0) @@ -60,7 +60,7 @@ GEM erubis (>= 2.6.6) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - bootstrap-sass (3.3.3) + bootstrap-sass (3.3.4.1) autoprefixer-rails (>= 5.0.0.1) sass (>= 3.2.19) brakeman (3.0.1) @@ -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) @@ -151,7 +154,7 @@ GEM escape_utils (0.2.4) eventmachine (1.0.4) excon (0.32.1) - execjs (2.0.2) + execjs (2.5.2) expression_parser (0.9.0) factory_girl (4.3.0) activesupport (>= 3.0.0) @@ -164,7 +167,7 @@ GEM faraday (>= 0.7.4, < 0.9) fastercsv (1.5.5) ffaker (1.22.1) - ffi (1.9.3) + ffi (1.9.8) fog (1.21.0) fog-brightbox fog-core (~> 1.21, >= 1.21.1) @@ -320,8 +323,8 @@ GEM addressable (~> 2.3) letter_opener (1.1.2) launchy (~> 2.2) - listen (2.3.1) - celluloid (>= 0.15.2) + listen (2.10.0) + celluloid (~> 0.16.0) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) lumberjack (1.0.4) @@ -453,8 +456,8 @@ GEM raindrops (0.13.0) rake (10.4.2) raphael-rails (2.1.2) - rb-fsevent (0.9.3) - rb-inotify (0.9.2) + rb-fsevent (0.9.4) + rb-inotify (0.9.5) ffi (>= 0.5.0) rdoc (3.12.2) json (~> 1.4) @@ -479,6 +482,8 @@ GEM redis-store (1.1.4) redis (>= 2.2) request_store (1.0.5) + rerun (0.10.0) + listen (~> 2.7, >= 2.7.3) rest-client (1.6.7) mime-types (>= 1.16) rinku (1.7.3) @@ -677,6 +682,7 @@ DEPENDENCIES byebug cal-heatmap-rails (~> 0.0.1) capybara (~> 2.2.1) + capybara-screenshot (~> 1.0.0) carrierwave charlock_holmes coffee-rails @@ -726,7 +732,6 @@ DEPENDENCIES jquery-turbolinks jquery-ui-rails kaminari (~> 0.15.1) - launchy letter_opener minitest (~> 5.3.0) mousetrap-rails @@ -760,6 +765,7 @@ DEPENDENCIES redcarpet (~> 3.2.3) redis-rails request_store + rerun (~> 0.10.0) rspec-rails (= 2.99) rubocop (= 0.28.0) rugments @@ -1 +1 @@ -7.10.0.pre +7.11.0.pre diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 020c103dbc5..bb9da147018 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/stylesheets/base/gl_bootstrap.scss b/app/assets/stylesheets/base/gl_bootstrap.scss index 62a3eade5c7..427f333423c 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 feff931156c..b8b163a42b2 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/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index facd7e19314..97b19deb3ed 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 e9757676908..8f6a766635a 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -39,6 +39,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :sign_in_text, :home_page_url, :max_attachment_size, + :default_project_visibility, + :default_snippet_visibility, restricted_visibility_levels: [] ) end diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb index 6067a87ee04..c121d2de7cb 100644 --- a/app/controllers/import/gitorious_controller.rb +++ b/app/controllers/import/gitorious_controller.rb @@ -6,7 +6,7 @@ class Import::GitoriousController < Import::BaseController 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/services_controller.rb b/app/controllers/projects/services_controller.rb index 73031851734..dc18bbd8d5b 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -1,12 +1,13 @@ class Projects::ServicesController < Projects::ApplicationController - ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :subdomain, + ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :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] + :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, + :notify, :color] # Authorize before_action :authorize_admin_project! before_action :service, only: [:edit, :update, :test] diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index 9954617c762..e13d4eaf101 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -63,7 +63,7 @@ module SubmoduleHelper namespace = components.pop.gsub(/^\.\.$/, '') if namespace.empty? - namespace = @project.group.path + namespace = @project.namespace.name end [ diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 0d573e72a80..66a1383d61b 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -10,7 +10,21 @@ module VisibilityLevelHelper 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 @@ module VisibilityLevelHelper 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/models/application_setting.rb b/app/models/application_setting.rb index 0d8365c4ff2..9406fb91939 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -17,6 +17,8 @@ # twitter_sharing_enabled :boolean default(TRUE) # restricted_visibility_levels :text # max_attachment_size :integer default(10) +# default_project_visibility :integer +# default_snippet_visibility :integer # class ApplicationSetting < ActiveRecord::Base @@ -51,7 +53,9 @@ class ApplicationSetting < ActiveRecord::Base 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_range.rb b/app/models/commit_range.rb new file mode 100644 index 00000000000..e6456198264 --- /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/project.rb b/app/models/project.rb index 293ee04f228..397232e98d8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -686,11 +686,21 @@ class Project < ActiveRecord::Base 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 07520eab5d1..3a15b2207ea 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, :api_version validates :token, presence: true, if: :activated? def title @@ -39,6 +39,10 @@ class HipchatService < Service [ { 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: 'api_version', + placeholder: 'Leave blank for default (v2)' }, { type: 'text', name: 'server', placeholder: 'Leave blank for default. https://hipchat.example.com' } ] @@ -52,17 +56,21 @@ class HipchatService < Service 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 def gate - options = { api_version: 'v2' } + options = { api_version: api_version || 'v2' } options[:server_url] = server unless server.blank? @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/services/projects/create_service.rb b/app/services/projects/create_service.rb index a7afcf8f64b..011f6f6145e 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -5,6 +5,8 @@ module Projects 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 @@ module Projects @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 1e4deb6ed39..50f208b11d1 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/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 4f3565c67eb..98d3e00153d 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -43,6 +43,14 @@ .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 - data_attrs = { toggle: 'buttons' } diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 14996dcd6a2..427f38018b0 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -60,7 +60,7 @@ = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do %div - = users_select_tag(:user_ids, multiple: true, email_user: true) + = users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all) %div.prepend-top-10 = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" %hr diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml index 552525f4a07..c2577a24982 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 e6aee22e529..f93caf90076 100644 --- a/app/views/groups/_settings_nav.html.haml +++ b/app/views/groups/_settings_nav.html.haml @@ -1,11 +1,11 @@ %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 diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 0ff1665455e..f72882d883e 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,5 +1,6 @@ %head %meta{charset: "utf-8"} + %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} %meta{content: "GitLab Community Edition", name: "description"} %title diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 281ce31fe9c..fc4656be079 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -1,49 +1,48 @@ %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } - .navbar-inner - .container + .container + .navbar-inner %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 1c164800b0e..0fa2ec9824d 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 372fe9a6d36..a3191593dae 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -5,60 +5,60 @@ %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 = 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 = 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 c5997e44370..d46dba4a240 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 38d84cd9b51..66870e84ceb 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 7cce9ffe3d5..74a8526dbd7 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 9f2932c79cd..31d8ed3ed86 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 a2a9d8a340b..01b3d70194f 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 edd05f2dd81..0de3a9e5bb7 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 e664ac2a52a..db37619136d 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 281a84a3d3c..f8b74809b76 100644 --- a/app/views/projects/_settings_nav.html.haml +++ b/app/views/projects/_settings_nav.html.haml @@ -1,31 +1,31 @@ %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: :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: :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 diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml deleted file mode 100644 index 42c8e685224..00000000000 --- 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/edit.html.haml b/app/views/projects/edit.html.haml index fbf04847e48..c09d794ef7f 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/new.html.haml b/app/views/projects/new.html.haml index a06c85b4251..47c69f89a97 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/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml index 2d4d5d030ab..7baddebde45 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 bb659dba0cf..5efe662665e 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/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml new file mode 100644 index 00000000000..1c6ec198d3d --- /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 00000000000..b07c4d20f12 --- /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 4e0663ea208..6783587bda9 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 9acff18e450..00000000000 --- 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 7042d07d5e8..30aa174edfb 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 694d7058317..77cfd9af335 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/config/gitlab.yml.example b/config/gitlab.yml.example index ba40671b162..93a01c0723f 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/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 00000000000..9b0f13f3fa7 --- /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 00000000000..51237354d9f --- /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 6db7e386c86..8683c0446fe 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: 20150421120000) do +ActiveRecord::Schema.define(version: 20150425173433) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -29,6 +29,8 @@ ActiveRecord::Schema.define(version: 20150421120000) do t.boolean "twitter_sharing_enabled", default: true 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 "broadcast_messages", force: true do |t| @@ -470,6 +472,7 @@ ActiveRecord::Schema.define(version: 20150421120000) do t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" + t.datetime "last_credential_check_at" t.string "avatar" t.string "confirmation_token" t.datetime "confirmed_at" @@ -477,7 +480,6 @@ ActiveRecord::Schema.define(version: 20150421120000) do t.string "unconfirmed_email" t.boolean "hide_no_ssh_key", default: false t.string "website_url", default: "", null: false - t.datetime "last_credential_check_at" t.string "github_access_token" t.string "gitlab_access_token" t.string "notification_email" diff --git a/doc/integration/gitlab_buttons_in_gmail.md b/doc/integration/gitlab_buttons_in_gmail.md index a9885cef109..e35bb8ba693 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/docker/Dockerfile b/docker/Dockerfile index bb25bb677ca..69e34955b7f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,7 @@ RUN apt-get update -q \ # If the Omnibus package version below is outdated please contribute a merge request to update it. # If you run GitLab Enterprise Edition point it to a location where you have downloaded it. RUN TMP_FILE=$(mktemp); \ - wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.9.2-omnibus-1_amd64.deb \ + wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab-ce_7.10.0~omnibus.2-1_amd64.deb \ && dpkg -i $TMP_FILE \ && rm -f $TMP_FILE diff --git a/docker/README.md b/docker/README.md index b7e8b0db7e7..a54739ae3d3 100644 --- a/docker/README.md +++ b/docker/README.md @@ -3,16 +3,19 @@ What is GitLab? GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster. A subscription gives you access to our support team and to GitLab Enterprise Edition that contains extra features aimed at larger organizations. -<https://about.gitlab.com> +Learn more on [https://about.gitlab.com](https://about.gitlab.com) - - -How to use these images +How to build and use images yourself ====================== -At this moment GitLab doesn't have official Docker images. For convinience we will use suffix _xy where xy is current version of GitLab. -Build your own based on the Omnibus packages with the following commands (it assumes you're in the GitLab repo root directory): +At this moment GitLab doesn't have official Docker images. +There are unofficial images at the bottom of this document. +But in this section we'll build our own. +For convinience we will use suffix _xy where xy is current version of GitLab. +Build your own based on the Omnibus packages with the following commands. +Run these from the GitLab repo root directory. +People using boot2docker should run it without sudo. ```bash sudo docker build --tag gitlab_data_image docker/data/ @@ -42,11 +45,11 @@ sudo docker run --detach --name gitlab_app_xy --publish 8080:80 --publish 2222:2 It might take a while before the docker container is responding to queries. You can follow the configuration process with `sudo docker logs -f gitlab_app_xy`. -You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker). +You can then go to [http://localhost:8080/](http://localhost:8080/) or [http://192.168.59.103:8080/](http://192.168.59.103:8080/) if you use boot2docker. + You can login with username `root` and password `5iveL!fe`. Next time, you can just use `sudo docker start gitlab_app` and `sudo docker stop gitlab_app`. - How to configure GitLab ======================== @@ -55,7 +58,7 @@ This container uses the official Omnibus GitLab distribution, so all configurati To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor: ```bash -sudo docker run -ti -e TERM=linux --rm --volumes-from gitlab_data ubuntu +sudo docker run -ti -e TERM=linux --rm --volumes-from gitlab_data ubuntu vi /etc/gitlab/gitlab.rb ``` @@ -86,3 +89,26 @@ sudo docker rmi gitlab_app_image_78 Troubleshooting ========================= Please see the [troubleshooting](troubleshooting.md) file in this directory. + + +Publish the images to Dockerhub +========================= +Login to Dockerhub with `sudo docker login` and run the following (replace '7.9.2' with the version you're using and 'Sytse Sijbrandij' with your name): + +```bash +sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab_app_xy sytse/gitlab-ce:7.9.2 +sudo docker push sytse/gitlab-ce:7.9.2 +sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab_data sytse/gitlab_data +sudo docker push sytse/gitlab_data +``` + +Use images published to Dockerhub +================================ +This examples uses the unofficial images made by GitLab CEO Sytse. + +```bash +sudo docker pull sytse/gitlab_data +sudo docker pull sytse/gitlab-ce:7.9.2 +sudo docker run --name gitlab_data_volume sytse/gitlab_data /bin/true +sudo docker run --detach --name gitlab_app_7_9_2 --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data_volume sytse/gitlab-ce:7.9.2 +``` diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index 5e588ceb780..93456a81ecf 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/support/capybara.rb b/features/support/capybara.rb new file mode 100644 index 00000000000..31dbf0feb2f --- /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 00000000000..1ab308cfa55 --- /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 be17065ccfd..f34302721ed 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -11,40 +11,18 @@ ENV['RAILS_ENV'] = 'test' 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/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index 3fd0823df06..45bb904ed7a 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -17,7 +17,7 @@ module Gitlab 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 8cdc3d4afae..1fa89dba448 100644 --- a/lib/gitlab/gitorious_import/client.rb +++ b/lib/gitlab/gitorious_import/client.rb @@ -14,7 +14,7 @@ module Gitlab 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/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb index baa97bee9bf..8764f7e474f 100644 --- a/lib/gitlab/markdown/commit_range_reference_filter.rb +++ b/lib/gitlab/markdown/commit_range_reference_filter.rb @@ -32,11 +32,8 @@ module Gitlab # 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 @@ module Gitlab # 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.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 66598127f6e..b20b29f5d0c 100644 --- a/lib/gitlab/markdown/commit_reference_filter.rb +++ b/lib/gitlab/markdown/commit_reference_filter.rb @@ -48,6 +48,8 @@ module Gitlab 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 @@ module Gitlab %(<a href="#{url}" title="#{title}" - class="#{klass}">#{project_ref}#{commit_ref}</a>) + class="#{klass}">#{project_ref}#{commit.short_id}</a>) else match end diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb index c9267cc3e9d..4b360369d37 100644 --- a/lib/gitlab/markdown/issue_reference_filter.rb +++ b/lib/gitlab/markdown/issue_reference_filter.rb @@ -48,6 +48,9 @@ module Gitlab 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 4c21192c0d3..a357f28458d 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -52,11 +52,13 @@ module Gitlab 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 40239523cda..7c28fe112ef 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -48,6 +48,8 @@ module Gitlab 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 ef4aa408a7e..a4303d96bef 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -12,7 +12,15 @@ module Gitlab # :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 @@ module Gitlab 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 ada67de992b..64a0a2696f7 100644 --- a/lib/gitlab/markdown/snippet_reference_filter.rb +++ b/lib/gitlab/markdown/snippet_reference_filter.rb @@ -48,6 +48,8 @@ module Gitlab 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 5fc8ed55fe2..28ec041b1d4 100644 --- a/lib/gitlab/markdown/user_reference_filter.rb +++ b/lib/gitlab/markdown/user_reference_filter.rb @@ -38,27 +38,11 @@ module Gitlab # 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 @@ module Gitlab 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/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 949dd5d26b1..e35f848fa6e 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -8,151 +8,70 @@ module Gitlab @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 - end - end.compact.uniq + # TODO (rspeicher): What about external issues? + + 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 0571574aa4f..9f1adc860d1 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -15,20 +15,20 @@ module Gitlab def namespace_name_regex - @namespace_name_regex ||= /\A[a-zA-Z0-9_\-\. ]*\z/.freeze + @namespace_name_regex ||= /\A[\p{Alnum}\p{Pd}_\. ]*\z/.freeze end def namespace_name_regex_message - "can contain only letters, digits, '_', '-', '.' and space." + "can contain only letters, digits, '_', '.', dash and space." end def project_name_regex - @project_name_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\. ]*\z/.freeze + @project_name_regex ||= /\A[\p{Alnum}_][\p{Alnum}\p{Pd}_\. ]*\z/.freeze end def project_name_regex_message - "can contain only letters, digits, '_', '-', '.' and space. " \ + "can contain only letters, digits, '_', '.', dash and space. " \ "It must start with letter, digit or '_'." end diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb index 43093c7d27e..f0e61aa2e81 100644 --- a/lib/gitlab/theme.rb +++ b/lib/gitlab/theme.rb @@ -7,33 +7,44 @@ module Gitlab 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/dev.rake b/lib/tasks/dev.rake index 058c7417040..b22c631c8ba 100644 --- a/lib/tasks/dev.rake +++ b/lib/tasks/dev.rake @@ -7,4 +7,9 @@ namespace :dev do Rake::Task["gitlab:setup"].invoke Rake::Task["gitlab:shell:setup"].invoke end + + desc 'GITLAB | Start/restart foreman and watch for changes' + task :foreman => :environment do + sh 'rerun --dir app,config,lib -- foreman start' + end end diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 04a2eb12db0..1a6303b6c82 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/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index e99c3f5bc11..e98b75afabc 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -117,34 +117,41 @@ describe SubmoduleHelper do context 'submodules with relative links' do let(:group) { create(:group) } let(:project) { create(:project, group: group) } + let(:commit_id) { sample_commit[:id] } before do self.instance_variable_set(:@project, project) end it 'one level down' do - commit_id = sample_commit[:id] result = relative_self_links('../test.git', commit_id) expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) end it 'two levels down' do - commit_id = sample_commit[:id] result = relative_self_links('../../test.git', commit_id) expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) end it 'one level down with namespace and repo' do - commit_id = sample_commit[:id] result = relative_self_links('../foobar/test.git', commit_id) expect(result).to eq(["/foobar/test", "/foobar/test/tree/#{commit_id}"]) end it 'two levels down with namespace and repo' do - commit_id = sample_commit[:id] result = relative_self_links('../foobar/baz/test.git', commit_id) expect(result).to eq(["/baz/test", "/baz/test/tree/#{commit_id}"]) end + + context 'personal project' do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + + it 'one level down with personal project' do + result = relative_self_links('../test.git', commit_id) + expect(result).to eq(["/#{user.username}/test", "/#{user.username}/test/tree/#{commit_id}"]) + end + end end end diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb new file mode 100644 index 00000000000..3840e64981f --- /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/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index f0804ce0056..7274cb309a0 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -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,6 +85,11 @@ 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 @@ -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 f792d7f696e..cc32a4fcf03 100644 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb @@ -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,6 +83,11 @@ 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 @@ -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 f95b37d6954..393bf32e196 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 @@ module Gitlab::Markdown 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 @@ module Gitlab::Markdown 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 @@ module Gitlab::Markdown 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 c84e568e172..9f898837466 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 0f66442269b..d6e745114f2 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 79533a90b55..a4b331157af 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 a5eb927072e..922502ada33 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 0f4ea2a24ed..9801dc16554 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -4,80 +4,6 @@ describe Gitlab::ReferenceExtractor do 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') @@ -139,12 +65,12 @@ describe Gitlab::ReferenceExtractor do 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/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 727884c41c5..7fdc8fa600d 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -1,3 +1,4 @@ +# coding: utf-8 require 'spec_helper' describe Gitlab::Regex do @@ -16,6 +17,8 @@ describe Gitlab::Regex do it { expect('GitLab CE').to match(Gitlab::Regex.project_name_regex) } it { expect('100 lines').to match(Gitlab::Regex.project_name_regex) } it { expect('gitlab.git').to match(Gitlab::Regex.project_name_regex) } + it { expect('Český název').to match(Gitlab::Regex.project_name_regex) } + it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) } it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) } end end diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb new file mode 100644 index 00000000000..31ee3e99cad --- /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/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 8ab847e6432..348f83c56ad 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -213,5 +213,21 @@ describe HipchatService do "<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 fb3ff552c8d..7a784796031 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -50,7 +50,6 @@ describe API::API, api: true do 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/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index c9025bdf133..f158ac87e2b 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -27,7 +27,7 @@ describe Projects::ForkService do 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 @@ describe Projects::ForkService do @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 @@ describe Projects::ForkService do 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 @@ describe Projects::ForkService do 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 @@ describe Projects::ForkService do 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 53ccaa4fd67..8fe51cf4add 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,19 +10,13 @@ end 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 00000000000..fed1ab6ee33 --- /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 9d0af29ff99..53fb6545553 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -53,7 +53,7 @@ def common_mentionable_setup extra_commits.each { |c| commitmap[c.short_id] = c } allow(project.repository).to receive(:commit) { |sha| commitmap[sha] } - + set_mentionable_text.call(ref_string) end end diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/reference_filter_spec_helper.rb index 0e035f0e597..06c39e1ada5 100644 --- a/spec/support/reference_filter_spec_helper.rb +++ b/spec/support/reference_filter_spec_helper.rb @@ -24,6 +24,20 @@ module ReferenceFilterSpecHelper 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) |