Merge branch 'reference-access-control' into 'master'
Only allow users to reference groups, projects, issues, MRs, commits they have access to. Addresses https://dev.gitlab.org/gitlab/gitlabhq/issues/2183. See merge request !1742
This commit is contained in:
commit
ecb58dacd6
22 changed files with 280 additions and 260 deletions
|
@ -81,6 +81,7 @@ v 7.9.3
|
||||||
- Warn when gitlab-shell version doesn't match requirement.
|
- Warn when gitlab-shell version doesn't match requirement.
|
||||||
- Skip email confirmation when set by admin or via LDAP.
|
- Skip email confirmation when set by admin or via LDAP.
|
||||||
|
|
||||||
|
- Only allow users to reference groups, projects, issues, MRs, commits they have access to.
|
||||||
|
|
||||||
v 7.9.2
|
v 7.9.2
|
||||||
- Contains no changes
|
- Contains no changes
|
||||||
|
|
3
Gemfile
3
Gemfile
|
@ -208,7 +208,6 @@ group :development do
|
||||||
gem "letter_opener"
|
gem "letter_opener"
|
||||||
gem 'quiet_assets', '~> 1.0.1'
|
gem 'quiet_assets', '~> 1.0.1'
|
||||||
gem 'rack-mini-profiler', require: false
|
gem 'rack-mini-profiler', require: false
|
||||||
gem "byebug"
|
|
||||||
|
|
||||||
# Better errors handler
|
# Better errors handler
|
||||||
gem 'better_errors'
|
gem 'better_errors'
|
||||||
|
@ -257,6 +256,8 @@ group :development, :test do
|
||||||
gem "spring", '~> 1.3.1'
|
gem "spring", '~> 1.3.1'
|
||||||
gem "spring-commands-rspec", '1.0.4'
|
gem "spring-commands-rspec", '1.0.4'
|
||||||
gem "spring-commands-spinach", '1.0.0'
|
gem "spring-commands-spinach", '1.0.0'
|
||||||
|
|
||||||
|
gem "byebug"
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
|
|
@ -134,12 +134,13 @@ module CommitsHelper
|
||||||
# avatar: true will prepend the avatar image
|
# avatar: true will prepend the avatar image
|
||||||
# size: size of the avatar image in px
|
# size: size of the avatar image in px
|
||||||
def commit_person_link(commit, options = {})
|
def commit_person_link(commit, options = {})
|
||||||
|
user = commit.send(options[:source])
|
||||||
|
|
||||||
source_name = clean(commit.send "#{options[:source]}_name".to_sym)
|
source_name = clean(commit.send "#{options[:source]}_name".to_sym)
|
||||||
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
|
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
|
||||||
|
|
||||||
user = User.find_for_commit(source_email, source_name)
|
person_name = user.try(:name) || source_name
|
||||||
person_name = user.nil? ? source_name : user.name
|
person_email = user.try(:email) || source_email
|
||||||
person_email = user.nil? ? source_email : user.email
|
|
||||||
|
|
||||||
text =
|
text =
|
||||||
if options[:avatar]
|
if options[:avatar]
|
||||||
|
|
|
@ -4,6 +4,7 @@ module Emails
|
||||||
@group_member = GroupMember.find(group_member_id)
|
@group_member = GroupMember.find(group_member_id)
|
||||||
@group = @group_member.group
|
@group = @group_member.group
|
||||||
@target_url = group_url(@group)
|
@target_url = group_url(@group)
|
||||||
|
@current_user = @group_member.user
|
||||||
mail(to: @group_member.user.email,
|
mail(to: @group_member.user.email,
|
||||||
subject: subject("Access to group was granted"))
|
subject: subject("Access to group was granted"))
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Emails
|
module Emails
|
||||||
module Profile
|
module Profile
|
||||||
def new_user_email(user_id, token = nil)
|
def new_user_email(user_id, token = nil)
|
||||||
@user = User.find(user_id)
|
@current_user = @user = User.find(user_id)
|
||||||
@target_url = user_url(@user)
|
@target_url = user_url(@user)
|
||||||
@token = token
|
@token = token
|
||||||
mail(to: @user.notification_email, subject: subject("Account was created for you"))
|
mail(to: @user.notification_email, subject: subject("Account was created for you"))
|
||||||
|
@ -9,13 +9,13 @@ module Emails
|
||||||
|
|
||||||
def new_email_email(email_id)
|
def new_email_email(email_id)
|
||||||
@email = Email.find(email_id)
|
@email = Email.find(email_id)
|
||||||
@user = @email.user
|
@current_user = @user = @email.user
|
||||||
mail(to: @user.notification_email, subject: subject("Email was added to your account"))
|
mail(to: @user.notification_email, subject: subject("Email was added to your account"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_ssh_key_email(key_id)
|
def new_ssh_key_email(key_id)
|
||||||
@key = Key.find(key_id)
|
@key = Key.find(key_id)
|
||||||
@user = @key.user
|
@current_user = @user = @key.user
|
||||||
@target_url = user_url(@user)
|
@target_url = user_url(@user)
|
||||||
mail(to: @user.notification_email, subject: subject("SSH key was added to your account"))
|
mail(to: @user.notification_email, subject: subject("SSH key was added to your account"))
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,12 +4,13 @@ module Emails
|
||||||
@project_member = ProjectMember.find user_project_id
|
@project_member = ProjectMember.find user_project_id
|
||||||
@project = @project_member.project
|
@project = @project_member.project
|
||||||
@target_url = namespace_project_url(@project.namespace, @project)
|
@target_url = namespace_project_url(@project.namespace, @project)
|
||||||
|
@current_user = @project_member.user
|
||||||
mail(to: @project_member.user.email,
|
mail(to: @project_member.user.email,
|
||||||
subject: subject("Access to project was granted"))
|
subject: subject("Access to project was granted"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def project_was_moved_email(project_id, user_id)
|
def project_was_moved_email(project_id, user_id)
|
||||||
@user = User.find user_id
|
@current_user = @user = User.find user_id
|
||||||
@project = Project.find project_id
|
@project = Project.find project_id
|
||||||
@target_url = namespace_project_url(@project.namespace, @project)
|
@target_url = namespace_project_url(@project.namespace, @project)
|
||||||
mail(to: @user.notification_email,
|
mail(to: @user.notification_email,
|
||||||
|
@ -28,7 +29,7 @@ module Emails
|
||||||
end
|
end
|
||||||
|
|
||||||
@project = Project.find(project_id)
|
@project = Project.find(project_id)
|
||||||
@author = User.find(author_id)
|
@current_user = @author = User.find(author_id)
|
||||||
@reverse_compare = reverse_compare
|
@reverse_compare = reverse_compare
|
||||||
@compare = compare
|
@compare = compare
|
||||||
@ref_name = Gitlab::Git.ref_name(ref)
|
@ref_name = Gitlab::Git.ref_name(ref)
|
||||||
|
|
|
@ -13,6 +13,9 @@ class Notify < ActionMailer::Base
|
||||||
add_template_helper MergeRequestsHelper
|
add_template_helper MergeRequestsHelper
|
||||||
add_template_helper EmailsHelper
|
add_template_helper EmailsHelper
|
||||||
|
|
||||||
|
attr_accessor :current_user
|
||||||
|
helper_method :current_user, :can?
|
||||||
|
|
||||||
default_url_options[:host] = Gitlab.config.gitlab.host
|
default_url_options[:host] = Gitlab.config.gitlab.host
|
||||||
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
|
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
|
||||||
default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
|
default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
|
||||||
|
@ -79,9 +82,8 @@ class Notify < ActionMailer::Base
|
||||||
#
|
#
|
||||||
# Returns a String containing the User's email address.
|
# Returns a String containing the User's email address.
|
||||||
def recipient(recipient_id)
|
def recipient(recipient_id)
|
||||||
if recipient = User.find(recipient_id)
|
@current_user = User.find(recipient_id)
|
||||||
recipient.notification_email
|
@current_user.notification_email
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Set the References header field
|
# Set the References header field
|
||||||
|
@ -154,4 +156,8 @@ class Notify < ActionMailer::Base
|
||||||
|
|
||||||
mail(headers, &block)
|
mail(headers, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can?
|
||||||
|
Ability.abilities.allowed?(user, action, subject)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -117,8 +117,8 @@ class Commit
|
||||||
|
|
||||||
# Discover issues should be closed when this commit is pushed to a project's
|
# Discover issues should be closed when this commit is pushed to a project's
|
||||||
# default branch.
|
# default branch.
|
||||||
def closes_issues(project)
|
def closes_issues(project, current_user = self.committer)
|
||||||
Gitlab::ClosingIssueExtractor.closed_by_message_in_project(safe_message, project)
|
Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Mentionable override.
|
# Mentionable override.
|
||||||
|
@ -126,6 +126,14 @@ class Commit
|
||||||
"commit #{id}"
|
"commit #{id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def author
|
||||||
|
User.find_for_commit(author_email, author_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def committer
|
||||||
|
User.find_for_commit(committer_email, committer_name)
|
||||||
|
end
|
||||||
|
|
||||||
def method_missing(m, *args, &block)
|
def method_missing(m, *args, &block)
|
||||||
@raw.send(m, *args, &block)
|
@raw.send(m, *args, &block)
|
||||||
end
|
end
|
||||||
|
|
|
@ -118,16 +118,16 @@ module Issuable
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return all users participating on the discussion
|
# Return all users participating on the discussion
|
||||||
def participants
|
def participants(current_user = self.author)
|
||||||
users = []
|
users = []
|
||||||
users << author
|
users << author
|
||||||
users << assignee if is_assigned?
|
users << assignee if is_assigned?
|
||||||
mentions = []
|
mentions = []
|
||||||
mentions << self.mentioned_users
|
mentions << self.mentioned_users(current_user)
|
||||||
|
|
||||||
notes.each do |note|
|
notes.each do |note|
|
||||||
users << note.author
|
users << note.author
|
||||||
mentions << note.mentioned_users
|
mentions << note.mentioned_users(current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
users.concat(mentions.reduce([], :|)).uniq
|
users.concat(mentions.reduce([], :|)).uniq
|
||||||
|
@ -140,7 +140,7 @@ module Issuable
|
||||||
return subscription.subscribed
|
return subscription.subscribed
|
||||||
end
|
end
|
||||||
|
|
||||||
participants.include?(user)
|
participants(user).include?(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def toggle_subscription(user)
|
def toggle_subscription(user)
|
||||||
|
|
|
@ -42,35 +42,22 @@ module Mentionable
|
||||||
Note.cross_reference_exists?(target, local_reference)
|
Note.cross_reference_exists?(target, local_reference)
|
||||||
end
|
end
|
||||||
|
|
||||||
def mentioned_users
|
def mentioned_users(current_user = nil)
|
||||||
users = []
|
return [] if mentionable_text.blank?
|
||||||
return users if mentionable_text.blank?
|
|
||||||
has_project = self.respond_to? :project
|
ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
|
||||||
matches = mentionable_text.scan(/@[a-zA-Z][a-zA-Z0-9_\-\.]*/)
|
ext.analyze(mentionable_text)
|
||||||
matches.each do |match|
|
ext.users.uniq
|
||||||
identifier = match.delete "@"
|
|
||||||
if identifier == "all"
|
|
||||||
users.push(*project.team.members.flatten)
|
|
||||||
elsif namespace = Namespace.find_by(path: identifier)
|
|
||||||
if namespace.is_a?(Group)
|
|
||||||
users.push(*namespace.users)
|
|
||||||
else
|
|
||||||
users << namespace.owner
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
users.uniq
|
|
||||||
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 references(p = project, text = mentionable_text)
|
def references(p = project, current_user = self.author, text = mentionable_text)
|
||||||
return [] if text.blank?
|
return [] if text.blank?
|
||||||
ext = Gitlab::ReferenceExtractor.new
|
|
||||||
ext.analyze(text, p)
|
|
||||||
|
|
||||||
(ext.issues_for(p) +
|
ext = Gitlab::ReferenceExtractor.new(p, current_user)
|
||||||
ext.merge_requests_for(p) +
|
ext.analyze(text)
|
||||||
ext.commits_for(p)).uniq - [local_reference]
|
|
||||||
|
(ext.issues + ext.merge_requests + ext.commits).uniq - [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+.
|
||||||
|
@ -96,7 +83,7 @@ module Mentionable
|
||||||
# Only proceed if the saved changes actually include a chance to an attr_mentionable field.
|
# Only proceed if the saved changes actually include a chance to an attr_mentionable field.
|
||||||
return unless mentionable_changed
|
return unless mentionable_changed
|
||||||
|
|
||||||
preexisting = references(p, original)
|
preexisting = references(p, self.author, original)
|
||||||
create_cross_references!(p, a, preexisting)
|
create_cross_references!(p, a, preexisting)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -257,11 +257,11 @@ class MergeRequest < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return the set of issues that will be closed if this merge request is accepted.
|
# Return the set of issues that will be closed if this merge request is accepted.
|
||||||
def closes_issues
|
def closes_issues(current_user = self.author)
|
||||||
if target_branch == project.default_branch
|
if target_branch == project.default_branch
|
||||||
issues = commits.flat_map { |c| c.closes_issues(project) }
|
issues = commits.flat_map { |c| c.closes_issues(project, current_user) }
|
||||||
issues.push(*Gitlab::ClosingIssueExtractor.
|
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
|
||||||
closed_by_message_in_project(description, project))
|
closed_by_message(description))
|
||||||
issues.uniq.sort_by(&:id)
|
issues.uniq.sort_by(&:id)
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
|
|
|
@ -70,7 +70,7 @@ class GitPushService
|
||||||
# Close issues if these commits were pushed to the project's default branch and the commit message matches the
|
# Close issues if these commits were pushed to the project's default branch and the commit message matches the
|
||||||
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
|
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
|
||||||
# a different branch.
|
# a different branch.
|
||||||
issues_to_close = commit.closes_issues(project)
|
issues_to_close = commit.closes_issues(project, user)
|
||||||
|
|
||||||
# Load commit author only if needed.
|
# Load commit author only if needed.
|
||||||
# For push with 1k commits it prevents 900+ requests in database
|
# For push with 1k commits it prevents 900+ requests in database
|
||||||
|
@ -87,7 +87,7 @@ class GitPushService
|
||||||
# Create cross-reference notes for any other references. Omit any issues that were referenced in an
|
# Create cross-reference notes for any other references. Omit any issues that were referenced in an
|
||||||
# issue-closing phrase, or have already been mentioned from this commit (probably from this commit
|
# issue-closing phrase, or have already been mentioned from this commit (probably from this commit
|
||||||
# being pushed to a different branch).
|
# being pushed to a different branch).
|
||||||
refs = commit.references(project) - issues_to_close
|
refs = commit.references(project, user) - issues_to_close
|
||||||
refs.reject! { |r| commit.has_mentioned?(r) }
|
refs.reject! { |r| commit.has_mentioned?(r) }
|
||||||
|
|
||||||
if refs.present?
|
if refs.present?
|
||||||
|
@ -127,6 +127,6 @@ class GitPushService
|
||||||
end
|
end
|
||||||
|
|
||||||
def commit_user(commit)
|
def commit_user(commit)
|
||||||
User.find_for_commit(commit.author_email, commit.author_name) || user
|
commit.author || user
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -123,32 +123,29 @@ class NotificationService
|
||||||
return true if note.note.start_with?('Status changed to closed')
|
return true if note.note.start_with?('Status changed to closed')
|
||||||
return true if note.cross_reference? && note.system == true
|
return true if note.cross_reference? && note.system == true
|
||||||
|
|
||||||
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
|
|
||||||
|
|
||||||
target = note.noteable
|
target = note.noteable
|
||||||
|
|
||||||
if target.respond_to?(:participants)
|
recipients = []
|
||||||
recipients = target.participants
|
|
||||||
else
|
|
||||||
recipients = note.mentioned_users
|
|
||||||
end
|
|
||||||
|
|
||||||
if note.commit_id.present?
|
if note.commit_id.present?
|
||||||
opts.merge!(commit_id: note.commit_id)
|
|
||||||
recipients << note.commit_author
|
recipients << note.commit_author
|
||||||
else
|
|
||||||
opts.merge!(noteable_id: note.noteable_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get users who left comment in thread
|
# Get users who left comment in thread
|
||||||
recipients = recipients.concat(User.where(id: Note.where(opts).pluck(:author_id)))
|
recipients = recipients.concat(noteable_commenters(note))
|
||||||
|
|
||||||
# Merge project watchers
|
# Merge project watchers
|
||||||
recipients = recipients.concat(project_watchers(note.project)).compact.uniq
|
recipients = recipients.concat(project_watchers(note.project)).compact.uniq
|
||||||
|
|
||||||
# Reject mention users unless mentioned in comment
|
# Reject users with Mention notification level
|
||||||
recipients = reject_mention_users(recipients - note.mentioned_users, note.project)
|
recipients = reject_mention_users(recipients, note.project)
|
||||||
recipients = recipients + note.mentioned_users
|
|
||||||
|
# Add explicitly mentioned users
|
||||||
|
if target.respond_to?(:participants)
|
||||||
|
recipients = recipients.concat(target.participants)
|
||||||
|
else
|
||||||
|
recipients = recipients.concat(note.mentioned_users)
|
||||||
|
end
|
||||||
|
|
||||||
# Reject mutes users
|
# Reject mutes users
|
||||||
recipients = reject_muted_users(recipients, note.project)
|
recipients = reject_muted_users(recipients, note.project)
|
||||||
|
@ -195,6 +192,18 @@ class NotificationService
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def noteable_commenters(note)
|
||||||
|
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
|
||||||
|
|
||||||
|
if note.commit_id.present?
|
||||||
|
opts.merge!(commit_id: note.commit_id)
|
||||||
|
else
|
||||||
|
opts.merge!(noteable_id: note.noteable_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
User.where(id: Note.where(opts).pluck(:author_id))
|
||||||
|
end
|
||||||
|
|
||||||
# Get project users with WATCH notification level
|
# Get project users with WATCH notification level
|
||||||
def project_watchers(project)
|
def project_watchers(project)
|
||||||
project_members = project_member_notification(project)
|
project_members = project_member_notification(project)
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
module Projects
|
module Projects
|
||||||
class ParticipantsService < BaseService
|
class ParticipantsService < BaseService
|
||||||
def initialize(project, user)
|
|
||||||
@project = project
|
|
||||||
@user = user
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute(note_type, note_id)
|
def execute(note_type, note_id)
|
||||||
participating =
|
participating =
|
||||||
if note_type && note_id
|
if note_type && note_id
|
||||||
|
@ -12,7 +7,7 @@ module Projects
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
project_members = sorted(@project.team.members)
|
project_members = sorted(project.team.members)
|
||||||
participants = all_members + groups + project_members + participating
|
participants = all_members + groups + project_members + participating
|
||||||
participants.uniq
|
participants.uniq
|
||||||
end
|
end
|
||||||
|
@ -20,11 +15,11 @@ module Projects
|
||||||
def participants_in(type, id)
|
def participants_in(type, id)
|
||||||
users = case type
|
users = case type
|
||||||
when "Issue"
|
when "Issue"
|
||||||
issue = @project.issues.find_by_iid(id)
|
issue = project.issues.find_by_iid(id)
|
||||||
issue ? issue.participants : []
|
issue ? issue.participants(current_user) : []
|
||||||
when "MergeRequest"
|
when "MergeRequest"
|
||||||
merge_request = @project.merge_requests.find_by_iid(id)
|
merge_request = project.merge_requests.find_by_iid(id)
|
||||||
merge_request ? merge_request.participants : []
|
merge_request ? merge_request.participants(current_user) : []
|
||||||
when "Commit"
|
when "Commit"
|
||||||
author_ids = Note.for_commit_id(id).pluck(:author_id).uniq
|
author_ids = Note.for_commit_id(id).pluck(:author_id).uniq
|
||||||
User.where(id: author_ids)
|
User.where(id: author_ids)
|
||||||
|
@ -41,14 +36,14 @@ module Projects
|
||||||
end
|
end
|
||||||
|
|
||||||
def groups
|
def groups
|
||||||
@user.authorized_groups.sort_by(&:path).map do |group|
|
current_user.authorized_groups.sort_by(&:path).map do |group|
|
||||||
count = group.users.count
|
count = group.users.count
|
||||||
{ username: group.path, name: "#{group.name} (#{count})" }
|
{ username: group.path, name: "#{group.name} (#{count})" }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def all_members
|
def all_members
|
||||||
count = @project.team.members.flatten.count
|
count = project.team.members.flatten.count
|
||||||
[{ username: "all", name: "All Project and Group Members (#{count})" }]
|
[{ username: "all", name: "All Project and Group Members (#{count})" }]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
.votes-holder.pull-right
|
.votes-holder.pull-right
|
||||||
#votes= render 'votes/votes_block', votable: @issue
|
#votes= render 'votes/votes_block', votable: @issue
|
||||||
.participants
|
.participants
|
||||||
%span= pluralize(@issue.participants.count, 'participant')
|
%span= pluralize(@issue.participants(current_user).count, 'participant')
|
||||||
- @issue.participants.each do |participant|
|
- @issue.participants(current_user).each do |participant|
|
||||||
= link_to_member(@project, participant, name: false, size: 24)
|
= link_to_member(@project, participant, name: false, size: 24)
|
||||||
.voting_notes#notes= render "projects/notes/notes_with_form"
|
.voting_notes#notes= render "projects/notes/notes_with_form"
|
||||||
%aside.col-md-3
|
%aside.col-md-3
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.participants
|
.participants
|
||||||
%span #{@merge_request.participants.count} participants
|
%span #{@merge_request.participants(current_user).count} participants
|
||||||
- @merge_request.participants.each do |participant|
|
- @merge_request.participants(current_user).each do |participant|
|
||||||
= link_to_member(@project, participant, name: false, size: 24)
|
= link_to_member(@project, participant, name: false, size: 24)
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
module Gitlab
|
module Gitlab
|
||||||
module ClosingIssueExtractor
|
class ClosingIssueExtractor
|
||||||
ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
|
ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
|
||||||
|
|
||||||
def self.closed_by_message_in_project(message, project)
|
def initialize(project, current_user = nil)
|
||||||
issues = []
|
@extractor = Gitlab::ReferenceExtractor.new(project, current_user)
|
||||||
|
end
|
||||||
|
|
||||||
unless message.nil?
|
def closed_by_message(message)
|
||||||
md = message.scan(ISSUE_CLOSING_REGEX)
|
return [] if message.nil?
|
||||||
|
|
||||||
|
closing_statements = message.scan(ISSUE_CLOSING_REGEX).
|
||||||
|
map { |ref| ref[0] }.join(" ")
|
||||||
|
|
||||||
md.each do |ref|
|
@extractor.analyze(closing_statements)
|
||||||
extractor = Gitlab::ReferenceExtractor.new
|
|
||||||
extractor.analyze(ref[0], project)
|
|
||||||
issues += extractor.issues_for(project)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
issues.uniq
|
@extractor.issues
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -192,6 +192,7 @@ module Gitlab
|
||||||
project_path = $LAST_MATCH_INFO[:project]
|
project_path = $LAST_MATCH_INFO[:project]
|
||||||
if project_path
|
if project_path
|
||||||
actual_project = ::Project.find_with_namespace(project_path)
|
actual_project = ::Project.find_with_namespace(project_path)
|
||||||
|
actual_project = nil unless can?(current_user, :read_project, actual_project)
|
||||||
project_prefix = project_path
|
project_prefix = project_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -251,6 +252,7 @@ module Gitlab
|
||||||
elsif namespace = Namespace.find_by(path: identifier)
|
elsif namespace = Namespace.find_by(path: identifier)
|
||||||
url =
|
url =
|
||||||
if namespace.is_a?(Group)
|
if namespace.is_a?(Group)
|
||||||
|
return nil unless can?(current_user, :read_group, namespace)
|
||||||
group_url(identifier, only_path: options[:reference_only_path])
|
group_url(identifier, only_path: options[:reference_only_path])
|
||||||
else
|
else
|
||||||
user_url(identifier, only_path: options[:reference_only_path])
|
user_url(identifier, only_path: options[:reference_only_path])
|
||||||
|
|
|
@ -1,94 +1,94 @@
|
||||||
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 :users, :labels, :issues, :merge_requests, :snippets, :commits, :commit_ranges
|
attr_accessor :project, :current_user, :references
|
||||||
|
|
||||||
include ::Gitlab::Markdown
|
include ::Gitlab::Markdown
|
||||||
|
|
||||||
def initialize
|
def initialize(project, current_user = nil)
|
||||||
@users, @labels, @issues, @merge_requests, @snippets, @commits, @commit_ranges =
|
@project = project
|
||||||
[], [], [], [], [], [], []
|
@current_user = current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
def analyze(string, project)
|
def can?(user, action, subject)
|
||||||
text = string.dup
|
Ability.abilities.allowed?(user, action, subject)
|
||||||
|
end
|
||||||
|
|
||||||
|
def analyze(text)
|
||||||
|
text = text.dup
|
||||||
|
|
||||||
# Remove preformatted/code blocks so that references are not included
|
# Remove preformatted/code blocks so that references are not included
|
||||||
text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) { |match| '' }
|
text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) { |match| '' }
|
||||||
text.gsub!(%r{^```.*?^```}m) { |match| '' }
|
text.gsub!(%r{^```.*?^```}m) { |match| '' }
|
||||||
|
|
||||||
parse_references(text, project)
|
@references = Hash.new { |hash, type| hash[type] = [] }
|
||||||
|
parse_references(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Given a valid project, resolve the extracted identifiers of the requested type to
|
# Given a valid project, resolve the extracted identifiers of the requested type to
|
||||||
# model objects.
|
# model objects.
|
||||||
|
|
||||||
def users_for(project)
|
def users
|
||||||
users.map do |entry|
|
references[:user].uniq.map do |project, identifier|
|
||||||
project.users.where(username: entry[:id]).first
|
if identifier == "all"
|
||||||
end.reject(&:nil?)
|
project.team.members.flatten
|
||||||
end
|
elsif namespace = Namespace.find_by(path: identifier)
|
||||||
|
if namespace.is_a?(Group)
|
||||||
def labels_for(project = nil)
|
namespace.users
|
||||||
labels.map do |entry|
|
else
|
||||||
project.labels.where(id: entry[:id]).first
|
namespace.owner
|
||||||
end.reject(&:nil?)
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def issues_for(project = nil)
|
|
||||||
issues.map do |entry|
|
|
||||||
if should_lookup?(project, entry[:project])
|
|
||||||
entry[:project].issues.where(iid: entry[:id]).first
|
|
||||||
end
|
end
|
||||||
end.reject(&:nil?)
|
end.flatten.compact.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge_requests_for(project = nil)
|
def labels
|
||||||
merge_requests.map do |entry|
|
references[:label].uniq.map do |project, identifier|
|
||||||
if should_lookup?(project, entry[:project])
|
project.labels.where(id: identifier).first
|
||||||
entry[:project].merge_requests.where(iid: entry[:id]).first
|
end.compact.uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def issues
|
||||||
|
references[:issue].uniq.map do |project, identifier|
|
||||||
|
if project.default_issues_tracker?
|
||||||
|
project.issues.where(iid: identifier).first
|
||||||
end
|
end
|
||||||
end.reject(&:nil?)
|
end.compact.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
def snippets_for(project)
|
def merge_requests
|
||||||
snippets.map do |entry|
|
references[:merge_request].uniq.map do |project, identifier|
|
||||||
project.snippets.where(id: entry[:id]).first
|
project.merge_requests.where(iid: identifier).first
|
||||||
end.reject(&:nil?)
|
end.compact.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
def commits_for(project = nil)
|
def snippets
|
||||||
commits.map do |entry|
|
references[:snippet].uniq.map do |project, identifier|
|
||||||
repo = entry[:project].repository if entry[:project]
|
project.snippets.where(id: identifier).first
|
||||||
if should_lookup?(project, entry[:project])
|
end.compact.uniq
|
||||||
repo.commit(entry[:id]) if repo
|
|
||||||
end
|
|
||||||
end.reject(&:nil?)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def commit_ranges_for(project = nil)
|
def commits
|
||||||
commit_ranges.map do |entry|
|
references[:commit].uniq.map do |project, identifier|
|
||||||
repo = entry[:project].repository if entry[:project]
|
repo = project.repository
|
||||||
if repo && should_lookup?(project, entry[:project])
|
repo.commit(identifier) if repo
|
||||||
from_id, to_id = entry[:id].split(/\.{2,3}/, 2)
|
end.compact.uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_ranges
|
||||||
|
references[:commit_range].uniq.map do |project, identifier|
|
||||||
|
repo = project.repository
|
||||||
|
if repo
|
||||||
|
from_id, to_id = identifier.split(/\.{2,3}/, 2)
|
||||||
[repo.commit(from_id), repo.commit(to_id)]
|
[repo.commit(from_id), repo.commit(to_id)]
|
||||||
end
|
end
|
||||||
end.reject(&:nil?)
|
end.compact.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def reference_link(type, identifier, project, _)
|
def reference_link(type, identifier, project, _)
|
||||||
# Append identifier to the appropriate collection.
|
references[type] << [project, identifier]
|
||||||
send("#{type}s") << { project: project, id: identifier }
|
|
||||||
end
|
|
||||||
|
|
||||||
def should_lookup?(project, entry_project)
|
|
||||||
if entry_project.nil?
|
|
||||||
false
|
|
||||||
else
|
|
||||||
project.nil? || entry_project.default_issues_tracker?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,11 @@ describe GitlabMarkdownHelper do
|
||||||
include ApplicationHelper
|
include ApplicationHelper
|
||||||
include IssuesHelper
|
include IssuesHelper
|
||||||
|
|
||||||
|
# TODO: Properly test this
|
||||||
|
def can?(*)
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
let!(:project) { create(:project) }
|
let!(:project) { create(:project) }
|
||||||
let(:empty_project) { create(:empty_project) }
|
let(:empty_project) { create(:empty_project) }
|
||||||
|
|
||||||
|
@ -15,6 +20,9 @@ describe GitlabMarkdownHelper do
|
||||||
let(:snippet) { create(:project_snippet, project: project) }
|
let(:snippet) { create(:project_snippet, project: project) }
|
||||||
let(:member) { project.project_members.where(user_id: user).first }
|
let(:member) { project.project_members.where(user_id: user).first }
|
||||||
|
|
||||||
|
# Helper expects a current_user method.
|
||||||
|
let(:current_user) { user }
|
||||||
|
|
||||||
def url_helper(image_name)
|
def url_helper(image_name)
|
||||||
File.join(root_url, 'assets', image_name)
|
File.join(root_url, 'assets', image_name)
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,126 +5,128 @@ describe Gitlab::ClosingIssueExtractor do
|
||||||
let(:issue) { create(:issue, project: project) }
|
let(:issue) { create(:issue, project: project) }
|
||||||
let(:iid1) { issue.iid }
|
let(:iid1) { issue.iid }
|
||||||
|
|
||||||
describe :closed_by_message_in_project do
|
subject { described_class.new(project, project.creator) }
|
||||||
|
|
||||||
|
describe "#closed_by_message" do
|
||||||
context 'with a single reference' do
|
context 'with a single reference' do
|
||||||
it do
|
it do
|
||||||
message = "Awesome commit (Closes ##{iid1})"
|
message = "Awesome commit (Closes ##{iid1})"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Awesome commit (closes ##{iid1})"
|
message = "Awesome commit (closes ##{iid1})"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Closed ##{iid1}"
|
message = "Closed ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "closed ##{iid1}"
|
message = "closed ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Closing ##{iid1}"
|
message = "Closing ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "closing ##{iid1}"
|
message = "closing ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Close ##{iid1}"
|
message = "Close ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "close ##{iid1}"
|
message = "close ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Awesome commit (Fixes ##{iid1})"
|
message = "Awesome commit (Fixes ##{iid1})"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Awesome commit (fixes ##{iid1})"
|
message = "Awesome commit (fixes ##{iid1})"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Fixed ##{iid1}"
|
message = "Fixed ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "fixed ##{iid1}"
|
message = "fixed ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Fixing ##{iid1}"
|
message = "Fixing ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "fixing ##{iid1}"
|
message = "fixing ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Fix ##{iid1}"
|
message = "Fix ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "fix ##{iid1}"
|
message = "fix ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Awesome commit (Resolves ##{iid1})"
|
message = "Awesome commit (Resolves ##{iid1})"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Awesome commit (resolves ##{iid1})"
|
message = "Awesome commit (resolves ##{iid1})"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Resolved ##{iid1}"
|
message = "Resolved ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "resolved ##{iid1}"
|
message = "resolved ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Resolving ##{iid1}"
|
message = "Resolving ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "resolving ##{iid1}"
|
message = "resolving ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "Resolve ##{iid1}"
|
message = "Resolve ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
message = "resolve ##{iid1}"
|
message = "resolve ##{iid1}"
|
||||||
expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
|
expect(subject.closed_by_message(message)).to eq([issue])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -137,28 +139,28 @@ describe Gitlab::ClosingIssueExtractor do
|
||||||
it 'fetches issues in single line message' do
|
it 'fetches issues in single line message' do
|
||||||
message = "Closes ##{iid1} and fix ##{iid2}"
|
message = "Closes ##{iid1} and fix ##{iid2}"
|
||||||
|
|
||||||
expect(subject.closed_by_message_in_project(message, project)).
|
expect(subject.closed_by_message(message)).
|
||||||
to eq([issue, other_issue])
|
to eq([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 ##{iid1}, closes ##{iid2}"
|
message = "Closes ##{iid1}, closes ##{iid2}"
|
||||||
|
|
||||||
expect(subject.closed_by_message_in_project(message, project)).
|
expect(subject.closed_by_message(message)).
|
||||||
to eq([issue, other_issue])
|
to eq([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 ##{iid1}, ##{iid2} and ##{iid3}"
|
message = "Closes ##{iid1}, ##{iid2} and ##{iid3}"
|
||||||
|
|
||||||
expect(subject.closed_by_message_in_project(message, project)).
|
expect(subject.closed_by_message(message)).
|
||||||
to eq([issue, other_issue, third_issue])
|
to eq([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 ##{iid1})\nAlso fixes ##{iid2}"
|
message = "Awesome commit (closes ##{iid1})\nAlso fixes ##{iid2}"
|
||||||
|
|
||||||
expect(subject.closed_by_message_in_project(message, project)).
|
expect(subject.closed_by_message(message)).
|
||||||
to eq([issue, other_issue])
|
to eq([issue, other_issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -166,7 +168,7 @@ describe Gitlab::ClosingIssueExtractor do
|
||||||
message = "Awesome commit (closes ##{iid1})\n"\
|
message = "Awesome commit (closes ##{iid1})\n"\
|
||||||
"Also fixing issues ##{iid2}, ##{iid3} and #4"
|
"Also fixing issues ##{iid2}, ##{iid3} and #4"
|
||||||
|
|
||||||
expect(subject.closed_by_message_in_project(message, project)).
|
expect(subject.closed_by_message(message)).
|
||||||
to eq([issue, other_issue, third_issue])
|
to eq([issue, other_issue, third_issue])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,73 +1,76 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Gitlab::ReferenceExtractor do
|
describe Gitlab::ReferenceExtractor do
|
||||||
|
let(:project) { create(:project) }
|
||||||
|
subject { Gitlab::ReferenceExtractor.new(project, project.creator) }
|
||||||
|
|
||||||
it 'extracts username references' do
|
it 'extracts username references' do
|
||||||
subject.analyze('this contains a @user reference', nil)
|
subject.analyze('this contains a @user reference')
|
||||||
expect(subject.users).to eq([{ project: nil, id: 'user' }])
|
expect(subject.references[:user]).to eq([[project, 'user']])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts issue references' do
|
it 'extracts issue references' do
|
||||||
subject.analyze('this one talks about issue #1234', nil)
|
subject.analyze('this one talks about issue #1234')
|
||||||
expect(subject.issues).to eq([{ project: nil, id: '1234' }])
|
expect(subject.references[:issue]).to eq([[project, '1234']])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts JIRA issue references' do
|
it 'extracts JIRA issue references' do
|
||||||
subject.analyze('this one talks about issue JIRA-1234', nil)
|
subject.analyze('this one talks about issue JIRA-1234')
|
||||||
expect(subject.issues).to eq([{ project: nil, id: 'JIRA-1234' }])
|
expect(subject.references[:issue]).to eq([[project, 'JIRA-1234']])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts merge request references' do
|
it 'extracts merge request references' do
|
||||||
subject.analyze("and here's !43, a merge request", nil)
|
subject.analyze("and here's !43, a merge request")
|
||||||
expect(subject.merge_requests).to eq([{ project: nil, id: '43' }])
|
expect(subject.references[:merge_request]).to eq([[project, '43']])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts snippet ids' do
|
it 'extracts snippet ids' do
|
||||||
subject.analyze('snippets like $12 get extracted as well', nil)
|
subject.analyze('snippets like $12 get extracted as well')
|
||||||
expect(subject.snippets).to eq([{ project: nil, id: '12' }])
|
expect(subject.references[:snippet]).to eq([[project, '12']])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts commit shas' do
|
it 'extracts commit shas' do
|
||||||
subject.analyze('commit shas 98cf0ae3 are pulled out as Strings', nil)
|
subject.analyze('commit shas 98cf0ae3 are pulled out as Strings')
|
||||||
expect(subject.commits).to eq([{ project: nil, id: '98cf0ae3' }])
|
expect(subject.references[:commit]).to eq([[project, '98cf0ae3']])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts commit ranges' do
|
it 'extracts commit ranges' do
|
||||||
subject.analyze('here you go, a commit range: 98cf0ae3...98cf0ae4', nil)
|
subject.analyze('here you go, a commit range: 98cf0ae3...98cf0ae4')
|
||||||
expect(subject.commit_ranges).to eq([{ project: nil, id: '98cf0ae3...98cf0ae4' }])
|
expect(subject.references[:commit_range]).to eq([[project, '98cf0ae3...98cf0ae4']])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts multiple references and preserves their order' do
|
it 'extracts multiple references and preserves their order' do
|
||||||
subject.analyze('@me and @you both care about this', nil)
|
subject.analyze('@me and @you both care about this')
|
||||||
expect(subject.users).to eq([
|
expect(subject.references[:user]).to eq([
|
||||||
{ project: nil, id: 'me' },
|
[project, 'me'],
|
||||||
{ project: nil, id: 'you' }
|
[project, 'you']
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'leaves the original note unmodified' do
|
it 'leaves the original note unmodified' do
|
||||||
text = 'issue #123 is just the worst, @user'
|
text = 'issue #123 is just the worst, @user'
|
||||||
subject.analyze(text, nil)
|
subject.analyze(text)
|
||||||
expect(text).to eq('issue #123 is just the worst, @user')
|
expect(text).to eq('issue #123 is just the worst, @user')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts no references for <pre>..</pre> blocks' do
|
it 'extracts no references for <pre>..</pre> blocks' do
|
||||||
subject.analyze("<pre>def puts '#1 issue'\nend\n</pre>```", nil)
|
subject.analyze("<pre>def puts '#1 issue'\nend\n</pre>```")
|
||||||
expect(subject.issues).to be_blank
|
expect(subject.issues).to be_blank
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts no references for <code>..</code> blocks' do
|
it 'extracts no references for <code>..</code> blocks' do
|
||||||
subject.analyze("<code>def puts '!1 request'\nend\n</code>```", nil)
|
subject.analyze("<code>def puts '!1 request'\nend\n</code>```")
|
||||||
expect(subject.merge_requests).to be_blank
|
expect(subject.merge_requests).to be_blank
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts no references for code blocks with language' do
|
it 'extracts no references for code blocks with language' do
|
||||||
subject.analyze("this code:\n```ruby\ndef puts '#1 issue'\nend\n```", nil)
|
subject.analyze("this code:\n```ruby\ndef puts '#1 issue'\nend\n```")
|
||||||
expect(subject.issues).to be_blank
|
expect(subject.issues).to be_blank
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'extracts issue references for invalid code blocks' do
|
it 'extracts issue references for invalid code blocks' do
|
||||||
subject.analyze('test: ```this one talks about issue #1234```', nil)
|
subject.analyze('test: ```this one talks about issue #1234```')
|
||||||
expect(subject.issues).to eq([{ project: nil, id: '1234' }])
|
expect(subject.references[:issue]).to eq([[project, '1234']])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles all possible kinds of references' do
|
it 'handles all possible kinds of references' do
|
||||||
|
@ -75,83 +78,79 @@ describe Gitlab::ReferenceExtractor do
|
||||||
expect(subject).to respond_to(*accessors)
|
expect(subject).to respond_to(*accessors)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a project' do
|
it 'accesses valid user objects' do
|
||||||
let(:project) { create(:project) }
|
@u_foo = create(:user, username: 'foo')
|
||||||
|
@u_bar = create(:user, username: 'bar')
|
||||||
|
@u_offteam = create(:user, username: 'offteam')
|
||||||
|
|
||||||
it 'accesses valid user objects on the project team' do
|
project.team << [@u_foo, :reporter]
|
||||||
@u_foo = create(:user, username: 'foo')
|
project.team << [@u_bar, :guest]
|
||||||
@u_bar = create(:user, username: 'bar')
|
|
||||||
create(:user, username: 'offteam')
|
|
||||||
|
|
||||||
project.team << [@u_foo, :reporter]
|
subject.analyze('@foo, @baduser, @bar, and @offteam')
|
||||||
project.team << [@u_bar, :guest]
|
expect(subject.users).to eq([@u_foo, @u_bar, @u_offteam])
|
||||||
|
end
|
||||||
|
|
||||||
subject.analyze('@foo, @baduser, @bar, and @offteam', project)
|
it 'accesses valid issue objects' do
|
||||||
expect(subject.users_for(project)).to eq([@u_foo, @u_bar])
|
@i0 = create(:issue, project: project)
|
||||||
end
|
@i1 = create(:issue, project: project)
|
||||||
|
|
||||||
it 'accesses valid issue objects' do
|
subject.analyze("##{@i0.iid}, ##{@i1.iid}, and #999.")
|
||||||
@i0 = create(:issue, project: project)
|
expect(subject.issues).to eq([@i0, @i1])
|
||||||
@i1 = create(:issue, project: project)
|
end
|
||||||
|
|
||||||
subject.analyze("##{@i0.iid}, ##{@i1.iid}, and #999.", project)
|
it 'accesses valid merge requests' do
|
||||||
expect(subject.issues_for(project)).to eq([@i0, @i1])
|
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa')
|
||||||
end
|
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb')
|
||||||
|
|
||||||
it 'accesses valid merge requests' do
|
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
|
||||||
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa')
|
expect(subject.merge_requests).to eq([@m1, @m0])
|
||||||
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb')
|
end
|
||||||
|
|
||||||
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.", project)
|
it 'accesses valid snippets' do
|
||||||
expect(subject.merge_requests_for(project)).to eq([@m1, @m0])
|
@s0 = create(:project_snippet, project: project)
|
||||||
end
|
@s1 = create(:project_snippet, project: project)
|
||||||
|
@s2 = create(:project_snippet)
|
||||||
|
|
||||||
it 'accesses valid snippets' do
|
subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}")
|
||||||
@s0 = create(:project_snippet, project: project)
|
expect(subject.snippets).to eq([@s0, @s1])
|
||||||
@s1 = create(:project_snippet, project: project)
|
end
|
||||||
@s2 = create(:project_snippet)
|
|
||||||
|
|
||||||
subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}", project)
|
it 'accesses valid commits' do
|
||||||
expect(subject.snippets_for(project)).to eq([@s0, @s1])
|
commit = project.repository.commit('master')
|
||||||
end
|
|
||||||
|
|
||||||
it 'accesses valid commits' do
|
subject.analyze("this references commits #{commit.sha[0..6]} and 012345")
|
||||||
commit = project.repository.commit('master')
|
extracted = subject.commits
|
||||||
|
expect(extracted.size).to eq(1)
|
||||||
|
expect(extracted[0].sha).to eq(commit.sha)
|
||||||
|
expect(extracted[0].message).to eq(commit.message)
|
||||||
|
end
|
||||||
|
|
||||||
subject.analyze("this references commits #{commit.sha[0..6]} and 012345",
|
it 'accesses valid commit ranges' do
|
||||||
project)
|
commit = project.repository.commit('master')
|
||||||
extracted = subject.commits_for(project)
|
earlier_commit = project.repository.commit('master~2')
|
||||||
expect(extracted.size).to eq(1)
|
|
||||||
expect(extracted[0].sha).to eq(commit.sha)
|
|
||||||
expect(extracted[0].message).to eq(commit.message)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'accesses valid commit ranges' do
|
subject.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}")
|
||||||
commit = project.repository.commit('master')
|
extracted = subject.commit_ranges
|
||||||
earlier_commit = project.repository.commit('master~2')
|
expect(extracted.size).to eq(1)
|
||||||
|
expect(extracted[0][0].sha).to eq(earlier_commit.sha)
|
||||||
subject.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}",
|
expect(extracted[0][0].message).to eq(earlier_commit.message)
|
||||||
project)
|
expect(extracted[0][1].sha).to eq(commit.sha)
|
||||||
extracted = subject.commit_ranges_for(project)
|
expect(extracted[0][1].message).to eq(commit.message)
|
||||||
expect(extracted.size).to eq(1)
|
|
||||||
expect(extracted[0][0].sha).to eq(earlier_commit.sha)
|
|
||||||
expect(extracted[0][0].message).to eq(earlier_commit.message)
|
|
||||||
expect(extracted[0][1].sha).to eq(commit.sha)
|
|
||||||
expect(extracted[0][1].message).to eq(commit.message)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a project with an underscore' do
|
context 'with a project with an underscore' do
|
||||||
let(:project) { create(:project, path: 'test_project') }
|
let(:other_project) { create(:project, path: 'test_project') }
|
||||||
let(:issue) { create(:issue, project: project) }
|
let(:issue) { create(:issue, project: other_project) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
other_project.team << [project.creator, :developer]
|
||||||
|
end
|
||||||
|
|
||||||
it 'handles project issue references' do
|
it 'handles project issue references' do
|
||||||
subject.analyze("this refers issue #{project.path_with_namespace}##{issue.iid}",
|
subject.analyze("this refers issue #{other_project.path_with_namespace}##{issue.iid}")
|
||||||
project)
|
extracted = subject.issues
|
||||||
extracted = subject.issues_for(project)
|
|
||||||
expect(extracted.size).to eq(1)
|
expect(extracted.size).to eq(1)
|
||||||
expect(extracted).to eq([issue])
|
expect(extracted).to eq([issue])
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue