Commit 2e243fc3 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' of dev.gitlab.org:gitlab/gitlabhq into ce-to-ee

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

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