Commit 71f61435 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' into new-sidebar

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>

Conflicts:
	app/controllers/snippets_controller.rb
parents 63c59119 aac27550
...@@ -954,7 +954,7 @@ Lint/Void: ...@@ -954,7 +954,7 @@ Lint/Void:
Rails/ActionFilter: Rails/ActionFilter:
Description: 'Enforces consistent use of action filter methods.' Description: 'Enforces consistent use of action filter methods.'
Enabled: false Enabled: true
Rails/DefaultScope: Rails/DefaultScope:
Description: 'Checks if the argument passed to default_scope is a block.' Description: 'Checks if the argument passed to default_scope is a block.'
......
...@@ -3,14 +3,16 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -3,14 +3,16 @@ Please view this file on the master branch, on stable branches it's out of date.
v 7.11.0 (unreleased) v 7.11.0 (unreleased)
- Fix clone URL field and X11 Primary selection (Dmitry Medvinsky) - Fix clone URL field and X11 Primary selection (Dmitry Medvinsky)
- Ignore invalid lines in .gitmodules - Ignore invalid lines in .gitmodules
- Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu)
- -
- - Add "Reply quoting selected text" shortcut key (`r`)
- -
- -
- -
- -
- -
- Improve new project command options (Ben Bodenmiller) - Improve new project command options (Ben Bodenmiller)
- Prevent sending empty messages to HipChat (Chulki Lee)
v 7.10.0 (unreleased) v 7.10.0 (unreleased)
- Ignore submodules that are defined in .gitmodules but are checked in as directories. - Ignore submodules that are defined in .gitmodules but are checked in as directories.
...@@ -38,7 +40,6 @@ v 7.10.0 (unreleased) ...@@ -38,7 +40,6 @@ v 7.10.0 (unreleased)
- Allow HTML tags in Markdown input - Allow HTML tags in Markdown input
- Fix code unfold not working on Compare commits page (Stan Hu) - Fix code unfold not working on Compare commits page (Stan Hu)
- Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik) - Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik)
- Include missing events and fix save functionality in admin service template settings form (Stan Hu)
- Fix "Import projects from" button to show the correct instructions (Stan Hu) - Fix "Import projects from" button to show the correct instructions (Stan Hu)
- Fix dots in Wiki slugs causing errors (Stan Hu) - Fix dots in Wiki slugs causing errors (Stan Hu)
- Make maximum attachment size configurable via Application Settings (Stan Hu) - Make maximum attachment size configurable via Application Settings (Stan Hu)
......
...@@ -42,7 +42,7 @@ gem "browser" ...@@ -42,7 +42,7 @@ gem "browser"
gem "gitlab_git", '~> 7.1.10' gem "gitlab_git", '~> 7.1.10'
# Ruby/Rack Git Smart-HTTP Server Handler # Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.rc2', require: 'grack' gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
# LDAP Auth # LDAP Auth
gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap" gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
...@@ -88,7 +88,7 @@ gem "six" ...@@ -88,7 +88,7 @@ gem "six"
gem "seed-fu" gem "seed-fu"
# Markup pipeline for GitLab # Markup pipeline for GitLab
gem 'html-pipeline-gitlab', '~> 0.1' gem 'html-pipeline', '~> 1.11.0'
# Markdown to HTML # Markdown to HTML
gem "github-markup" gem "github-markup"
...@@ -143,7 +143,7 @@ gem "redis-rails" ...@@ -143,7 +143,7 @@ gem "redis-rails"
gem 'tinder', '~> 1.9.2' gem 'tinder', '~> 1.9.2'
# HipChat integration # HipChat integration
gem "hipchat", "~> 1.4.0" gem 'hipchat', '~> 1.5.0'
# Flowdock integration # Flowdock integration
gem "gitlab-flowdock-git-hook", "~> 0.4.2" gem "gitlab-flowdock-git-hook", "~> 0.4.2"
...@@ -251,7 +251,8 @@ group :development, :test do ...@@ -251,7 +251,8 @@ group :development, :test do
# PhantomJS driver for Capybara # PhantomJS driver for Capybara
gem 'poltergeist', '~> 1.5.1' gem 'poltergeist', '~> 1.5.1'
gem 'jasmine', '2.0.2' gem 'jasmine', '~> 2.2.0'
gem 'jasmine-rails'
gem "spring", '~> 1.3.1' gem "spring", '~> 1.3.1'
gem "spring-commands-rspec", '1.0.4' gem "spring-commands-rspec", '1.0.4'
......
...@@ -199,7 +199,7 @@ GEM ...@@ -199,7 +199,7 @@ GEM
gitlab-flowdock-git-hook (0.4.2.2) gitlab-flowdock-git-hook (0.4.2.2)
gitlab-grit (>= 2.4.1) gitlab-grit (>= 2.4.1)
multi_json multi_json
gitlab-grack (2.0.0) gitlab-grack (2.0.2)
rack (~> 1.5.1) rack (~> 1.5.1)
gitlab-grit (2.7.2) gitlab-grit (2.7.2)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
...@@ -271,20 +271,15 @@ GEM ...@@ -271,20 +271,15 @@ GEM
hashie (2.1.2) hashie (2.1.2)
highline (1.6.21) highline (1.6.21)
hike (1.2.3) hike (1.2.3)
hipchat (1.4.0) hipchat (1.5.0)
httparty httparty
mimemagic
hitimes (1.2.2) hitimes (1.2.2)
html-pipeline (1.11.0) html-pipeline (1.11.0)
activesupport (>= 2) activesupport (>= 2)
nokogiri (~> 1.4) nokogiri (~> 1.4)
html-pipeline-gitlab (0.2.0)
actionpack (~> 4)
gitlab_emoji (~> 0.1)
html-pipeline (~> 1.11.0)
mime-types
sanitize (~> 2.1)
http_parser.rb (0.5.3) http_parser.rb (0.5.3)
httparty (0.13.0) httparty (0.13.3)
json (~> 1.8) json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpauth (0.2.1) httpauth (0.2.1)
...@@ -292,12 +287,17 @@ GEM ...@@ -292,12 +287,17 @@ GEM
i18n (0.7.0) i18n (0.7.0)
ice_cube (0.11.1) ice_cube (0.11.1)
ice_nine (0.10.0) ice_nine (0.10.0)
jasmine (2.0.2) jasmine (2.2.0)
jasmine-core (~> 2.0.0) jasmine-core (~> 2.2)
phantomjs phantomjs
rack (>= 1.2.1) rack (>= 1.2.1)
rake rake
jasmine-core (2.0.0) jasmine-core (2.2.0)
jasmine-rails (0.10.8)
jasmine-core (>= 1.3, < 3.0)
phantomjs (>= 1.9)
railties (>= 3.2.0)
sprockets-rails
jquery-atwho-rails (0.3.3) jquery-atwho-rails (0.3.3)
jquery-rails (3.1.0) jquery-rails (3.1.0)
railties (>= 3.0, < 5.0) railties (>= 3.0, < 5.0)
...@@ -329,6 +329,7 @@ GEM ...@@ -329,6 +329,7 @@ GEM
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
mimemagic (0.3.0)
mini_portile (0.6.1) mini_portile (0.6.1)
minitest (5.3.5) minitest (5.3.5)
mousetrap-rails (1.4.6) mousetrap-rails (1.4.6)
...@@ -391,7 +392,7 @@ GEM ...@@ -391,7 +392,7 @@ GEM
parser (2.2.0.2) parser (2.2.0.2)
ast (>= 1.1, < 3.0) ast (>= 1.1, < 3.0)
pg (0.15.1) pg (0.15.1)
phantomjs (1.9.2.0) phantomjs (1.9.8.0)
poltergeist (1.5.1) poltergeist (1.5.1)
capybara (~> 2.1) capybara (~> 2.1)
cliver (~> 0.3.1) cliver (~> 0.3.1)
...@@ -700,7 +701,7 @@ DEPENDENCIES ...@@ -700,7 +701,7 @@ DEPENDENCIES
gemnasium-gitlab-service (~> 0.2) gemnasium-gitlab-service (~> 0.2)
github-markup github-markup
gitlab-flowdock-git-hook (~> 0.4.2) gitlab-flowdock-git-hook (~> 0.4.2)
gitlab-grack (~> 2.0.0.rc2) gitlab-grack (~> 2.0.2)
gitlab-linguist (~> 3.0.1) gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1) gitlab_emoji (~> 0.1)
gitlab_git (~> 7.1.10) gitlab_git (~> 7.1.10)
...@@ -714,10 +715,11 @@ DEPENDENCIES ...@@ -714,10 +715,11 @@ DEPENDENCIES
guard-rspec guard-rspec
guard-spinach guard-spinach
haml-rails haml-rails
hipchat (~> 1.4.0) hipchat (~> 1.5.0)
html-pipeline-gitlab (~> 0.1) html-pipeline (~> 1.11.0)
httparty httparty
jasmine (= 2.0.2) jasmine (~> 2.2.0)
jasmine-rails
jquery-atwho-rails (~> 0.3.3) jquery-atwho-rails (~> 0.3.3)
jquery-rails jquery-rails
jquery-scrollto-rails jquery-scrollto-rails
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
#= require shortcuts #= require shortcuts
#= require shortcuts_navigation #= require shortcuts_navigation
#= require shortcuts_dashboard_navigation #= require shortcuts_dashboard_navigation
#= require shortcuts_issueable #= require shortcuts_issuable
#= require shortcuts_network #= require shortcuts_network
#= require cal-heatmap #= require cal-heatmap
#= require_tree . #= require_tree .
...@@ -173,6 +173,7 @@ $ -> ...@@ -173,6 +173,7 @@ $ ->
$(@).closest(".diff-file").find(".notes_holder").toggle() $(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault() e.preventDefault()
$(document).off "click", '.js-confirm-danger'
$(document).on "click", '.js-confirm-danger', (e) -> $(document).on "click", '.js-confirm-danger', (e) ->
e.preventDefault() e.preventDefault()
btn = $(e.target) btn = $(e.target)
......
...@@ -8,11 +8,13 @@ class @ConfirmDangerModal ...@@ -8,11 +8,13 @@ class @ConfirmDangerModal
submit = $('.js-confirm-danger-submit') submit = $('.js-confirm-danger-submit')
submit.disable() submit.disable()
$('.js-confirm-danger-input').off 'input'
$('.js-confirm-danger-input').on 'input', -> $('.js-confirm-danger-input').on 'input', ->
if rstrip($(@).val()) is project_path if rstrip($(@).val()) is project_path
submit.enable() submit.enable()
else else
submit.disable() submit.disable()
$('.js-confirm-danger-submit').off 'click'
$('.js-confirm-danger-submit').on 'click', => $('.js-confirm-danger-submit').on 'click', =>
@form.submit() @form.submit()
...@@ -22,7 +22,7 @@ class Dispatcher ...@@ -22,7 +22,7 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show' when 'projects:issues:show'
new Issue() new Issue()
shortcut_handler = new ShortcutsIssueable() shortcut_handler = new ShortcutsIssuable()
new ZenMode() new ZenMode()
when 'projects:milestones:show' when 'projects:milestones:show'
new Milestone() new Milestone()
...@@ -47,7 +47,7 @@ class Dispatcher ...@@ -47,7 +47,7 @@ class Dispatcher
new IssuableForm($('.merge-request-form')) new IssuableForm($('.merge-request-form'))
when 'projects:merge_requests:show' when 'projects:merge_requests:show'
new Diff() new Diff()
shortcut_handler = new ShortcutsIssueable() shortcut_handler = new ShortcutsIssuable()
new ZenMode() new ZenMode()
when "projects:merge_requests:diffs" when "projects:merge_requests:diffs"
new Diff() new Diff()
......
#= require jquery
#= require mousetrap
#= require shortcuts_navigation
class @ShortcutsIssuable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
Mousetrap.bind('a', ->
$('.js-assignee').select2('open')
return false
)
Mousetrap.bind('m', ->
$('.js-milestone').select2('open')
return false
)
Mousetrap.bind('r', =>
@replyWithSelectedText()
return false
)
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_requests')
else
@enabledHelp.push('.hidden-shortcut.issues')
replyWithSelectedText: ->
if window.getSelection
selected = window.getSelection().toString()
replyField = $('.js-main-target-form #note_note')
return if selected.trim() == ""
# Put a '>' character before each non-empty line in the selection
quote = _.map selected.split("\n"), (val) ->
"> #{val}\n" if val.trim() != ''
# If replyField already has some content, add a newline before our quote
separator = replyField.val().trim() != "" and "\n" or ''
replyField.val (_, current) ->
current + separator + quote.join('') + "\n"
# Trigger autosave for the added text
replyField.trigger('input')
# Focus the input field
replyField.focus()
#= require shortcuts_navigation
class @ShortcutsIssueable extends ShortcutsNavigation
constructor: (isMergeRequest) ->
super()
Mousetrap.bind('a', ->
$('.js-assignee').select2('open')
return false
)
Mousetrap.bind('m', ->
$('.js-milestone').select2('open')
return false
)
if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_reuests')
else
@enabledHelp.push('.hidden-shortcut.issues')
#= require d3
#= require jquery
#= require stat_graph_contributors_util
class @ContributorsStatGraph class @ContributorsStatGraph
init: (log) -> init: (log) ->
@parsed_log = ContributorsStatGraphUtil.parse_log(log) @parsed_log = ContributorsStatGraphUtil.parse_log(log)
......
#= require d3
#= require jquery
#= require underscore
class @ContributorsGraph class @ContributorsGraph
MARGIN: MARGIN:
top: 20 top: 20
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# Automatically sets the layout and ensures an administrator is logged in # Automatically sets the layout and ensures an administrator is logged in
class Admin::ApplicationController < ApplicationController class Admin::ApplicationController < ApplicationController
layout 'admin' layout 'admin'
before_filter :authenticate_admin! before_action :authenticate_admin!
def authenticate_admin! def authenticate_admin!
return render_404 unless current_user.is_admin? return render_404 unless current_user.is_admin?
......
class Admin::ApplicationSettingsController < Admin::ApplicationController class Admin::ApplicationSettingsController < Admin::ApplicationController
before_filter :set_application_setting before_action :set_application_setting
def show def show
end end
......
class Admin::BroadcastMessagesController < Admin::ApplicationController class Admin::BroadcastMessagesController < Admin::ApplicationController
before_filter :broadcast_messages before_action :broadcast_messages
def index def index
@broadcast_message = BroadcastMessage.new @broadcast_message = BroadcastMessage.new
......
class Admin::DeployKeysController < Admin::ApplicationController class Admin::DeployKeysController < Admin::ApplicationController
before_filter :deploy_keys, only: [:index] before_action :deploy_keys, only: [:index]
before_filter :deploy_key, only: [:show, :destroy] before_action :deploy_key, only: [:show, :destroy]
def index def index
......
class Admin::GroupsController < Admin::ApplicationController class Admin::GroupsController < Admin::ApplicationController
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :members_update] before_action :group, only: [:edit, :show, :update, :destroy, :project_update, :members_update]
def index def index
@groups = Group.all @groups = Group.all
......
class Admin::KeysController < Admin::ApplicationController class Admin::KeysController < Admin::ApplicationController
before_filter :user, only: [:show, :destroy] before_action :user, only: [:show, :destroy]
def show def show
@key = user.keys.find(params[:id]) @key = user.keys.find(params[:id])
......
class Admin::ProjectsController < Admin::ApplicationController class Admin::ProjectsController < Admin::ApplicationController
before_filter :project, only: [:show, :transfer] before_action :project, only: [:show, :transfer]
before_filter :group, only: [:show, :transfer] before_action :group, only: [:show, :transfer]
before_filter :repository, only: [:show, :transfer] before_action :repository, only: [:show, :transfer]
def index def index
@projects = Project.all @projects = Project.all
......
class Admin::ServicesController < Admin::ApplicationController class Admin::ServicesController < Admin::ApplicationController
before_filter :service, only: [:edit, :update] before_action :service, only: [:edit, :update]
def index def index
@services = services_templates @services = services_templates
......
class Admin::UsersController < Admin::ApplicationController class Admin::UsersController < Admin::ApplicationController
before_filter :user, only: [:show, :edit, :update, :destroy] before_action :user, only: [:show, :edit, :update, :destroy]
def index def index
@users = User.order_name_asc.filter(params[:filter]) @users = User.order_name_asc.filter(params[:filter])
......
...@@ -6,15 +6,15 @@ class ApplicationController < ActionController::Base ...@@ -6,15 +6,15 @@ class ApplicationController < ActionController::Base
PER_PAGE = 20 PER_PAGE = 20
before_filter :authenticate_user_from_token! before_action :authenticate_user_from_token!
before_filter :authenticate_user! before_action :authenticate_user!
before_filter :reject_blocked! before_action :reject_blocked!
before_filter :check_password_expiration before_action :check_password_expiration
before_filter :ldap_security_check before_action :ldap_security_check
before_filter :default_headers before_action :default_headers
before_filter :add_gon_variables before_action :add_gon_variables
before_filter :configure_permitted_parameters, if: :devise_controller? before_action :configure_permitted_parameters, if: :devise_controller?
before_filter :require_email, unless: :devise_controller? before_action :require_email, unless: :devise_controller?
protect_from_forgery with: :exception protect_from_forgery with: :exception
......
class Dashboard::MilestonesController < ApplicationController class Dashboard::MilestonesController < ApplicationController
before_filter :load_projects before_action :load_projects
def index def index
project_milestones = case params[:state] project_milestones = case params[:state]
......
class Dashboard::ProjectsController < ApplicationController class Dashboard::ProjectsController < ApplicationController
before_filter :event_filter before_action :event_filter
def starred def starred
@projects = current_user.starred_projects @projects = current_user.starred_projects
......
class DashboardController < ApplicationController class DashboardController < ApplicationController
respond_to :html respond_to :html
before_filter :load_projects, except: [:projects] before_action :load_projects, except: [:projects]
before_filter :event_filter, only: :show before_action :event_filter, only: :show
def show def show
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
......
class Explore::GroupsController < ApplicationController class Explore::GroupsController < ApplicationController
skip_before_filter :authenticate_user!, skip_before_action :authenticate_user!,
:reject_blocked, :set_current_user_for_observers :reject_blocked, :set_current_user_for_observers
layout "explore" layout "explore"
......
class Explore::ProjectsController < ApplicationController class Explore::ProjectsController < ApplicationController
skip_before_filter :authenticate_user!, skip_before_action :authenticate_user!,
:reject_blocked :reject_blocked
layout 'explore' layout 'explore'
......
class Groups::GroupMembersController < Groups::ApplicationController class Groups::GroupMembersController < Groups::ApplicationController
skip_before_filter :authenticate_user!, only: [:index] skip_before_action :authenticate_user!, only: [:index]
before_filter :group before_action :group
# Authorize # Authorize
before_filter :authorize_read_group! before_action :authorize_read_group!
before_filter :authorize_admin_group!, except: [:index, :leave] before_action :authorize_admin_group!, except: [:index, :leave]
layout :determine_layout layout :determine_layout
......
class Groups::MilestonesController < ApplicationController class Groups::MilestonesController < ApplicationController
layout 'group' layout 'group'
before_filter :authorize_group_milestone!, only: :update before_action :authorize_group_milestone!, only: :update
def index def index
project_milestones = case params[:state] project_milestones = case params[:state]
......
class GroupsController < Groups::ApplicationController class GroupsController < Groups::ApplicationController
skip_before_filter :authenticate_user!, only: [:show, :issues, :merge_requests] skip_before_action :authenticate_user!, only: [:show, :issues, :merge_requests]
respond_to :html respond_to :html
before_filter :group, except: [:new, :create] before_action :group, except: [:new, :create]
# Authorize # Authorize
before_filter :authorize_read_group!, except: [:new, :create] before_action :authorize_read_group!, except: [:new, :create]
before_filter :authorize_admin_group!, only: [:edit, :update, :destroy, :projects] before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects]
before_filter :authorize_create_group!, only: [:new, :create] before_action :authorize_create_group!, only: [:new, :create]
# Load group projects # Load group projects
before_filter :load_projects, except: [:new, :create, :projects, :edit, :update] before_action :load_projects, except: [:new, :create, :projects, :edit, :update]
before_filter :event_filter, only: :show before_action :event_filter, only: :show
before_filter :set_title, only: [:new, :create] before_action :set_title, only: [:new, :create]
layout :determine_layout layout :determine_layout
......
class Import::BitbucketController < Import::BaseController class Import::BitbucketController < Import::BaseController
before_filter :verify_bitbucket_import_enabled before_action :verify_bitbucket_import_enabled
before_filter :bitbucket_auth, except: :callback before_action :bitbucket_auth, except: :callback
rescue_from OAuth::Error, with: :bitbucket_unauthorized rescue_from OAuth::Error, with: :bitbucket_unauthorized
......
class Import::GithubController < Import::BaseController class Import::GithubController < Import::BaseController
before_filter :verify_github_import_enabled before_action :verify_github_import_enabled
before_filter :github_auth, except: :callback before_action :github_auth, except: :callback
rescue_from Octokit::Unauthorized, with: :github_unauthorized rescue_from Octokit::Unauthorized, with: :github_unauthorized
......
class Import::GitlabController < Import::BaseController class Import::GitlabController < Import::BaseController
before_filter :verify_gitlab_import_enabled before_action :verify_gitlab_import_enabled
before_filter :gitlab_auth, except: :callback before_action :gitlab_auth, except: :callback
rescue_from OAuth2::Error, with: :gitlab_unauthorized rescue_from OAuth2::Error, with: :gitlab_unauthorized
......
class Import::GoogleCodeController < Import::BaseController class Import::GoogleCodeController < Import::BaseController
before_filter :user_map, only: [:new_user_map, :create_user_map] before_action :user_map, only: [:new_user_map, :create_user_map]
def new def new
...@@ -68,7 +68,7 @@ class Import::GoogleCodeController < Import::BaseController ...@@ -68,7 +68,7 @@ class Import::GoogleCodeController < Import::BaseController
def status def status
unless client.valid? unless client.valid?
return redirect_to new_import_google_path return redirect_to new_import_google_code_path
end end
@repos = client.repos @repos = client.repos
......
class InvitesController < ApplicationController class InvitesController < ApplicationController
before_filter :member before_action :member
skip_before_filter :authenticate_user!, only: :decline skip_before_action :authenticate_user!, only: :decline
respond_to :html respond_to :html
......
class NamespacesController < ApplicationController class NamespacesController < ApplicationController
skip_before_filter :authenticate_user! skip_before_action :authenticate_user!
def show def show
namespace = Namespace.find_by(path: params[:id]) namespace = Namespace.find_by(path: params[:id])
......
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
before_filter :authenticate_user! before_action :authenticate_user!
layout "profile" layout "profile"
def index def index
......
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
before_filter :authenticate_resource_owner! before_action :authenticate_resource_owner!
layout "profile" layout "profile"
def new def new
......
class Profiles::KeysController < ApplicationController class Profiles::KeysController < ApplicationController
layout "profile" layout "profile"
skip_before_filter :authenticate_user!, only: [:get_keys] skip_before_action :authenticate_user!, only: [:get_keys]
def index def index
@keys = current_user.keys @keys = current_user.keys
......
class Profiles::PasswordsController < ApplicationController class Profiles::PasswordsController < ApplicationController
layout :determine_layout layout :determine_layout
skip_before_filter :check_password_expiration, only: [:new, :create] skip_before_action :check_password_expiration, only: [:new, :create]
before_filter :set_user before_action :set_user
before_filter :set_title before_action :set_title
before_filter :authorize_change_password! before_action :authorize_change_password!
def new def new
end end
......
class ProfilesController < ApplicationController class ProfilesController < ApplicationController
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
before_filter :user before_action :user
before_filter :authorize_change_username!, only: :update_username before_action :authorize_change_username!, only: :update_username
skip_before_filter :require_email, only: [:show, :update] skip_before_action :require_email, only: [:show, :update]
layout 'profile' layout 'profile'
......
class Projects::ApplicationController < ApplicationController class Projects::ApplicationController < ApplicationController
before_filter :project before_action :project
before_filter :repository before_action :repository
layout :determine_layout layout :determine_layout
def authenticate_user! def authenticate_user!
......
class Projects::AvatarsController < Projects::ApplicationController class Projects::AvatarsController < Projects::ApplicationController
layout 'project' layout 'project'
before_filter :project before_action :project
def show def show
@blob = @project.repository.blob_at_branch('master', @project.avatar_in_git) @blob = @project.repository.blob_at_branch('master', @project.avatar_in_git)
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
class Projects::BlameController < Projects::ApplicationController class Projects::BlameController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :assign_ref_vars before_action :assign_ref_vars
before_filter :authorize_download_code! before_action :authorize_download_code!
def show def show
@blame = Gitlab::Git::Blame.new(@repository, @commit.id, @path) @blame = Gitlab::Git::Blame.new(@repository, @commit.id, @path)
......
...@@ -6,15 +6,15 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -6,15 +6,15 @@ class Projects::BlobController < Projects::ApplicationController
# Raised when given an invalid file path # Raised when given an invalid file path
class InvalidPathError < StandardError; end class InvalidPathError < StandardError; end
before_filter :require_non_empty_project, except: [:new, :create] before_action :require_non_empty_project, except: [:new, :create]
before_filter :authorize_download_code! before_action :authorize_download_code!
before_filter :authorize_push_code!, only: [:destroy] before_action :authorize_push_code!, only: [:destroy]
before_filter :assign_blob_vars before_action :assign_blob_vars
before_filter :commit, except: [:new, :create] before_action :commit, except: [:new, :create]
before_filter :blob, except: [:new, :create] before_action :blob, except: [:new, :create]
before_filter :from_merge_request, only: [:edit, :update] before_action :from_merge_request, only: [:edit, :update]
before_filter :after_edit_path, only: [:edit, :update] before_action :after_edit_path, only: [:edit, :update]
before_filter :require_branch_head, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update]
def new def new
commit unless @repository.empty? commit unless @repository.empty?
......
class Projects::BranchesController < Projects::ApplicationController class Projects::BranchesController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
# Authorize # Authorize
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :authorize_download_code! before_action :authorize_download_code!
before_filter :authorize_push_code!, only: [:create, :destroy] before_action :authorize_push_code!, only: [:create, :destroy]
def index def index
@sort = params[:sort] || 'name' @sort = params[:sort] || 'name'
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
# Not to be confused with CommitsController, plural. # Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController class Projects::CommitController < Projects::ApplicationController
# Authorize # Authorize
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :authorize_download_code! before_action :authorize_download_code!
before_filter :commit before_action :commit
def show def show
return git_not_found! unless @commit return git_not_found! unless @commit
......
...@@ -3,9 +3,9 @@ require "base64" ...@@ -3,9 +3,9 @@ require "base64"
class Projects::CommitsController < Projects::ApplicationController class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :assign_ref_vars before_action :assign_ref_vars
before_filter :authorize_download_code! before_action :authorize_download_code!
def show def show
@repo = @project.repository @repo = @project.repository
......
class Projects::CompareController < Projects::ApplicationController class Projects::CompareController < Projects::ApplicationController
# Authorize # Authorize
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :authorize_download_code! before_action :authorize_download_code!
def index def index
end end
......
...@@ -2,7 +2,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -2,7 +2,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
respond_to :html respond_to :html
# Authorize # Authorize
before_filter :authorize_admin_project! before_action :authorize_admin_project!
layout "project_settings" layout "project_settings"
......
class Projects::ForksController < Projects::ApplicationController class Projects::ForksController < Projects::ApplicationController
# Authorize # Authorize
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :authorize_download_code! before_action :authorize_download_code!
def new def new
@namespaces = current_user.manageable_namespaces @namespaces = current_user.manageable_namespaces
......
class Projects::GraphsController < Projects::ApplicationController class Projects::GraphsController < Projects::ApplicationController
# Authorize # Authorize
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :authorize_download_code! before_action :authorize_download_code!
def show def show
respond_to do |format| respond_to do |format|
......
class Projects::HooksController < Projects::ApplicationController class Projects::HooksController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_admin_project! before_action :authorize_admin_project!
respond_to :html respond_to :html
......
class Projects::ImportsController < Projects::ApplicationController class Projects::ImportsController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_admin_project! before_action :authorize_admin_project!
before_filter :require_no_repo before_action :require_no_repo
before_filter :redirect_if_progress, except: :show before_action :redirect_if_progress, except: :show
def new def new
end end
......
class Projects::IssuesController < Projects::ApplicationController class Projects::IssuesController < Projects::ApplicationController
before_filter :module_enabled before_action :module_enabled
before_filter :issue, only: [:edit, :update, :show, :toggle_subscription] before_action :issue, only: [:edit, :update, :show, :toggle_subscription]
# Allow read any issue # Allow read any issue
before_filter :authorize_read_issue! before_action :authorize_read_issue!
# Allow write(create) issue # Allow write(create) issue
before_filter :authorize_write_issue!, only: [:new, :create] before_action :authorize_write_issue!, only: [:new, :create]
# Allow modify issue # Allow modify issue
before_filter :authorize_modify_issue!, only: [:edit, :update] before_action :authorize_modify_issue!, only: [:edit, :update]
# Allow issues bulk update # Allow issues bulk update
before_filter :authorize_admin_issues!, only: [:bulk_update] before_action :authorize_admin_issues!, only: [:bulk_update]
respond_to :html respond_to :html
......
class Projects::LabelsController < Projects::ApplicationController class Projects::LabelsController < Projects::ApplicationController
before_filter :module_enabled before_action :module_enabled
before_filter :label, only: [:edit, :update, :destroy] before_action :label, only: [:edit, :update, :destroy]
before_filter :authorize_labels! before_action :authorize_labels!
before_filter :authorize_admin_labels!, except: [:index] before_action :authorize_admin_labels!, except: [:index]
respond_to :js, :html respond_to :js, :html
......
require 'gitlab/satellite/satellite' require 'gitlab/satellite/satellite'
class Projects::MergeRequestsController < Projects::ApplicationController class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :module_enabled before_action :module_enabled
before_filter :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status, :toggle_subscription] before_action :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status, :toggle_subscription]
before_filter :closes_issues, only: [:edit, :update, :show, :diffs] before_action :closes_issues, only: [:edit, :update, :show, :diffs]
before_filter :validates_merge_request, only: [:show, :diffs] before_action :validates_merge_request, only: [:show, :diffs]
before_filter :define_show_vars, only: [:show, :diffs] before_action :define_show_vars, only: [:show, :diffs]
# Allow read any merge_request # Allow read any merge_request
before_filter :authorize_read_merge_request! before_action :authorize_read_merge_request!
# Allow write(create) merge_request # Allow write(create) merge_request
before_filter :authorize_write_merge_request!, only: [:new, :create] before_action :authorize_write_merge_request!, only: [:new, :create]
# Allow modify merge_request # Allow modify merge_request
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] before_action :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
def index def index
terms = params['issue_search'] terms = params['issue_search']
......
class Projects::MilestonesController < Projects::ApplicationController class Projects::MilestonesController < Projects::ApplicationController
before_filter :module_enabled before_action :module_enabled
before_filter :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests] before_action :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests]
# Allow read any milestone # Allow read any milestone
before_filter :authorize_read_milestone! before_action :authorize_read_milestone!
# Allow admin milestone # Allow admin milestone
before_filter :authorize_admin_milestone!, except: [:index, :show] before_action :authorize_admin_milestone!, except: [:index, :show]
respond_to :html respond_to :html
......
...@@ -2,9 +2,9 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -2,9 +2,9 @@ class Projects::NetworkController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include ApplicationHelper include ApplicationHelper
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :assign_ref_vars before_action :assign_ref_vars
before_filter :authorize_download_code! before_action :authorize_download_code!
def show def show
respond_to do |format| respond_to do |format|
......
class Projects::NotesController < Projects::ApplicationController class Projects::NotesController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_read_note! before_action :authorize_read_note!
before_filter :authorize_write_note!, only: [:create] before_action :authorize_write_note!, only: [:create]
before_filter :authorize_admin_note!, only: [:update, :destroy] before_action :authorize_admin_note!, only: [:update, :destroy]
before_filter :find_current_user_notes, except: [:destroy, :delete_attachment] before_action :find_current_user_notes, except: [:destroy, :delete_attachment]
def index def index
current_fetched_at = Time.now.to_i current_fetched_at = Time.now.to_i
......
class Projects::ProjectMembersController < Projects::ApplicationController class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_admin_project!, except: :leave before_action :authorize_admin_project!, except: :leave
layout "project_settings" layout "project_settings"
......
class Projects::ProtectedBranchesController < Projects::ApplicationController class Projects::ProtectedBranchesController < Projects::ApplicationController
# Authorize # Authorize
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :authorize_admin_project! before_action :authorize_admin_project!
layout "project_settings" layout "project_settings"
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
class Projects::RawController < Projects::ApplicationController class Projects::RawController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :assign_ref_vars before_action :assign_ref_vars
before_filter :authorize_download_code! before_action :authorize_download_code!
def show def show
@blob = @repository.blob_at(@commit.id, @path) @blob = @repository.blob_at(@commit.id, @path)
......
class Projects::RefsController < Projects::ApplicationController class Projects::RefsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :assign_ref_vars before_action :assign_ref_vars
before_filter :authorize_download_code! before_action :authorize_download_code!
def switch def switch
respond_to do |format| respond_to do |format|
......
class Projects::RepositoriesController < Projects::ApplicationController class Projects::RepositoriesController < Projects::ApplicationController
# Authorize # Authorize
before_filter :require_non_empty_project, except: :create before_action :require_non_empty_project, except: :create
before_filter :authorize_download_code! before_action :authorize_download_code!
before_filter :authorize_admin_project!, only: :create before_action :authorize_admin_project!, only: :create
def create def create
@project.create_repository @project.create_repository
......
class Projects::ServicesController < Projects::ApplicationController class Projects::ServicesController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_admin_project! before_action :authorize_admin_project!
before_filter :service, only: [:edit, :update, :test] before_action :service, only: [:edit, :update, :test]
respond_to :html respond_to :html
......
class Projects::SnippetsController < Projects::ApplicationController class Projects::SnippetsController < Projects::ApplicationController
before_filter :module_enabled before_action :module_enabled
before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read any snippet # Allow read any snippet
before_filter :authorize_read_project_snippet! before_action :authorize_read_project_snippet!
# Allow write(create) snippet # Allow write(create) snippet
before_filter :authorize_write_project_snippet!, only: [:new, :create] before_action :authorize_write_project_snippet!, only: [:new, :create]
# Allow modify snippet # Allow modify snippet
before_filter :authorize_modify_project_snippet!, only: [:edit, :update] before_action :authorize_modify_project_snippet!, only: [:edit, :update]
# Allow destroy snippet # Allow destroy snippet
before_filter :authorize_admin_project_snippet!, only: [:destroy] before_action :authorize_admin_project_snippet!, only: [:destroy]
respond_to :html respond_to :html
......
class Projects::TagsController < Projects::ApplicationController class Projects::TagsController < Projects::ApplicationController
# Authorize # Authorize
before_filter :require_non_empty_project before_action :require_non_empty_project
before_filter :authorize_download_code! before_action :authorize_download_code!
before_filter :authorize_push_code!, only: [:create] before_action :authorize_push_code!, only: [:create]
before_filter :authorize_admin_project!, only: [:destroy] before_action :authorize_admin_project!, only: [:destroy]
def index def index
sorted = VersionSorter.rsort(@repository.tag_names) sorted = VersionSorter.rsort(@repository.tag_names)
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
class Projects::TreeController < Projects::ApplicationController class Projects::TreeController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
before_filter :require_non_empty_project, except: [:new, :create] before_action :require_non_empty_project, except: [:new, :create]
before_filter :assign_ref_vars before_action :assign_ref_vars
before_filter :authorize_download_code! before_action :authorize_download_code!
def show def show
if tree.entries.empty? if tree.entries.empty?
......
...@@ -4,8 +4,8 @@ class Projects::UploadsController < Projects::ApplicationController ...@@ -4,8 +4,8 @@ class Projects::UploadsController < Projects::ApplicationController
# We want to skip these filters for only the `show` action if `image?` is true, # We want to skip these filters for only the `show` action if `image?` is true,
# but `skip_before_filter` doesn't work with both `only` and `if`, so we accomplish the same like this. # but `skip_before_filter` doesn't work with both `only` and `if`, so we accomplish the same like this.
skipped_filters = [:authenticate_user!, :reject_blocked!, :project, :repository] skipped_filters = [:authenticate_user!, :reject_blocked!, :project, :repository]
skip_before_filter *skipped_filters, only: [:show] skip_before_action *skipped_filters, only: [:show]
before_filter *skipped_filters, only: [:show], unless: :image? before_action *skipped_filters, only: [:show], unless: :image?
def create def create
link_to_file = ::Projects::UploadService.new(project, params[:file]). link_to_file = ::Projects::UploadService.new(project, params[:file]).
......
require 'project_wiki' require 'project_wiki'
class Projects::WikisController < Projects::ApplicationController class Projects::WikisController < Projects::ApplicationController
before_filter :authorize_read_wiki! before_action :authorize_read_wiki!
before_filter :authorize_write_wiki!, only: [:edit, :create, :history] before_action :authorize_write_wiki!, only: [:edit, :create, :history]
before_filter :authorize_admin_wiki!, only: :destroy before_action :authorize_admin_wiki!, only: :destroy
before_filter :load_project_wiki before_action :load_project_wiki
include WikiHelper include WikiHelper
def pages def pages
......
class ProjectsController < ApplicationController class ProjectsController < ApplicationController
prepend_before_filter :render_go_import, only: [:show] prepend_before_filter :render_go_import, only: [:show]
skip_before_filter :authenticate_user!, only: [:show] skip_before_action :authenticate_user!, only: [:show]
before_filter :project, except: [:new, :create] before_action :project, except: [:new, :create]
before_filter :repository, except: [:new, :create] before_action :repository, except: [:new, :create]
# Authorize # Authorize
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
before_filter :set_title, only: [:new, :create] before_action :set_title, only: [:new, :create]
before_filter :event_filter, only: :show before_action :event_filter, only: :show
layout 'navless', only: [:new, :create, :fork] layout 'navless', only: [:new, :create, :fork]
......
class RegistrationsController < Devise::RegistrationsController class RegistrationsController < Devise::RegistrationsController
before_filter :signup_enabled? before_action :signup_enabled?
def new def new
redirect_to(new_user_session_path) redirect_to(new_user_session_path)
......
class SnippetsController < ApplicationController class SnippetsController < ApplicationController
before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
before_filter :authorize_modify_snippet!, only: [:edit, :update]
before_filter :authorize_admin_snippet!, only: [:destroy]
before_filter :set_title # Allow modify snippet
before_action :authorize_modify_snippet!, only: [:edit, :update]
skip_before_filter :authenticate_user!, only: [:index, :user_index, :show, :raw] # Allow destroy snippet
before_action :authorize_admin_snippet!, only: [:destroy]
before_action :set_title
skip_before_action :authenticate_user!, only: [:index, :user_index, :show, :raw]
respond_to :html respond_to :html
......
class UploadsController < ApplicationController class UploadsController < ApplicationController
skip_before_filter :authenticate_user! skip_before_action :authenticate_user!
before_filter :find_model, :authorize_access! before_action :find_model, :authorize_access!
def show def show
uploader = @model.send(upload_mount) uploader = @model.send(upload_mount)
......
class UsersController < ApplicationController class UsersController < ApplicationController
skip_before_filter :authenticate_user! skip_before_action :authenticate_user!
before_filter :set_user before_action :set_user
layout :determine_layout layout :determine_layout
def show def show
......
...@@ -255,12 +255,16 @@ module ApplicationHelper ...@@ -255,12 +255,16 @@ module ApplicationHelper
# #
# Returns `html_options`, adding `rel: nofollow` for external links # Returns `html_options`, adding `rel: nofollow` for external links
def add_nofollow(link, html_options = {}) def add_nofollow(link, html_options = {})
begin
uri = URI(link) uri = URI(link)
if uri && uri.absolute? && uri.host != Gitlab.config.gitlab.host if uri && uri.absolute? && uri.host != Gitlab.config.gitlab.host
rel = html_options.fetch(:rel, '') rel = html_options.fetch(:rel, '')
html_options[:rel] = (rel + ' nofollow').strip html_options[:rel] = (rel + ' nofollow').strip
end end
rescue URI::Error
# noop
end
html_options html_options
end end
......
...@@ -74,6 +74,7 @@ module GitlabMarkdownHelper ...@@ -74,6 +74,7 @@ module GitlabMarkdownHelper
end end
end end
# TODO (rspeicher): This should be its own filter
def create_relative_links(text) def create_relative_links(text)
paths = extract_paths(text) paths = extract_paths(text)
......
...@@ -108,4 +108,7 @@ module IssuesHelper ...@@ -108,4 +108,7 @@ module IssuesHelper
xml.summary issue.title xml.summary issue.title
end end
end end
# Required for Gitlab::Markdown::IssueReferenceFilter
module_function :url_for_issue, :title_for_issue
end end
module LabelsHelper module LabelsHelper
include ActionView::Helpers::TagHelper
def project_label_names def project_label_names
@project.labels.pluck(:title) @project.labels.pluck(:title)
end end
...@@ -7,9 +9,13 @@ module LabelsHelper ...@@ -7,9 +9,13 @@ module LabelsHelper
label_color = label.color || Label::DEFAULT_COLOR label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color) text_color = text_color_for_bg(label_color)
content_tag :span, class: 'label color-label', style: "background-color:#{label_color};color:#{text_color}" do # Intentionally not using content_tag here so that this method can be called
label.name # by LabelReferenceFilter
end span = %(<span class="label color-label") +
%( style="background-color: #{label_color}; color: #{text_color}">) +
escape_once(label.name) + '</span>'
span.html_safe
end end
def suggested_colors def suggested_colors
...@@ -42,13 +48,16 @@ module LabelsHelper ...@@ -42,13 +48,16 @@ module LabelsHelper
r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex) r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex)
if (r + g + b) > 500 if (r + g + b) > 500
"#333" '#333333'
else else
"#FFF" '#FFFFFF'
end end
end end
def project_labels_options(project) def project_labels_options(project)
options_from_collection_for_select(project.labels, 'name', 'name', params[:label_name]) options_from_collection_for_select(project.labels, 'name', 'name', params[:label_name])
end end
# Required for Gitlab::Markdown::LabelReferenceFilter
module_function :render_colored_label, :text_color_for_bg, :escape_once
end end
...@@ -27,7 +27,7 @@ class Label < ActiveRecord::Base ...@@ -27,7 +27,7 @@ class Label < ActiveRecord::Base
# Don't allow '?', '&', and ',' for label titles # Don't allow '?', '&', and ',' for label titles
validates :title, validates :title,
presence: true, presence: true,
format: { with: /\A[^&\?,&]+\z/ }, format: { with: /\A[^&\?,]+\z/ },
uniqueness: { scope: :project_id } uniqueness: { scope: :project_id }
default_scope { order(title: :asc) } default_scope { order(title: :asc) }
......
...@@ -50,8 +50,9 @@ class HipchatService < Service ...@@ -50,8 +50,9 @@ class HipchatService < Service
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
message = create_message(data)
gate[room].send('GitLab', create_message(data)) return unless message.present?
gate[room].send('GitLab', message)
end end
private private
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
%p #{@service.description} template %p #{@service.description} template
= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |f| = form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |form|
- if @service.errors.any? - if @service.errors.any?
#error_explanation #error_explanation
.alert.alert-danger .alert.alert-danger
...@@ -15,80 +15,68 @@ ...@@ -15,80 +15,68 @@
= markdown @service.help = markdown @service.help
.form-group .form-group
= f.label :active, "Active", class: "control-label" = form.label :active, "Active", class: "control-label"
.col-sm-10 .col-sm-10
= f.check_box :active = form.check_box :active
- if @service.supported_events.length > 1 - if @service.supported_events.length > 1
.form-group .form-group
= f.label :url, "Trigger", class: 'control-label' = form.label :url, "Trigger", class: 'control-label'
.col-sm-10 .col-sm-10
- if @service.supported_events.include?("push") - if @service.supported_events.include?("push")
%div %div
= f.check_box :push_events, class: 'pull-left' = form.check_box :push_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :push_events, class: 'list-label' do = form.label :push_events, class: 'list-label' do
%strong Push events %strong Push events
%p.light %p.light
This url will be triggered by a push to the repository This url will be triggered by a push to the repository
- if @service.supported_events.include?("tag_push") - if @service.supported_events.include?("tag_push")
%div %div
= f.check_box :tag_push_events, class: 'pull-left' = form.check_box :tag_push_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :tag_push_events, class: 'list-label' do = form.label :tag_push_events, class: 'list-label' do
%strong Tag push events %strong Tag push events
%p.light %p.light
This url will be triggered when a new tag is pushed to the repository This url will be triggered when a new tag is pushed to the repository
- if @service.supported_events.include?("note") - if @service.supported_events.include?("note")
%div %div
= f.check_box :note_events, class: 'pull-left' = form.check_box :note_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :note_events, class: 'list-label' do = form.label :note_events, class: 'list-label' do
%strong Comments %strong Comments
%p.light %p.light
This url will be triggered when someone adds a comment This url will be triggered when someone adds a comment
- if @service.supported_events.include?("issue") - if @service.supported_events.include?("issue")
%div %div
= f.check_box :issues_events, class: 'pull-left' = form.check_box :issues_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :issues_events, class: 'list-label' do = form.label :issues_events, class: 'list-label' do
%strong Issues events %strong Issues events
%p.light %p.light
This url will be triggered when an issue is created This url will be triggered when an issue is created
- if @service.supported_events.include?("merge_request") - if @service.supported_events.include?("merge_request")
%div %div
= f.check_box :merge_requests_events, class: 'pull-left' = form.check_box :merge_requests_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :merge_requests_events, class: 'list-label' do = form.label :merge_requests_events, class: 'list-label' do
%strong Merge Request events %strong Merge Request events
%p.light %p.light
This url will be triggered when a merge request is created This url will be triggered when a merge request is created
- @service.fields.each do |field| - @service.fields.each do |field|
- name = field[:name]
- title = field[:title] || name.humanize
- value = @service.send(name) unless field[:type] == 'password'
- type = field[:type] - type = field[:type]
- placeholder = field[:placeholder]
- choices = field[:choices]
- default_choice = field[:default_choice]
- help = field[:help]
.form-group - if type == 'fieldset'
= f.label name, title, class: "control-label" - fields = field[:fields]
.col-sm-10 - legend = field[:legend]
- if type == 'text'
= f.text_field name, class: "form-control", placeholder: placeholder %fieldset
- elsif type == 'textarea' %legend= legend
= f.text_area name, rows: 5, class: "form-control", placeholder: placeholder - fields.each do |subfield|
- elsif type == 'checkbox' = render 'shared/field', form: form, field: subfield
= f.check_box name - else
- elsif type == 'select' = render 'shared/field', form: form, field: field
= f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password'
= f.password_field name, class: 'form-control'
- if help
%span.help-block= help
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = form.submit 'Save', class: 'btn btn-save'
...@@ -187,7 +187,11 @@ ...@@ -187,7 +187,11 @@
%td.shortcut %td.shortcut
.key m .key m
%td Change milestone %td Change milestone
%tbody{ class: 'hidden-shortcut merge_reuests', style: 'display:none' } %tr
%td.shortcut
.key r
%td Reply (quoting selected text)
%tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' }
%tr %tr
%th %th
%th Merge Requests %th Merge Requests
...@@ -199,6 +203,10 @@ ...@@ -199,6 +203,10 @@
%td.shortcut %td.shortcut
.key m .key m
%td Change milestone %td Change milestone
%tr
%td.shortcut
.key r
%td Reply (quoting selected text)
:javascript :javascript
......
...@@ -8,9 +8,31 @@ ...@@ -8,9 +8,31 @@
Customize how Google Code email addresses and usernames are imported into GitLab. Customize how Google Code email addresses and usernames are imported into GitLab.
In the next step, you'll be able to select the projects you want to import. In the next step, you'll be able to select the projects you want to import.
%p %p
The user map is a JSON document mapping Google Code users (as keys) to the way they will be imported into GitLab (as values). By default the username is masked to ensure users' privacy. The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.
%ul
%li
%strong Default: Directly import the Google Code email address or username
%p %p
To map a Google Code user to a full name or GitLab user, simply replace the value, e.g. <code>"johnsmith@gmail.com": "John Smith"</code> or <code>"johnsmith@gmail.com": "@johnsmith"</code>. Be sure to preserve the surrounding double quotes and other punctuation. <code>"johnsmith@example.com": "johnsm...@example.com"</code>
will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com.
The email address or username is masked to ensure the user's privacy.
%li
%strong Map a Google Code user to a GitLab user
%p
<code>"johnsmith@example.com": "@johnsmith"</code>
will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com,
and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com.
%li
%strong Map a Google Code user to a full name
%p
<code>"johnsmith@example.com": "John Smith"</code>
will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.
%li
%strong Map a Google Code user to a full email address
%p
<code>"johnsmith@example.com": "johnsmith@example.com"</code>
will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com.
By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address.
.form-group .form-group
.col-sm-12 .col-sm-12
......
...@@ -9,5 +9,5 @@ ...@@ -9,5 +9,5 @@
%div %div
.md-write-holder .md-write-holder
= yield = yield
.md-preview-holder.hide .md.md-preview-holder.hide
.js-md-preview{class: (preview_class if defined?(preview_class))} .js-md-preview{class: (preview_class if defined?(preview_class))}
...@@ -82,12 +82,12 @@ ...@@ -82,12 +82,12 @@
.mr-compare.merge-request .mr-compare.merge-request
%ul.nav.nav-tabs.merge-request-tabs %ul.nav.nav-tabs.merge-request-tabs
%li.commits-tab{data: {action: 'commits'}} %li.commits-tab{data: {action: 'commits', toggle: 'tab'}}
= link_to url_for(params) do = link_to url_for(params) do
%i.fa.fa-history %i.fa.fa-history
Commits Commits
%span.badge= @commits.size %span.badge= @commits.size
%li.diffs-tab{data: {action: 'diffs'}} %li.diffs-tab{data: {action: 'diffs', toggle: 'tab'}}
= link_to url_for(params) do = link_to url_for(params) do
%i.fa.fa-list-alt %i.fa.fa-list-alt
Changes Changes
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
%hr %hr
= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f| = form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |form|
- if @service.errors.any? - if @service.errors.any?
.alert.alert-danger .alert.alert-danger
%ul %ul
...@@ -23,83 +23,71 @@ ...@@ -23,83 +23,71 @@
= markdown @service.help = markdown @service.help
.form-group .form-group
= f.label :active, "Active", class: "control-label" = form.label :active, "Active", class: "control-label"
.col-sm-10 .col-sm-10
= f.check_box :active = form.check_box :active
- if @service.supported_events.length > 1 - if @service.supported_events.length > 1
.form-group .form-group
= f.label :url, "Trigger", class: 'control-label' = form.label :url, "Trigger", class: 'control-label'
.col-sm-10 .col-sm-10
- if @service.supported_events.include?("push") - if @service.supported_events.include?("push")
%div %div
= f.check_box :push_events, class: 'pull-left' = form.check_box :push_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :push_events, class: 'list-label' do = form.label :push_events, class: 'list-label' do
%strong Push events %strong Push events
%p.light %p.light
This url will be triggered by a push to the repository This url will be triggered by a push to the repository
- if @service.supported_events.include?("tag_push") - if @service.supported_events.include?("tag_push")
%div %div
= f.check_box :tag_push_events, class: 'pull-left' = form.check_box :tag_push_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :tag_push_events, class: 'list-label' do = form.label :tag_push_events, class: 'list-label' do
%strong Tag push events %strong Tag push events
%p.light %p.light
This url will be triggered when a new tag is pushed to the repository This url will be triggered when a new tag is pushed to the repository
- if @service.supported_events.include?("note") - if @service.supported_events.include?("note")
%div %div
= f.check_box :note_events, class: 'pull-left' = form.check_box :note_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :note_events, class: 'list-label' do = form.label :note_events, class: 'list-label' do
%strong Comments %strong Comments
%p.light %p.light
This url will be triggered when someone adds a comment This url will be triggered when someone adds a comment
- if @service.supported_events.include?("issue") - if @service.supported_events.include?("issue")
%div %div
= f.check_box :issues_events, class: 'pull-left' = form.check_box :issues_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :issues_events, class: 'list-label' do = form.label :issues_events, class: 'list-label' do
%strong Issues events %strong Issues events
%p.light %p.light
This url will be triggered when an issue is created This url will be triggered when an issue is created
- if @service.supported_events.include?("merge_request") - if @service.supported_events.include?("merge_request")
%div %div
= f.check_box :merge_requests_events, class: 'pull-left' = form.check_box :merge_requests_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
= f.label :merge_requests_events, class: 'list-label' do = form.label :merge_requests_events, class: 'list-label' do
%strong Merge Request events %strong Merge Request events
%p.light %p.light
This url will be triggered when a merge request is created This url will be triggered when a merge request is created
- @service.fields.each do |field| - @service.fields.each do |field|
- name = field[:name]
- title = field[:title] || name.humanize
- value = service_field_value(field[:type], @service.send(name))
- type = field[:type] - type = field[:type]
- placeholder = field[:placeholder]
- choices = field[:choices]
- default_choice = field[:default_choice]
- help = field[:help]
.form-group - if type == 'fieldset'
= f.label name, title, class: "control-label" - fields = field[:fields]
.col-sm-10 - legend = field[:legend]
- if type == 'text'
= f.text_field name, class: "form-control", placeholder: placeholder %fieldset
- elsif type == 'textarea' %legend= legend
= f.text_area name, rows: 5, class: "form-control", placeholder: placeholder - fields.each do |subfield|
- elsif type == 'checkbox' = render 'shared/field', form: form, field: subfield
= f.check_box name - else
- elsif type == 'select' = render 'shared/field', form: form, field: field
= f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password'
= f.password_field name, placeholder: value, class: 'form-control'
- if help
%span.help-block= help
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = form.submit 'Save', class: 'btn btn-save'
&nbsp; &nbsp;
- if @service.valid? && @service.activated? - if @service.valid? && @service.activated?
- disabled = @service.can_test? ? '':'disabled' - disabled = @service.can_test? ? '':'disabled'
......
- name = field[:name]
- title = field[:title] || name.humanize
- value = service_field_value(field[:type], @service.send(name))
- type = field[:type]
- placeholder = field[:placeholder]
- choices = field[:choices]
- default_choice = field[:default_choice]
- help = field[:help]
.form-group
= form.label name, title, class: "control-label"
.col-sm-10
- if type == 'text'
= form.text_field name, class: "form-control", placeholder: placeholder
- elsif type == 'textarea'
= form.text_area name, rows: 5, class: "form-control", placeholder: placeholder
- elsif type == 'checkbox'
= form.check_box name
- elsif type == 'select'
= form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password'
= form.password_field name, placeholder: value, class: 'form-control'
- if help
%span.help-block= help
...@@ -2,6 +2,7 @@ require 'sidekiq/web' ...@@ -2,6 +2,7 @@ require 'sidekiq/web'
require 'api/api' require 'api/api'
Gitlab::Application.routes.draw do Gitlab::Application.routes.draw do
mount JasmineRails::Engine => '/specs' if defined?(JasmineRails)
use_doorkeeper do use_doorkeeper do
controllers applications: 'oauth/applications', controllers applications: 'oauth/applications',
authorized_applications: 'oauth/authorized_applications', authorized_applications: 'oauth/authorized_applications',
......
...@@ -470,7 +470,6 @@ ActiveRecord::Schema.define(version: 20150417122318) do ...@@ -470,7 +470,6 @@ ActiveRecord::Schema.define(version: 20150417122318) do
t.integer "notification_level", default: 1, null: false t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at" t.datetime "password_expires_at"
t.integer "created_by_id" t.integer "created_by_id"
t.datetime "last_credential_check_at"
t.string "avatar" t.string "avatar"
t.string "confirmation_token" t.string "confirmation_token"
t.datetime "confirmed_at" t.datetime "confirmed_at"
...@@ -478,6 +477,7 @@ ActiveRecord::Schema.define(version: 20150417122318) do ...@@ -478,6 +477,7 @@ ActiveRecord::Schema.define(version: 20150417122318) do
t.string "unconfirmed_email" t.string "unconfirmed_email"
t.boolean "hide_no_ssh_key", default: false t.boolean "hide_no_ssh_key", default: false
t.string "website_url", default: "", null: false t.string "website_url", default: "", null: false
t.datetime "last_credential_check_at"
t.string "github_access_token" t.string "github_access_token"
t.string "gitlab_access_token" t.string "gitlab_access_token"
t.string "notification_email" t.string "notification_email"
......
...@@ -16,7 +16,7 @@ To enable Slack integration you must create an Incoming WebHooks integration on ...@@ -16,7 +16,7 @@ To enable Slack integration you must create an Incoming WebHooks integration on
1. Choose the channel name you want to send notifications to 1. Choose the channel name you want to send notifications to
1. Click **Add Incoming WebHooks Integration**Add Integrations. 1. Click **Add Incoming WebHooks Integration**
- Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**. - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
1. Copy the **Webhook URL**, we'll need this later for GitLab. 1. Copy the **Webhook URL**, we'll need this later for GitLab.
...@@ -32,10 +32,15 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this ...@@ -32,10 +32,15 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this
1. Navigate to Settings -> Services -> Slack 1. Navigate to Settings -> Services -> Slack
1. Fill in your Slack details 1. Pick the triggers you want to activate
1. Fill in your Slack details
- Webhook: Paste the Webhook URL from the step above
- Username: Fill this in if you want to change the username of the bot
- Channel: Fill this in if you want to change the channel where the messages will be posted
- Mark it as active - Mark it as active
- Paste in the webhook URL you got from Slack
1. Save your settings
Have fun :) Have fun :)
......
...@@ -163,7 +163,7 @@ Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported ...@@ -163,7 +163,7 @@ Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported
## Special GitLab References ## Special GitLab References
GFM recognized special references. GFM recognizes special references.
You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project. You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project.
...@@ -171,19 +171,30 @@ GFM will turn that reference into a link so you can navigate between them easily ...@@ -171,19 +171,30 @@ GFM will turn that reference into a link so you can navigate between them easily
GFM will recognize the following: GFM will recognize the following:
- @foo : for specific team members or groups | input | references |
- @all : for the whole team |-----------------------:|:---------------------------|
- #123 : for issues | `@user_name` | specific user |
- !123 : for merge requests | `@group_name` | specific group |
- $123 : for snippets | `@all` | entire team |
- 1234567 : for commits | `#123` | issue |
- \[file\](path/to/file) : for file references | `!123` | merge request |
| `$123` | snippet |
GFM also recognizes references to commits, issues, and merge requests in other projects: | `~123` | label by ID |
| `~bug` | one-word label by name |
- namespace/project#123 : for issues | `~"feature request"` | multi-word label by name |
- namespace/project!123 : for merge requests | `9ba12248` | specific commit |
- namespace/project@1234567 : for commits | `9ba12248...b19a04f5` | commit range comparison |
| `[README](doc/README)` | repository file references |
GFM also recognizes certain cross-project references:
| input | references |
|----------------------------------------:|:------------------------|
| `namespace/project#123` | issue |
| `namespace/project!123` | merge request |
| `namespace/project$123` | snippet |
| `namespace/project@9ba12248` | specific commit |
| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
## Task Lists ## Task Lists
......
...@@ -162,6 +162,7 @@ Options: ...@@ -162,6 +162,7 @@ Options:
``` ```
BACKUP=timestamp_of_backup (required if more than one backup exists) BACKUP=timestamp_of_backup (required if more than one backup exists)
force=yes (do not ask if the authorized_keys file should get regenerated)
``` ```
Example output: Example output:
......
...@@ -126,7 +126,7 @@ sudo apt-get install nodejs ...@@ -126,7 +126,7 @@ sudo apt-get install nodejs
```bash ```bash
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
sudo -u git -H git fetch sudo -u git -H git fetch
sudo -u git -H git checkout v2.6.0 sudo -u git -H git checkout v2.6.2
``` ```
## 7. Install libs, migrations, etc. ## 7. Install libs, migrations, etc.
......
...@@ -42,6 +42,8 @@ sudo -u git -H git checkout v2.6.0 ...@@ -42,6 +42,8 @@ sudo -u git -H git checkout v2.6.0
### 4. Install libs, migrations, etc. ### 4. Install libs, migrations, etc.
Please refer to the [Node.js setup documentation](https://github.com/joyent/node/wiki/installing-node.js-via-package-manager#debian-and-ubuntu-based-linux-distributions) if you aren't running default GitLab server setup.
```bash ```bash
sudo apt-get install nodejs sudo apt-get install nodejs
......
# From 7.9 to 7.10
### 0. Stop server
sudo service gitlab stop
### 1. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 2. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 7-10-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 7-10-stable-ee
```
### 3. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
sudo -u git -H git checkout v2.6.2
```
### 4. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without ... postgres')
sudo -u git -H bundle install --without development test postgres --deployment
# PostgreSQL installations (note: the line below states '--without ... mysql')
sudo -u git -H bundle install --without development test mysql --deployment
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
### 5. Update config files
#### New configuration options for `gitlab.yml`
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-9-stable:config/gitlab.yml.example origin/7-10-stable:config/gitlab.yml.example
```
#### Change Nginx settings
* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings.
* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings.
* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
#### Setup time zone (optional)
Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
### 6. Start application
sudo service gitlab start
sudo service nginx restart
### 7. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check with:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations upgrade is complete!
### 8. GitHub settings (if applicable)
If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it
only contains a root URL (ex. `https://gitlab.example.com/`)
## Things went south? Revert to previous version (7.9)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 7.8 to 7.8](7.8-to-7.9.md), except for the database migration
(The backup is already migrated to the previous version)
### 2. Restore from the backup:
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
\ No newline at end of file
...@@ -26,12 +26,11 @@ If you have local changes to your GitLab repository the script will stash them a ...@@ -26,12 +26,11 @@ If you have local changes to your GitLab repository the script will stash them a
Note: GitLab 7.9 adds `nodejs` as a dependency. GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX). GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies) Note: GitLab 7.9 adds `nodejs` as a dependency. GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX). GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
# Starting with GitLab version 7.0 upgrader script has been moved to bin directory
cd /home/git/gitlab cd /home/git/gitlab
if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb; else sudo -u git -H ruby script/upgrade.rb; fi sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"v7.10.0"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute'
# to perform a non-interactive install (no user input required) you can add -y # to perform a non-interactive install (no user input required) you can add -y
# if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi # sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"v7.10.0"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y
## 3. Start application ## 3. Start application
...@@ -66,11 +65,12 @@ Here is a one line command with step 1 to 5 for the next time you upgrade: ...@@ -66,11 +65,12 @@ Here is a one line command with step 1 to 5 for the next time you upgrade:
cd /home/git/gitlab; \ cd /home/git/gitlab; \
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
sudo service gitlab stop; \ sudo service gitlab stop; \
if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \ sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"v7.10.0"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y; \
cd /home/git/gitlab-shell; \ cd /home/git/gitlab-shell; \
sudo -u git -H git fetch; \ sudo -u git -H git fetch; \
sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \
cd /home/git/gitlab; \ cd /home/git/gitlab; \
sudo service gitlab start; \ sudo service gitlab start; \
sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production sudo service nginx restart; \
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
``` ```
...@@ -30,7 +30,10 @@ module Gitlab ...@@ -30,7 +30,10 @@ module Gitlab
def user_map def user_map
@user_map ||= begin @user_map ||= begin
user_map = Hash.new { |hash, user| Client.mask_email(user) } user_map = Hash.new do |hash, user|
# Replace ... by \.\.\., so `johnsm...@gmail.com` isn't autolinked.
Client.mask_email(user).sub("...", "\\.\\.\\.")
end
import_data = project.import_data.try(:data) import_data = project.import_data.try(:data)
stored_user_map = import_data["user_map"] if import_data stored_user_map = import_data["user_map"] if import_data
...@@ -203,25 +206,25 @@ module Gitlab ...@@ -203,25 +206,25 @@ module Gitlab
end end
def linkify_issues(s) def linkify_issues(s)
s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2') s = s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2')
s = s.gsub(/([Cc]omment) #([0-9]+)/, '\1 \2')
s
end end
def escape_for_markdown(s) def escape_for_markdown(s)
s = s.gsub("*", "\\*") # No headings and lists
s = s.gsub("#", "\\#") s = s.gsub(/^#/, "\\#")
s = s.gsub(/^-/, "\\-")
# No inline code
s = s.gsub("`", "\\`") s = s.gsub("`", "\\`")
s = s.gsub(":", "\\:")
s = s.gsub("-", "\\-") # Carriage returns make me sad
s = s.gsub("+", "\\+")
s = s.gsub("_", "\\_")
s = s.gsub("(", "\\(")
s = s.gsub(")", "\\)")
s = s.gsub("[", "\\[")
s = s.gsub("]", "\\]")
s = s.gsub("<", "\\<")
s = s.gsub(">", "\\>")
s = s.gsub("\r", "") s = s.gsub("\r", "")
# Markdown ignores single newlines, but we need them as <br />.
s = s.gsub("\n", " \n") s = s.gsub("\n", " \n")
s s
end end
...@@ -276,11 +279,18 @@ module Gitlab ...@@ -276,11 +279,18 @@ module Gitlab
if raw_updates.has_key?("blockedOn") if raw_updates.has_key?("blockedOn")
blocked_ons = raw_updates["blockedOn"].map do |raw_blocked_on| blocked_ons = raw_updates["blockedOn"].map do |raw_blocked_on|
name, id = raw_blocked_on.split(":", 2) name, id = raw_blocked_on.split(":", 2)
deleted = name.start_with?("-")
name = name[1..-1] if deleted
text =
if name == project.import_source if name == project.import_source
"##{id}" "##{id}"
else else
"#{project.namespace.path}/#{name}##{id}" "#{project.namespace.path}/#{name}##{id}"
end end
text = "~~#{text}~~" if deleted
text
end end
updates << "*Blocked on: #{blocked_ons.join(", ")}*" updates << "*Blocked on: #{blocked_ons.join(", ")}*"
end end
...@@ -288,11 +298,18 @@ module Gitlab ...@@ -288,11 +298,18 @@ module Gitlab
if raw_updates.has_key?("blocking") if raw_updates.has_key?("blocking")
blockings = raw_updates["blocking"].map do |raw_blocked_on| blockings = raw_updates["blocking"].map do |raw_blocked_on|
name, id = raw_blocked_on.split(":", 2) name, id = raw_blocked_on.split(":", 2)
deleted = name.start_with?("-")
name = name[1..-1] if deleted
text =
if name == project.import_source if name == project.import_source
"##{id}" "##{id}"
else else
"#{project.namespace.path}/#{name}##{id}" "#{project.namespace.path}/#{name}##{id}"
end end
text = "~~#{text}~~" if deleted
text
end end
updates << "*Blocking: #{blockings.join(", ")}*" updates << "*Blocking: #{blockings.join(", ")}*"
end end
...@@ -340,7 +357,7 @@ module Gitlab ...@@ -340,7 +357,7 @@ module Gitlab
def format_issue_body(author, date, content, attachments) def format_issue_body(author, date, content, attachments)
body = [] body = []
body << "*By #{author} on #{date}*" body << "*By #{author} on #{date} (imported from Google Code)*"
body << "---" body << "---"
if content.blank? if content.blank?
......
require 'html/pipeline' require 'html/pipeline'
require 'html/pipeline/gitlab'
module Gitlab module Gitlab
# Custom parser for GitLab-flavored Markdown # Custom parser for GitLab-flavored Markdown
...@@ -10,11 +9,11 @@ module Gitlab ...@@ -10,11 +9,11 @@ module Gitlab
# Supported reference formats are: # Supported reference formats are:
# * @foo for team members # * @foo for team members
# * #123 for issues # * #123 for issues
# * #JIRA-123 for Jira issues # * JIRA-123 for Jira issues
# * !123 for merge requests # * !123 for merge requests
# * $123 for snippets # * $123 for snippets
# * 123456 for commits # * 1c002d for specific commit
# * 123456...7890123 for commit ranges (comparisons) # * 1c002d...35cfb2 for commit ranges (comparisons)
# #
# It also parses Emoji codes to insert images. See # It also parses Emoji codes to insert images. See
# http://www.emoji-cheat-sheet.com/ for a list of the supported icons. # http://www.emoji-cheat-sheet.com/ for a list of the supported icons.
...@@ -30,10 +29,6 @@ module Gitlab ...@@ -30,10 +29,6 @@ module Gitlab
# >> gfm(":trollface:") # >> gfm(":trollface:")
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" /> # => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
module Markdown module Markdown
include IssuesHelper
attr_reader :options, :html_options
# Public: Parse the provided text with GitLab-Flavored Markdown # Public: Parse the provided text with GitLab-Flavored Markdown
# #
# text - the source text # text - the source text
...@@ -65,47 +60,24 @@ module Gitlab ...@@ -65,47 +60,24 @@ module Gitlab
reference_only_path: true reference_only_path: true
) )
@options = options pipeline = HTML::Pipeline.new(filters)
@html_options = html_options
# TODO: add popups with additional information context = {
# SanitizationFilter
# Used markdown pipelines in GitLab: whitelist: sanitization_whitelist,
# GitlabEmojiFilter - performs emoji replacement.
# SanitizationFilter - remove unsafe HTML tags and attributes
#
# see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
filters = [
HTML::Pipeline::Gitlab::GitlabEmojiFilter,
HTML::Pipeline::SanitizationFilter
]
whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST
whitelist[:attributes][:all].push('class', 'id')
whitelist[:elements].push('span')
# Remove the rel attribute that the sanitize gem adds, and remove the # EmojiFilter
# href attribute if it contains inline javascript
fix_anchors = lambda do |env|
name, node = env[:node_name], env[:node]
if name == 'a'
node.remove_attribute('rel')
if node['href'] && node['href'].match('javascript:')
node.remove_attribute('href')
end
end
end
whitelist[:transformers].push(fix_anchors)
markdown_context = {
asset_root: Gitlab.config.gitlab.url, asset_root: Gitlab.config.gitlab.url,
asset_host: Gitlab::Application.config.asset_host, asset_host: Gitlab::Application.config.asset_host,
whitelist: whitelist
}
markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline # ReferenceFilter
current_user: current_user,
only_path: options[:reference_only_path],
project: project,
reference_class: html_options[:class]
}
result = markdown_pipeline.call(text, markdown_context) result = pipeline.call(text, context)
save_options = 0 save_options = 0
if options[:xhtml] if options[:xhtml]
...@@ -114,21 +86,6 @@ module Gitlab ...@@ -114,21 +86,6 @@ module Gitlab
text = result[:output].to_html(save_with: save_options) text = result[:output].to_html(save_with: save_options)
# Extract pre blocks so they are not altered
# from http://github.github.com/github-flavored-markdown/
text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) { |match| extract_piece(match) }
# Extract links with probably parsable hrefs
text.gsub!(%r{<a.*?>.*?</a>}m) { |match| extract_piece(match) }
# Extract images with probably parsable src
text.gsub!(%r{<img.*?>}m) { |match| extract_piece(match) }
text = parse(text, project)
# Insert pre block extractions
text.gsub!(/\{gfm-extraction-(\h{32})\}/) do
insert_piece($1)
end
if options[:parse_tasks] if options[:parse_tasks]
text = parse_tasks(text) text = parse_tasks(text)
end end
...@@ -138,242 +95,53 @@ module Gitlab ...@@ -138,242 +95,53 @@ module Gitlab
private private
def extract_piece(text) # Filters used in our pipeline
@extractions ||= {}
md5 = Digest::MD5.hexdigest(text)
@extractions[md5] = text
"{gfm-extraction-#{md5}}"
end
def insert_piece(id)
@extractions[id]
end
# Private: Parses text for references
# #
# text - Text to parse # SanitizationFilter should come first so that all generated reference HTML
# goes through untouched.
# #
# Returns parsed text # See https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
def parse(text, project = @project) def filters
parse_references(text, project) if project [
HTML::Pipeline::SanitizationFilter,
text
end Gitlab::Markdown::EmojiFilter,
NAME_STR = Gitlab::Regex::NAMESPACE_REGEX_STR Gitlab::Markdown::UserReferenceFilter,
PROJ_STR = "(?<project>#{NAME_STR}/#{NAME_STR})" Gitlab::Markdown::IssueReferenceFilter,
Gitlab::Markdown::ExternalIssueReferenceFilter,
REFERENCE_PATTERN = %r{ Gitlab::Markdown::MergeRequestReferenceFilter,
(?<prefix>\W)? # Prefix Gitlab::Markdown::SnippetReferenceFilter,
( # Reference Gitlab::Markdown::CommitRangeReferenceFilter,
@(?<user>#{NAME_STR}) # User name Gitlab::Markdown::CommitReferenceFilter,
|~(?<label>\d+) # Label ID Gitlab::Markdown::LabelReferenceFilter,
|(?<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
|(?<skip>gfm-extraction-[\h]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit
)
(?<suffix>\W)? # Suffix
}x.freeze
TYPES = [: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.select{|t| !$~[t].nil?}.first
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 &amp;
def html_entity?(prefix, suffix)
prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
end end
# Private: Dispatches to a dedicated processing method based on reference # Customize the SanitizationFilter whitelist
#
# reference - Object reference ("@1234", "!567", etc.)
# identifier - Object identifier (Issue ID, SHA hash, etc.)
# #
# Returns string rendered by the processing method # - Allow `class` and `id` attributes on all elements
def reference_link(type, identifier, project = @project, prefix_text = nil) # - Allow `span` elements
send("reference_#{type}", identifier, project, prefix_text) # - Remove `rel` attributes from `a` elements
end # - Remove `a` nodes with `javascript:` in the `href` attribute
def sanitization_whitelist
def reference_user(identifier, project = @project, _ = nil) whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST
link_options = html_options.merge( whitelist[:attributes][:all].push('class', 'id')
class: "gfm gfm-project_member #{html_options[:class]}" whitelist[:elements].push('span')
)
if identifier == "all"
link_to(
"@all",
namespace_project_url(project.namespace, project, only_path: options[:reference_only_path]),
link_options
)
elsif namespace = Namespace.find_by(path: identifier)
url =
if namespace.is_a?(Group)
return nil unless can?(current_user, :read_group, namespace)
group_url(identifier, only_path: options[:reference_only_path])
else
user_url(identifier, only_path: options[:reference_only_path])
end
link_to("@#{identifier}", url, link_options)
end
end
def reference_label(identifier, project = @project, _ = nil)
if label = project.labels.find_by(id: identifier)
link_options = html_options.merge(
class: "gfm gfm-label #{html_options[:class]}"
)
link_to(
render_colored_label(label),
namespace_project_issues_path(project.namespace, project, label_name: label.name),
link_options
)
end
end
def reference_issue(identifier, project = @project, prefix_text = nil)
if project.default_issues_tracker?
if project.issue_exists? identifier
url = url_for_issue(identifier, project, only_path: options[:reference_only_path])
title = title_for_issue(identifier, project)
link_options = html_options.merge(
title: "Issue: #{title}",
class: "gfm gfm-issue #{html_options[:class]}"
)
link_to("#{prefix_text}##{identifier}", url, link_options)
end
else
if project.external_issue_tracker.present?
reference_external_issue(identifier, project,
prefix_text)
end
end
end
def reference_merge_request(identifier, project = @project, prefix_text = nil)
if merge_request = project.merge_requests.find_by(iid: identifier)
link_options = html_options.merge(
title: "Merge Request: #{merge_request.title}",
class: "gfm gfm-merge_request #{html_options[:class]}"
)
url = namespace_project_merge_request_url(project.namespace, project,
merge_request,
only_path: options[:reference_only_path])
link_to("#{prefix_text}!#{identifier}", url, link_options)
end
end
def reference_snippet(identifier, project = @project, _ = nil)
if snippet = project.snippets.find_by(id: identifier)
link_options = html_options.merge(
title: "Snippet: #{snippet.title}",
class: "gfm gfm-snippet #{html_options[:class]}"
)
link_to(
"$#{identifier}",
namespace_project_snippet_url(project.namespace, project, snippet,
only_path: options[:reference_only_path]),
link_options
)
end
end
def reference_commit(identifier, project = @project, prefix_text = nil) fix_anchors = lambda do |env|
if project.valid_repo? && commit = project.repository.commit(identifier) name, node = env[:node_name], env[:node]
link_options = html_options.merge( if name == 'a'
title: commit.link_title, node.remove_attribute('rel')
class: "gfm gfm-commit #{html_options[:class]}" if node['href'] && node['href'].match('javascript:')
) node.remove_attribute('href')
prefix_text = "#{prefix_text}@" if prefix_text
link_to(
"#{prefix_text}#{identifier}",
namespace_project_commit_url( project.namespace, project, commit,
only_path: options[:reference_only_path]),
link_options
)
end
end end
def reference_commit_range(identifier, project = @project, prefix_text = nil)
from_id, to_id = identifier.split(/\.{2,3}/, 2)
inclusive = identifier !~ /\.{3}/
from_id << "^" if inclusive
if project.valid_repo? &&
from = project.repository.commit(from_id) &&
to = project.repository.commit(to_id)
link_options = html_options.merge(
title: "Commits #{from_id} through #{to_id}",
class: "gfm gfm-commit_range #{html_options[:class]}"
)
prefix_text = "#{prefix_text}@" if prefix_text
link_to(
"#{prefix_text}#{identifier}",
namespace_project_compare_url(project.namespace, project,
from: from_id, to: to_id,
only_path: options[:reference_only_path]),
link_options
)
end end
end end
def reference_external_issue(identifier, project = @project, prefix_text = nil) whitelist[:transformers].push(fix_anchors)
url = url_for_issue(identifier, project, only_path: options[:reference_only_path])
title = project.external_issue_tracker.title
link_options = html_options.merge( whitelist
title: "Issue in #{title}",
class: "gfm gfm-issue #{html_options[:class]}"
)
link_to("#{prefix_text}##{identifier}", url, link_options)
end end
# Turn list items that start with "[ ]" into HTML checkbox inputs. # Turn list items that start with "[ ]" into HTML checkbox inputs.
......
module Gitlab
module Markdown
# HTML filter that replaces commit range references with links.
#
# This filter supports cross-project references.
class CommitRangeReferenceFilter < ReferenceFilter
include CrossProjectReference
# Public: Find commit range references in text
#
# CommitRangeReferenceFilter.references_in(text) do |match, commit_range, project_ref|
# "<a href=...>#{commit_range}</a>"
# end
#
# text - String text to search.
#
# Yields the String match, the String commit range, and an optional String
# of the external project reference.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(COMMIT_RANGE_PATTERN) do |match|
yield match, $~[:commit_range], $~[:project]
end
end
def initialize(*args)
super
@commit_map = {}
end
# 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})/
def call
replace_text_nodes_matching(COMMIT_RANGE_PATTERN) do |content|
commit_range_link_filter(content)
end
end
# Replace commit range references in text with links to compare the commit
# ranges.
#
# text - String text to replace references in.
#
# Returns a String with commit range references replaced with links. All
# 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|
project = self.project_from_ref(project_ref)
from_id, to_id = split_commit_range(commit_range)
if valid_range?(project, from_id, to_id)
url = url_for_commit_range(project, from_id, to_id)
title = "Commits #{from_id} through #{to_id}"
klass = reference_class(:commit_range)
project_ref += '@' if project_ref
%(<a href="#{url}"
title="#{title}"
class="#{klass}">#{project_ref}#{commit_range}</a>)
else
match
end
end
end
def split_commit_range(range)
from_id, to_id = range.split(/\.{2,3}/, 2)
from_id << "^" if range !~ /\.{3}/
[from_id, to_id]
end
def commit(id)
unless @commit_map[id]
@commit_map[id] = project.repository.commit(id)
end
@commit_map[id]
end
def valid_range?(project, from_id, to_id)
project && project.valid_repo? && commit(from_id) && commit(to_id)
end
def url_for_commit_range(project, from_id, to_id)
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])
end
end
end
end
module Gitlab
module Markdown
# HTML filter that replaces commit references with links.
#
# This filter supports cross-project references.
class CommitReferenceFilter < ReferenceFilter
include CrossProjectReference
# Public: Find commit references in text
#
# CommitReferenceFilter.references_in(text) do |match, commit, project_ref|
# "<a href=...>#{commit}</a>"
# end
#
# text - String text to search.
#
# Yields the String match, the String commit identifier, and an optional
# String of the external project reference.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(COMMIT_PATTERN) do |match|
yield match, $~[:commit], $~[:project]
end
end
# Pattern used to extract commit references from text
#
# The SHA1 sum can be between 6 and 40 hex characters.
#
# This pattern supports cross-project references.
COMMIT_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit>\h{6,40})/
def call
replace_text_nodes_matching(COMMIT_PATTERN) do |content|
commit_link_filter(content)
end
end
# Replace commit references in text with links to the commit specified.
#
# text - String text to replace references in.
#
# Returns a String with commit references replaced with links. All links
# have `gfm` and `gfm-commit` class names attached for styling.
def commit_link_filter(text)
self.class.references_in(text) do |match, commit_ref, project_ref|
project = self.project_from_ref(project_ref)
if commit = commit_from_ref(project, commit_ref)
url = url_for_commit(project, commit)
title = escape_once(commit.link_title)
klass = reference_class(:commit)
project_ref += '@' if project_ref
%(<a href="#{url}"
title="#{title}"
class="#{klass}">#{project_ref}#{commit_ref}</a>)
else
match
end
end
end
def commit_from_ref(project, commit_ref)
if project && project.valid_repo?
project.repository.commit(commit_ref)
end
end
def url_for_commit(project, commit)
h = Rails.application.routes.url_helpers
h.namespace_project_commit_url(project.namespace, project, commit,
only_path: context[:only_path])
end
end
end
end
module Gitlab
module Markdown
# Common methods for ReferenceFilters that support an optional cross-project
# reference.
module CrossProjectReference
NAMING_PATTERN = Gitlab::Regex::NAMESPACE_REGEX_STR
PROJECT_PATTERN = "(?<project>#{NAMING_PATTERN}/#{NAMING_PATTERN})"
# Given a cross-project reference string, get the Project record
#
# Defaults to value of `context[:project]` if:
# * No reference is given OR
# * Reference given doesn't exist
#
# ref - String reference.
#
# Returns a Project, or nil if the reference can't be accessed
def project_from_ref(ref)
return context[:project] unless ref
other = Project.find_with_namespace(ref)
return nil unless other && user_can_reference_project?(other)
other
end
def user_can_reference_project?(project, user = context[:current_user])
Ability.abilities.allowed?(user, :read_project, project)
end
end
end
end
require 'gitlab_emoji'
require 'html/pipeline/filter'
require 'action_controller'
module Gitlab
module Markdown
# HTML filter that replaces :emoji: with images.
#
# Based on HTML::Pipeline::EmojiFilter
#
# Context options:
# :asset_root
# :asset_host
class EmojiFilter < HTML::Pipeline::Filter
IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
def call
doc.search('text()').each do |node|
content = node.to_html
next unless content.include?(':')
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
html = emoji_image_filter(content)
next if html == content
node.replace(html)
end
doc
end
# Replace :emoji: with corresponding images.
#
# text - String text to replace :emoji: in.
#
# Returns a String with :emoji: replaced with images.
def emoji_image_filter(text)
text.gsub(emoji_pattern) do |match|
name = $1
"<img class='emoji' title=':#{name}:' alt=':#{name}:' src='#{emoji_url(name)}' height='20' width='20' align='absmiddle' />"
end
end
private
def emoji_url(name)
emoji_path = "emoji/#{emoji_filename(name)}"
if context[:asset_host]
# Asset host is specified.
url_to_image(emoji_path)
elsif context[:asset_root]
# Gitlab url is specified
File.join(context[:asset_root], url_to_image(emoji_path))
else
# All other cases
url_to_image(emoji_path)
end
end
def url_to_image(image)
ActionController::Base.helpers.url_to_image(image)
end
# Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern
@emoji_pattern ||= /:(#{Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
end
def emoji_pattern
self.class.emoji_pattern
end
def emoji_filename(name)
"#{Emoji.emoji_filename(name)}.png"
end
end
end
end
module Gitlab
module Markdown
# HTML filter that replaces external issue tracker references with links.
# References are ignored if the project doesn't use an external issue
# tracker.
class ExternalIssueReferenceFilter < ReferenceFilter
# Public: Find `JIRA-123` issue references in text
#
# ExternalIssueReferenceFilter.references_in(text) do |match, issue|
# "<a href=...>##{issue}</a>"
# end
#
# text - String text to search.
#
# Yields the String match and the String issue reference.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(ISSUE_PATTERN) do |match|
yield match, $~[:issue]
end
end
# Pattern used to extract `JIRA-123` issue references from text
ISSUE_PATTERN = /(?<issue>([A-Z\-]+-)\d+)/
def call
# Early return if the project isn't using an external tracker
return doc if project.nil? || project.default_issues_tracker?
replace_text_nodes_matching(ISSUE_PATTERN) do |content|
issue_link_filter(content)
end
end
# Replace `JIRA-123` issue references in text with links to the referenced
# issue's details page.
#
# text - String text to replace references in.
#
# Returns a String with `JIRA-123` references replaced with links. All
# links have `gfm` and `gfm-issue` class names attached for styling.
def issue_link_filter(text)
project = context[:project]
self.class.references_in(text) do |match, issue|
url = url_for_issue(issue, project, only_path: context[:only_path])
title = escape_once("Issue in #{project.external_issue_tracker.title}")
klass = reference_class(:issue)
%(<a href="#{url}"
title="#{title}"
class="#{klass}">#{issue}</a>)
end
end
def url_for_issue(*args)
IssuesHelper.url_for_issue(*args)
end
end
end
end
module Gitlab
module Markdown
# HTML filter that replaces issue references with links. References to
# issues that do not exist are ignored.
#
# This filter supports cross-project references.
class IssueReferenceFilter < ReferenceFilter
include CrossProjectReference
# Public: Find `#123` issue references in text
#
# IssueReferenceFilter.references_in(text) do |match, issue, project_ref|
# "<a href=...>##{issue}</a>"
# end
#
# text - String text to search.
#
# Yields the String match, the Integer issue ID, and an optional String of
# the external project reference.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(ISSUE_PATTERN) do |match|
yield match, $~[:issue].to_i, $~[:project]
end
end
# Pattern used to extract `#123` issue references from text
#
# This pattern supports cross-project references.
ISSUE_PATTERN = /#{PROJECT_PATTERN}?\#(?<issue>([a-zA-Z\-]+-)?\d+)/
def call
replace_text_nodes_matching(ISSUE_PATTERN) do |content|
issue_link_filter(content)
end
end
# Replace `#123` issue references in text with links to the referenced
# issue's details page.
#
# text - String text to replace references in.
#
# Returns a String with `#123` references replaced with links. All links
# have `gfm` and `gfm-issue` class names attached for styling.
def issue_link_filter(text)
self.class.references_in(text) do |match, issue, project_ref|
project = self.project_from_ref(project_ref)
if project && project.issue_exists?(issue)
url = url_for_issue(issue, project, only_path: context[:only_path])
title = escape_once("Issue: #{title_for_issue(issue, project)}")
klass = reference_class(:issue)
%(<a href="#{url}"
title="#{title}"
class="#{klass}">#{project_ref}##{issue}</a>)
else
match
end
end
end
def url_for_issue(*args)
IssuesHelper.url_for_issue(*args)
end
def title_for_issue(*args)
IssuesHelper.title_for_issue(*args)
end
end
end
end
module Gitlab
module Markdown
# HTML filter that replaces label references with links.
class LabelReferenceFilter < ReferenceFilter
# Public: Find label references in text
#
# LabelReferenceFilter.references_in(text) do |match, id, name|
# "<a href=...>#{Label.find(id)}</a>"
# end
#
# text - String text to search.
#
# Yields the String match, an optional Integer label ID, and an optional
# String label name.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(LABEL_PATTERN) do |match|
yield match, $~[:label_id].to_i, $~[:label_name]
end
end
# Pattern used to extract label references from text
#
# TODO (rspeicher): Limit to double quotes (meh) or disallow single quotes in label names (bad).
LABEL_PATTERN = %r{
~(
(?<label_id>\d+) | # Integer-based label ID, or
(?<label_name>
[A-Za-z0-9_-]+ | # String-based single-word label title
['"][^&\?,]+['"] # String-based multi-word label surrounded in quotes
)
)
}x
def call
replace_text_nodes_matching(LABEL_PATTERN) do |content|
label_link_filter(content)
end
end
# Replace label references in text with links to the label specified.
#
# text - String text to replace references in.
#
# Returns a String with label references replaced with links. All links
# have `gfm` and `gfm-label` class names attached for styling.
def label_link_filter(text)
project = context[:project]
self.class.references_in(text) do |match, id, name|
params = label_params(id, name)
if label = project.labels.find_by(params)
url = url_for_label(project, label)
klass = reference_class(:label)
%(<a href="#{url}" class="#{klass}">#{render_colored_label(label)}</a>)
else
match
end
end
end
def url_for_label(project, label)
h = Rails.application.routes.url_helpers
h.namespace_project_issues_path(project.namespace, project,
label_name: label.name,
only_path: context[:only_path])
end
def render_colored_label(label)
LabelsHelper.render_colored_label(label)
end
# Parameters to pass to `Label.find_by` based on the given arguments
#
# id - Integer ID to pass. If present, returns {id: id}
# name - String name to pass. If `id` is absent, finds by name without
# surrounding quotes.
#
# Returns a Hash.
def label_params(id, name)
if id > 0
{ id: id }
else
# TODO (rspeicher): Don't strip single quotes if we decide to only use double quotes for surrounding.
{ name: name.tr('\'"', '') }
end
end
end
end
end
module Gitlab
module Markdown
# HTML filter that replaces merge request references with links. References
# to merge requests that do not exist are ignored.
#
# This filter supports cross-project references.
class MergeRequestReferenceFilter < ReferenceFilter
include CrossProjectReference
# Public: Find `!123` merge request references in text
#
# MergeRequestReferenceFilter.references_in(text) do |match, merge_request, project_ref|
# "<a href=...>##{merge_request}</a>"
# end
#
# text - String text to search.
#
# Yields the String match, the Integer merge request ID, and an optional
# String of the external project reference.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(MERGE_REQUEST_PATTERN) do |match|
yield match, $~[:merge_request].to_i, $~[:project]
end
end
# Pattern used to extract `!123` merge request references from text
#
# This pattern supports cross-project references.
MERGE_REQUEST_PATTERN = /#{PROJECT_PATTERN}?!(?<merge_request>\d+)/
def call
replace_text_nodes_matching(MERGE_REQUEST_PATTERN) do |content|
merge_request_link_filter(content)
end
end
# Replace `!123` merge request references in text with links to the
# referenced merge request's details page.
#
# text - String text to replace references in.
#
# Returns a String with `!123` references replaced with links. All links
# have `gfm` and `gfm-merge_request` class names attached for styling.
def merge_request_link_filter(text)
self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref)
if project && merge_request = project.merge_requests.find_by(iid: id)
title = escape_once("Merge Request: #{merge_request.title}")
klass = reference_class(:merge_request)
url = url_for_merge_request(merge_request, project)
%(<a href="#{url}"
title="#{title}"
class="#{klass}">#{project_ref}!#{id}</a>)
else
match
end
end
end
# TODO (rspeicher): Cleanup
def url_for_merge_request(mr, project)
h = Rails.application.routes.url_helpers
h.namespace_project_merge_request_url(project.namespace, project, mr,
only_path: context[:only_path])
end
end
end
end
require 'active_support/core_ext/string/output_safety'
require 'html/pipeline'
module Gitlab
module Markdown
# Base class for GitLab Flavored Markdown reference filters.
#
# References within <pre>, <code>, <a>, and <style> elements are ignored.
#
# Context options:
# :project (required) - Current project, ignored if reference is cross-project.
# :reference_class - Custom CSS class added to reference links.
# :only_path - Generate path-only links.
#
class ReferenceFilter < HTML::Pipeline::Filter
def escape_once(html)
ERB::Util.html_escape_once(html)
end
# Don't look for references in text nodes that are children of these
# elements.
IGNORE_PARENTS = %w(pre code a style).to_set
def ignored_ancestry?(node)
has_ancestor?(node, IGNORE_PARENTS)
end
def project
context[:project]
end
def reference_class(type)
"gfm gfm-#{type} #{context[:reference_class]}".strip
end
# Iterate through the document's text nodes, yielding the current node's
# content if:
#
# * The `project` context value is present AND
# * The node's content matches `pattern` AND
# * The node is not an ancestor of an ignored node type
#
# pattern - Regex pattern against which to match the node's content
#
# Yields the current node's String contents. The result of the block will
# replace the node's existing content and update the current document.
#
# Returns the updated Nokogiri::XML::Document object.
def replace_text_nodes_matching(pattern)
return doc if project.nil?
doc.search('text()').each do |node|
content = node.to_html
next unless content.match(pattern)
next if ignored_ancestry?(node)
html = yield content
next if html == content
node.replace(html)
end
doc
end
# Ensure that a :project key exists in context
#
# Note that while the key might exist, its value could be nil!
def validate
needs :project
end
end
end
end
module Gitlab
module Markdown
# HTML filter that replaces snippet references with links. References to
# snippets that do not exist are ignored.
#
# This filter supports cross-project references.
class SnippetReferenceFilter < ReferenceFilter
include CrossProjectReference
# Public: Find `$123` snippet references in text
#
# SnippetReferenceFilter.references_in(text) do |match, snippet|
# "<a href=...>$#{snippet}</a>"
# end
#
# text - String text to search.
#
# Yields the String match, the Integer snippet ID, and an optional String
# of the external project reference.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(SNIPPET_PATTERN) do |match|
yield match, $~[:snippet].to_i, $~[:project]
end
end
# Pattern used to extract `$123` snippet references from text
#
# This pattern supports cross-project references.
SNIPPET_PATTERN = /#{PROJECT_PATTERN}?\$(?<snippet>\d+)/
def call
replace_text_nodes_matching(SNIPPET_PATTERN) do |content|
snippet_link_filter(content)
end
end
# Replace `$123` snippet references in text with links to the referenced
# snippets's details page.
#
# text - String text to replace references in.
#
# Returns a String with `$123` references replaced with links. All links
# have `gfm` and `gfm-snippet` class names attached for styling.
def snippet_link_filter(text)
self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref)
if project && snippet = project.snippets.find_by(id: id)
title = escape_once("Snippet: #{snippet.title}")
klass = reference_class(:snippet)
url = url_for_snippet(snippet, project)
%(<a href="#{url}"
title="#{title}"
class="#{klass}">#{project_ref}$#{id}</a>)
else
match
end
end
end
def url_for_snippet(snippet, project)
h = Rails.application.routes.url_helpers
h.namespace_project_snippet_url(project.namespace, project, snippet,
only_path: context[:only_path])
end
end
end
end
module Gitlab
module Markdown
# HTML filter that replaces user or group references with links.
#
# A special `@all` reference is also supported.
class UserReferenceFilter < ReferenceFilter
# Public: Find `@user` user references in text
#
# UserReferenceFilter.references_in(text) do |match, username|
# "<a href=...>@#{user}</a>"
# end
#
# text - String text to search.
#
# Yields the String match, and the String user name.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(USER_PATTERN) do |match|
yield match, $~[:user]
end
end
# Pattern used to extract `@user` user references from text
USER_PATTERN = /@(?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})/
def call
replace_text_nodes_matching(USER_PATTERN) do |content|
user_link_filter(content)
end
end
# Replace `@user` user references in text with links to the referenced
# user's profile page.
#
# text - String text to replace references in.
#
# 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
else
match
end
end
end
private
def urls
Rails.application.routes.url_helpers
end
def group_url(*args)
urls.group_url(*args)
end
def user_url(*args)
urls.user_url(*args)
end
def link_to_all(project)
urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
end
def user_can_reference_group?(group)
Ability.abilities.allowed?(context[:current_user], :read_group, group)
end
end
end
end
...@@ -3,8 +3,6 @@ module Gitlab ...@@ -3,8 +3,6 @@ module Gitlab
class ReferenceExtractor class ReferenceExtractor
attr_accessor :project, :current_user, :references attr_accessor :project, :current_user, :references
include ::Gitlab::Markdown
def initialize(project, current_user = nil) def initialize(project, current_user = nil)
@project = project @project = project
@current_user = current_user @current_user = current_user
...@@ -34,7 +32,7 @@ module Gitlab ...@@ -34,7 +32,7 @@ module Gitlab
project.team.members.flatten project.team.members.flatten
elsif namespace = Namespace.find_by(path: identifier) elsif namespace = Namespace.find_by(path: identifier)
if namespace.is_a?(Group) if namespace.is_a?(Group)
namespace.users namespace.users if can?(current_user, :read_group, namespace)
else else
namespace.owner namespace.owner
end end
...@@ -87,6 +85,72 @@ module Gitlab ...@@ -87,6 +85,72 @@ module Gitlab
private 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 &amp;
def html_entity?(prefix, suffix)
prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
end
def reference_link(type, identifier, project, _) def reference_link(type, identifier, project, _)
references[type] << [project, identifier] references[type] << [project, identifier]
end end
......
...@@ -249,6 +249,16 @@ describe ApplicationHelper do ...@@ -249,6 +249,16 @@ describe ApplicationHelper do
expect(link_to('Example', 'http://example.foo/bar')). expect(link_to('Example', 'http://example.foo/bar')).
to eq '<a href="http://example.foo/bar">Example</a>' to eq '<a href="http://example.foo/bar">Example</a>'
end end
it 'should not raise an error when given a bad URI' do
expect { link_to('default', 'if real=1 RANDOM; if real>1 IDLHS; if real>500 LHS') }.
not_to raise_error
end
it 'should not raise an error when given a bad mailto URL' do
expect { link_to('email', 'mailto://foo.bar@example.es?subject=Subject%20Line') }.
not_to raise_error
end
end end
describe 'markup_render' do describe 'markup_render' do
......
...@@ -2,451 +2,29 @@ require 'spec_helper' ...@@ -2,451 +2,29 @@ require 'spec_helper'
describe GitlabMarkdownHelper do describe GitlabMarkdownHelper do
include ApplicationHelper include ApplicationHelper
include IssuesHelper
# TODO: Properly test this
def can?(*)
true
end
let!(:project) { create(:project) } let!(:project) { create(:project) }
let(:empty_project) { create(:empty_project) }
let(:user) { create(:user, username: 'gfm') } let(:user) { create(:user, username: 'gfm') }
let(:commit) { project.repository.commit } let(:commit) { project.repository.commit }
let(:earlier_commit){ project.repository.commit("HEAD~2") }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:snippet) { create(:project_snippet, project: project) } let(:snippet) { create(:project_snippet, project: project) }
let(:member) { project.project_members.where(user_id: user).first }
# Helper expects a current_user method. # Helper expects a current_user method.
let(:current_user) { user } let(:current_user) { user }
def url_helper(image_name)
File.join(root_url, 'assets', image_name)
end
before do before do
# Helper expects a @project instance variable # Helper expects a @project instance variable
@project = project @project = project
@ref = 'markdown'
@repository = project.repository
@request.host = Gitlab.config.gitlab.host
end end
describe "#gfm" do describe "#gfm" do
it "should return unaltered text if project is nil" do
actual = "Testing references: ##{issue.iid}"
expect(gfm(actual)).not_to eq(actual)
@project = nil
expect(gfm(actual)).to eq(actual)
end
it "should not alter non-references" do
actual = expected = "_Please_ *stop* 'helping' and all the other b*$#%' you do."
expect(gfm(actual)).to eq(expected)
end
it "should not touch HTML entities" do
allow(@project.issues).to receive(:where).
with(id: '39').and_return([issue])
actual = 'We&#39;ll accept good pull requests.'
expect(gfm(actual)).to eq("We'll accept good pull requests.")
end
it "should forward HTML options to links" do it "should forward HTML options to links" do
expect(gfm("Fixed in #{commit.id}", @project, class: 'foo')). expect(gfm("Fixed in #{commit.id}", @project, class: 'foo')).
to have_selector('a.gfm.foo') to have_selector('a.gfm.foo')
end end
describe "referencing a commit range" do
let(:expected) { namespace_project_compare_path(project.namespace, project, from: earlier_commit.id, to: commit.id) }
it "should link using a full id" do
actual = "What happened in #{earlier_commit.id}...#{commit.id}"
expect(gfm(actual)).to match(expected)
end
it "should link using a short id" do
actual = "What happened in #{earlier_commit.short_id}...#{commit.short_id}"
expected = namespace_project_compare_path(project.namespace, project, from: earlier_commit.short_id, to: commit.short_id)
expect(gfm(actual)).to match(expected)
end
it "should link inclusively" do
actual = "What happened in #{earlier_commit.id}..#{commit.id}"
expected = namespace_project_compare_path(project.namespace, project, from: "#{earlier_commit.id}^", to: commit.id)
expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
actual = "(see #{earlier_commit.id}...#{commit.id})"
expect(gfm(actual)).to match(expected)
end
it "should keep whitespace intact" do
actual = "Changes #{earlier_commit.id}...#{commit.id} dramatically"
expected = /Changes <a.+>#{earlier_commit.id}...#{commit.id}<\/a> dramatically/
expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
actual = expected = "What happened in #{earlier_commit.id.reverse}...#{commit.id.reverse}"
expect(gfm(actual)).to eq(expected)
end
it "should include a title attribute" do
actual = "What happened in #{earlier_commit.id}...#{commit.id}"
expect(gfm(actual)).to match(/title="Commits #{earlier_commit.id} through #{commit.id}"/)
end
it "should include standard gfm classes" do
actual = "What happened in #{earlier_commit.id}...#{commit.id}"
expect(gfm(actual)).to match(/class="\s?gfm gfm-commit_range\s?"/)
end
end
describe "referencing a commit" do
let(:expected) { namespace_project_commit_path(project.namespace, project, commit) }
it "should link using a full id" do
actual = "Reverts #{commit.id}"
expect(gfm(actual)).to match(expected)
end
it "should link using a short id" do
actual = "Backported from #{commit.short_id}"
expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
actual = "Reverted (see #{commit.id})"
expect(gfm(actual)).to match(expected)
end
it "should keep whitespace intact" do
actual = "Changes #{commit.id} dramatically"
expected = /Changes <a.+>#{commit.id}<\/a> dramatically/
expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
actual = expected = "What happened in #{commit.id.reverse}"
expect(gfm(actual)).to eq(expected)
end
it "should include a title attribute" do
actual = "Reverts #{commit.id}"
expect(gfm(actual)).to match(/title="#{commit.link_title}"/)
end
it "should include standard gfm classes" do
actual = "Reverts #{commit.id}"
expect(gfm(actual)).to match(/class="\s?gfm gfm-commit\s?"/)
end
end
describe "referencing a team member" do
let(:actual) { "@#{user.username} you are right." }
let(:expected) { user_path(user) }
before do
project.team << [user, :master]
end
it "should link using a simple name" do
expect(gfm(actual)).to match(expected)
end
it "should link using a name with dots" do
user.update_attributes(name: "alphA.Beta")
expect(gfm(actual)).to match(expected)
end
it "should link using name with underscores" do
user.update_attributes(name: "ping_pong_king")
expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
actual = "Mail the admin (@#{user.username})"
expect(gfm(actual)).to match(expected)
end
it "should keep whitespace intact" do
actual = "Yes, @#{user.username} is right."
expected = /Yes, <a.+>@#{user.username}<\/a> is right/
expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
actual = expected = "@#{user.username.reverse} you are right."
expect(gfm(actual)).to eq(expected)
end
it "should include standard gfm classes" do
expect(gfm(actual)).to match(/class="\s?gfm gfm-project_member\s?"/)
end
end
# Shared examples for referencing an object
#
# Expects the following attributes to be available in the example group:
#
# - object - The object itself
# - reference - The object reference string (e.g., #1234, $1234, !1234)
#
# Currently limited to Snippets, Issues and MergeRequests
shared_examples 'referenced object' do
let(:actual) { "Reference to #{reference}" }
let(:expected) { polymorphic_path([project.namespace, project, object]) }
it "should link using a valid id" do
expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
# Wrap the reference in parenthesis
expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
# Append some text to the end of the reference
expect(gfm(actual.gsub(reference, "#{reference}, right?"))).
to match(expected)
end
it "should keep whitespace intact" do
actual = "Referenced #{reference} already."
expected = /Referenced <a.+>[^\s]+<\/a> already/
expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
# Modify the reference string so it's still parsed, but is invalid
reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
expect(gfm(actual)).to eq(actual)
end
it "should include a title attribute" do
title = "#{object.class.to_s.titlecase}: #{object.title}"
expect(gfm(actual)).to match(/title="#{title}"/)
end
it "should include standard gfm classes" do
css = object.class.to_s.underscore
expect(gfm(actual)).to match(/class="\s?gfm gfm-#{css}\s?"/)
end
end
# Shared examples for referencing an object in a different project
#
# Expects the following attributes to be available in the example group:
#
# - object - The object itself
# - reference - The object reference string (e.g., #1234, $1234, !1234)
# - other_project - The project that owns the target object
#
# Currently limited to Snippets, Issues and MergeRequests
shared_examples 'cross-project referenced object' do
let(:project_path) { @other_project.path_with_namespace }
let(:full_reference) { "#{project_path}#{reference}" }
let(:actual) { "Reference to #{full_reference}" }
let(:expected) do
if object.is_a?(Commit)
namespace_project_commit_path(@other_project.namespace, @other_project, object)
else
polymorphic_path([@other_project.namespace, @other_project, object])
end
end
it 'should link using a valid id' do
expect(gfm(actual)).to match(
/#{expected}.*#{Regexp.escape(full_reference)}/
)
end
it 'should link with adjacent text' do
# Wrap the reference in parenthesis
expect(gfm(actual.gsub(full_reference, "(#{full_reference})"))).to(
match(expected)
)
# Append some text to the end of the reference
expect(gfm(actual.gsub(full_reference, "#{full_reference}, right?"))).
to(match(expected))
end
it 'should keep whitespace intact' do
actual = "Referenced #{full_reference} already."
expected = /Referenced <a.+>[^\s]+<\/a> already/
expect(gfm(actual)).to match(expected)
end
it 'should not link with an invalid id' do
# Modify the reference string so it's still parsed, but is invalid
if object.is_a?(Commit)
reference.gsub!(/^(.).+$/, '\1' + '12345abcd')
else
reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
end
expect(gfm(actual)).to eq(actual)
end
it 'should include a title attribute' do
if object.is_a?(Commit)
title = object.link_title
else
title = "#{object.class.to_s.titlecase}: #{object.title}"
end
expect(gfm(actual)).to match(/title="#{title}"/)
end
it 'should include standard gfm classes' do
css = object.class.to_s.underscore
expect(gfm(actual)).to match(/class="\s?gfm gfm-#{css}\s?"/)
end
end
describe "referencing an issue" do
let(:object) { issue }
let(:reference) { "##{issue.iid}" }
include_examples 'referenced object'
end
context 'cross-repo references' do
before(:all) do
@other_project = create(:project, :public)
@commit2 = @other_project.repository.commit
@issue2 = create(:issue, project: @other_project)
@merge_request2 = create(:merge_request,
source_project: @other_project,
target_project: @other_project)
end
describe 'referencing an issue in another project' do
let(:object) { @issue2 }
let(:reference) { "##{@issue2.iid}" }
include_examples 'cross-project referenced object'
end
describe 'referencing an merge request in another project' do
let(:object) { @merge_request2 }
let(:reference) { "!#{@merge_request2.iid}" }
include_examples 'cross-project referenced object'
end
describe 'referencing a commit in another project' do
let(:object) { @commit2 }
let(:reference) { "@#{@commit2.id}" }
include_examples 'cross-project referenced object'
end
end
describe "referencing a Jira issue" do
let(:actual) { "Reference to JIRA-#{issue.iid}" }
let(:expected) { "http://jira.example/browse/JIRA-#{issue.iid}" }
let(:reference) { "JIRA-#{issue.iid}" }
before do
jira = @project.create_jira_service if @project.jira_service.nil?
properties = {"title"=>"JIRA tracker", "project_url"=>"http://jira.example/issues/?jql=project=A", "issues_url"=>"http://jira.example/browse/:id", "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa"}
jira.update_attributes(properties: properties, active: true)
end
after do
@project.jira_service.destroy! unless @project.jira_service.nil?
end
it "should link using a valid id" do
expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
# Wrap the reference in parenthesis
expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
# Append some text to the end of the reference
expect(gfm(actual.gsub(reference, "#{reference}, right?"))).
to match(expected)
end
it "should keep whitespace intact" do
actual = "Referenced #{reference} already."
expected = /Referenced <a.+>[^\s]+<\/a> already/
expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
# Modify the reference string so it's still parsed, but is invalid
invalid_reference = actual.gsub(/(\d+)$/, "r45")
expect(gfm(invalid_reference)).to eq(invalid_reference)
end
it "should include a title attribute" do
title = "Issue in JIRA tracker"
expect(gfm(actual)).to match(/title="#{title}"/)
end
it "should include standard gfm classes" do
expect(gfm(actual)).to match(/class="\s?gfm gfm-issue\s?"/)
end
end
describe "referencing a merge request" do
let(:object) { merge_request }
let(:reference) { "!#{merge_request.iid}" }
include_examples 'referenced object'
end
describe "referencing a snippet" do
let(:object) { snippet }
let(:reference) { "$#{snippet.id}" }
let(:actual) { "Reference to #{reference}" }
let(:expected) { namespace_project_snippet_path(project.namespace, project, object) }
it "should link using a valid id" do
expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
# Wrap the reference in parenthesis
expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
# Append some text to the end of the reference
expect(gfm(actual.gsub(reference, "#{reference}, right?"))).to match(expected)
end
it "should keep whitespace intact" do
actual = "Referenced #{reference} already."
expected = /Referenced <a.+>[^\s]+<\/a> already/
expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
# Modify the reference string so it's still parsed, but is invalid
reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
expect(gfm(actual)).to eq(actual)
end
it "should include a title attribute" do
title = "Snippet: #{object.title}"
expect(gfm(actual)).to match(/title="#{title}"/)
end
it "should include standard gfm classes" do
css = object.class.to_s.underscore
expect(gfm(actual)).to match(/class="\s?gfm gfm-snippet\s?"/)
end
end
describe "referencing multiple objects" do describe "referencing multiple objects" do
let(:actual) { "!#{merge_request.iid} -> #{commit.id} -> ##{issue.iid}" } let(:actual) { "!#{merge_request.iid} -> #{commit.id} -> ##{issue.iid}" }
...@@ -466,90 +44,159 @@ describe GitlabMarkdownHelper do ...@@ -466,90 +44,159 @@ describe GitlabMarkdownHelper do
end end
end end
describe "emoji" do context 'parse_tasks: true' do
it "matches at the start of a string" do before(:all) do
expect(gfm(":+1:")).to match(/<img/) @source_text_asterisk = <<-EOT.strip_heredoc
end * [ ] valid unchecked task
* [x] valid lowercase checked task
* [X] valid uppercase checked task
* [ ] valid unchecked nested task
* [x] valid checked nested task
it "matches at the end of a string" do [ ] not an unchecked task - no list item
expect(gfm("This gets a :-1:")).to match(/<img/) [x] not a checked task - no list item
end
it "matches with adjacent text" do * [ ] not an unchecked task - too many spaces
expect(gfm("+1 (:+1:)")).to match(/<img/) * [x ] not a checked task - too many spaces
end * [] not an unchecked task - no spaces
* Not a task [ ] - not at beginning
EOT
it "has a title attribute" do @source_text_dash = <<-EOT.strip_heredoc
expect(gfm(":-1:")).to match(/title=":-1:"/) - [ ] valid unchecked task
- [x] valid lowercase checked task
- [X] valid uppercase checked task
- [ ] valid unchecked nested task
- [x] valid checked nested task
EOT
end end
it "has an alt attribute" do it 'should render checkboxes at beginning of asterisk list items' do
expect(gfm(":-1:")).to match(/alt=":-1:"/) rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
expect(rendered_text).to match(
/<input.*checkbox.*valid lowercase checked task/
)
expect(rendered_text).to match(
/<input.*checkbox.*valid uppercase checked task/
)
end end
it "has an emoji class" do it 'should render checkboxes at beginning of dash list items' do
expect(gfm(":+1:")).to match('class="emoji"') rendered_text = markdown(@source_text_dash, parse_tasks: true)
expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
expect(rendered_text).to match(
/<input.*checkbox.*valid lowercase checked task/
)
expect(rendered_text).to match(
/<input.*checkbox.*valid uppercase checked task/
)
end end
it "sets height and width" do it 'should render checkboxes for nested tasks' do
actual = gfm(":+1:") rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(actual).to match(/width="20"/)
expect(actual).to match(/height="20"/) expect(rendered_text).to match(
/<input.*checkbox.*valid unchecked nested task/
)
expect(rendered_text).to match(
/<input.*checkbox.*valid checked nested task/
)
end end
it "keeps whitespace intact" do it 'should not be confused by whitespace before bullets' do
expect(gfm('This deserves a :+1: big time.')). rendered_text_asterisk = markdown(@source_text_asterisk,
to match(/deserves a <img.+> big time/) parse_tasks: true)
rendered_text_dash = markdown(@source_text_dash, parse_tasks: true)
expect(rendered_text_asterisk).to match(
/<input.*checkbox.*valid unchecked nested task/
)
expect(rendered_text_asterisk).to match(
/<input.*checkbox.*valid checked nested task/
)
expect(rendered_text_dash).to match(
/<input.*checkbox.*valid unchecked nested task/
)
expect(rendered_text_dash).to match(
/<input.*checkbox.*valid checked nested task/
)
end end
it "ignores invalid emoji" do it 'should not render checkboxes outside of list items' do
expect(gfm(":invalid-emoji:")).not_to match(/<img/) rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(rendered_text).not_to match(
/<input.*checkbox.*not an unchecked task - no list item/
)
expect(rendered_text).not_to match(
/<input.*checkbox.*not a checked task - no list item/
)
end end
it "should work independent of reference links (i.e. without @project being set)" do it 'should not render checkboxes with invalid formatting' do
@project = nil rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(gfm(":+1:")).to match(/<img/)
expect(rendered_text).not_to match(
/<input.*checkbox.*not an unchecked task - too many spaces/
)
expect(rendered_text).not_to match(
/<input.*checkbox.*not a checked task - too many spaces/
)
expect(rendered_text).not_to match(
/<input.*checkbox.*not an unchecked task - no spaces/
)
expect(rendered_text).not_to match(
/Not a task.*<input.*checkbox.*not at beginning/
)
end end
end end
end end
describe "#link_to_gfm" do describe '#link_to_gfm' do
let(:commit_path) { namespace_project_commit_path(project.namespace, project, commit) } let(:commit_path) { namespace_project_commit_path(project.namespace, project, commit) }
let(:issues) { create_list(:issue, 2, project: project) } let(:issues) { create_list(:issue, 2, project: project) }
it "should handle references nested in links with all the text" do it 'should handle references nested in links with all the text' do
actual = link_to_gfm("This should finally fix ##{issues[0].iid} and ##{issues[1].iid} for real", commit_path) actual = link_to_gfm("This should finally fix ##{issues[0].iid} and ##{issues[1].iid} for real", commit_path)
doc = Nokogiri::HTML.parse(actual)
# Break the result into groups of links with their content, without # Make sure we didn't create invalid markup
# closing tags expect(doc.errors).to be_empty
groups = actual.split("</a>")
# Leading commit link # Leading commit link
expect(groups[0]).to match(/href="#{commit_path}"/) expect(doc.css('a')[0].attr('href')).to eq commit_path
expect(groups[0]).to match(/This should finally fix $/) expect(doc.css('a')[0].text).to eq 'This should finally fix '
# First issue link # First issue link
expect(groups[1]). expect(doc.css('a')[1].attr('href')).
to match(/href="#{namespace_project_issue_path(project.namespace, project, issues[0])}"/) to eq namespace_project_issue_path(project.namespace, project, issues[0])
expect(groups[1]).to match(/##{issues[0].iid}$/) expect(doc.css('a')[1].text).to eq "##{issues[0].iid}"
# Internal commit link # Internal commit link
expect(groups[2]).to match(/href="#{commit_path}"/) expect(doc.css('a')[2].attr('href')).to eq commit_path
expect(groups[2]).to match(/ and /) expect(doc.css('a')[2].text).to eq ' and '
# Second issue link # Second issue link
expect(groups[3]). expect(doc.css('a')[3].attr('href')).
to match(/href="#{namespace_project_issue_path(project.namespace, project, issues[1])}"/) to eq namespace_project_issue_path(project.namespace, project, issues[1])
expect(groups[3]).to match(/##{issues[1].iid}$/) expect(doc.css('a')[3].text).to eq "##{issues[1].iid}"
# Trailing commit link # Trailing commit link
expect(groups[4]).to match(/href="#{commit_path}"/) expect(doc.css('a')[4].attr('href')).to eq commit_path
expect(groups[4]).to match(/ for real$/) expect(doc.css('a')[4].text).to eq ' for real'
end end
it "should forward HTML options" do it 'should forward HTML options' do
actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo') actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
expect(actual).to have_selector 'a.gfm.gfm-commit.foo' doc = Nokogiri::HTML.parse(actual)
expect(doc.css('a')).to satisfy do |v|
# 'foo' gets added to all links
v.all? { |a| a.attr('class').match(/foo$/) }
end
end end
it "escapes HTML passed in as the body" do it "escapes HTML passed in as the body" do
...@@ -560,20 +207,7 @@ describe GitlabMarkdownHelper do ...@@ -560,20 +207,7 @@ describe GitlabMarkdownHelper do
end end
describe "#markdown" do describe "#markdown" do
it "should handle references in paragraphs" do # TODO (rspeicher) - This block tests multiple different contexts. Break this up!
actual = "\n\nLorem ipsum dolor sit amet. #{commit.id} Nam pulvinar sapien eget.\n"
expected = namespace_project_commit_path(project.namespace, project, commit)
expect(markdown(actual)).to match(expected)
end
it "should handle references in headers" do
actual = "\n# Working around ##{issue.iid}\n## Apply !#{merge_request.iid}"
expect(markdown(actual, no_header_anchors: true)).
to match(%r{<h1[^<]*>Working around <a.+>##{issue.iid}</a></h1>})
expect(markdown(actual, no_header_anchors: true)).
to match(%r{<h2[^<]*>Apply <a.+>!#{merge_request.iid}</a></h2>})
end
it "should add ids and links to headers" do it "should add ids and links to headers" do
# Test every rule except nested tags. # Test every rule except nested tags.
...@@ -590,35 +224,15 @@ describe GitlabMarkdownHelper do ...@@ -590,35 +224,15 @@ describe GitlabMarkdownHelper do
) )
end end
it "should handle references in lists" do # REFERENCES (PART TWO: THE REVENGE) ---------------------------------------
project.team << [user, :master]
actual = "\n* dark: ##{issue.iid}\n* light by @#{member.user.username}"
expect(markdown(actual)).
to match(%r{<li>dark: <a.+>##{issue.iid}</a></li>})
expect(markdown(actual)).
to match(%r{<li>light by <a.+>@#{member.user.username}</a></li>})
end
it "should not link the apostrophe to issue 39" do
project.team << [user, :master]
allow(project.issues).
to receive(:where).with(iid: '39').and_return([issue])
actual = "Yes, it is @#{member.user.username}'s task."
expected = /Yes, it is <a.+>@#{member.user.username}<\/a>'s task/
expect(markdown(actual)).to match(expected)
end
it "should not link the apostrophe to issue 39 in code blocks" do it "should handle references in headers" do
project.team << [user, :master] actual = "\n# Working around ##{issue.iid}\n## Apply !#{merge_request.iid}"
allow(project.issues).
to receive(:where).with(iid: '39').and_return([issue])
actual = "Yes, `it is @#{member.user.username}'s task.`" expect(markdown(actual, no_header_anchors: true)).
expected = /Yes, <code>it is @gfm\'s task.<\/code>/ to match(%r{<h1[^<]*>Working around <a.+>##{issue.iid}</a></h1>})
expect(markdown(actual)).to match(expected) expect(markdown(actual, no_header_anchors: true)).
to match(%r{<h2[^<]*>Apply <a.+>!#{merge_request.iid}</a></h2>})
end end
it "should handle references in <em>" do it "should handle references in <em>" do
...@@ -628,66 +242,50 @@ describe GitlabMarkdownHelper do ...@@ -628,66 +242,50 @@ describe GitlabMarkdownHelper do
to match(%r{Apply <em><a.+>!#{merge_request.iid}</a></em>}) to match(%r{Apply <em><a.+>!#{merge_request.iid}</a></em>})
end end
it "should handle tables" do # CODE BLOCKS -------------------------------------------------------------
actual = %Q{| header 1 | header 2 |
| -------- | -------- |
| cell 1 | cell 2 |
| cell 3 | cell 4 |}
expect(markdown(actual)).to match(/\A<table/)
end
it "should leave code blocks untouched" do it "should leave code blocks untouched" do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:user_color_scheme_class).and_return(:white) allow(helper).to receive(:user_color_scheme_class).and_return(:white)
target_html = "<pre class=\"code highlight white plaintext\"><code>some code from $#{snippet.id}\nhere too\n</code></pre>\n" target_html = "<pre class=\"code highlight white plaintext\"><code>some code from $#{snippet.id}\nhere too\n</code></pre>\n"
expect(helper.markdown("\n some code from $#{snippet.id}\n here too\n")). expect(markdown("\n some code from $#{snippet.id}\n here too\n")).
to eq(target_html) to eq(target_html)
expect(helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n")). expect(markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n")).
to eq(target_html) to eq(target_html)
end end
it "should leave inline code untouched" do it "should leave inline code untouched" do
expect(markdown("\nDon't use `$#{snippet.id}` here.\n")).to eq( expect(markdown("Don't use `$#{snippet.id}` here.")).
"<p>Don't use <code>$#{snippet.id}</code> here.</p>\n" to eq "<p>Don't use <code>$#{snippet.id}</code> here.</p>\n"
)
end end
# REF-LIKE AUTOLINKS? -----------------------------------------------------
# Basically: Don't parse references inside `<a>` tags.
it "should leave ref-like autolinks untouched" do it "should leave ref-like autolinks untouched" do
expect(markdown("look at http://example.tld/#!#{merge_request.iid}")).to eq("<p>look at <a href=\"http://example.tld/#!#{merge_request.iid}\">http://example.tld/#!#{merge_request.iid}</a></p>\n") expect(markdown("look at http://example.tld/#!#{merge_request.iid}")).to eq("<p>look at <a href=\"http://example.tld/#!#{merge_request.iid}\">http://example.tld/#!#{merge_request.iid}</a></p>\n")
end end
it "should leave ref-like href of 'manual' links untouched" do it "should leave ref-like href of 'manual' links untouched" do
expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a class=\"gfm gfm-merge_request \" href=\"#{namespace_project_merge_request_path(project.namespace, project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n") expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a href=\"#{namespace_project_merge_request_path(project.namespace, project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\" class=\"gfm gfm-merge_request\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n")
end end
it "should leave ref-like src of images untouched" do it "should leave ref-like src of images untouched" do
expect(markdown("screen shot: ![some image](http://example.tld/#!#{merge_request.iid})")).to eq("<p>screen shot: <img src=\"http://example.tld/#!#{merge_request.iid}\" alt=\"some image\"></p>\n") expect(markdown("screen shot: ![some image](http://example.tld/#!#{merge_request.iid})")).to eq("<p>screen shot: <img src=\"http://example.tld/#!#{merge_request.iid}\" alt=\"some image\"></p>\n")
end end
it "should generate absolute urls for refs" do # RELATIVE URLS -----------------------------------------------------------
expect(markdown("##{issue.iid}")).to include(namespace_project_issue_path(project.namespace, project, issue)) # TODO (rspeicher): These belong in a relative link filter spec
end
it "should generate absolute urls for emoji" do context 'relative links' do
expect(markdown(':smile:')).to( context 'with a valid repository' do
include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/#{Emoji.emoji_filename('smile')}.png)) before do
) @repository = project.repository
end @ref = 'markdown'
it "should generate absolute urls for emoji if relative url is present" do
allow(Gitlab.config.gitlab).to receive(:url).and_return('http://localhost/gitlab/root')
expect(markdown(":smile:")).to include("src=\"http://localhost/gitlab/root/assets/emoji/#{Emoji.emoji_filename('smile')}.png")
end
it "should generate absolute urls for emoji if asset_host is present" do
allow(Gitlab::Application.config).to receive(:asset_host).and_return("https://cdn.example.com")
ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com")
expect(markdown(":smile:")).to include("src=\"https://cdn.example.com/assets/emoji/#{Emoji.emoji_filename('smile')}.png")
end end
it "should handle relative urls for a file in master" do it "should handle relative urls for a file in master" do
actual = "[GitLab API doc](doc/api/README.md)\n" actual = "[GitLab API doc](doc/api/README.md)\n"
expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n" expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n"
...@@ -740,6 +338,24 @@ describe GitlabMarkdownHelper do ...@@ -740,6 +338,24 @@ describe GitlabMarkdownHelper do
actual = '<dl><dt>Term</dt><dd>Definition</dd></dl>' actual = '<dl><dt>Term</dt><dd>Definition</dd></dl>'
expect(markdown(actual)).to match(actual) expect(markdown(actual)).to match(actual)
end end
end
context 'with an empty repository' do
before do
@project = create(:empty_project)
@repository = @project.repository
end
it "should not touch relative urls" do
actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
expected = "<p><a href=\"doc/api/README.md\">GitLab API doc</a></p>\n"
expect(markdown(actual)).to match(expected)
end
end
end
# SANITIZATION ------------------------------------------------------------
# TODO (rspeicher): These are testing SanitizationFilter, not `markdown`
it 'should sanitize tags that are not whitelisted' do it 'should sanitize tags that are not whitelisted' do
actual = '<textarea>no inputs allowed</textarea> <blink>no blinks</blink>' actual = '<textarea>no inputs allowed</textarea> <blink>no blinks</blink>'
...@@ -767,20 +383,7 @@ describe GitlabMarkdownHelper do ...@@ -767,20 +383,7 @@ describe GitlabMarkdownHelper do
end end
end end
describe 'markdown for empty repository' do describe '#render_wiki_content' do
before do
@project = empty_project
@repository = empty_project.repository
end
it "should not touch relative urls" do
actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
expected = "<p><a href=\"doc/api/README.md\">GitLab API doc</a></p>\n"
expect(markdown(actual)).to match(expected)
end
end
describe "#render_wiki_content" do
before do before do
@wiki = double('WikiPage') @wiki = double('WikiPage')
allow(@wiki).to receive(:content).and_return('wiki content') allow(@wiki).to receive(:content).and_return('wiki content')
...@@ -803,114 +406,4 @@ describe GitlabMarkdownHelper do ...@@ -803,114 +406,4 @@ describe GitlabMarkdownHelper do
helper.render_wiki_content(@wiki) helper.render_wiki_content(@wiki)
end end
end end
describe '#gfm_with_tasks' do
before(:all) do
@source_text_asterisk = <<EOT.gsub(/^\s{8}/, '')
* [ ] valid unchecked task
* [x] valid lowercase checked task
* [X] valid uppercase checked task
* [ ] valid unchecked nested task
* [x] valid checked nested task
[ ] not an unchecked task - no list item
[x] not a checked task - no list item
* [ ] not an unchecked task - too many spaces
* [x ] not a checked task - too many spaces
* [] not an unchecked task - no spaces
* Not a task [ ] - not at beginning
EOT
@source_text_dash = <<EOT.gsub(/^\s{8}/, '')
- [ ] valid unchecked task
- [x] valid lowercase checked task
- [X] valid uppercase checked task
- [ ] valid unchecked nested task
- [x] valid checked nested task
EOT
end
it 'should render checkboxes at beginning of asterisk list items' do
rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
expect(rendered_text).to match(
/<input.*checkbox.*valid lowercase checked task/
)
expect(rendered_text).to match(
/<input.*checkbox.*valid uppercase checked task/
)
end
it 'should render checkboxes at beginning of dash list items' do
rendered_text = markdown(@source_text_dash, parse_tasks: true)
expect(rendered_text).to match(/<input.*checkbox.*valid unchecked task/)
expect(rendered_text).to match(
/<input.*checkbox.*valid lowercase checked task/
)
expect(rendered_text).to match(
/<input.*checkbox.*valid uppercase checked task/
)
end
it 'should render checkboxes for nested tasks' do
rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(rendered_text).to match(
/<input.*checkbox.*valid unchecked nested task/
)
expect(rendered_text).to match(
/<input.*checkbox.*valid checked nested task/
)
end
it 'should not be confused by whitespace before bullets' do
rendered_text_asterisk = markdown(@source_text_asterisk,
parse_tasks: true)
rendered_text_dash = markdown(@source_text_dash, parse_tasks: true)
expect(rendered_text_asterisk).to match(
/<input.*checkbox.*valid unchecked nested task/
)
expect(rendered_text_asterisk).to match(
/<input.*checkbox.*valid checked nested task/
)
expect(rendered_text_dash).to match(
/<input.*checkbox.*valid unchecked nested task/
)
expect(rendered_text_dash).to match(
/<input.*checkbox.*valid checked nested task/
)
end
it 'should not render checkboxes outside of list items' do
rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(rendered_text).not_to match(
/<input.*checkbox.*not an unchecked task - no list item/
)
expect(rendered_text).not_to match(
/<input.*checkbox.*not a checked task - no list item/
)
end
it 'should not render checkboxes with invalid formatting' do
rendered_text = markdown(@source_text_asterisk, parse_tasks: true)
expect(rendered_text).not_to match(
/<input.*checkbox.*not an unchecked task - too many spaces/
)
expect(rendered_text).not_to match(
/<input.*checkbox.*not a checked task - too many spaces/
)
expect(rendered_text).not_to match(
/<input.*checkbox.*not an unchecked task - no spaces/
)
expect(rendered_text).not_to match(
/Not a task.*<input.*checkbox.*not at beginning/
)
end
end
end end
require 'spec_helper' require 'spec_helper'
describe LabelsHelper do describe LabelsHelper do
it { expect(text_color_for_bg('#EEEEEE')).to eq('#333') } it { expect(text_color_for_bg('#EEEEEE')).to eq('#333333') }
it { expect(text_color_for_bg('#222E2E')).to eq('#FFF') } it { expect(text_color_for_bg('#222E2E')).to eq('#FFFFFF') }
end end
#= require jquery
#= require jasmine-fixture
#= require shortcuts_issuable
describe 'ShortcutsIssuable', ->
beforeEach ->
@shortcut = new ShortcutsIssuable()
describe '#replyWithSelectedText', ->
# Stub window.getSelection to return the provided String.
stubSelection = (text) ->
window.getSelection = -> text
beforeEach ->
@selector = 'form.js-main-target-form textarea#note_note'
affix(@selector)
describe 'with empty selection', ->
it 'does nothing', ->
stubSelection('')
@shortcut.replyWithSelectedText()
expect($(@selector).val()).toBe('')
describe 'with any selection', ->
beforeEach ->
stubSelection('Selected text.')
it 'leaves existing input intact', ->
$(@selector).val('This text was already here.')
expect($(@selector).val()).toBe('This text was already here.')
@shortcut.replyWithSelectedText()
expect($(@selector).val()).
toBe("This text was already here.\n> Selected text.\n\n")
it 'triggers `input`', ->
triggered = false
$(@selector).on 'input', -> triggered = true
@shortcut.replyWithSelectedText()
expect(triggered).toBe(true)
it 'triggers `focus`', ->
focused = false
$(@selector).on 'focus', -> focused = true
@shortcut.replyWithSelectedText()
expect(focused).toBe(true)
describe 'with a one-line selection', ->
it 'quotes the selection', ->
stubSelection('This text has been selected.')
@shortcut.replyWithSelectedText()
expect($(@selector).val()).
toBe("> This text has been selected.\n\n")
describe 'with a multi-line selection', ->
it 'quotes the selected lines as a group', ->
stubSelection(
"""
Selected line one.
Selected line two.
Selected line three.
"""
)
@shortcut.replyWithSelectedText()
expect($(@selector).val()).
toBe(
"""
> Selected line one.
> Selected line two.
> Selected line three.
"""
)
//= require stat_graph_contributors_graph
describe("ContributorsGraph", function () { describe("ContributorsGraph", function () {
describe("#set_x_domain", function () { describe("#set_x_domain", function () {
it("set the x_domain", function () { it("set the x_domain", function () {
......
//= require stat_graph_contributors_util
describe("ContributorsStatGraphUtil", function () { describe("ContributorsStatGraphUtil", function () {
describe("#parse_log", function () { describe("#parse_log", function () {
......
//= require stat_graph
describe("StatGraph", function () { describe("StatGraph", function () {
describe("#get_log", function () { describe("#get_log", function () {
......
# src_files # path to parent directory of spec_files
# relative path from Rails.root
# #
# Return an array of filepaths relative to src_dir to include before jasmine specs. # Alternatively accept an array of directory to include external spec files
# Default: [] # spec_dir:
# - spec/javascripts
# - ../engine/spec/javascripts
# #
# EXAMPLE: # defaults to spec/javascripts
# spec_dir: spec/javascripts
# src_files:
# - lib/source1.js
# - lib/source2.js
# - dist/**/*.js
#
src_files:
- assets/application.js
# stylesheets
#
# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
# Default: []
#
# EXAMPLE:
#
# stylesheets:
# - css/style.css
# - stylesheets/*.css
#
stylesheets:
- stylesheets/**/*.css
# helpers # list of file expressions to include as helpers into spec runner
# # relative path from spec_dir
# Return an array of filepaths relative to spec_dir to include before jasmine specs.
# Default: ["helpers/**/*.js"]
#
# EXAMPLE:
#
# helpers:
# - helpers/**/*.js
#
helpers: helpers:
- helpers/**/*.js - "helpers/**/*.{js.coffee,js,coffee}"
# spec_files # list of file expressions to include as specs into spec runner
# # relative path from spec_dir
# Return an array of filepaths relative to spec_dir to include.
# Default: ["**/*[sS]pec.js"]
#
# EXAMPLE:
#
# spec_files:
# - **/*[sS]pec.js
#
spec_files: spec_files:
- '**/*[sS]pec.js' - "**/*[Ss]pec.{js.coffee,js,coffee}"
# src_dir
#
# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
# Default: project root
#
# EXAMPLE:
#
# src_dir: public
#
src_dir:
# spec_dir
#
# Spec directory path. Your spec_files must be returned relative to this path.
# Default: spec/javascripts
#
# EXAMPLE:
#
# spec_dir: spec/javascripts
#
spec_dir: spec/javascripts
...@@ -8,4 +8,8 @@ ...@@ -8,4 +8,8 @@
# config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] } # config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] }
#end #end
# #
#Example: prevent PhantomJS auto install, uses PhantomJS already on your path.
#Jasmine.configure do |config|
# config.prevent_phantom_js_auto_install = true
#end
#
...@@ -57,10 +57,11 @@ describe Gitlab::GoogleCodeImport::Importer do ...@@ -57,10 +57,11 @@ describe Gitlab::GoogleCodeImport::Importer do
expect(issue.label_names).to include("Type: Enhancement") expect(issue.label_names).to include("Type: Enhancement")
expect(issue.title).to eq("Scrolling through tasks") expect(issue.title).to eq("Scrolling through tasks")
expect(issue.state).to eq("closed") expect(issue.state).to eq("closed")
expect(issue.description).to include("schattenpr...") expect(issue.description).to include("schattenpr\\.\\.\\.")
expect(issue.description).to include("November 18, 2009 00:20") expect(issue.description).to include("November 18, 2009 00:20")
expect(issue.description).to include('I like to scroll through the tasks with my scrollwheel \(like in fluxbox\).') expect(issue.description).to include("Google Code")
expect(issue.description).to include('Patch is attached that adds two new mouse\-actions \(next\_taskprev\_task\)') expect(issue.description).to include('I like to scroll through the tasks with my scrollwheel (like in fluxbox).')
expect(issue.description).to include('Patch is attached that adds two new mouse-actions (next_task+prev_task)')
expect(issue.description).to include('that can be used for exactly that purpose.') expect(issue.description).to include('that can be used for exactly that purpose.')
expect(issue.description).to include('all the best!') expect(issue.description).to include('all the best!')
expect(issue.description).to include('[tint2_task_scrolling.diff](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/tint2_task_scrolling.diff)') expect(issue.description).to include('[tint2_task_scrolling.diff](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/tint2_task_scrolling.diff)')
......
require 'spec_helper'
module Gitlab::Markdown
describe CommitRangeReferenceFilter do
include ReferenceFilterSpecHelper
let(:project) { create(:project) }
let(:commit1) { project.repository.commit }
let(:commit2) { project.repository.commit("HEAD~2") }
it 'requires project context' do
expect { described_class.call('Commit Range 1c002d..d200c1', {}) }.
to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit Range #{commit1.id}..#{commit2.id}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
let(:reference) { "#{commit1.id}...#{commit2.id}" }
let(:reference2) { "#{commit1.id}..#{commit2.id}" }
it 'links to a valid two-dot reference' do
doc = filter("See #{reference2}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, from: "#{commit1.id}^", to: commit2.id)
end
it 'links to a valid three-dot reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id)
end
it 'links to a valid short ID' do
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
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid commit IDs' do
exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
expect(project).to receive(:valid_repo?).and_return(true)
expect(project.repository).to receive(:commit).with(commit1.id.reverse)
expect(filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Commits #{commit1.id} through #{commit2.id}"
end
it 'includes default classes' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
end
it 'includes an optional custom class' do
doc = filter("See #{reference}", reference_class: 'custom')
expect(doc.css('a').first.attr('class')).to include 'custom'
end
it 'supports an :only_path option' do
doc = filter("See #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
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
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
let(:commit1) { project.repository.commit }
let(:commit2) { project.repository.commit("HEAD~2") }
let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" }
context 'when user can access reference' do
before { allow_cross_reference! }
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: commit2.id)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id.reverse}...#{commit2.id}"
expect(filter(act).to_html).to eq exp
exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id}...#{commit2.id.reverse}"
expect(filter(act).to_html).to eq exp
end
end
context 'when user cannot access reference' do
before { disallow_cross_reference! }
it 'ignores valid references' do
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp
end
end
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe CommitReferenceFilter do
include ReferenceFilterSpecHelper
let(:project) { create(:project) }
let(:commit) { project.repository.commit }
it 'requires project context' do
expect { described_class.call('Commit 1c002d', {}) }.
to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
let(:reference) { commit.id }
# Let's test a variety of commit SHA sizes just to be paranoid
[6, 8, 12, 18, 20, 32, 40].each do |size|
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.attr('href')).
to eq urls.namespace_project_commit_url(project.namespace, project, reference)
end
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid commit IDs' do
exp = act = "See #{reference.reverse}"
expect(project).to receive(:valid_repo?).and_return(true)
expect(project.repository).to receive(:commit).with(reference.reverse)
expect(filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq commit.link_title
end
it 'escapes the title attribute' do
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}"
end
it 'includes default classes' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
end
it 'includes an optional custom class' do
doc = filter("See #{reference}", reference_class: 'custom')
expect(doc.css('a').first.attr('class')).to include 'custom'
end
it 'supports an :only_path context' do
doc = filter("See #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
let(:commit) { project.repository.commit }
let(:reference) { "#{project2.path_with_namespace}@#{commit.id}" }
context 'when user can access reference' do
before { allow_cross_reference! }
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/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
end
context 'when user cannot access reference' do
before { disallow_cross_reference! }
it 'ignores valid references' do
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp
end
end
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe CrossProjectReference do
# context in the html-pipeline sense, not in the rspec sense
let(:context) do
{
current_user: double('user'),
project: double('project')
}
end
include described_class
describe '#project_from_ref' do
context 'when no project was referenced' do
it 'returns the project from context' do
expect(project_from_ref(nil)).to eq context[:project]
end
end
context 'when referenced project does not exist' do
it 'returns nil' do
expect(project_from_ref('invalid/reference')).to be_nil
end
end
context 'when referenced project exists' do
let(:project2) { double('referenced project') }
before do
expect(Project).to receive(:find_with_namespace).
with('cross/reference').and_return(project2)
end
context 'and the user has permission to read it' do
it 'returns the referenced project' do
expect(self).to receive(:user_can_reference_project?).
with(project2).and_return(true)
expect(project_from_ref('cross/reference')).to eq project2
end
end
context 'and the user does not have permission to read it' do
it 'returns nil' do
expect(self).to receive(:user_can_reference_project?).
with(project2).and_return(false)
expect(project_from_ref('cross/reference')).to be_nil
end
end
end
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe EmojiFilter do
def filter(html, contexts = {})
described_class.call(html, contexts)
end
before do
ActionController::Base.asset_host = 'https://foo.com'
end
it 'replaces supported emoji' do
doc = filter('<p>:heart:</p>')
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
end
it 'ignores unsupported emoji' do
exp = act = '<p>:foo:</p>'
doc = filter(act)
expect(doc.to_html).to match Regexp.escape(exp)
end
it 'correctly encodes the URL' do
doc = filter('<p>:+1:</p>')
expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
end
it 'matches at the start of a string' do
doc = filter(':+1:')
expect(doc.css('img').size).to eq 1
end
it 'matches at the end of a string' do
doc = filter('This gets a :-1:')
expect(doc.css('img').size).to eq 1
end
it 'matches with adjacent text' do
doc = filter('+1 (:+1:)')
expect(doc.css('img').size).to eq 1
end
it 'matches multiple emoji in a row' do
doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:')
expect(doc.css('img').size).to eq 3
end
it 'has a title attribute' do
doc = filter(':-1:')
expect(doc.css('img').first.attr('title')).to eq ':-1:'
end
it 'has an alt attribute' do
doc = filter(':-1:')
expect(doc.css('img').first.attr('alt')).to eq ':-1:'
end
it 'has an align attribute' do
doc = filter(':8ball:')
expect(doc.css('img').first.attr('align')).to eq 'absmiddle'
end
it 'has an emoji class' do
doc = filter(':cat:')
expect(doc.css('img').first.attr('class')).to eq 'emoji'
end
it 'has height and width attributes' do
doc = filter(':dog:')
img = doc.css('img').first
expect(img.attr('width')).to eq '20'
expect(img.attr('height')).to eq '20'
end
it 'keeps whitespace intact' do
doc = filter('This deserves a :+1:, big time.')
expect(doc.to_html).to match(/^This deserves a <img.+>, big time\.\z/)
end
it 'uses a custom asset_root context' do
root = Gitlab.config.gitlab.url + 'gitlab/root'
doc = filter(':smile:', asset_root: root)
expect(doc.css('img').first.attr('src')).to start_with(root)
end
it 'uses a custom asset_host context' do
ActionController::Base.asset_host = 'https://cdn.example.com'
doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?')
expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com')
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe ExternalIssueReferenceFilter do
include ReferenceFilterSpecHelper
def helper
IssuesHelper
end
let(:project) { create(:empty_project) }
let(:issue) { double('issue', iid: 123) }
context 'JIRA issue references' do
let(:reference) { "JIRA-#{issue.iid}" }
before do
jira = project.create_jira_service
props = {
'title' => 'JIRA tracker',
'project_url' => 'http://jira.example/issues/?jql=project=A',
'issues_url' => 'http://jira.example/browse/:id',
'new_issue_url' => 'http://jira.example/secure/CreateIssue.jspa'
}
jira.update_attributes(properties: props, active: true)
end
after do
project.jira_service.destroy
end
it 'requires project context' do
expect { described_class.call('Issue JIRA-123', {}) }.
to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue JIRA-#{issue.iid}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
it 'ignores valid references when using default tracker' do
expect(project).to receive(:default_issues_tracker?).and_return(true)
exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
end
%w(pre code a style).each do |elem|
it "ignores references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
it 'links to a valid reference' do
doc = filter("Issue #{reference}")
expect(doc.css('a').first.attr('href'))
.to eq helper.url_for_issue(reference, project)
end
it 'links to the external tracker' do
doc = filter("Issue #{reference}")
link = doc.css('a').first.attr('href')
expect(link).to eq "http://jira.example/browse/#{reference}"
end
it 'links with adjacent text' do
doc = filter("Issue (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
end
it 'includes a title attribute' do
doc = filter("Issue #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker"
end
it 'escapes the title attribute' do
allow(project.external_issue_tracker).to receive(:title).
and_return(%{"></a>whatever<a title="})
doc = filter("Issue #{reference}")
expect(doc.text).to eq "Issue #{reference}"
end
it 'includes default classes' do
doc = filter("Issue #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
end
it 'includes an optional custom class' do
doc = filter("Issue #{reference}", reference_class: 'custom')
expect(doc.css('a').first.attr('class')).to include 'custom'
end
it 'supports an :only_path context' do
doc = filter("Issue #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true)
end
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe IssueReferenceFilter do
include ReferenceFilterSpecHelper
def helper
IssuesHelper
end
let(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
it 'requires project context' do
expect { described_class.call('Issue #123', {}) }.
to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue ##{issue.iid}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
let(:reference) { "##{issue.iid}" }
it 'ignores valid references when using non-default tracker' do
expect(project).to receive(:issue_exists?).with(issue.iid).and_return(false)
exp = act = "Issue ##{issue.iid}"
expect(filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid issue IDs' do
exp = act = "Fixed ##{issue.iid + 1}"
expect(project).to receive(:issue_exists?).with(issue.iid + 1)
expect(filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("Issue #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
end
it 'escapes the title attribute' do
issue.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Issue #{reference}")
expect(doc.text).to eq "Issue #{reference}"
end
it 'includes default classes' do
doc = filter("Issue #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
end
it 'includes an optional custom class' do
doc = filter("Issue #{reference}", reference_class: 'custom')
expect(doc.css('a').first.attr('class')).to include 'custom'
end
it 'supports an :only_path context' do
doc = filter("Issue #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
expect(link).to eq helper.url_for_issue(issue.iid, project, only_path: true)
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" }
context 'when user can access reference' do
before { allow_cross_reference! }
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(Project).to receive(:issue_exists?).
with(issue.iid).and_return(false)
exp = act = "Issue ##{issue.iid}"
expect(filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{project2.path_with_namespace}##{issue.iid + 1}"
expect(filter(act).to_html).to eq exp
end
end
context 'when user cannot access reference' do
before { disallow_cross_reference! }
it 'ignores valid references' do
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp
end
end
end
end
end
require 'spec_helper'
require 'html/pipeline'
module Gitlab::Markdown
describe LabelReferenceFilter do
include ReferenceFilterSpecHelper
let(:project) { create(:empty_project) }
let(:label) { create(:label, project: project) }
let(:reference) { "~#{label.id}" }
it 'requires project context' do
expect { described_class.call('Label ~123', {}) }.
to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Label #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
it 'includes default classes' do
doc = filter("Label #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
end
it 'includes an optional custom class' do
doc = filter("Label #{reference}", reference_class: 'custom')
expect(doc.css('a').first.attr('class')).to include 'custom'
end
it 'supports an :only_path context' do
doc = filter("Label #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
expect(link).to eq urls.namespace_project_issues_url(project.namespace, project, label_name: label.name, only_path: true)
end
describe 'label span element' do
it 'includes default classes' do
doc = filter("Label #{reference}")
expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
end
it 'includes a style attribute' do
doc = filter("Label #{reference}")
expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
end
end
context 'Integer-based references' do
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label IDs' do
exp = act = "Label ~#{label.id + 1}"
expect(filter(act).to_html).to eq exp
end
end
context 'String-based single-word references' do
let(:label) { create(:label, name: 'gfm', project: project) }
let(:reference) { "~#{label.name}" }
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm'
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label names' do
exp = act = "Label ~#{label.name.reverse}"
expect(filter(act).to_html).to eq exp
end
end
context 'String-based multi-word references in quotes' do
let(:label) { create(:label, name: 'gfm references', project: project) }
context 'in single quotes' do
let(:reference) { "~'#{label.name}'" }
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm references'
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label names' do
exp = act = "Label ~'#{label.name.reverse}'"
expect(filter(act).to_html).to eq exp
end
end
context 'in double quotes' do
let(:reference) { %(~"#{label.name}") }
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm references'
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label names' do
exp = act = %(Label ~"#{label.name.reverse}")
expect(filter(act).to_html).to eq exp
end
end
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe MergeRequestReferenceFilter do
include ReferenceFilterSpecHelper
let(:project) { create(:project) }
let(:merge) { create(:merge_request, source_project: project) }
it 'requires project context' do
expect { described_class.call('MergeRequest !123', {}) }.
to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Merge !#{merge.iid}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
let(:reference) { "!#{merge.iid}" }
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_merge_request_url(project.namespace, project, merge)
end
it 'links with adjacent text' do
doc = filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid merge IDs' do
exp = act = "Merge !#{merge.iid + 1}"
expect(filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("Merge #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
end
it 'escapes the title attribute' do
merge.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Merge #{reference}")
expect(doc.text).to eq "Merge #{reference}"
end
it 'includes default classes' do
doc = filter("Merge #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end
it 'includes an optional custom class' do
doc = filter("Merge #{reference}", reference_class: 'custom')
expect(doc.css('a').first.attr('class')).to include 'custom'
end
it 'supports an :only_path context' do
doc = filter("Merge #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
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
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2) }
let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" }
context 'when user can access reference' do
before { allow_cross_reference! }
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_merge_request_url(project2.namespace,
project, merge)
end
it 'links with adjacent text' do
doc = filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid merge IDs on the referenced project' do
exp = act = "Merge #{project2.path_with_namespace}!#{merge.iid + 1}"
expect(filter(act).to_html).to eq exp
end
end
context 'when user cannot access reference' do
before { disallow_cross_reference! }
it 'ignores valid references' do
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp
end
end
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe SnippetReferenceFilter do
include ReferenceFilterSpecHelper
let(:project) { create(:empty_project) }
let(:snippet) { create(:project_snippet, project: project) }
let(:reference) { "$#{snippet.id}" }
it 'requires project context' do
expect { described_class.call('Snippet $123', {}) }.
to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Snippet #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_snippet_url(project.namespace, project, snippet)
end
it 'links with adjacent text' do
doc = filter("Snippet (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs' do
exp = act = "Snippet $#{snippet.id + 1}"
expect(filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("Snippet #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
end
it 'escapes the title attribute' do
snippet.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Snippet #{reference}")
expect(doc.text).to eq "Snippet #{reference}"
end
it 'includes default classes' do
doc = filter("Snippet #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
end
it 'includes an optional custom class' do
doc = filter("Snippet #{reference}", reference_class: 'custom')
expect(doc.css('a').first.attr('class')).to include 'custom'
end
it 'supports an :only_path context' do
doc = filter("Snippet #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" }
context 'when user can access reference' do
before { allow_cross_reference! }
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs on the referenced project' do
exp = act = "See #{project2.path_with_namespace}$#{snippet.id + 1}"
expect(filter(act).to_html).to eq exp
end
end
context 'when user cannot access reference' do
before { disallow_cross_reference! }
it 'ignores valid references' do
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp
end
end
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe UserReferenceFilter do
include ReferenceFilterSpecHelper
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
it 'requires project context' do
expect { described_class.call('Example @mention', {}) }.
to raise_error(ArgumentError, /:project/)
end
it 'ignores invalid users' do
exp = act = 'Hey @somebody'
expect(filter(act).to_html).to eq(exp)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Hey @#{user.username}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'mentioning a user' do
it 'links to a User' do
doc = filter("Hey @#{user.username}")
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
end
# TODO (rspeicher): This test might be overkill
it 'links to a User with a period' do
user = create(:user, name: 'alphA.Beta')
doc = filter("Hey @#{user.username}")
expect(doc.css('a').length).to eq 1
end
# TODO (rspeicher): This test might be overkill
it 'links to a User with an underscore' do
user = create(:user, name: 'ping_pong_king')
doc = filter("Hey @#{user.username}")
expect(doc.css('a').length).to eq 1
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)
doc = filter("Hey @#{group.name}", current_user: user)
expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
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}"
end
end
it 'links with adjacent text' do
skip 'TODO (rspeicher): Re-enable when usernames can\'t end in periods.'
doc = filter("Mention me (@#{user.username}.)")
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'
end
it 'includes an optional custom class' do
doc = filter("Hey @#{user.username}", reference_class: 'custom')
expect(doc.css('a').first.attr('class')).to include 'custom'
end
it 'supports an :only_path context' do
doc = filter("Hey @#{user.username}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
expect(link).to eq urls.user_path(user)
end
end
end
...@@ -74,7 +74,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -74,7 +74,7 @@ describe Gitlab::ReferenceExtractor do
end end
it 'handles all possible kinds of references' do it 'handles all possible kinds of references' do
accessors = Gitlab::Markdown::TYPES.map { |t| "#{t}s".to_sym } accessors = described_class::TYPES.map { |t| "#{t}s".to_sym }
expect(subject).to respond_to(*accessors) expect(subject).to respond_to(*accessors)
end end
...@@ -106,6 +106,15 @@ describe Gitlab::ReferenceExtractor do ...@@ -106,6 +106,15 @@ describe Gitlab::ReferenceExtractor do
expect(subject.merge_requests).to eq([@m1, @m0]) expect(subject.merge_requests).to eq([@m1, @m0])
end end
it 'accesses valid labels' do
@l0 = create(:label, title: 'one', project: project)
@l1 = create(:label, title: 'two', project: project)
@l2 = create(:label)
subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}")
expect(subject.labels).to eq([@l0, @l1])
end
it 'accesses valid snippets' do it 'accesses valid snippets' do
@s0 = create(:project_snippet, project: project) @s0 = create(:project_snippet, project: project)
@s1 = create(:project_snippet, project: project) @s1 = create(:project_snippet, project: project)
......
...@@ -110,17 +110,22 @@ describe API::API, api: true do ...@@ -110,17 +110,22 @@ describe API::API, api: true do
end end
it 'should return 400 error if name not given' do it 'should return 400 error if name not given' do
post api('/users', admin), email: 'test@example.com', password: 'pass1234' post api('/users', admin), attributes_for(:user).except(:name)
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
it 'should return 400 error if password not given' do it 'should return 400 error if password not given' do
post api('/users', admin), email: 'test@example.com', name: 'test' post api('/users', admin), attributes_for(:user).except(:password)
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
it "should return 400 error if email not given" do it 'should return 400 error if email not given' do
post api('/users', admin), password: 'pass1234', name: 'test' post api('/users', admin), attributes_for(:user).except(:email)
expect(response.status).to eq(400)
end
it 'should return 400 error if username not given' do
post api('/users', admin), attributes_for(:user).except(:username)
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
......
# Common methods and setup for Gitlab::Markdown reference filter specs
#
# Must be included into specs manually
module ReferenceFilterSpecHelper
extend ActiveSupport::Concern
included do
before { set_default_url_options }
end
# Allow *_url helpers to work
def set_default_url_options
Rails.application.routes.default_url_options = {
host: 'example.foo'
}
end
# Shortcut to Rails' auto-generated routes helpers, to avoid including the
# module
def urls
Rails.application.routes.url_helpers
end
# Perform `call` on the described class
#
# Automatically passes the current `project` value to the context if none is
# provided.
#
# html - String text to pass to the filter's `call` method.
# contexts - Hash context for the filter. (default: {project: project})
#
# Returns the String text returned by the filter's `call` method.
def filter(html, contexts = {})
contexts.reverse_merge!(project: project)
described_class.call(html, contexts)
end
def allow_cross_reference!
allow_any_instance_of(described_class).
to receive(:user_can_reference_project?).and_return(true)
end
def disallow_cross_reference!
allow_any_instance_of(described_class).
to receive(:user_can_reference_project?).and_return(false)
end
end
/* jasmine-fixture - 1.2.2
* Makes injecting HTML snippets into the DOM easy & clean!
* https://github.com/searls/jasmine-fixture
*/
(function() {
var createHTMLBlock,
__slice = [].slice;
(function($) {
var ewwSideEffects, jasmineFixture, originalAffix, originalJasmineDotFixture, originalJasmineFixture, root, _, _ref;
root = this;
originalJasmineFixture = root.jasmineFixture;
originalJasmineDotFixture = (_ref = root.jasmine) != null ? _ref.fixture : void 0;
originalAffix = root.affix;
_ = function(list) {
return {
inject: function(iterator, memo) {
var item, _i, _len, _results;
_results = [];
for (_i = 0, _len = list.length; _i < _len; _i++) {
item = list[_i];
_results.push(memo = iterator(memo, item));
}
return _results;
}
};
};
root.jasmineFixture = function($) {
var $whatsTheRootOf, affix, create, jasmineFixture, noConflict;
affix = function(selectorOptions) {
return create.call(this, selectorOptions, true);
};
create = function(selectorOptions, attach) {
var $top;
$top = null;
_(selectorOptions.split(/[ ](?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) {
var $el;
if (elementSelector === ">") {
return $parent;
}
$el = createHTMLBlock($, elementSelector);
if (attach || $top) {
$el.appendTo($parent);
}
$top || ($top = $el);
return $el;
}, $whatsTheRootOf(this));
return $top;
};
noConflict = function() {
var currentJasmineFixture, _ref1;
currentJasmineFixture = jasmine.fixture;
root.jasmineFixture = originalJasmineFixture;
if ((_ref1 = root.jasmine) != null) {
_ref1.fixture = originalJasmineDotFixture;
}
root.affix = originalAffix;
return currentJasmineFixture;
};
$whatsTheRootOf = function(that) {
if (that.jquery != null) {
return that;
} else if ($('#jasmine_content').length > 0) {
return $('#jasmine_content');
} else {
return $('<div id="jasmine_content"></div>').appendTo('body');
}
};
jasmineFixture = {
affix: affix,
create: create,
noConflict: noConflict
};
ewwSideEffects(jasmineFixture);
return jasmineFixture;
};
ewwSideEffects = function(jasmineFixture) {
var _ref1;
if ((_ref1 = root.jasmine) != null) {
_ref1.fixture = jasmineFixture;
}
$.fn.affix = root.affix = jasmineFixture.affix;
return afterEach(function() {
return $('#jasmine_content').remove();
});
};
if ($) {
return jasmineFixture = root.jasmineFixture($);
} else {
return root.affix = function() {
var nowJQueryExists;
nowJQueryExists = window.jQuery || window.$;
if (nowJQueryExists != null) {
jasmineFixture = root.jasmineFixture(nowJQueryExists);
return affix.call.apply(affix, [this].concat(__slice.call(arguments)));
} else {
throw new Error("jasmine-fixture requires jQuery to be defined at window.jQuery or window.$");
}
};
}
})(window.jQuery || window.$);
createHTMLBlock = (function() {
var bindData, bindEvents, parseAttributes, parseClasses, parseContents, parseEnclosure, parseReferences, parseVariableScope, regAttr, regAttrDfn, regAttrs, regCBrace, regClass, regClasses, regData, regDatas, regEvent, regEvents, regExclamation, regId, regReference, regTag, regTagNotContent, regZenTagDfn;
createHTMLBlock = function($, ZenObject, data, functions, indexes) {
var ZenCode, arr, block, blockAttrs, blockClasses, blockHTML, blockId, blockTag, blocks, el, el2, els, forScope, indexName, inner, len, obj, origZenCode, paren, result, ret, zc, zo;
if ($.isPlainObject(ZenObject)) {
ZenCode = ZenObject.main;
} else {
ZenCode = ZenObject;
ZenObject = {
main: ZenCode
};
}
origZenCode = ZenCode;
if (indexes === undefined) {
indexes = {};
}
if (ZenCode.charAt(0) === "!" || $.isArray(data)) {
if ($.isArray(data)) {
forScope = ZenCode;
} else {
obj = parseEnclosure(ZenCode, "!");
obj = obj.substring(obj.indexOf(":") + 1, obj.length - 1);
forScope = parseVariableScope(ZenCode);
}
while (forScope.charAt(0) === "@") {
forScope = parseVariableScope("!for:!" + parseReferences(forScope, ZenObject));
}
zo = ZenObject;
zo.main = forScope;
el = $();
if (ZenCode.substring(0, 5) === "!for:" || $.isArray(data)) {
if (!$.isArray(data) && obj.indexOf(":") > 0) {
indexName = obj.substring(0, obj.indexOf(":"));
obj = obj.substr(obj.indexOf(":") + 1);
}
arr = ($.isArray(data) ? data : data[obj]);
zc = zo.main;
if ($.isArray(arr) || $.isPlainObject(arr)) {
$.map(arr, function(value, index) {
var next;
zo.main = zc;
if (indexName !== undefined) {
indexes[indexName] = index;
}
if (!$.isPlainObject(value)) {
value = {
value: value
};
}
next = createHTMLBlock($, zo, value, functions, indexes);
if (el.length !== 0) {
return $.each(next, function(index, value) {
return el.push(value);
});
}
});
}
if (!$.isArray(data)) {
ZenCode = ZenCode.substr(obj.length + 6 + forScope.length);
} else {
ZenCode = "";
}
} else if (ZenCode.substring(0, 4) === "!if:") {
result = parseContents("!" + obj + "!", data, indexes);
if (result !== "undefined" || result !== "false" || result !== "") {
el = createHTMLBlock($, zo, data, functions, indexes);
}
ZenCode = ZenCode.substr(obj.length + 5 + forScope.length);
}
ZenObject.main = ZenCode;
} else if (ZenCode.charAt(0) === "(") {
paren = parseEnclosure(ZenCode, "(", ")");
inner = paren.substring(1, paren.length - 1);
ZenCode = ZenCode.substr(paren.length);
zo = ZenObject;
zo.main = inner;
el = createHTMLBlock($, zo, data, functions, indexes);
} else {
blocks = ZenCode.match(regZenTagDfn);
block = blocks[0];
if (block.length === 0) {
return "";
}
if (block.indexOf("@") >= 0) {
ZenCode = parseReferences(ZenCode, ZenObject);
zo = ZenObject;
zo.main = ZenCode;
return createHTMLBlock($, zo, data, functions, indexes);
}
block = parseContents(block, data, indexes);
blockClasses = parseClasses($, block);
if (regId.test(block)) {
blockId = regId.exec(block)[1];
}
blockAttrs = parseAttributes(block, data);
blockTag = (block.charAt(0) === "{" ? "span" : "div");
if (ZenCode.charAt(0) !== "#" && ZenCode.charAt(0) !== "." && ZenCode.charAt(0) !== "{") {
blockTag = regTag.exec(block)[1];
}
if (block.search(regCBrace) !== -1) {
blockHTML = block.match(regCBrace)[1];
}
blockAttrs = $.extend(blockAttrs, {
id: blockId,
"class": blockClasses,
html: blockHTML
});
el = $("<" + blockTag + ">", blockAttrs);
el.attr(blockAttrs);
el = bindEvents(block, el, functions);
el = bindData(block, el, data);
ZenCode = ZenCode.substr(blocks[0].length);
ZenObject.main = ZenCode;
}
if (ZenCode.length > 0) {
if (ZenCode.charAt(0) === ">") {
if (ZenCode.charAt(1) === "(") {
zc = parseEnclosure(ZenCode.substr(1), "(", ")");
ZenCode = ZenCode.substr(zc.length + 1);
} else if (ZenCode.charAt(1) === "!") {
obj = parseEnclosure(ZenCode.substr(1), "!");
forScope = parseVariableScope(ZenCode.substr(1));
zc = obj + forScope;
ZenCode = ZenCode.substr(zc.length + 1);
} else {
len = Math.max(ZenCode.indexOf("+"), ZenCode.length);
zc = ZenCode.substring(1, len);
ZenCode = ZenCode.substr(len);
}
zo = ZenObject;
zo.main = zc;
els = $(createHTMLBlock($, zo, data, functions, indexes));
els.appendTo(el);
}
if (ZenCode.charAt(0) === "+") {
zo = ZenObject;
zo.main = ZenCode.substr(1);
el2 = createHTMLBlock($, zo, data, functions, indexes);
$.each(el2, function(index, value) {
return el.push(value);
});
}
}
ret = el;
return ret;
};
bindData = function(ZenCode, el, data) {
var datas, i, split;
if (ZenCode.search(regDatas) === 0) {
return el;
}
datas = ZenCode.match(regDatas);
if (datas === null) {
return el;
}
i = 0;
while (i < datas.length) {
split = regData.exec(datas[i]);
if (split[3] === undefined) {
$(el).data(split[1], data[split[1]]);
} else {
$(el).data(split[1], data[split[3]]);
}
i++;
}
return el;
};
bindEvents = function(ZenCode, el, functions) {
var bindings, fn, i, split;
if (ZenCode.search(regEvents) === 0) {
return el;
}
bindings = ZenCode.match(regEvents);
if (bindings === null) {
return el;
}
i = 0;
while (i < bindings.length) {
split = regEvent.exec(bindings[i]);
if (split[2] === undefined) {
fn = functions[split[1]];
} else {
fn = functions[split[2]];
}
$(el).bind(split[1], fn);
i++;
}
return el;
};
parseAttributes = function(ZenBlock, data) {
var attrStrs, attrs, i, parts;
if (ZenBlock.search(regAttrDfn) === -1) {
return undefined;
}
attrStrs = ZenBlock.match(regAttrDfn);
attrs = {};
i = 0;
while (i < attrStrs.length) {
parts = regAttr.exec(attrStrs[i]);
attrs[parts[1]] = "";
if (parts[3] !== undefined) {
attrs[parts[1]] = parseContents(parts[3], data);
}
i++;
}
return attrs;
};
parseClasses = function($, ZenBlock) {
var classes, clsString, i;
ZenBlock = ZenBlock.match(regTagNotContent)[0];
if (ZenBlock.search(regClasses) === -1) {
return undefined;
}
classes = ZenBlock.match(regClasses);
clsString = "";
i = 0;
while (i < classes.length) {
clsString += " " + regClass.exec(classes[i])[1];
i++;
}
return $.trim(clsString);
};
parseContents = function(ZenBlock, data, indexes) {
var html;
if (indexes === undefined) {
indexes = {};
}
html = ZenBlock;
if (data === undefined) {
return html;
}
while (regExclamation.test(html)) {
html = html.replace(regExclamation, function(str, str2) {
var begChar, fn, val;
begChar = "";
if (str.indexOf("!for:") > 0 || str.indexOf("!if:") > 0) {
return str;
}
if (str.charAt(0) !== "!") {
begChar = str.charAt(0);
str = str.substring(2, str.length - 1);
}
fn = new Function("data", "indexes", "var r=undefined;" + "with(data){try{r=" + str + ";}catch(e){}}" + "with(indexes){try{if(r===undefined)r=" + str + ";}catch(e){}}" + "return r;");
val = unescape(fn(data, indexes));
return begChar + val;
});
}
html = html.replace(/\\./g, function(str) {
return str.charAt(1);
});
return unescape(html);
};
parseEnclosure = function(ZenCode, open, close, count) {
var index, ret;
if (close === undefined) {
close = open;
}
index = 1;
if (count === undefined) {
count = (ZenCode.charAt(0) === open ? 1 : 0);
}
if (count === 0) {
return;
}
while (count > 0 && index < ZenCode.length) {
if (ZenCode.charAt(index) === close && ZenCode.charAt(index - 1) !== "\\") {
count--;
} else {
if (ZenCode.charAt(index) === open && ZenCode.charAt(index - 1) !== "\\") {
count++;
}
}
index++;
}
ret = ZenCode.substring(0, index);
return ret;
};
parseReferences = function(ZenCode, ZenObject) {
ZenCode = ZenCode.replace(regReference, function(str) {
var fn;
str = str.substr(1);
fn = new Function("objs", "var r=\"\";" + "with(objs){try{" + "r=" + str + ";" + "}catch(e){}}" + "return r;");
return fn(ZenObject, parseReferences);
});
return ZenCode;
};
parseVariableScope = function(ZenCode) {
var forCode, rest, tag;
if (ZenCode.substring(0, 5) !== "!for:" && ZenCode.substring(0, 4) !== "!if:") {
return undefined;
}
forCode = parseEnclosure(ZenCode, "!");
ZenCode = ZenCode.substr(forCode.length);
if (ZenCode.charAt(0) === "(") {
return parseEnclosure(ZenCode, "(", ")");
}
tag = ZenCode.match(regZenTagDfn)[0];
ZenCode = ZenCode.substr(tag.length);
if (ZenCode.length === 0 || ZenCode.charAt(0) === "+") {
return tag;
} else if (ZenCode.charAt(0) === ">") {
rest = "";
rest = parseEnclosure(ZenCode.substr(1), "(", ")", 1);
return tag + ">" + rest;
}
return undefined;
};
regZenTagDfn = /([#\.\@]?[\w-]+|\[([\w-!?=:"']+(="([^"]|\\")+")? {0,})+\]|\~[\w$]+=[\w$]+|&[\w$]+(=[\w$]+)?|[#\.\@]?!([^!]|\\!)+!){0,}(\{([^\}]|\\\})+\})?/i;
regTag = /(\w+)/i;
regId = /(?:^|\b)#([\w-!]+)/i;
regTagNotContent = /((([#\.]?[\w-]+)?(\[([\w!]+(="([^"]|\\")+")? {0,})+\])?)+)/i;
/*
See lookahead syntax (?!) at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
*/
regClasses = /(\.[\w-]+)(?!["\w])/g;
regClass = /\.([\w-]+)/i;
regReference = /(@[\w$_][\w$_\d]+)/i;
regAttrDfn = /(\[([\w-!]+(="?([^"]|\\")+"?)? {0,})+\])/ig;
regAttrs = /([\w-!]+(="([^"]|\\")+")?)/g;
regAttr = /([\w-!]+)(="?((([\w]+(\[.*?\])+)|[^"\]]|\\")+)"?)?/i;
regCBrace = /\{(([^\}]|\\\})+)\}/i;
regExclamation = /(?:([^\\]|^))!([^!]|\\!)+!/g;
regEvents = /\~[\w$]+(=[\w$]+)?/g;
regEvent = /\~([\w$]+)=([\w$]+)/i;
regDatas = /&[\w$]+(=[\w$]+)?/g;
regData = /&([\w$]+)(=([\w$]+))?/i;
return createHTMLBlock;
})();
}).call(this);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment