Commit a62bd121 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into zj/gitlab-ce-index-milestone-title-label

parents 0108cdf4 38785046
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.2.0 (unreleased)
- Show last project commit to default branch on project home page
- Highlight comment based on anchor in URL
v 8.1.0 (unreleased) v 8.1.0 (unreleased)
- Fix nonatomic database update potentially causing project star counts to go negative (Stan Hu)
- Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu) - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu)
- Speed up load times of issue detail pages by roughly 1.5x - Speed up load times of issue detail pages by roughly 1.5x
- Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu) - Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu)
...@@ -55,6 +60,7 @@ v 8.1.0 (unreleased) ...@@ -55,6 +60,7 @@ v 8.1.0 (unreleased)
- Fix position of hamburger in header for smaller screens (Han Loong Liauw) - Fix position of hamburger in header for smaller screens (Han Loong Liauw)
- Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji) - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji)
- Persist filters when sorting on admin user page (Jerry Lukins) - Persist filters when sorting on admin user page (Jerry Lukins)
- Allow dashboard and group issues/MRs to be filtered by label
- Add spellcheck=false to certain input fields - Add spellcheck=false to certain input fields
- Invalidate stored service password if the endpoint URL is changed - Invalidate stored service password if the endpoint URL is changed
- Project names are not fully shown if group name is too big, even on group page view - Project names are not fully shown if group name is too big, even on group page view
...@@ -64,6 +70,7 @@ v 8.1.0 (unreleased) ...@@ -64,6 +70,7 @@ v 8.1.0 (unreleased)
- Hide passwords from services API (Alex Lossent) - Hide passwords from services API (Alex Lossent)
- Fix: Images cannot show when projects' path was changed - Fix: Images cannot show when projects' path was changed
- Optimize query when filtering on issuables (Zeger-Jan van de Weg) - Optimize query when filtering on issuables (Zeger-Jan van de Weg)
- Fix padding of outdated discussion item.
v 8.0.4 v 8.0.4
- Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu)
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
line-height: 36px; line-height: 36px;
} }
.content-block,
.gray-content-block { .gray-content-block {
margin: -$gl-padding; margin: -$gl-padding;
background-color: $background-color; background-color: $background-color;
...@@ -27,6 +28,10 @@ ...@@ -27,6 +28,10 @@
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
color: $gl-gray; color: $gl-gray;
&.white {
background-color: white;
}
&.top-block { &.top-block {
border-top: none; border-top: none;
} }
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
} }
.select2-results .select2-result-label { .select2-results .select2-result-label {
padding: 16px; padding: 9px;
} }
.select2-drop{ .select2-drop{
...@@ -143,4 +143,4 @@ ...@@ -143,4 +143,4 @@
.ajax-users-dropdown { .ajax-users-dropdown {
min-width: 250px !important; min-width: 250px !important;
} }
\ No newline at end of file
...@@ -13,6 +13,10 @@ ...@@ -13,6 +13,10 @@
border-bottom: 1px solid #ECEEF1; border-bottom: 1px solid #ECEEF1;
border-right: 1px solid #ECEEF1; border-right: 1px solid #ECEEF1;
&:target {
background: $hover;
}
&:last-child { &:last-child {
border-bottom: none; border-bottom: none;
} }
......
@mixin md-typography { @mixin md-typography {
color: $md-text-color; color: $md-text-color;
word-wrap: break-word;
a { a {
color: $md-link-color; color: $md-link-color;
...@@ -73,6 +74,8 @@ ...@@ -73,6 +74,8 @@
} }
blockquote { blockquote {
color: #7f8fa4;
font-size: inherit;
padding: 8px 21px; padding: 8px 21px;
margin: 12px 0 12px; margin: 12px 0 12px;
border-left: 3px solid #e7e9ed; border-left: 3px solid #e7e9ed;
...@@ -80,7 +83,7 @@ ...@@ -80,7 +83,7 @@
blockquote p { blockquote p {
color: #7f8fa4 !important; color: #7f8fa4 !important;
font-size: 15px; font-size: inherit;
line-height: 1.5; line-height: 1.5;
} }
...@@ -112,9 +115,9 @@ ...@@ -112,9 +115,9 @@
font-weight: inherit; font-weight: inherit;
} }
ul, ol {
ul { padding: 0;
color: #5c5d5e; margin: 6px 0 6px 18px !important;
} }
li { li {
...@@ -136,6 +139,33 @@ ...@@ -136,6 +139,33 @@
text-decoration: none; text-decoration: none;
} }
} }
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
a.anchor {
// Setting `display: none` would prevent the anchor being scrolled to, so
// instead we set the height to 0 and it gets updated on hover.
height: 0;
}
&:hover > a.anchor {
$size: 16px;
position: absolute;
right: 100%;
top: 50%;
margin-top: -$size/2;
margin-right: 0px;
padding-right: 20px;
display: inline-block;
width: $size;
height: $size;
background-image: image-url("icon-link.png");
background-size: contain;
background-repeat: no-repeat;
}
}
} }
...@@ -202,49 +232,11 @@ a > code { ...@@ -202,49 +232,11 @@ a > code {
} }
/** /**
* Wiki typography * Apply Markdown typography
* *
*/ */
.wiki { .wiki {
@include md-typography; @include md-typography;
word-wrap: break-word;
padding: 7px;
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
a.anchor {
// Setting `display: none` would prevent the anchor being scrolled to, so
// instead we set the height to 0 and it gets updated on hover.
height: 0;
}
&:hover > a.anchor {
$size: 16px;
position: absolute;
right: 100%;
top: 50%;
margin-top: -$size/2;
margin-right: 0px;
padding-right: 20px;
display: inline-block;
width: $size;
height: $size;
background-image: image-url("icon-link.png");
background-size: contain;
background-repeat: no-repeat;
}
}
ul,ol {
padding: 0;
margin: 6px 0 6px 18px !important;
}
ol {
color: #5c5d5e;
}
} }
.md-area { .md-area {
......
...@@ -68,3 +68,7 @@ body.modal-open { ...@@ -68,3 +68,7 @@ body.modal-open {
.modal .modal-dialog { .modal .modal-dialog {
width: 860px; width: 860px;
} }
.documentation {
padding: 7px;
}
...@@ -30,7 +30,6 @@ ul.notes { ...@@ -30,7 +30,6 @@ ul.notes {
.discussion-header, .discussion-header,
.note-header { .note-header {
@extend .cgray; @extend .cgray;
padding-bottom: 15px;
a:hover { a:hover {
text-decoration: none; text-decoration: none;
...@@ -75,6 +74,10 @@ ul.notes { ...@@ -75,6 +74,10 @@ ul.notes {
} }
} }
.discussion-body {
padding-top: 15px;
}
.discussion { .discussion {
overflow: hidden; overflow: hidden;
display: block; display: block;
......
...@@ -511,3 +511,38 @@ pre.light-well { ...@@ -511,3 +511,38 @@ pre.light-well {
margin-top: -1px; margin-top: -1px;
} }
} }
.project-last-commit {
margin: 0 7px;
.ci-status {
margin-right: 16px;
}
.commit-row-message {
color: $gl-gray;
}
.commit_short_id {
margin-right: 5px;
color: $gl-link-color;
font-weight: 600;
}
.commit-author-link {
margin-left: 7px;
text-decoration: none;
.avatar {
float: none;
margin-right: 4px;
}
.commit-author-name {
font-weight: 600;
}
}
}
.project-show-readme .readme-holder {
padding: 7px;
}
module Ci module Ci
class ProjectsController < Ci::ApplicationController class ProjectsController < Ci::ApplicationController
before_action :project before_action :project, except: [:index]
before_action :authenticate_user!, except: [:build, :badge] before_action :authenticate_user!, except: [:index, :build, :badge]
before_action :authorize_access_project!, except: [:badge] before_action :authorize_access_project!, except: [:index, :badge]
before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml] before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :no_cache, only: [:badge] before_action :no_cache, only: [:badge]
protect_from_forgery protect_from_forgery
def show
# Temporary compatibility with CI badges pointing to CI project page
redirect_to namespace_project_path(project.gl_project.namespace, project.gl_project)
end
# Project status badge # Project status badge
# Image with build status for sha or ref # Image with build status for sha or ref
def badge def badge
......
...@@ -19,7 +19,8 @@ module GitlabMarkdownHelper ...@@ -19,7 +19,8 @@ module GitlabMarkdownHelper
escape_once(body) escape_once(body)
end end
gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: current_user) user = current_user if defined?(current_user)
gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body) fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a' if fragment.children.size == 1 && fragment.children[0].name == 'a'
...@@ -45,29 +46,39 @@ module GitlabMarkdownHelper ...@@ -45,29 +46,39 @@ module GitlabMarkdownHelper
end end
def markdown(text, context = {}) def markdown(text, context = {})
return "" unless text.present?
context.reverse_merge!( context.reverse_merge!(
current_user: current_user,
path: @path, path: @path,
pipeline: :default,
project: @project, project: @project,
project_wiki: @project_wiki, project_wiki: @project_wiki,
ref: @ref ref: @ref
) )
Gitlab::Markdown.render(text, context) user = current_user if defined?(current_user)
html = Gitlab::Markdown.render(text, context)
Gitlab::Markdown.post_process(html, pipeline: context[:pipeline], project: @project, user: user)
end end
# TODO (rspeicher): Remove all usages of this helper and just call `markdown` # TODO (rspeicher): Remove all usages of this helper and just call `markdown`
# with a custom pipeline depending on the content being rendered # with a custom pipeline depending on the content being rendered
def gfm(text, options = {}) def gfm(text, options = {})
return "" unless text.present?
options.reverse_merge!( options.reverse_merge!(
current_user: current_user,
path: @path, path: @path,
pipeline: :default,
project: @project, project: @project,
project_wiki: @project_wiki, project_wiki: @project_wiki,
ref: @ref ref: @ref
) )
Gitlab::Markdown.gfm(text, options) user = current_user if defined?(current_user)
html = Gitlab::Markdown.gfm(text, options)
Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
end end
def asciidoc(text) def asciidoc(text)
......
...@@ -92,11 +92,19 @@ module LabelsHelper ...@@ -92,11 +92,19 @@ module LabelsHelper
end end
end end
def project_labels_options(project) def projects_labels_options
labels = project.labels.to_a labels =
labels.unshift(Label::None) if @project
labels.unshift(Label::Any) @project.labels
options_from_collection_for_select(labels, 'name', 'title', params[:label_name]) else
Label.where(project_id: @projects)
end
grouped_labels = Labels::GroupService.new(labels).execute
grouped_labels.unshift(Label::None)
grouped_labels.unshift(Label::Any)
options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
end end
# Required for Gitlab::Markdown::LabelReferenceFilter # Required for Gitlab::Markdown::LabelReferenceFilter
......
...@@ -113,7 +113,7 @@ module ProjectsHelper ...@@ -113,7 +113,7 @@ module ProjectsHelper
nav_tabs << :merge_requests nav_tabs << :merge_requests
end end
if can?(current_user, :read_build, project) if project.gitlab_ci? && can?(current_user, :read_build, project)
nav_tabs << :builds nav_tabs << :builds
end end
......
...@@ -2,13 +2,13 @@ class Commit ...@@ -2,13 +2,13 @@ class Commit
extend ActiveModel::Naming extend ActiveModel::Naming
include ActiveModel::Conversion include ActiveModel::Conversion
include Mentionable
include Participable include Participable
include Mentionable
include Referable include Referable
include StaticModel include StaticModel
attr_mentionable :safe_message attr_mentionable :safe_message
participant :author, :committer, :notes, :mentioned_users participant :author, :committer, :notes
attr_accessor :project attr_accessor :project
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
# #
module Issuable module Issuable
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Mentionable
include Participable include Participable
include Mentionable
included do included do
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
...@@ -47,8 +47,7 @@ module Issuable ...@@ -47,8 +47,7 @@ module Issuable
prefix: true prefix: true
attr_mentionable :title, :description attr_mentionable :title, :description
participant :author, :assignee, :notes_with_associations
participant :author, :assignee, :notes_with_associations, :mentioned_users
end end
module ClassMethods module ClassMethods
......
...@@ -20,6 +20,12 @@ module Mentionable ...@@ -20,6 +20,12 @@ module Mentionable
end end
end end
included do
if self < Participable
participant ->(current_user) { mentioned_users(current_user, load_lazy_references: false) }
end
end
# Returns the text used as the body of a Note when this object is referenced # Returns the text used as the body of a Note when this object is referenced
# #
# By default this will be the class name and the result of calling # By default this will be the class name and the result of calling
...@@ -41,22 +47,22 @@ module Mentionable ...@@ -41,22 +47,22 @@ module Mentionable
self self
end end
def all_references(current_user = self.author, text = self.mentionable_text) def all_references(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
ext = Gitlab::ReferenceExtractor.new(self.project, current_user) ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
ext.analyze(text) ext.analyze(text)
ext ext
end end
def mentioned_users(current_user = nil) def mentioned_users(current_user = nil, load_lazy_references: true)
all_references(current_user).users.uniq all_references(current_user, load_lazy_references: load_lazy_references).users
end end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def referenced_mentionables(current_user = self.author, text = self.mentionable_text) def referenced_mentionables(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
return [] if text.blank? return [] if text.blank?
refs = all_references(current_user, text) refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
(refs.issues + refs.merge_requests + refs.commits).uniq - [local_reference] (refs.issues + refs.merge_requests + refs.commits) - [local_reference]
end end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
# #
# # ... # # ...
# #
# participant :author, :assignee, :mentioned_users, :notes # participant :author, :assignee, :notes, ->(current_user) { mentioned_users(current_user) }
# end # end
# #
# issue = Issue.last # issue = Issue.last
...@@ -27,7 +27,7 @@ module Participable ...@@ -27,7 +27,7 @@ module Participable
module ClassMethods module ClassMethods
def participant(*attrs) def participant(*attrs)
participant_attrs.concat(attrs.map(&:to_s)) participant_attrs.concat(attrs)
end end
def participant_attrs def participant_attrs
...@@ -37,33 +37,39 @@ module Participable ...@@ -37,33 +37,39 @@ module Participable
# Be aware that this method makes a lot of sql queries. # Be aware that this method makes a lot of sql queries.
# Save result into variable if you are going to reuse it inside same request # Save result into variable if you are going to reuse it inside same request
def participants(current_user = self.author) def participants(current_user = self.author, load_lazy_references: true)
self.class.participant_attrs.flat_map do |attr| participants = self.class.participant_attrs.flat_map do |attr|
meth = method(attr)
value = value =
if meth.arity == 1 || meth.arity == -1 if attr.respond_to?(:call)
meth.call(current_user) instance_exec(current_user, &attr)
else else
meth.call send(attr)
end end
participants_for(value, current_user) participants_for(value, current_user)
end.compact.uniq.select do |user| end.compact.uniq
user.can?(:read_project, self.project)
if load_lazy_references
participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq
participants.select! do |user|
user.can?(:read_project, project)
end
end end
participants
end end
private private
def participants_for(value, current_user = nil) def participants_for(value, current_user = nil)
case value case value
when User when User, Gitlab::Markdown::ReferenceFilter::LazyReference
[value] [value]
when Enumerable, ActiveRecord::Relation when Enumerable, ActiveRecord::Relation
value.flat_map { |v| participants_for(v, current_user) } value.flat_map { |v| participants_for(v, current_user) }
when Participable when Participable
value.participants(current_user) value.participants(current_user, load_lazy_references: false)
end end
end end
end end
class GroupLabel
attr_accessor :title, :labels
alias_attribute :name, :title
def initialize(title, labels)
@title = title
@labels = labels
end
end
class GroupMilestone class GroupMilestone
attr_accessor :title, :milestones
alias_attribute :name, :title alias_attribute :name, :title
def initialize(title, milestones) def initialize(title, milestones)
...@@ -7,18 +7,10 @@ class GroupMilestone ...@@ -7,18 +7,10 @@ class GroupMilestone
@milestones = milestones @milestones = milestones
end end
def title
@title
end
def safe_title def safe_title
@title.parameterize @title.parameterize
end end
def milestones
@milestones
end
def projects def projects
milestones.map { |milestone| milestone.project } milestones.map { |milestone| milestone.project }
end end
......
...@@ -22,14 +22,14 @@ require 'carrierwave/orm/activerecord' ...@@ -22,14 +22,14 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator' require 'file_size_validator'
class Note < ActiveRecord::Base class Note < ActiveRecord::Base
include Mentionable
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Participable include Participable
include Mentionable
default_value_for :system, false default_value_for :system, false
attr_mentionable :note attr_mentionable :note
participant :author, :mentioned_users participant :author
belongs_to :project belongs_to :project
belongs_to :noteable, polymorphic: true belongs_to :noteable, polymorphic: true
......
...@@ -706,12 +706,15 @@ class User < ActiveRecord::Base ...@@ -706,12 +706,15 @@ class User < ActiveRecord::Base
end end
def toggle_star(project) def toggle_star(project)
user_star_project = users_star_projects. UsersStarProject.transaction do
where(project: project, user: self).take user_star_project = users_star_projects.
if user_star_project where(project: project, user: self).lock(true).first
user_star_project.destroy
else if user_star_project
UsersStarProject.create!(project: project, user: self) user_star_project.destroy
else
UsersStarProject.create!(project: project, user: self)
end
end end
end end
......
module Labels
class GroupService < ::BaseService
def initialize(project_labels)
@project_labels = project_labels.group_by(&:title)
end
def execute
build(@project_labels)
end
def label(title)
if title
group_label = @project_labels[title].group_by(&:title)
build(group_label).first
else
nil
end
end
private
def build(label)
label.map { |title, labels| GroupLabel.new(title, labels) }
end
end
end
.wiki
%h1
GitLab CI is now integrated in GitLab UI
%h2 For existing projects
%p
Check the following pages to find the CI status you're looking for:
%ul
%li Projects page - shows CI status for each project.
%li Project commits page - show CI status for each commit.
%h2 For new projects
%p
If you want to enable CI for a new project it is easy as adding
= link_to ".gitlab-ci.yml", "http://doc.gitlab.com/ce/ci/yaml/README.html"
file to your repository
%div{xmlns: "http://www.w3.org/1999/xhtml"} %div{xmlns: "http://www.w3.org/1999/xhtml"}
- if issue.description.present? = markdown(issue.description, pipeline: :atom, project: issue.project)
= markdown(issue.description, xhtml: true, reference_only_path: false, project: issue.project)
%div{xmlns: "http://www.w3.org/1999/xhtml"} %div{xmlns: "http://www.w3.org/1999/xhtml"}
- if merge_request.description.present? = markdown(merge_request.description, pipeline: :atom, project: merge_request.project)
= markdown(merge_request.description, xhtml: true, reference_only_path: false, project: merge_request.project)
%div{xmlns: "http://www.w3.org/1999/xhtml"} %div{xmlns: "http://www.w3.org/1999/xhtml"}
= markdown(note.note, xhtml: true, reference_only_path: false, project: note.project) = markdown(note.note, pipeline: :atom, project: note.project)
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%i %i
at at
= commit[:timestamp].to_time.to_s(:short) = commit[:timestamp].to_time.to_s(:short)
%blockquote= markdown(escape_once(commit[:message]), xhtml: true, reference_only_path: false, project: event.project) %blockquote= markdown(escape_once(commit[:message]), pipeline: :atom, project: event.project)
- if event.commits_count > 15 - if event.commits_count > 15
%p %p
%i %i
......
%div %div
= markdown(@note.note, reference_only_path: false) = markdown(@note.note, pipeline: :email)
-if @issue.description -if @issue.description
= markdown(@issue.description, reference_only_path: false) = markdown(@issue.description, pipeline: :email)
- if @issue.assignee_id.present? - if @issue.assignee_id.present?
%p %p
......
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name} Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
-if @merge_request.description -if @merge_request.description
= markdown(@merge_request.description, reference_only_path: false) = markdown(@merge_request.description, pipeline: :email)
.project-last-commit
- ci_commit = project.ci_commit(commit.sha)
- if ci_commit
= link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
= ci_status_icon(ci_commit)
= ci_commit.status
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
&middot;
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by
= commit_author_link(commit, avatar: true, size: 24)
...@@ -17,6 +17,6 @@ ...@@ -17,6 +17,6 @@
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br %br
The import will time out after 4 minutes. For big repositories, use a clone/push combination. The import will time out after 4 minutes. For big repositories, use a clone/push combination.
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"} For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}
.form-actions .form-actions
= f.submit 'Start import', class: "btn btn-create", tabindex: 4 = f.submit 'Start import', class: "btn btn-create", tabindex: 4
...@@ -64,6 +64,10 @@ ...@@ -64,6 +64,10 @@
= icon("exclamation-triangle fw") = icon("exclamation-triangle fw")
Archived project! Repository is read-only Archived project! Repository is read-only
- if @repository.commit
.content-block.second-block.white
= render 'projects/last_commit', commit: @repository.commit, project: @project
%section %section
- if prefer_readme? - if prefer_readme?
.project-show-readme .project-show-readme
......
...@@ -42,11 +42,10 @@ ...@@ -42,11 +42,10 @@
class: 'select2 trigger-submit', include_blank: true, class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Milestone'}) data: {placeholder: 'Milestone'})
- if @project .filter-item.inline.labels-filter
.filter-item.inline.labels-filter = select_tag('label_name', projects_labels_options,
= select_tag('label_name', project_labels_options(@project), class: 'select2 trigger-submit', include_blank: true,
class: 'select2 trigger-submit', include_blank: true, data: {placeholder: 'Label'})
data: {placeholder: 'Label'})
.pull-right .pull-right
= render 'shared/sort_dropdown' = render 'shared/sort_dropdown'
......
class AddCiProjectsGlProjectIdIndex < ActiveRecord::Migration
def change
add_index :ci_commits, :gl_project_id
end
end
class AddCiBuildsAndProjectsIndexes < ActiveRecord::Migration
def change
add_index :ci_projects, :gitlab_id
add_index :ci_projects, :shared_runners_enabled
add_index :ci_builds, :type
add_index :ci_builds, :status
end
end
class AddNotesLineCodeIndex < ActiveRecord::Migration
def change
add_index :notes, :line_code
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151008130321) do ActiveRecord::Schema.define(version: 20151016195706) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -115,6 +115,8 @@ ActiveRecord::Schema.define(version: 20151008130321) do ...@@ -115,6 +115,8 @@ ActiveRecord::Schema.define(version: 20151008130321) do
add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree
create_table "ci_commits", force: true do |t| create_table "ci_commits", force: true do |t|
t.integer "project_id" t.integer "project_id"
...@@ -130,6 +132,7 @@ ActiveRecord::Schema.define(version: 20151008130321) do ...@@ -130,6 +132,7 @@ ActiveRecord::Schema.define(version: 20151008130321) do
t.integer "gl_project_id" t.integer "gl_project_id"
end end
add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree
add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree
...@@ -189,6 +192,9 @@ ActiveRecord::Schema.define(version: 20151008130321) do ...@@ -189,6 +192,9 @@ ActiveRecord::Schema.define(version: 20151008130321) do
t.text "generated_yaml_config" t.text "generated_yaml_config"
end end
add_index "ci_projects", ["gitlab_id"], name: "index_ci_projects_on_gitlab_id", using: :btree
add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree
create_table "ci_runner_projects", force: true do |t| create_table "ci_runner_projects", force: true do |t|
t.integer "runner_id", null: false t.integer "runner_id", null: false
t.integer "project_id", null: false t.integer "project_id", null: false
...@@ -531,6 +537,7 @@ ActiveRecord::Schema.define(version: 20151008130321) do ...@@ -531,6 +537,7 @@ ActiveRecord::Schema.define(version: 20151008130321) do
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree
add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
......
...@@ -325,6 +325,7 @@ GitLab Shell is an SSH access and repository management software developed speci ...@@ -325,6 +325,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git
cd gitlab-git-http-server cd gitlab-git-http-server
sudo -u git -H git checkout 0.3.0
sudo -u git -H make sudo -u git -H make
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
......
...@@ -84,6 +84,7 @@ Now we download `gitlab-git-http-server` and install it in `/home/git/gitlab-git ...@@ -84,6 +84,7 @@ Now we download `gitlab-git-http-server` and install it in `/home/git/gitlab-git
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git
cd gitlab-git-http-server cd gitlab-git-http-server
sudo -u git -H git checkout 0.2.14
sudo -u git -H make sudo -u git -H make
``` ```
......
...@@ -31,6 +31,12 @@ Feature: Project ...@@ -31,6 +31,12 @@ Feature: Project
And I visit project "Shop" page And I visit project "Shop" page
Then I should see project "Shop" README Then I should see project "Shop" README
Scenario: I should see last commit with CI
Given project "Shop" has CI enabled
Given project "Shop" has CI build
And I visit project "Shop" page
And I should see last commit with CI status
@javascript @javascript
Scenario: I should see project activity Scenario: I should see project activity
When I visit project "Shop" activity page When I visit project "Shop" activity page
......
...@@ -206,4 +206,11 @@ module SharedProject ...@@ -206,4 +206,11 @@ module SharedProject
project = Project.find_by(name: "Shop") project = Project.find_by(name: "Shop")
create :ci_commit, gl_project: project, sha: project.commit.sha create :ci_commit, gl_project: project, sha: project.commit.sha
end end
step 'I should see last commit with CI status' do
page.within ".project-last-commit" do
expect(page).to have_content(project.commit.sha[0..6])
expect(page).to have_content("skipped")
end
end
end end
...@@ -7,6 +7,14 @@ module Gitlab ...@@ -7,6 +7,14 @@ module Gitlab
module Markdown module Markdown
# Convert a Markdown String into an HTML-safe String of HTML # Convert a Markdown String into an HTML-safe String of HTML
# #
# Note that while the returned HTML will have been sanitized of dangerous
# HTML, it may post a risk of information leakage if it's not also passed
# through `post_process`.
#
# Also note that the returned String is always HTML, not XHTML. Views
# requiring XHTML, such as Atom feeds, need to call `post_process` on the
# result, providing the appropriate `pipeline` option.
#
# markdown - Markdown String # markdown - Markdown String
# context - Hash of context options passed to our HTML Pipeline # context - Hash of context options passed to our HTML Pipeline
# #
...@@ -31,6 +39,33 @@ module Gitlab ...@@ -31,6 +39,33 @@ module Gitlab
renderer.render(markdown) renderer.render(markdown)
end end
# Perform post-processing on an HTML String
#
# This method is used to perform state-dependent changes to a String of
# HTML, such as removing references that the current user doesn't have
# permission to make (`RedactorFilter`).
#
# html - String to process
# options - Hash of options to customize output
# :pipeline - Symbol pipeline type
# :project - Project
# :user - User object
#
# Returns an HTML-safe String
def self.post_process(html, options)
context = {
project: options[:project],
current_user: options[:user]
}
doc = post_processor.to_document(html, context)
if options[:pipeline] == :atom
doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
else
doc.to_html
end.html_safe
end
# Provide autoload paths for filters to prevent a circular dependency error # Provide autoload paths for filters to prevent a circular dependency error
autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter' autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter'
autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter' autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter'
...@@ -41,6 +76,7 @@ module Gitlab ...@@ -41,6 +76,7 @@ module Gitlab
autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter' autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter'
autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter' autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter'
autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter' autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter'
autoload :RedactorFilter, 'gitlab/markdown/redactor_filter'
autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter' autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter' autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter'
autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter' autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter'
...@@ -50,26 +86,20 @@ module Gitlab ...@@ -50,26 +86,20 @@ module Gitlab
autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter'
autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter' autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter'
# Public: Parse the provided text with GitLab-Flavored Markdown # Public: Parse the provided HTML with GitLab-Flavored Markdown
#
# html - HTML String
# options - A Hash of options used to customize output (default: {})
# :no_header_anchors - Disable header anchors in TableOfContentsFilter
# :path - Current path String
# :pipeline - Symbol pipeline type
# :project - Current Project object
# :project_wiki - Current ProjectWiki object
# :ref - Current ref String
# #
# text - the source text # Returns an HTML-safe String
# options - A Hash of options used to customize output (default: {}): def self.gfm(html, options = {})
# :xhtml - output XHTML instead of HTML return '' unless html.present?
# :reference_only_path - Use relative path for reference links
def self.gfm(text, options = {})
return text if text.nil?
# Duplicate the string so we don't alter the original, then call to_str
# to cast it back to a String instead of a SafeBuffer. This is required
# for gsub calls to work as we need them to.
text = text.dup.to_str
options.reverse_merge!(
xhtml: false,
reference_only_path: true,
project: options[:project],
current_user: options[:current_user]
)
@pipeline ||= HTML::Pipeline.new(filters) @pipeline ||= HTML::Pipeline.new(filters)
...@@ -78,41 +108,36 @@ module Gitlab ...@@ -78,41 +108,36 @@ module Gitlab
pipeline: options[:pipeline], pipeline: options[:pipeline],
# EmojiFilter # EmojiFilter
asset_root: Gitlab.config.gitlab.base_url,
asset_host: Gitlab::Application.config.asset_host, asset_host: Gitlab::Application.config.asset_host,
asset_root: Gitlab.config.gitlab.base_url,
# TableOfContentsFilter
no_header_anchors: options[:no_header_anchors],
# ReferenceFilter # ReferenceFilter
current_user: options[:current_user], only_path: only_path_pipeline?(options[:pipeline]),
only_path: options[:reference_only_path], project: options[:project],
project: options[:project],
# RelativeLinkFilter # RelativeLinkFilter
project_wiki: options[:project_wiki],
ref: options[:ref], ref: options[:ref],
requested_path: options[:path], requested_path: options[:path],
project_wiki: options[:project_wiki]
}
result = @pipeline.call(text, context)
save_options = 0 # TableOfContentsFilter
if options[:xhtml] no_header_anchors: options[:no_header_anchors]
save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML }
end
text = result[:output].to_html(save_with: save_options)
text.html_safe @pipeline.to_html(html, context).html_safe
end end
private private
def self.renderer # Check if a pipeline enables the `only_path` context option
@markdown ||= begin #
renderer = Redcarpet::Render::HTML.new # Returns Boolean
Redcarpet::Markdown.new(renderer, redcarpet_options) def self.only_path_pipeline?(pipeline)
case pipeline
when :atom, :email
false
else
true
end end
end end
...@@ -130,6 +155,17 @@ module Gitlab ...@@ -130,6 +155,17 @@ module Gitlab
}.freeze }.freeze
end end
def self.renderer
@markdown ||= begin
renderer = Redcarpet::Render::HTML.new
Redcarpet::Markdown.new(renderer, redcarpet_options)
end
end
def self.post_processor
@post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter])
end
# Filters used in our pipeline # Filters used in our pipeline
# #
# SanitizationFilter should come first so that all generated reference HTML # SanitizationFilter should come first so that all generated reference HTML
......
...@@ -26,6 +26,18 @@ module Gitlab ...@@ -26,6 +26,18 @@ module Gitlab
end end
end end
def self.referenced_by(node)
project = Project.find(node.attr("data-project")) rescue nil
return unless project
id = node.attr("data-commit-range")
range = CommitRange.new(id, project)
return unless range.valid_commits?
{ commit_range: range }
end
def initialize(*args) def initialize(*args)
super super
...@@ -53,13 +65,11 @@ module Gitlab ...@@ -53,13 +65,11 @@ module Gitlab
range = CommitRange.new(id, project) range = CommitRange.new(id, project)
if range.valid_commits? if range.valid_commits?
push_result(:commit_range, range)
url = url_for_commit_range(project, range) url = url_for_commit_range(project, range)
title = range.reference_title title = range.reference_title
klass = reference_class(:commit_range) klass = reference_class(:commit_range)
data = data_attribute(project.id) data = data_attribute(project: project.id, commit_range: id)
project_ref += '@' if project_ref project_ref += '@' if project_ref
......
...@@ -26,6 +26,18 @@ module Gitlab ...@@ -26,6 +26,18 @@ module Gitlab
end end
end end
def self.referenced_by(node)
project = Project.find(node.attr("data-project")) rescue nil
return unless project
id = node.attr("data-commit")
commit = commit_from_ref(project, id)
return unless commit
{ commit: commit }
end
def call def call
replace_text_nodes_matching(Commit.reference_pattern) do |content| replace_text_nodes_matching(Commit.reference_pattern) do |content|
commit_link_filter(content) commit_link_filter(content)
...@@ -39,17 +51,15 @@ module Gitlab ...@@ -39,17 +51,15 @@ module Gitlab
# Returns a String with commit references replaced with links. All links # Returns a String with commit references replaced with links. All links
# have `gfm` and `gfm-commit` class names attached for styling. # have `gfm` and `gfm-commit` class names attached for styling.
def commit_link_filter(text) def commit_link_filter(text)
self.class.references_in(text) do |match, commit_ref, project_ref| self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref) project = self.project_from_ref(project_ref)
if commit = commit_from_ref(project, commit_ref) if commit = self.class.commit_from_ref(project, id)
push_result(:commit, commit)
url = url_for_commit(project, commit) url = url_for_commit(project, commit)
title = escape_once(commit.link_title) title = escape_once(commit.link_title)
klass = reference_class(:commit) klass = reference_class(:commit)
data = data_attribute(project.id) data = data_attribute(project: project.id, commit: id)
project_ref += '@' if project_ref project_ref += '@' if project_ref
...@@ -62,9 +72,9 @@ module Gitlab ...@@ -62,9 +72,9 @@ module Gitlab
end end
end end
def commit_from_ref(project, commit_ref) def self.commit_from_ref(project, id)
if project && project.valid_repo? if project && project.valid_repo?
project.commit(commit_ref) project.commit(id)
end end
end end
......
...@@ -13,18 +13,11 @@ module Gitlab ...@@ -13,18 +13,11 @@ module Gitlab
# #
# ref - String reference. # ref - String reference.
# #
# Returns a Project, or nil if the reference can't be accessed # Returns a Project, or nil if the reference can't be found
def project_from_ref(ref) def project_from_ref(ref)
return context[:project] unless ref return context[:project] unless ref
other = Project.find_with_namespace(ref) Project.find_with_namespace(ref)
return nil unless other && user_can_reference_project?(other)
other
end
def user_can_reference_project?(project, user = context[:current_user])
Ability.abilities.allowed?(user, :read_project, project)
end end
end end
end end
......
...@@ -47,8 +47,9 @@ module Gitlab ...@@ -47,8 +47,9 @@ module Gitlab
title = escape_once("Issue in #{project.external_issue_tracker.title}") title = escape_once("Issue in #{project.external_issue_tracker.title}")
klass = reference_class(:issue) klass = reference_class(:issue)
data = data_attribute(project: project.id)
%(<a href="#{url}" %(<a href="#{url}" #{data}
title="#{title}" title="#{title}"
class="#{klass}">#{match}</a>) class="#{klass}">#{match}</a>)
end end
......
...@@ -27,6 +27,10 @@ module Gitlab ...@@ -27,6 +27,10 @@ module Gitlab
end end
end end
def self.referenced_by(node)
{ issue: LazyReference.new(Issue, node.attr("data-issue")) }
end
def call def call
replace_text_nodes_matching(Issue.reference_pattern) do |content| replace_text_nodes_matching(Issue.reference_pattern) do |content|
issue_link_filter(content) issue_link_filter(content)
...@@ -45,13 +49,11 @@ module Gitlab ...@@ -45,13 +49,11 @@ module Gitlab
project = self.project_from_ref(project_ref) project = self.project_from_ref(project_ref)
if project && issue = project.get_issue(id) if project && issue = project.get_issue(id)
push_result(:issue, issue)
url = url_for_issue(id, project, only_path: context[:only_path]) url = url_for_issue(id, project, only_path: context[:only_path])
title = escape_once("Issue: #{issue.title}") title = escape_once("Issue: #{issue.title}")
klass = reference_class(:issue) klass = reference_class(:issue)
data = data_attribute(project.id) data = data_attribute(project: project.id, issue: issue.id)
%(<a href="#{url}" #{data} %(<a href="#{url}" #{data}
title="#{title}" title="#{title}"
......
...@@ -22,6 +22,10 @@ module Gitlab ...@@ -22,6 +22,10 @@ module Gitlab
end end
end end
def self.referenced_by(node)
{ label: LazyReference.new(Label, node.attr("data-label")) }
end
def call def call
replace_text_nodes_matching(Label.reference_pattern) do |content| replace_text_nodes_matching(Label.reference_pattern) do |content|
label_link_filter(content) label_link_filter(content)
...@@ -41,11 +45,9 @@ module Gitlab ...@@ -41,11 +45,9 @@ module Gitlab
params = label_params(id, name) params = label_params(id, name)
if label = project.labels.find_by(params) if label = project.labels.find_by(params)
push_result(:label, label)
url = url_for_label(project, label) url = url_for_label(project, label)
klass = reference_class(:label) klass = reference_class(:label)
data = data_attribute(project.id) data = data_attribute(project: project.id, label: label.id)
%(<a href="#{url}" #{data} %(<a href="#{url}" #{data}
class="#{klass}">#{render_colored_label(label)}</a>) class="#{klass}">#{render_colored_label(label)}</a>)
......
...@@ -27,6 +27,10 @@ module Gitlab ...@@ -27,6 +27,10 @@ module Gitlab
end end
end end
def self.referenced_by(node)
{ merge_request: LazyReference.new(MergeRequest, node.attr("data-merge-request")) }
end
def call def call
replace_text_nodes_matching(MergeRequest.reference_pattern) do |content| replace_text_nodes_matching(MergeRequest.reference_pattern) do |content|
merge_request_link_filter(content) merge_request_link_filter(content)
...@@ -45,11 +49,9 @@ module Gitlab ...@@ -45,11 +49,9 @@ module Gitlab
project = self.project_from_ref(project_ref) project = self.project_from_ref(project_ref)
if project && merge_request = project.merge_requests.find_by(iid: id) 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}") title = escape_once("Merge Request: #{merge_request.title}")
klass = reference_class(:merge_request) klass = reference_class(:merge_request)
data = data_attribute(project.id) data = data_attribute(project: project.id, merge_request: merge_request.id)
url = url_for_merge_request(merge_request, project) url = url_for_merge_request(merge_request, project)
......
require 'gitlab/markdown'
require 'html/pipeline/filter'
module Gitlab
module Markdown
# HTML filter that removes references to records that the current user does
# not have permission to view.
#
# Expected to be run in its own post-processing pipeline.
#
class RedactorFilter < HTML::Pipeline::Filter
def call
doc.css('a.gfm').each do |node|
unless user_can_reference?(node)
node.replace(node.text)
end
end
doc
end
private
def user_can_reference?(node)
if node.has_attribute?('data-reference-filter')
reference_type = node.attr('data-reference-filter')
reference_filter = reference_type.constantize
reference_filter.user_can_reference?(current_user, node, context)
else
true
end
end
def current_user
context[:current_user]
end
end
end
end
...@@ -11,30 +11,57 @@ module Gitlab ...@@ -11,30 +11,57 @@ module Gitlab
# Context options: # Context options:
# :project (required) - Current project, ignored if reference is cross-project. # :project (required) - Current project, ignored if reference is cross-project.
# :only_path - Generate path-only links. # :only_path - Generate path-only links.
#
# Results:
# :references - A Hash of references that were found and replaced.
class ReferenceFilter < HTML::Pipeline::Filter class ReferenceFilter < HTML::Pipeline::Filter
def initialize(*args) LazyReference = Struct.new(:klass, :ids) do
super def self.load(refs)
lazy_references, values = refs.partition { |ref| ref.is_a?(self) }
lazy_values = lazy_references.group_by(&:klass).flat_map do |klass, refs|
ids = refs.flat_map(&:ids)
klass.where(id: ids)
end
values + lazy_values
end
def load
self.klass.where(id: self.ids)
end
end
def self.user_can_reference?(user, node, context)
if node.has_attribute?('data-project')
project_id = node.attr('data-project').to_i
return true if project_id == context[:project].try(:id)
project = Project.find(project_id) rescue nil
Ability.abilities.allowed?(user, :read_project, project)
else
true
end
end
result[:references] = Hash.new { |hash, type| hash[type] = [] } def self.referenced_by(node)
raise NotImplementedError, "#{self} does not implement #{__method__}"
end end
# Returns a data attribute String to attach to a reference link # Returns a data attribute String to attach to a reference link
# #
# id - Object ID # attributes - Hash, where the key becomes the data attribute name and the
# type - Object type (default: :project) # value is the data attribute value
# #
# Examples: # Examples:
# #
# data_attribute(1) # => "data-project-id=\"1\"" # data_attribute(project: 1, issue: 2)
# data_attribute(2, :user) # => "data-user-id=\"2\"" # # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\""
# data_attribute(3, :group) # => "data-group-id=\"3\"" #
# data_attribute(project: 3, merge_request: 4)
# # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\""
# #
# Returns a String # Returns a String
def data_attribute(id, type = :project) def data_attribute(attributes = {})
%Q(data-#{type}-id="#{id}") attributes[:reference_filter] = self.class.name
attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{value}") }.join(" ")
end end
def escape_once(html) def escape_once(html)
...@@ -59,16 +86,6 @@ module Gitlab ...@@ -59,16 +86,6 @@ module Gitlab
context[:project] context[:project]
end 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) def reference_class(type)
"gfm gfm-#{type}" "gfm gfm-#{type}"
end end
...@@ -85,7 +102,7 @@ module Gitlab ...@@ -85,7 +102,7 @@ module Gitlab
# Yields the current node's String contents. The result of the block will # Yields the current node's String contents. The result of the block will
# replace the node's existing content and update the current document. # replace the node's existing content and update the current document.
# #
# Returns the updated Nokogiri::XML::Document object. # Returns the updated Nokogiri::HTML::DocumentFragment object.
def replace_text_nodes_matching(pattern) def replace_text_nodes_matching(pattern)
return doc if project.nil? return doc if project.nil?
......
require 'gitlab/markdown'
require 'html/pipeline/filter'
module Gitlab
module Markdown
# HTML filter that gathers all referenced records that the current user has
# permission to view.
#
# Expected to be run in its own post-processing pipeline.
#
class ReferenceGathererFilter < HTML::Pipeline::Filter
def initialize(*)
super
result[:references] ||= Hash.new { |hash, type| hash[type] = [] }
end
def call
doc.css('a.gfm').each do |node|
gather_references(node)
end
load_lazy_references unless context[:load_lazy_references] == false
doc
end
private
def gather_references(node)
return unless node.has_attribute?('data-reference-filter')
reference_type = node.attr('data-reference-filter')
reference_filter = reference_type.constantize
return if context[:reference_filter] && reference_filter != context[:reference_filter]
return unless reference_filter.user_can_reference?(current_user, node, context)
references = reference_filter.referenced_by(node)
return unless references
references.each do |type, values|
Array.wrap(values).each do |value|
result[:references][type] << value
end
end
end
# Will load all references of one type using one query.
def load_lazy_references
refs = result[:references]
refs.each do |type, values|
refs[type] = ReferenceFilter::LazyReference.load(values)
end
end
def current_user
context[:current_user]
end
end
end
end
...@@ -27,6 +27,10 @@ module Gitlab ...@@ -27,6 +27,10 @@ module Gitlab
end end
end end
def self.referenced_by(node)
{ snippet: LazyReference.new(Snippet, node.attr("data-snippet")) }
end
def call def call
replace_text_nodes_matching(Snippet.reference_pattern) do |content| replace_text_nodes_matching(Snippet.reference_pattern) do |content|
snippet_link_filter(content) snippet_link_filter(content)
...@@ -45,11 +49,9 @@ module Gitlab ...@@ -45,11 +49,9 @@ module Gitlab
project = self.project_from_ref(project_ref) project = self.project_from_ref(project_ref)
if project && snippet = project.snippets.find_by(id: id) if project && snippet = project.snippets.find_by(id: id)
push_result(:snippet, snippet)
title = escape_once("Snippet: #{snippet.title}") title = escape_once("Snippet: #{snippet.title}")
klass = reference_class(:snippet) klass = reference_class(:snippet)
data = data_attribute(project.id) data = data_attribute(project: project.id, snippet: snippet.id)
url = url_for_snippet(snippet, project) url = url_for_snippet(snippet, project)
......
...@@ -23,6 +23,31 @@ module Gitlab ...@@ -23,6 +23,31 @@ module Gitlab
end end
end end
def self.referenced_by(node)
if node.has_attribute?('data-group')
group = Group.find(node.attr('data-group')) rescue nil
return unless group
{ user: group.users }
elsif node.has_attribute?('data-user')
{ user: LazyReference.new(User, node.attr('data-user')) }
elsif node.has_attribute?('data-project')
project = Project.find(node.attr('data-project')) rescue nil
return unless project
{ user: project.team.members.flatten }
end
end
def self.user_can_reference?(user, node, context)
if node.has_attribute?('data-group')
group = Group.find(node.attr('data-group')) rescue nil
Ability.abilities.allowed?(user, :read_group, group)
else
super
end
end
def call def call
replace_text_nodes_matching(User.reference_pattern) do |content| replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content) user_link_filter(content)
...@@ -61,14 +86,12 @@ module Gitlab ...@@ -61,14 +86,12 @@ module Gitlab
def link_to_all def link_to_all
project = context[:project] project = context[:project]
# FIXME (rspeicher): Law of Demeter
push_result(:user, *project.team.members.flatten)
url = urls.namespace_project_url(project.namespace, project, url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path]) only_path: context[:only_path])
data = data_attribute(project: project.id)
text = User.reference_prefix + 'all' text = User.reference_prefix + 'all'
%(<a href="#{url}" class="#{link_class}">#{text}</a>) %(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end end
def link_to_namespace(namespace) def link_to_namespace(namespace)
...@@ -80,30 +103,20 @@ module Gitlab ...@@ -80,30 +103,20 @@ module Gitlab
end end
def link_to_group(group, namespace) 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]) url = urls.group_url(group, only_path: context[:only_path])
data = data_attribute(namespace.id, :group) data = data_attribute(group: namespace.id)
text = Group.reference_prefix + group text = Group.reference_prefix + group
%(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>) %(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end end
def link_to_user(user, namespace) def link_to_user(user, namespace)
push_result(:user, namespace.owner)
url = urls.user_url(user, only_path: context[:only_path]) url = urls.user_url(user, only_path: context[:only_path])
data = data_attribute(namespace.owner_id, :user) data = data_attribute(user: namespace.owner_id)
text = User.reference_prefix + user text = User.reference_prefix + user
%(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>) %(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
end end
def user_can_reference_group?(group)
Ability.abilities.allowed?(context[:current_user], :read_group, group)
end
end end
end end
end end
...@@ -3,11 +3,12 @@ require 'gitlab/markdown' ...@@ -3,11 +3,12 @@ require 'gitlab/markdown'
module Gitlab module Gitlab
# Extract possible GFM references from an arbitrary String for further processing. # Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor class ReferenceExtractor
attr_accessor :project, :current_user attr_accessor :project, :current_user, :load_lazy_references
def initialize(project, current_user = nil) def initialize(project, current_user = nil, load_lazy_references: true)
@project = project @project = project
@current_user = current_user @current_user = current_user
@load_lazy_references = load_lazy_references
end end
def analyze(text) def analyze(text)
...@@ -28,7 +29,7 @@ module Gitlab ...@@ -28,7 +29,7 @@ module Gitlab
type = type.to_sym type = type.to_sym
return references[type] if references.has_key?(type) return references[type] if references.has_key?(type)
references[type] = pipeline_result(type).uniq references[type] = pipeline_result(type)
end end
end end
...@@ -41,21 +42,32 @@ module Gitlab ...@@ -41,21 +42,32 @@ module Gitlab
def pipeline_result(filter_type) def pipeline_result(filter_type)
return [] if @text.blank? return [] if @text.blank?
klass = filter_type.to_s.camelize + 'ReferenceFilter' klass = "#{filter_type.to_s.camelize}ReferenceFilter"
filter = Gitlab::Markdown.const_get(klass) filter = Gitlab::Markdown.const_get(klass)
context = { context = {
project: project, project: project,
current_user: current_user, current_user: current_user,
# We don't actually care about the links generated # We don't actually care about the links generated
only_path: true, only_path: true,
ignore_blockquotes: true ignore_blockquotes: true,
# ReferenceGathererFilter
load_lazy_references: false,
reference_filter: filter
} }
pipeline = HTML::Pipeline.new([filter], context) pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
result = pipeline.call(@text) result = pipeline.call(@text)
result[:references][filter_type] values = result[:references][filter_type].uniq
if @load_lazy_references
values = Gitlab::Markdown::ReferenceFilter::LazyReference.load(values).uniq
end
values
end end
end end
end end
...@@ -220,7 +220,7 @@ describe 'GitLab Markdown', feature: true do ...@@ -220,7 +220,7 @@ describe 'GitLab Markdown', feature: true do
end end
end end
# `markdown` calls these two methods # Fake a `current_user` helper
def current_user def current_user
@feat.user @feat.user
end end
......
...@@ -11,12 +11,15 @@ describe GitlabMarkdownHelper do ...@@ -11,12 +11,15 @@ describe GitlabMarkdownHelper do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:snippet) { create(:project_snippet, project: project) } let(:snippet) { create(:project_snippet, project: project) }
# Helper expects a current_user method.
let(:current_user) { user }
before do before do
# Ensure the generated reference links aren't redacted
project.team << [user, :master]
# Helper expects a @project instance variable # Helper expects a @project instance variable
@project = project helper.instance_variable_set(:@project, project)
# Stub the `current_user` helper
allow(helper).to receive(:current_user).and_return(user)
end end
describe "#markdown" do describe "#markdown" do
...@@ -25,23 +28,23 @@ describe GitlabMarkdownHelper do ...@@ -25,23 +28,23 @@ describe GitlabMarkdownHelper do
it "should link to the merge request" do it "should link to the merge request" do
expected = namespace_project_merge_request_path(project.namespace, project, merge_request) expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
expect(markdown(actual)).to match(expected) expect(helper.markdown(actual)).to match(expected)
end end
it "should link to the commit" do it "should link to the commit" do
expected = namespace_project_commit_path(project.namespace, project, commit) expected = namespace_project_commit_path(project.namespace, project, commit)
expect(markdown(actual)).to match(expected) expect(helper.markdown(actual)).to match(expected)
end end
it "should link to the issue" do it "should link to the issue" do
expected = namespace_project_issue_path(project.namespace, project, issue) expected = namespace_project_issue_path(project.namespace, project, issue)
expect(markdown(actual)).to match(expected) expect(helper.markdown(actual)).to match(expected)
end end
end end
describe "override default project" do describe "override default project" do
let(:actual) { issue.to_reference } let(:actual) { issue.to_reference }
let(:second_project) { create(:project) } let(:second_project) { create(:project, :public) }
let(:second_issue) { create(:issue, project: second_project) } let(:second_issue) { create(:issue, project: second_project) }
it 'should link to the issue' do it 'should link to the issue' do
...@@ -56,7 +59,7 @@ describe GitlabMarkdownHelper do ...@@ -56,7 +59,7 @@ describe GitlabMarkdownHelper do
let(:issues) { create_list(:issue, 2, project: project) } let(:issues) { create_list(:issue, 2, project: project) }
it 'should handle references nested in links with all the text' do it 'should handle references nested in links with all the text' do
actual = link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path) actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
doc = Nokogiri::HTML.parse(actual) doc = Nokogiri::HTML.parse(actual)
# Make sure we didn't create invalid markup # Make sure we didn't create invalid markup
...@@ -86,7 +89,7 @@ describe GitlabMarkdownHelper do ...@@ -86,7 +89,7 @@ describe GitlabMarkdownHelper do
end end
it 'should forward HTML options' do it 'should forward HTML options' do
actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo') actual = helper.link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
doc = Nokogiri::HTML.parse(actual) doc = Nokogiri::HTML.parse(actual)
expect(doc.css('a')).to satisfy do |v| expect(doc.css('a')).to satisfy do |v|
...@@ -97,13 +100,13 @@ describe GitlabMarkdownHelper do ...@@ -97,13 +100,13 @@ describe GitlabMarkdownHelper do
it "escapes HTML passed in as the body" do it "escapes HTML passed in as the body" do
actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}" actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}"
expect(link_to_gfm(actual, commit_path)). expect(helper.link_to_gfm(actual, commit_path)).
to match('&lt;h1&gt;test&lt;/h1&gt;') to match('&lt;h1&gt;test&lt;/h1&gt;')
end end
it 'ignores reference links when they are the entire body' do it 'ignores reference links when they are the entire body' do
text = issues[0].to_reference text = issues[0].to_reference
act = link_to_gfm(text, '/foo') act = helper.link_to_gfm(text, '/foo')
expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>) expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>)
end end
......
...@@ -14,11 +14,6 @@ describe LabelsHelper do ...@@ -14,11 +14,6 @@ describe LabelsHelper do
expect(label).not_to receive(:project) expect(label).not_to receive(:project)
link_to_label(label) link_to_label(label)
end end
it 'includes option for "No Label"' do
result = project_labels_options(project)
expect(result).to include('No Label')
end
end end
context 'without @project set' do context 'without @project set' do
......
...@@ -140,28 +140,28 @@ describe Gitlab::ClosingIssueExtractor do ...@@ -140,28 +140,28 @@ describe Gitlab::ClosingIssueExtractor do
message = "Closes #{reference} and fix #{reference2}" message = "Closes #{reference} and fix #{reference2}"
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to eq([issue, other_issue]) to match_array([issue, other_issue])
end end
it 'fetches comma-separated issues references in single line message' do it 'fetches comma-separated issues references in single line message' do
message = "Closes #{reference}, closes #{reference2}" message = "Closes #{reference}, closes #{reference2}"
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to eq([issue, other_issue]) to match_array([issue, other_issue])
end end
it 'fetches comma-separated issues numbers in single line message' do it 'fetches comma-separated issues numbers in single line message' do
message = "Closes #{reference}, #{reference2} and #{reference3}" message = "Closes #{reference}, #{reference2} and #{reference3}"
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to eq([issue, other_issue, third_issue]) to match_array([issue, other_issue, third_issue])
end end
it 'fetches issues in multi-line message' do it 'fetches issues in multi-line message' do
message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}" message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to eq([issue, other_issue]) to match_array([issue, other_issue])
end end
it 'fetches issues in hybrid message' do it 'fetches issues in hybrid message' do
...@@ -169,7 +169,7 @@ describe Gitlab::ClosingIssueExtractor do ...@@ -169,7 +169,7 @@ describe Gitlab::ClosingIssueExtractor do
"Also fixing issues #{reference2}, #{reference3} and #4" "Also fixing issues #{reference2}, #{reference3} and #4"
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to eq([issue, other_issue, third_issue]) to match_array([issue, other_issue, third_issue])
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab::Markdown ...@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe CommitRangeReferenceFilter do describe CommitRangeReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let(:commit1) { project.commit } let(:commit1) { project.commit }
let(:commit2) { project.commit("HEAD~2") } let(:commit2) { project.commit("HEAD~2") }
...@@ -75,12 +75,20 @@ module Gitlab::Markdown ...@@ -75,12 +75,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("See #{reference}") doc = filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-commit-range attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-commit-range')
expect(link.attr('data-commit-range')).to eq range.to_reference
end end
it 'supports an :only_path option' do it 'supports an :only_path option' do
...@@ -92,59 +100,45 @@ module Gitlab::Markdown ...@@ -92,59 +100,45 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do it 'adds to the results hash' do
result = pipeline_result("See #{reference}") result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit_range]).not_to be_empty expect(result[:references][:commit_range]).not_to be_empty
end end
end end
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) } let(:project2) { create(:project, :public, namespace: namespace) }
let(:reference) { range.to_reference(project) } let(:reference) { range.to_reference(project) }
before do before do
range.project = project2 range.project = project2
end end
context 'when user can access reference' do it 'links to a valid reference' do
before { allow_cross_reference! } doc = filter("See #{reference}")
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do expect(doc.css('a').first.attr('href')).
exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
expect(filter(act).to_html).to eq exp end
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" it 'links with adjacent text' do
expect(filter(act).to_html).to eq exp doc = filter("Fixed (#{reference}.)")
end
it 'adds to the results hash' do exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
result = pipeline_result("See #{reference}") expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
expect(result[:references][:commit_range]).not_to be_empty
end
end end
context 'when user cannot access reference' do it 'ignores invalid commit IDs on the referenced project' do
before { disallow_cross_reference! } exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
expect(filter(act).to_html).to eq exp
it 'ignores valid references' do exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
exp = act = "See #{reference}" expect(filter(act).to_html).to eq exp
end
expect(filter(act).to_html).to eq exp it 'adds to the results hash' do
end result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit_range]).not_to be_empty
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab::Markdown ...@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe CommitReferenceFilter do describe CommitReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let(:commit) { project.commit } let(:commit) { project.commit }
it 'requires project context' do it 'requires project context' do
...@@ -71,12 +71,20 @@ module Gitlab::Markdown ...@@ -71,12 +71,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("See #{reference}") doc = filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-commit attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-commit')
expect(link.attr('data-commit')).to eq commit.id
end end
it 'supports an :only_path context' do it 'supports an :only_path context' do
...@@ -88,53 +96,39 @@ module Gitlab::Markdown ...@@ -88,53 +96,39 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do it 'adds to the results hash' do
result = pipeline_result("See #{reference}") result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit]).not_to be_empty expect(result[:references][:commit]).not_to be_empty
end end
end end
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) } let(:project2) { create(:project, :public, namespace: namespace) }
let(:commit) { project2.commit } let(:commit) { project2.commit }
let(:reference) { commit.to_reference(project) } let(:reference) { commit.to_reference(project) }
context 'when user can access reference' do it 'links to a valid reference' do
before { allow_cross_reference! } doc = filter("See #{reference}")
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
exp = Regexp.escape(project2.to_reference) expect(doc.css('a').first.attr('href')).
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/) to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end end
it 'ignores invalid commit IDs on the referenced project' do it 'links with adjacent text' do
exp = act = "Committed #{invalidate_reference(reference)}" doc = filter("Fixed (#{reference}.)")
expect(filter(act).to_html).to eq exp
end
it 'adds to the results hash' do exp = Regexp.escape(project2.to_reference)
result = pipeline_result("See #{reference}") expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
expect(result[:references][:commit]).not_to be_empty
end
end end
context 'when user cannot access reference' do it 'ignores invalid commit IDs on the referenced project' do
before { disallow_cross_reference! } exp = act = "Committed #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
it 'ignores valid references' do end
exp = act = "See #{reference}"
expect(filter(act).to_html).to eq exp it 'adds to the results hash' do
end result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit]).not_to be_empty
end end
end end
end end
......
...@@ -2,20 +2,16 @@ require 'spec_helper' ...@@ -2,20 +2,16 @@ require 'spec_helper'
module Gitlab::Markdown module Gitlab::Markdown
describe CrossProjectReference do describe CrossProjectReference do
# context in the html-pipeline sense, not in the rspec sense
let(:context) do
{
current_user: double('user'),
project: double('project')
}
end
include described_class include described_class
describe '#project_from_ref' do describe '#project_from_ref' do
context 'when no project was referenced' do context 'when no project was referenced' do
it 'returns the project from context' do it 'returns the project from context' do
expect(project_from_ref(nil)).to eq context[:project] project = double
allow(self).to receive(:context).and_return({ project: project })
expect(project_from_ref(nil)).to eq project
end end
end end
...@@ -26,29 +22,13 @@ module Gitlab::Markdown ...@@ -26,29 +22,13 @@ module Gitlab::Markdown
end end
context 'when referenced project exists' do context 'when referenced project exists' do
let(:project2) { double('referenced project') } it 'returns the referenced project' do
project2 = double('referenced project')
before do
expect(Project).to receive(:find_with_namespace). expect(Project).to receive(:find_with_namespace).
with('cross/reference').and_return(project2) with('cross/reference').and_return(project2)
end
context 'and the user has permission to read it' do
it 'returns the referenced project' do
expect(self).to receive(:user_can_reference_project?).
with(project2).and_return(true)
expect(project_from_ref('cross/reference')).to eq project2
end
end
context 'and the user does not have permission to read it' do
it 'returns nil' do
expect(self).to receive(:user_can_reference_project?).
with(project2).and_return(false)
expect(project_from_ref('cross/reference')).to be_nil expect(project_from_ref('cross/reference')).to eq project2
end
end end
end end
end end
......
...@@ -8,7 +8,7 @@ module Gitlab::Markdown ...@@ -8,7 +8,7 @@ module Gitlab::Markdown
IssuesHelper IssuesHelper
end end
let(:project) { create(:empty_project) } let(:project) { create(:empty_project, :public) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
it 'requires project context' do it 'requires project context' do
...@@ -68,12 +68,20 @@ module Gitlab::Markdown ...@@ -68,12 +68,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("Issue #{reference}") doc = filter("Issue #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-issue attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-issue')
expect(link.attr('data-issue')).to eq issue.id.to_s
end end
it 'supports an :only_path context' do it 'supports an :only_path context' do
...@@ -85,60 +93,46 @@ module Gitlab::Markdown ...@@ -85,60 +93,46 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do it 'adds to the results hash' do
result = pipeline_result("Fixed #{reference}") result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue] expect(result[:references][:issue]).to eq [issue]
end end
end end
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, namespace: namespace) } let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) } let(:issue) { create(:issue, project: project2) }
let(:reference) { issue.to_reference(project) } let(:reference) { issue.to_reference(project) }
context 'when user can access reference' do it 'ignores valid references when cross-reference project uses external tracker' do
before { allow_cross_reference! } expect_any_instance_of(Project).to receive(:get_issue).
with(issue.iid).and_return(nil)
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(Project).to receive(:get_issue).
with(issue.iid).and_return(nil)
exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
end
it 'links to a valid reference' do exp = act = "Issue #{reference}"
doc = filter("See #{reference}") expect(filter(act).to_html).to eq exp
end
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid issue IDs on the referenced project' do it 'links to a valid reference' do
exp = act = "Fixed #{invalidate_reference(reference)}" doc = filter("See #{reference}")
expect(filter(act).to_html).to eq exp expect(doc.css('a').first.attr('href')).
end to eq helper.url_for_issue(issue.iid, project2)
end
it 'adds to the results hash' do it 'links with adjacent text' do
result = pipeline_result("Fixed #{reference}") doc = filter("Fixed (#{reference}.)")
expect(result[:references][:issue]).to eq [issue] expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
end end
context 'when user cannot access reference' do it 'ignores invalid issue IDs on the referenced project' do
before { disallow_cross_reference! } exp = act = "Fixed #{invalidate_reference(reference)}"
it 'ignores valid references' do expect(filter(act).to_html).to eq exp
exp = act = "See #{reference}" end
expect(filter(act).to_html).to eq exp it 'adds to the results hash' do
end result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end end
end end
end end
......
...@@ -5,7 +5,7 @@ module Gitlab::Markdown ...@@ -5,7 +5,7 @@ module Gitlab::Markdown
describe LabelReferenceFilter do describe LabelReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:empty_project) } let(:project) { create(:empty_project, :public) }
let(:label) { create(:label, project: project) } let(:label) { create(:label, project: project) }
let(:reference) { label.to_reference } let(:reference) { label.to_reference }
...@@ -25,12 +25,20 @@ module Gitlab::Markdown ...@@ -25,12 +25,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("Label #{reference}") doc = filter("Label #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-label attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-label')
expect(link.attr('data-label')).to eq label.id.to_s
end end
it 'supports an :only_path context' do it 'supports an :only_path context' do
...@@ -42,7 +50,7 @@ module Gitlab::Markdown ...@@ -42,7 +50,7 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do it 'adds to the results hash' do
result = pipeline_result("Label #{reference}") result = reference_pipeline_result("Label #{reference}")
expect(result[:references][:label]).to eq [label] expect(result[:references][:label]).to eq [label]
end end
......
...@@ -4,7 +4,7 @@ module Gitlab::Markdown ...@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe MergeRequestReferenceFilter do describe MergeRequestReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let(:merge) { create(:merge_request, source_project: project) } let(:merge) { create(:merge_request, source_project: project) }
it 'requires project context' do it 'requires project context' do
...@@ -56,12 +56,20 @@ module Gitlab::Markdown ...@@ -56,12 +56,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("Merge #{reference}") doc = filter("Merge #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-merge-request attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-merge-request')
expect(link.attr('data-merge-request')).to eq merge.id.to_s
end end
it 'supports an :only_path context' do it 'supports an :only_path context' do
...@@ -73,53 +81,39 @@ module Gitlab::Markdown ...@@ -73,53 +81,39 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do it 'adds to the results hash' do
result = pipeline_result("Merge #{reference}") result = reference_pipeline_result("Merge #{reference}")
expect(result[:references][:merge_request]).to eq [merge] expect(result[:references][:merge_request]).to eq [merge]
end end
end end
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) } let(:project2) { create(:project, :public, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2) } let(:merge) { create(:merge_request, source_project: project2) }
let(:reference) { merge.to_reference(project) } let(:reference) { merge.to_reference(project) }
context 'when user can access reference' do it 'links to a valid reference' do
before { allow_cross_reference! } doc = filter("See #{reference}")
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_merge_request_url(project2.namespace,
project, merge)
end
it 'links with adjacent text' do
doc = filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid merge IDs on the referenced project' do
exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp expect(doc.css('a').first.attr('href')).
end to eq urls.namespace_project_merge_request_url(project2.namespace,
project, merge)
end
it 'adds to the results hash' do it 'links with adjacent text' do
result = pipeline_result("Merge #{reference}") doc = filter("Merge (#{reference}.)")
expect(result[:references][:merge_request]).to eq [merge] expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
end end
context 'when user cannot access reference' do it 'ignores invalid merge IDs on the referenced project' do
before { disallow_cross_reference! } exp = act = "Merge #{invalidate_reference(reference)}"
it 'ignores valid references' do expect(filter(act).to_html).to eq exp
exp = act = "See #{reference}" end
expect(filter(act).to_html).to eq exp it 'adds to the results hash' do
end result = reference_pipeline_result("Merge #{reference}")
expect(result[:references][:merge_request]).to eq [merge]
end end
end end
end end
......
require 'spec_helper'
module Gitlab::Markdown
describe RedactorFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
it 'ignores non-GFM links' do
html = %(See <a href="https://google.com/">Google</a>)
doc = filter(html, current_user: double)
expect(doc.css('a').length).to eq 1
end
def reference_link(data)
link_to('text', '', class: 'gfm', data: data)
end
context 'with data-project' do
it 'removes unpermitted Project references' do
user = create(:user)
project = create(:empty_project)
link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 0
end
it 'allows permitted Project references' do
user = create(:user)
project = create(:empty_project)
project.team << [user, :master]
link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1
end
it 'handles invalid Project references' do
link = reference_link(project: 12345, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
expect { filter(link) }.not_to raise_error
end
end
context "for user references" do
context 'with data-group' do
it 'removes unpermitted Group references' do
user = create(:user)
group = create(:group)
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 0
end
it 'allows permitted Group references' do
user = create(:user)
group = create(:group)
group.add_developer(user)
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1
end
it 'handles invalid Group references' do
link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
expect { filter(link) }.not_to raise_error
end
end
context 'with data-user' do
it 'allows any User reference' do
user = create(:user)
link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
doc = filter(link)
expect(doc.css('a').length).to eq 1
end
end
end
end
end
require 'spec_helper'
module Gitlab::Markdown
describe ReferenceGathererFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
def reference_link(data)
link_to('text', '', class: 'gfm', data: data)
end
context "for issue references" do
context 'with data-project' do
it 'removes unpermitted Project references' do
user = create(:user)
project = create(:empty_project)
issue = create(:issue, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
result = pipeline_result(link, current_user: user)
expect(result[:references][:issue]).to be_empty
end
it 'allows permitted Project references' do
user = create(:user)
project = create(:empty_project)
issue = create(:issue, project: project)
project.team << [user, :master]
link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
result = pipeline_result(link, current_user: user)
expect(result[:references][:issue]).to eq([issue])
end
it 'handles invalid Project references' do
link = reference_link(project: 12345, issue: 12345, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
expect { pipeline_result(link) }.not_to raise_error
end
end
end
context "for user references" do
context 'with data-group' do
it 'removes unpermitted Group references' do
user = create(:user)
group = create(:group)
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
result = pipeline_result(link, current_user: user)
expect(result[:references][:user]).to be_empty
end
it 'allows permitted Group references' do
user = create(:user)
group = create(:group)
group.add_developer(user)
link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
result = pipeline_result(link, current_user: user)
expect(result[:references][:user]).to eq([user])
end
it 'handles invalid Group references' do
link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
expect { pipeline_result(link) }.not_to raise_error
end
end
context 'with data-user' do
it 'allows any User reference' do
user = create(:user)
link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
result = pipeline_result(link)
expect(result[:references][:user]).to eq([user])
end
end
end
end
end
...@@ -4,7 +4,7 @@ module Gitlab::Markdown ...@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe SnippetReferenceFilter do describe SnippetReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:empty_project) } let(:project) { create(:empty_project, :public) }
let(:snippet) { create(:project_snippet, project: project) } let(:snippet) { create(:project_snippet, project: project) }
let(:reference) { snippet.to_reference } let(:reference) { snippet.to_reference }
...@@ -55,12 +55,20 @@ module Gitlab::Markdown ...@@ -55,12 +55,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
end end
it 'includes a data-project-id attribute' do it 'includes a data-project attribute' do
doc = filter("Snippet #{reference}") doc = filter("Snippet #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project-id') expect(link).to have_attribute('data-project')
expect(link.attr('data-project-id')).to eq project.id.to_s expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-snippet attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-snippet')
expect(link.attr('data-snippet')).to eq snippet.id.to_s
end end
it 'supports an :only_path context' do it 'supports an :only_path context' do
...@@ -72,52 +80,38 @@ module Gitlab::Markdown ...@@ -72,52 +80,38 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do it 'adds to the results hash' do
result = pipeline_result("Snippet #{reference}") result = reference_pipeline_result("Snippet #{reference}")
expect(result[:references][:snippet]).to eq [snippet] expect(result[:references][:snippet]).to eq [snippet]
end end
end end
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, namespace: namespace) } let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) } let(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { snippet.to_reference(project) } let(:reference) { snippet.to_reference(project) }
context 'when user can access reference' do it 'links to a valid reference' do
before { allow_cross_reference! } doc = filter("See #{reference}")
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs on the referenced project' do
exp = act = "See #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp expect(doc.css('a').first.attr('href')).
end to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'adds to the results hash' do it 'links with adjacent text' do
result = pipeline_result("Snippet #{reference}") doc = filter("See (#{reference}.)")
expect(result[:references][:snippet]).to eq [snippet] expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
end end
context 'when user cannot access reference' do it 'ignores invalid snippet IDs on the referenced project' do
before { disallow_cross_reference! } exp = act = "See #{invalidate_reference(reference)}"
it 'ignores valid references' do expect(filter(act).to_html).to eq exp
exp = act = "See #{reference}" end
expect(filter(act).to_html).to eq exp it 'adds to the results hash' do
end result = reference_pipeline_result("Snippet #{reference}")
expect(result[:references][:snippet]).to eq [snippet]
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab::Markdown ...@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe UserReferenceFilter do describe UserReferenceFilter do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:empty_project) } let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:reference) { user.to_reference } let(:reference) { user.to_reference }
...@@ -39,7 +39,7 @@ module Gitlab::Markdown ...@@ -39,7 +39,7 @@ module Gitlab::Markdown
end end
it 'adds to the results hash' do it 'adds to the results hash' do
result = pipeline_result("Hey #{reference}") result = reference_pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [project.creator] expect(result[:references][:user]).to eq [project.creator]
end end
end end
...@@ -64,59 +64,40 @@ module Gitlab::Markdown ...@@ -64,59 +64,40 @@ module Gitlab::Markdown
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
end end
it 'includes a data-user-id attribute' do it 'includes a data-user attribute' do
doc = filter("Hey #{reference}") doc = filter("Hey #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-user-id') expect(link).to have_attribute('data-user')
expect(link.attr('data-user-id')).to eq user.namespace.owner_id.to_s expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
end end
it 'adds to the results hash' do it 'adds to the results hash' do
result = pipeline_result("Hey #{reference}") result = reference_pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [user] expect(result[:references][:user]).to eq [user]
end end
end end
context 'mentioning a group' do context 'mentioning a group' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:user) { create(:user) }
let(:reference) { group.to_reference } let(:reference) { group.to_reference }
context 'that the current user can read' do it 'links to the Group' do
before do doc = filter("Hey #{reference}")
group.add_developer(user) expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
end end
it 'links to the Group' do
doc = filter("Hey #{reference}", current_user: user)
expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
end
it 'includes a data-group-id attribute' do
doc = filter("Hey #{reference}", current_user: user)
link = doc.css('a').first
expect(link).to have_attribute('data-group-id') it 'includes a data-group attribute' do
expect(link.attr('data-group-id')).to eq group.id.to_s doc = filter("Hey #{reference}")
end link = doc.css('a').first
it 'adds to the results hash' do expect(link).to have_attribute('data-group')
result = pipeline_result("Hey #{reference}", current_user: user) expect(link.attr('data-group')).to eq group.id.to_s
expect(result[:references][:user]).to eq group.users
end
end end
context 'that the current user cannot read' do it 'adds to the results hash' do
it 'ignores references to the Group' do result = reference_pipeline_result("Hey #{reference}")
doc = filter("Hey #{reference}", current_user: user) expect(result[:references][:user]).to eq group.users
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
end end
......
...@@ -13,7 +13,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -13,7 +13,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_bar, :guest] project.team << [@u_bar, :guest]
subject.analyze('@foo, @baduser, @bar, and @offteam') subject.analyze('@foo, @baduser, @bar, and @offteam')
expect(subject.users).to eq([@u_foo, @u_bar, @u_offteam]) expect(subject.users).to match_array([@u_foo, @u_bar, @u_offteam])
end end
it 'ignores user mentions inside specific elements' do it 'ignores user mentions inside specific elements' do
...@@ -37,7 +37,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -37,7 +37,7 @@ describe Gitlab::ReferenceExtractor do
> @offteam > @offteam
}) })
expect(subject.users).to eq([]) expect(subject.users).to match_array([])
end end
it 'accesses valid issue objects' do it 'accesses valid issue objects' do
...@@ -45,7 +45,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -45,7 +45,7 @@ describe Gitlab::ReferenceExtractor do
@i1 = create(:issue, project: project) @i1 = create(:issue, project: project)
subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.") subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.")
expect(subject.issues).to eq([@i0, @i1]) expect(subject.issues).to match_array([@i0, @i1])
end end
it 'accesses valid merge requests' do it 'accesses valid merge requests' do
...@@ -53,7 +53,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -53,7 +53,7 @@ describe Gitlab::ReferenceExtractor do
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict') @m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.") subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
expect(subject.merge_requests).to eq([@m1, @m0]) expect(subject.merge_requests).to match_array([@m1, @m0])
end end
it 'accesses valid labels' do it 'accesses valid labels' do
...@@ -62,7 +62,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -62,7 +62,7 @@ describe Gitlab::ReferenceExtractor do
@l2 = create(:label) @l2 = create(:label)
subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}") subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}")
expect(subject.labels).to eq([@l0, @l1]) expect(subject.labels).to match_array([@l0, @l1])
end end
it 'accesses valid snippets' do it 'accesses valid snippets' do
...@@ -71,7 +71,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -71,7 +71,7 @@ describe Gitlab::ReferenceExtractor do
@s2 = create(:project_snippet) @s2 = create(:project_snippet)
subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}") subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}")
expect(subject.snippets).to eq([@s0, @s1]) expect(subject.snippets).to match_array([@s0, @s1])
end end
it 'accesses valid commits' do it 'accesses valid commits' do
...@@ -109,7 +109,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -109,7 +109,7 @@ describe Gitlab::ReferenceExtractor do
subject.analyze("this refers issue #{issue.to_reference(project)}") subject.analyze("this refers issue #{issue.to_reference(project)}")
extracted = subject.issues extracted = subject.issues
expect(extracted.size).to eq(1) expect(extracted.size).to eq(1)
expect(extracted).to eq([issue]) expect(extracted).to match_array([issue])
end end
end end
end end
...@@ -29,12 +29,19 @@ module FilterSpecHelper ...@@ -29,12 +29,19 @@ module FilterSpecHelper
# #
# Returns the Hash # Returns the Hash
def pipeline_result(body, contexts = {}) def pipeline_result(body, contexts = {})
contexts.reverse_merge!(project: project) contexts.reverse_merge!(project: project) if defined?(project)
pipeline = HTML::Pipeline.new([described_class], contexts) pipeline = HTML::Pipeline.new([described_class], contexts)
pipeline.call(body) pipeline.call(body)
end end
def reference_pipeline_result(body, contexts = {})
contexts.reverse_merge!(project: project) if defined?(project)
pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts)
pipeline.call(body)
end
# Modify a String reference to make it invalid # Modify a String reference to make it invalid
# #
# Commit SHAs get reversed, IDs get incremented by 1, all other Strings get # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
...@@ -55,20 +62,6 @@ module FilterSpecHelper ...@@ -55,20 +62,6 @@ module FilterSpecHelper
end end
end end
# Stub CrossProjectReference#user_can_reference_project? to return true for
# the current test
def allow_cross_reference!
allow_any_instance_of(described_class).
to receive(:user_can_reference_project?).and_return(true)
end
# Stub CrossProjectReference#user_can_reference_project? to return false for
# the current test
def disallow_cross_reference!
allow_any_instance_of(described_class).
to receive(:user_can_reference_project?).and_return(false)
end
# Shortcut to Rails' auto-generated routes helpers, to avoid including the # Shortcut to Rails' auto-generated routes helpers, to avoid including the
# module # module
def urls def urls
......
...@@ -15,18 +15,17 @@ class MarkdownFeature ...@@ -15,18 +15,17 @@ class MarkdownFeature
end end
def group def group
unless @group @group ||= create(:group).tap do |group|
@group = create(:group) group.add_developer(user)
@group.add_developer(user)
end end
@group
end end
# Direct references ---------------------------------------------------------- # Direct references ----------------------------------------------------------
def project def project
@project ||= create(:project) @project ||= create(:project).tap do |project|
project.team << [user, :master]
end
end end
def issue def issue
...@@ -46,12 +45,10 @@ class MarkdownFeature ...@@ -46,12 +45,10 @@ class MarkdownFeature
end end
def commit_range def commit_range
unless @commit_range @commit_range ||= begin
commit2 = project.commit('HEAD~3') commit2 = project.commit('HEAD~3')
@commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project) CommitRange.new("#{commit.id}...#{commit2.id}", project)
end end
@commit_range
end end
def simple_label def simple_label
...@@ -65,13 +62,12 @@ class MarkdownFeature ...@@ -65,13 +62,12 @@ class MarkdownFeature
# Cross-references ----------------------------------------------------------- # Cross-references -----------------------------------------------------------
def xproject def xproject
unless @xproject @xproject ||= begin
namespace = create(:namespace, name: 'cross-reference') namespace = create(:namespace, name: 'cross-reference')
@xproject = create(:project, namespace: namespace) create(:project, namespace: namespace) do |project|
@xproject.team << [user, :developer] project.team << [user, :developer]
end
end end
@xproject
end end
def xissue def xissue
...@@ -91,12 +87,10 @@ class MarkdownFeature ...@@ -91,12 +87,10 @@ class MarkdownFeature
end end
def xcommit_range def xcommit_range
unless @xcommit_range @xcommit_range ||= begin
xcommit2 = xproject.commit('HEAD~2') xcommit2 = xproject.commit('HEAD~2')
@xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject) CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end end
@xcommit_range
end end
def raw_markdown def raw_markdown
......
...@@ -50,6 +50,8 @@ def common_mentionable_setup ...@@ -50,6 +50,8 @@ def common_mentionable_setup
} }
extra_commits.each { |c| commitmap[c.short_id] = c } extra_commits.each { |c| commitmap[c.short_id] = c }
allow(Project).to receive(:find).and_call_original
allow(Project).to receive(:find).with(project.id.to_s).and_return(project)
allow(project.repository).to receive(:commit) { |sha| commitmap[sha] } allow(project.repository).to receive(:commit) { |sha| commitmap[sha] }
set_mentionable_text.call(ref_string) set_mentionable_text.call(ref_string)
......
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