Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce
This commit is contained in:
commit
278c3ba401
|
|
@ -1,6 +1,7 @@
|
|||
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)
|
||||
|
|
|
|||
5
Gemfile
5
Gemfile
|
|
@ -223,14 +223,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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -677,6 +680,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 +730,6 @@ DEPENDENCIES
|
|||
jquery-turbolinks
|
||||
jquery-ui-rails
|
||||
kaminari (~> 0.15.1)
|
||||
launchy
|
||||
letter_opener
|
||||
minitest (~> 5.3.0)
|
||||
mousetrap-rails
|
||||
|
|
|
|||
|
|
@ -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', ->
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@
|
|||
color: #888;
|
||||
text-shadow: 0 1px 1px #fff;
|
||||
}
|
||||
i[class~="fa"] {
|
||||
i.fa {
|
||||
line-height: 14px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@
|
|||
p > code {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
li {
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ ul.notes {
|
|||
display: none;
|
||||
float: right;
|
||||
|
||||
[class~="fa"] {
|
||||
i.fa {
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
vertical-align: middle;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
if @project.avatar.present? && @project.avatar.image?
|
||||
new_params[:avatar] = @project.avatar
|
||||
end
|
||||
|
||||
if namespace = @params[:namespace]
|
||||
project.namespace = namespace
|
||||
else
|
||||
project.namespace = @current_user.namespace
|
||||
end
|
||||
new_project = CreateService.new(current_user, new_params).execute
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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", |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -2,48 +2,47 @@
|
|||
.navbar-inner
|
||||
.container
|
||||
%div.app_logo
|
||||
= link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do
|
||||
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
|
||||
= brand_header_logo
|
||||
%h3 GitLab
|
||||
%h1.title= title
|
||||
|
||||
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
|
||||
%button.navbar-toggle{type: 'button', data: {target: '.navbar-collapse', toggle: 'collapse'}}
|
||||
%span.sr-only Toggle navigation
|
||||
%i.fa.fa-bars
|
||||
= icon('bars')
|
||||
|
||||
.navbar-collapse.collapse
|
||||
%ul.nav.navbar-nav
|
||||
%li.hidden-sm.hidden-xs
|
||||
= render "layouts/search"
|
||||
= render 'layouts/search'
|
||||
%li.visible-sm.visible-xs
|
||||
= link_to search_path, title: "Search", class: 'has_bottom_tooltip', 'data-original-title' => 'Search area' do
|
||||
%i.fa.fa-search
|
||||
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
|
||||
= icon('search')
|
||||
%li
|
||||
= link_to help_path, title: 'Help', class: 'has_bottom_tooltip',
|
||||
'data-original-title' => 'Help' do
|
||||
%i.fa.fa-question-circle
|
||||
= link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do
|
||||
= icon('question-circle')
|
||||
%li
|
||||
= link_to explore_root_path, title: "Explore", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do
|
||||
%i.fa.fa-globe
|
||||
= link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
|
||||
= icon('globe')
|
||||
%li
|
||||
= link_to user_snippets_path(current_user), title: "Your snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'Your snippets' do
|
||||
%i.fa.fa-clipboard
|
||||
= link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do
|
||||
= icon('clipboard')
|
||||
- if current_user.is_admin?
|
||||
%li
|
||||
= link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do
|
||||
%i.fa.fa-cogs
|
||||
= link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
|
||||
= icon('cogs')
|
||||
- if current_user.can_create_project?
|
||||
%li
|
||||
= link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do
|
||||
%i.fa.fa-plus
|
||||
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
|
||||
= icon('plus')
|
||||
%li
|
||||
= link_to profile_path, title: "Profile settings", class: 'has_bottom_tooltip', 'data-original-title' => 'Profile settings"' do
|
||||
%i.fa.fa-user
|
||||
= link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
|
||||
= icon('user')
|
||||
%li
|
||||
= link_to destroy_user_session_path, class: "logout", method: :delete, title: "Sign out", class: 'has_bottom_tooltip', 'data-original-title' => 'Sign out' do
|
||||
%i.fa.fa-sign-out
|
||||
= link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
|
||||
= icon('sign-out')
|
||||
%li.hidden-xs
|
||||
= link_to current_user, class: "profile-pic has_bottom_tooltip", id: 'profile-pic', 'data-original-title' => 'Your profile' do
|
||||
= link_to current_user, class: 'profile-pic', id: 'profile-pic', data: {toggle: 'tooltip', placement: 'bottom'} do
|
||||
= image_tag avatar_icon(current_user.email, 60), alt: 'User activity'
|
||||
|
||||
= render 'shared/outdated_browser'
|
||||
|
|
|
|||
|
|
@ -15,7 +15,3 @@
|
|||
= yield
|
||||
|
||||
= yield :embedded_scripts
|
||||
|
||||
:coffeescript
|
||||
$('.page-sidebar-collapsed .nav-sidebar a').tooltip placement: "right"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 valid_range?(project, from_id, to_id)
|
||||
url = url_for_commit_range(project, from_id, to_id)
|
||||
if range.valid_commits?
|
||||
push_result(:commit_range, range)
|
||||
|
||||
title = "Commits #{from_id} through #{to_id}"
|
||||
url = url_for_commit_range(project, range)
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)}")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 user_url(*args)
|
||||
urls.user_url(*args)
|
||||
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_all(project)
|
||||
urls.namespace_project_url(project.namespace, project,
|
||||
only_path: context[:only_path])
|
||||
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 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_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)
|
||||
|
|
|
|||
|
|
@ -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})"
|
||||
# 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
|
||||
|
||||
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
|
||||
context = {
|
||||
project: project,
|
||||
current_user: current_user,
|
||||
# We don't actually care about the links generated
|
||||
only_path: true
|
||||
}
|
||||
|
||||
TYPES = %i(user issue label merge_request snippet commit commit_range).freeze
|
||||
pipeline = HTML::Pipeline.new([filter], context)
|
||||
result = pipeline.call(@_text)
|
||||
|
||||
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]
|
||||
result[:references][filter_type]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
||||
id ||= Gitlab.config.gitlab.default_theme
|
||||
|
||||
themes[id]
|
||||
end
|
||||
|
||||
def self.type_css_class_by_id(id)
|
||||
types = {
|
||||
def self.css_class_by_id(id)
|
||||
id ||= Gitlab.config.gitlab.default_theme
|
||||
classes[id]
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}" }
|
||||
|
||||
doc = filter("Hey @#{group.name}", current_user: user)
|
||||
expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
|
||||
context 'that the current user can read' do
|
||||
before do
|
||||
group.add_user(user, Gitlab::Access::DEVELOPER)
|
||||
end
|
||||
|
||||
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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -35,6 +35,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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue