#73: merge master into branch

This commit is contained in:
Vokhmin Alexey V 2013-04-08 22:55:21 +04:00
commit 38ce6115c2
49 changed files with 860 additions and 149 deletions

View File

@ -23,12 +23,12 @@ gem 'state_machine'
gem 'grack', :git => 'git://github.com/rdblue/grack.git', :require => 'git_http'
gem "grit", :git => 'git://github.com/warpc/grit.git' #, :path => '~/Sites/code/grit'
gem 'charlock_holmes', '~> 0.6.9' #, :git => 'git://github.com/brianmario/charlock_holmes.git', :branch => 'bundle-icu'
gem 'github-linguist', '~> 2.2.1', :require => 'linguist'
gem 'github-linguist', '~> 2.3.4', :require => 'linguist'
gem 'diff-display', '~> 0.0.1'
# Wiki
gem "gollum", '~> 2.1.3'
gem "redcarpet", "~> 2.1.1"
gem "redcarpet", '~> 2.2.2'
gem 'creole'
gem 'rdiscount'
# gem 'org-ruby'
@ -53,6 +53,7 @@ gem 'rack-throttle'
gem 'rest-client', '~> 1.6.6'
gem 'attr_encrypted', '1.2.1'
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
group :assets do
gem 'sass-rails', '~> 3.2.5'

View File

@ -121,10 +121,11 @@ GEM
railties (>= 3.0.0)
ffi (1.0.11)
fssm (0.2.10)
github-linguist (2.2.1)
gemoji (1.2.1)
github-linguist (2.3.4)
charlock_holmes (~> 0.6.6)
escape_utils (~> 0.2.3)
mime-types (~> 1.18)
mime-types (~> 1.19)
pygments.rb (>= 0.2.13)
github-markdown (0.5.3)
github-markup (0.7.5)
@ -257,7 +258,7 @@ GEM
rdiscount (2.0.7.1)
rdoc (3.12.2)
json (~> 1.4)
redcarpet (2.1.1)
redcarpet (2.2.2)
redis (3.0.3)
redis-namespace (1.2.1)
redis (~> 3.0.0)
@ -391,7 +392,8 @@ DEPENDENCIES
devise (~> 2.1.2)
diff-display (~> 0.0.1)
factory_girl_rails (~> 4.0.0)
github-linguist (~> 2.2.1)
gemoji (~> 1.2.1)
github-linguist (~> 2.3.4)
gollum (~> 2.1.3)
grack!
grit!
@ -415,7 +417,7 @@ DEPENDENCIES
rails3-generators
rails3-jquery-autocomplete (~> 1.0.7)
rdiscount
redcarpet (~> 2.1.1)
redcarpet (~> 2.2.2)
redhillonrails_core!
resque (~> 1.21.0)
resque-status (~> 0.3.3)

View File

@ -1909,6 +1909,15 @@ table#myTable thead tr.search th form.button_to div input {
}
}
.activity .state {
float: right;
padding: 3px 10px;
margin: 3px 0;
font-size: 12px;
color: white;
border-radius: 2px;
}
article .activity .top {
.created {
@ -1955,7 +1964,6 @@ article .activity .top {
}
}
#assigned-popup {
z-index: 1001;
position: absolute;
@ -2007,3 +2015,7 @@ article .activity .top {
}
}
}
.cm-s-default.md_and_cm p img {
max-width: 800px;
}

View File

@ -53,7 +53,7 @@ class Projects::CommentsController < Projects::BaseController
end
def find_or_build_comment
@comment = params[:id].present? && Comment.find(params[:id]) ||
@comment = params[:id].present? && Comment.where(:automatic => false).find(params[:id]) ||
current_user.comments.build(params[:comment]) {|c| c.commentable = @commentable; c.project = @project}
end
end

View File

@ -38,9 +38,12 @@ class Projects::IssuesController < Projects::BaseController
end
def create
@assignee_uname = params[:assignee_uname]
@issue.user_id = current_user.id
unless can?(:write, @project)
@issue.assignee_id = nil
@issue.labelings = []
end
if @issue.save
@issue.subscribe_creator(current_user.id)
flash[:notice] = I18n.t("flash.issue.saved")
@ -56,6 +59,12 @@ class Projects::IssuesController < Projects::BaseController
end
def update
unless can?(:write, @project)
params.delete :update_labels
[:assignee_id, :labelings, :labelings_attributes].each do |k|
params[:issue].delete k
end if params[:issue]
end
@issue.labelings.destroy_all if params[:update_labels]
if params[:issue] && status = params[:issue][:status]
@issue.set_close(current_user) if status == 'closed'

View File

@ -38,7 +38,7 @@ class Projects::PullRequestsController < Projects::BaseController
authorize! :read, to_project
@pull = to_project.pull_requests.new pull_params
@pull.issue.assignee_id = (params[:issue] || {})[:assignee_id]
@pull.issue.assignee_id = (params[:issue] || {})[:assignee_id] if can?(:write, to_project)
@pull.issue.user, @pull.issue.project, @pull.from_project = current_user, to_project, @project
@pull.from_project_owner_uname = @pull.from_project.owner.uname
@pull.from_project_name = @pull.from_project.name

View File

@ -38,15 +38,6 @@ module ApplicationHelper
end
end
def markdown(text)
unless @redcarpet
html_options = {filter_html: true, hard_wrap: true, with_toc_data: true}
options = {no_intraemphasis: true, tables: true, fenced_code_blocks: true, autolink: true, strikethrough: true, lax_html_blocks: true}
@redcarpet = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(html_options), options)
end
@redcarpet.render(text).html_safe
end
def local_alert(text, type = 'error')
html = "<div class='flash'><div class='alert #{type}'> #{text}"
html << link_to('×', '#', :class => 'close close-alert', 'data-dismiss' => 'alert')

View File

@ -0,0 +1,62 @@
# This module is based on
# https://github.com/gitlabhq/gitlabhq/blob/7665b1de7eed4addd7b94786c84e6674710e6377/app/helpers/gitlab_markdown_helper.rb
module GitlabMarkdownHelper
include Modules::Models::Markdown
# Use this in places where you would normally use link_to(gfm(...), ...).
#
# It solves a problem occurring with nested links (i.e.
# "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be
# interpreted as intended. Browsers will parse something like
# "<a>outer text </a><a>gfm ref</a> more outer text" (notice the last part is
# not linked any more). link_to_gfm corrects that. It wraps all parts to
# explicitly produce the correct linking behavior (i.e.
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
def link_to_gfm(body, url, html_options = {})
return "" if body.blank?
escaped_body = if body =~ /^\<img/
body
else
escape_once(body)
end
gfm_body = gfm(escaped_body, html_options)
gfm_body.gsub!(%r{<a.*?>.*?</a>}m) do |match|
"</a>#{match}#{link_to("", url, html_options)[0..-5]}" # "</a>".length +1
end
link_to(gfm_body.html_safe, url, html_options)
end
def markdown(text)
unless @markdown
gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self,
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
filter_html: true,
with_toc_data: true,
hard_wrap: true)
@markdown = Redcarpet::Markdown.new(gitlab_renderer,
# see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
no_intra_emphasis: true,
tables: true,
fenced_code_blocks: true,
autolink: true,
strikethrough: true,
lax_html_blocks: true,
space_after_headers: true,
superscript: true)
end
@markdown.render(text).html_safe
end
def render_wiki_content(wiki_page)
if wiki_page.format == :markdown
markdown(wiki_page.content)
else
wiki_page.formatted_content.html_safe
end
end
end

View File

@ -1,7 +1,7 @@
# -*- encoding : utf-8 -*-
module PullRequestHelper
def merge_activity comments, commits
common_comments, pull_comments = comments.partition {|c| c.data.blank?}
common_comments, pull_comments = comments.partition {|c| c.automatic || c.data.blank?}
common_comments = common_comments.map{ |c| [c.created_at, c] }
pull_comments = pull_comments.group_by(&:data).map{|data, c| [c.first.created_at, [data || {}, [c].flatten]]}
commits = commits.map{ |c| [(c.committed_date || c.authored_date), c] }
@ -23,7 +23,7 @@ module PullRequestHelper
end
def pull_header pull
str = "#{t '.header'} #{t 'from'} <span class='label-bootstrap label-info font14'> \
str = "#{t '.header'} #{t 'from'} <span class='state label-bootstrap label-info font14'> \
#{show_ref pull, 'from'}</span> \
#{t 'into'} <span class='label-bootstrap label-info font14'> \
#{show_ref pull, 'to'}</span>"

View File

@ -11,7 +11,7 @@ module UsersHelper
elsif subject.kind_of? Group
image_path('ava-big.png')
else
gravatar_url(subject.email, subject.avatar.styles[size].geometry.split('x').first)
gravatar_url(subject.email, User::AVATAR_SIZES[size])
end
end

View File

@ -43,6 +43,7 @@ class ActivityFeedObserver < ActiveRecord::Observer
end
when 'Comment'
return if record.automatic
if record.issue_comment?
subscribes = record.commentable.subscribes
subscribes.each do |subscribe|
@ -76,6 +77,7 @@ class ActivityFeedObserver < ActiveRecord::Observer
)
end
end
Comment.create_link_on_issues_from_item(record)
when 'GitHook'
return unless record.project
@ -90,7 +92,6 @@ class ActivityFeedObserver < ActiveRecord::Observer
:change_type => change_type, :project_owner => record.project.owner.uname}
else
if record.message # online update
#FIXME using oldrev is a hack (only for online edit).
last_commits, commits = [[record.newrev, record.message]], []
else
commits = record.project.repo.commits_between(record.oldrev, record.newrev)
@ -104,6 +105,7 @@ class ActivityFeedObserver < ActiveRecord::Observer
commits = commits[0...-3]
options.merge!({:other_commits_count => commits.count, :other_commits => "#{commits[0].sha[0..9]}...#{commits[-1].sha[0..9]}"})
end
Comment.create_link_on_issues_from_item(record, last_commits) if last_commits.count > 0
end
options.merge!({:user_id => record.user.id, :user_name => record.user.name, :user_email => record.user.email}) if record.user
@ -170,7 +172,9 @@ class ActivityFeedObserver < ActiveRecord::Observer
)
end
end
when 'Comment'
# dont remove outdated issues link
Comment.create_link_on_issues_from_item(record)
end
end
end

View File

@ -3,13 +3,14 @@ class Avatar < ActiveRecord::Base
self.abstract_class = true
MAX_AVATAR_SIZE = 5.megabyte
AVATAR_SIZES = {:micro => 16, :small => 30, :medium => 40, :big => 81}
has_attached_file :avatar, :styles =>
{ :micro => { :geometry => "16x16#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
:small => { :geometry => "30x30#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
:medium => { :geometry => "40x40#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
:big => { :geometry => "81x81#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'}
}
AVATAR_SIZES_HASH = {}.tap do |styles|
AVATAR_SIZES.each do |name, size|
styles[name] = { :geometry => "#{size}x#{size}#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'}
end
end
has_attached_file :avatar, :styles => AVATAR_SIZES_HASH
validates_inclusion_of :avatar_file_size, :in => (0..MAX_AVATAR_SIZE), :allow_nil => true
attr_accessible :avatar

View File

@ -1,5 +1,11 @@
# -*- encoding : utf-8 -*-
class Comment < ActiveRecord::Base
# regexp take from http://code.google.com/p/concerto-platform/source/browse/v3/cms/lib/CodeMirror/mode/gfm/gfm.js?spec=svn861&r=861#71
# User/Project#Num
# User#Num
# #Num
ISSUES_REGEX = /(?:[a-zA-Z0-9\-_]*\/)?(?:[a-zA-Z0-9\-_]*)?[#!][0-9]+/
belongs_to :commentable, :polymorphic => true, :touch => true
belongs_to :user
belongs_to :project
@ -85,7 +91,7 @@ class Comment < ActiveRecord::Base
end
def pull_comment?
return true if commentable.is_a?(Issue) && commentable.pull_request.present?
commentable.is_a?(Issue) && commentable.pull_request.present?
end
def set_additional_data params
@ -129,6 +135,57 @@ class Comment < ActiveRecord::Base
return true
end
def self.create_link_on_issues_from_item item, commits = nil
linker = item.user
elements = if item.is_a? Comment
[[item, item.body]]
elsif item.is_a? GitHook
commits
end
current_ability = Ability.new(linker)
elements.each do |element|
element[1].scan(ISSUES_REGEX).each do |hash|
delimiter = if hash.include? '!'
'!'
elsif hash.include? '#'
'#'
else
raise 'Unknown delimiter for the hash tag!'
end
issue = Issue.find_by_hash_tag hash, current_ability, item.project, delimiter
next unless issue
# dont create link to the same issue
next if item.respond_to?(:commentable) && issue == item.try(:commentable)
find_dup = {:automatic => true, :commentable_type => issue.class.name, :commentable_id => issue.id}
if item.is_a? GitHook
find_dup.merge! :created_from_commit_hash => element[0].hex
elsif item.commentable_type == 'Issue'
find_dup.merge! :created_from_issue_id => item.commentable_id
elsif item.commentable_type == 'Grit::Commit'
find_dup.merge! :created_from_commit_hash => item.commentable_id
end
next if Comment.exists? find_dup # dont create duplicate link to issue
comment = linker.comments.new :body => 'automatic comment'
comment.commentable, comment.project, comment.automatic = issue, issue.project, true
comment.data = {:from_project_id => item.project.id}
if item.is_a? GitHook
next unless item.project.repo.commit element[0]
comment.created_from_commit_hash = element[0].hex
else
comment.data.merge! :comment_id => item.id
if item.commentable_type == 'Issue'
comment.created_from_issue_id = item.commentable_id
elsif item.commentable_type == 'Grit::Commit'
comment.created_from_commit_hash = item.commentable_id
end
end
comment.save
end
end
end
protected
def subscribe_on_reply

View File

@ -67,6 +67,17 @@ class Issue < ActiveRecord::Base
recipients
end
def self.find_by_hash_tag hash_tag, current_ability, project, delimiter = '#'
hash_tag =~ /([a-zA-Z0-9\-_]*\/)?([a-zA-Z0-9\-_]*)?#{delimiter}([0-9]+)/
owner_uname = Regexp.last_match[1].presence || Regexp.last_match[2].presence || project.owner.uname
project_name = Regexp.last_match[1] ? Regexp.last_match[2] : project.name
serial_id = Regexp.last_match[3]
project = Project.find_by_owner_and_name(owner_uname.chomp('/'), project_name)
return nil unless project
return nil unless current_ability.can? :show, project
project.issues.where(:serial_id => serial_id).first
end
protected
def set_serial_id

View File

@ -181,7 +181,7 @@ class PullRequest < ActiveRecord::Base
system 'git', 'remote', 'add', 'head', from_project.path
end
end
clean # Need testing
#clean # Need testing
end
Dir.chdir(path) do

View File

@ -1,15 +1,34 @@
# -*- encoding : utf-8 -*-
class CommentPresenter < ApplicationPresenter
include PullRequestHelper
attr_accessor :comment, :options
attr_reader :header, :image, :date, :caption, :content, :buttons
attr_reader :header, :image, :date, :caption, :content, :buttons, :is_reference_to_issue
def initialize(comment, opts = {})
@comment = comment
@user = comment.user
@options = opts
@is_reference_to_issue = !!(comment.automatic && comment.created_from_issue_id) # is it reference issue from another issue
@comment, @user, @options = comment, comment.user, opts
unless @is_reference_to_issue
@content = @comment.body
else
issue = Issue.where(:id => comment.created_from_issue_id).first
@referenced_issue = issue.pull_request || issue
if issue && Comment.exists?(comment.data[:comment_id])
title = if issue == opts[:commentable]
"#{issue.serial_id}"
elsif issue.project.owner == opts[:commentable].project.owner
"#{issue.project.name}##{issue.serial_id}"
else
"#{issue.project.name_with_owner}##{issue.serial_id}"
end
title = "<span style=\"color: #777;\">#{title}</span>:"
issue_link = project_issue_path(issue.project, issue)
@content = "<a href=\"#{issue_link}\">#{title} #{issue.title}</a>".html_safe
else
@content = t 'layout.comments.removed'
end
end
end
def expandable?
@ -17,7 +36,7 @@ class CommentPresenter < ApplicationPresenter
end
def buttons?
true
!@is_reference_to_issue # dont show for automatic comment
end
def content?
@ -28,24 +47,33 @@ class CommentPresenter < ApplicationPresenter
false
end
def issue_referenced_state?
@referenced_issue # show state of the existing referenced issue
end
def buttons
project, commentable = options[:project], options[:commentable]
path = helpers.project_commentable_comment_path(project, commentable, comment)
res = [link_to(t("layout.link"), "#{helpers.project_commentable_path(project, commentable)}##{comment_anchor}", :class => "#{@options[:in_discussion].present? ? 'in_discussion_' : ''}link_to_comment").html_safe]
res = [link_to(t('layout.link'), "#{helpers.project_commentable_path(project, commentable)}##{comment_anchor}", :class => "#{@options[:in_discussion].present? ? 'in_discussion_' : ''}link_to_comment").html_safe]
if controller.can? :update, @comment
res << link_to(t("layout.edit"), path, :id => "comment-#{comment.id}", :class => "edit_comment").html_safe
res << link_to(t('layout.edit'), path, :id => "comment-#{comment.id}", :class => "edit_comment").html_safe
end
if controller.can? :destroy, @comment
res << link_to(t("layout.delete"), path, :method => "delete",
:confirm => t("layout.comments.confirm_delete")).html_safe
res << link_to(t('layout.delete'), path, :method => "delete",
:confirm => t('layout.comments.confirm_delete')).html_safe
end
res
end
def header
res = link_to "#{@user.uname} (#{@user.name})", user_path(@user.uname)
res += ' ' + t("layout.comments.has_commented")
user_link = link_to @user.fullname, user_path(@user.uname)
res = unless @is_reference_to_issue
"#{user_link} #{t 'layout.comments.has_commented'}"
else
t 'layout.comments.reference', :user => user_link
end
res.html_safe
end
def image
@ -74,4 +102,12 @@ class CommentPresenter < ApplicationPresenter
"#{before}comment#{@comment.id}"
end
def issue_referenced_state
if @referenced_issue.is_a? Issue
statuses = {'open' => 'success', 'closed' => 'important'}
content_tag :span, t("layout.issues.status.#{@referenced_issue.status}"), :class => "state label-bootstrap label-#{statuses[@referenced_issue.status]}"
else
pull_status_label @referenced_issue
end.html_safe
end
end

View File

@ -2,33 +2,54 @@
class GitPresenters::CommitAsMessagePresenter < ApplicationPresenter
include CommitHelper
attr_accessor :commit, :options
attr_reader :header, :image, :date, :caption, :content, :expandable
attr_accessor :commit
attr_reader :header, :image, :date, :caption, :content, :expandable, :is_reference_to_issue, :committer
def initialize(commit, opts = {})
@commit = commit
@options = opts
comment = opts[:comment]
@is_reference_to_issue = !!comment # is it reference issue from commit
@project = if comment
Project.where(:id => opts[:comment].data[:from_project_id]).first
else
opts[:project]
end
if @project
commit = commit || @project.repo.commit(comment.created_from_commit_hash.to_s(16))
@committer = User.where(:email => commit.committer.email).first || commit.committer
@commit_hash = commit.id
@committed_date, @authored_date = commit.committed_date, commit.authored_date
@commit_message = commit.message
else
@committer = t('layout.commits.unknown_committer')
@commit_hash = comment.created_from_commit_hash
@committed_date = @authored_date = comment.created_at
@commit_message = t('layout.commits.deleted')
end
prepare_message
end
def header
@header ||= if options[:project].present?
I18n.t("layout.messages.commits.header",
:committer => committer_link, :commit => commit_link, :project => options[:project].name)
@header ||= if @is_reference_to_issue
I18n.t('layout.commits.reference', :committer => committer_link, :commit => commit_link)
elsif @project.present?
I18n.t('layout.messages.commits.header',
:committer => committer_link, :commit => commit_link, :project => @project.name)
end.html_safe
end
def image
c = committer
@image ||= if c.class == User
helpers.avatar_url(c, :medium)
@image ||= if committer.is_a? User
helpers.avatar_url(committer, :medium)
elsif committer.is_a? Grit::Actor
helpers.avatar_url_by_email(committer.email, :medium)
else
helpers.avatar_url_by_email(c.email, :medium)
helpers.gravatar_url('Unknown', User::AVATAR_SIZES[:medium])
end
end
def date
@date ||= I18n.l(@commit.committed_date || @commit.authored_date, :format => :long)
@date ||= I18n.l(@committed_date || @authored_date, :format => :long)
end
def expandable?
@ -51,26 +72,32 @@ class GitPresenters::CommitAsMessagePresenter < ApplicationPresenter
false
end
protected
def committer
@committer ||= User.where(:email => @commit.committer.email).first || @commit.committer
def issue_referenced_state?
false
end
protected
def committer_link
@committer_link ||= if committer.is_a? User
link_to committer.uname, user_path(committer)
else
elsif committer.is_a? Grit::Actor
mail_to committer.email, committer.name
else # unknown committer
committer
end
end
def commit_link
link_to shortest_hash_id(@commit.id), commit_path(options[:project], @commit.id)
if @project
link_to shortest_hash_id(@commit_hash), commit_path(@project, @commit_hash)
else
shortest_hash_id(@commit_hash)
end
end
def prepare_message
(@caption, @content) = @commit.message.split("\n\n", 2)
(@caption, @content) = @commit_message.split("\n\n", 2)
@caption = 'empty message' unless @caption.present?
if @caption.length > 72
tmp = '...' + @caption[69..-1]

View File

@ -13,7 +13,7 @@
- if can? :show, @platform
%li{:class => (act == :index && contr == :maintainers) ? 'active' : nil}
= link_to t("layout.platforms.maintainers"), platform_maintainers_path(@platform)
- if can? :show, @platform
- if @platform.main? && can?(:show, @platform)
%li{:class => (contr == :mass_builds && [:index, :create].include?(act)) ? 'active' : ''}
= link_to t("layout.platforms.mass_build"), platform_mass_builds_path(@platform)
- if can? :read, @platform.products.build

View File

@ -1,7 +1,7 @@
= render 'platforms/base/submenu'
= render 'platforms/base/sidebar'
= render 'form' if can? :edit, @platform
= render 'form' if can? :create, @platform.mass_builds.new
%table.tablesorter.unbordered{:cellpadding => "0", :cellspacing => "0"}
%thead

View File

@ -1,5 +1,6 @@
- CommentPresenter.present(comment, data) do |presenter|
= render 'shared/feed_message', :presenter => presenter
-unless comment.automatic
#open-comment.comment.hidden{:class => "comment-#{comment.id}"}
=render 'projects/comments/button_md_help'
%h3.tmargin0= t("layout.comments.edit_header")

View File

@ -2,5 +2,9 @@
.hr
%h3#block-list= t("layout.comments.comments_header")
- list.each do |comment|
-unless comment.created_from_commit_hash
= render 'projects/comments/comment', :comment => comment, :data => {:project => project, :commentable => commentable}
-else
- GitPresenters::CommitAsMessagePresenter.present(nil, :comment => comment) do |presenter|
= render 'shared/feed_message', :presenter => presenter, :item_no => "-#{comment.id}"
= render "projects/comments/markdown_help"

View File

@ -17,23 +17,27 @@
_This will also be italic_
**This text will be bold**
__This will also be bold__
%p=link_to t('layout.comments.md_cheatsheet.emoji_header'), 'http://www.emoji-cheat-sheet.com/', :target => '_blank'
%pre
=":smile:"
=image_tag(image_path('emoji/smile.png'), :class => 'emoji', :title => 'smile', :alt => 'smile', :size => "20x20")
=" :+1:"
=image_tag(image_path('emoji/+1.png'), :class => 'emoji', :title => '+1', :alt => '+1', :size => "20x20")
.col
%h3=t 'layout.comments.md_cheatsheet.lists'
%p=t 'layout.comments.md_cheatsheet.unordered'
%p{:style => 'float:left;margin-left:20px;'}=t 'layout.comments.md_cheatsheet.unordered'
%p{:style => 'float:right;margin-right:40px;'}=t 'layout.comments.md_cheatsheet.ordered'
.both
%pre
:preserve
* Item 1
* Item 2
* Item 2a
* Item 2b
%p=t 'layout.comments.md_cheatsheet.ordered'
%pre
:preserve
1. Item 1
2. Item 2
3. Item 3
* Item 3a
* Item 1 1. Item 1
* Item 2 2. Item 2
* Item 2a 3. Item 3
* Item 2b * Item 3a
* Item 3b
%p=t 'layout.comments.md_cheatsheet.reference_format'
%pre
=preserve t('layout.comments.md_cheatsheet.reference_format_example').html_safe
.col
%h3=t 'layout.comments.md_cheatsheet.miscellaneous'
%p=t 'layout.comments.md_cheatsheet.images'
@ -84,3 +88,4 @@
:preserve
I think you should use an
`[addr]` element here instead.
.both

View File

@ -1,4 +1,5 @@
=render 'title_body', :f => f, :id => 'new'
- if can?(:write, @project)
.leftlist= t('activerecord.attributes.issue.assignee') + ':'
#assigned-container.rightlist
=render 'user_container', :user => @issue.assignee

View File

@ -22,4 +22,4 @@
= link_to image_tag(avatar_url(issue.assignee), :alt => 'avatar', :class => 'avatar'), user_path(issue.assignee) if issue.assignee
%a.answers{:href => "#{path}#block-list"}
= image_tag 'answers.png', :class => 'pic'
.count= issue.comments.count
.count=issue.comments.where(:automatic => false).count

View File

@ -1,8 +1,8 @@
-content_for :sidebar do
- can_manage = can?(:update, @issue) && @issue.persisted? || @issue.new_record?
- if @issue.persisted?
.bordered.nopadding
%h3=t('activerecord.attributes.issue.status')
- can_manage = can?(:update, @issue)
#switcher.issue_status{:class => "#{@issue.closed? ? 'switcher-off' : 'switcher'} #{can_manage ? "switch_issue_status" : ''}"}
.swleft=t('layout.issues.status.open')
.swright=t('layout.issues.status.closed')
@ -11,7 +11,7 @@
=hidden_field_tag "issue_status", @issue.closed? ? 'closed' : 'open', :name => "issue[status]"
.block
%h3=t('layout.issues.labels')
- if can_manage
- if can?(:write, @project)
.current_labels
- (@project.labels || []).each do |label|
- is_issue_label = @issue.labels.include? label
@ -29,6 +29,6 @@
.label.nopointer
.labeltext.selected{:style => "background: ##{label.color};"}=label.name
.both
- if can_manage && @issue.persisted?
- if can?(:write, @project) && @issue.persisted?
=link_to(t('layout.issues.label_manage'), '#', :class => "button tmargin10 manage_labels")
=link_to(t('layout.issues.done'), '#', :class => "button tmargin10 update_labels", :style => 'display:none')

View File

@ -6,5 +6,5 @@
%span= t('layout.issues.is_assigned')
- else
%span= t('layout.issues.no_one_is_assigned')
-if can?(:update, @issue) || @issue.new_record?
-if can?(:write, @project)
%span.icon-share

View File

@ -5,7 +5,11 @@
-if item.is_a? Comment
=render 'projects/git/commits/commits_small', :commits => commits_queue if commits_queue.present?
-commits_queue.clear
-unless item.created_from_commit_hash
= render 'projects/comments/comment', :comment => item, :data => {:project => @project, :commentable => @commentable}
-else
- GitPresenters::CommitAsMessagePresenter.present(nil, :comment => item) do |presenter|
= render 'shared/feed_message', :presenter => presenter, :item_no => "-#{item.id}"
-elsif item.is_a? Grit::Commit
-commits_queue << item
-elsif item.is_a? Array
@ -22,4 +26,3 @@
=render 'projects/pull_requests/discussion_comments', :item => item, :diff_counter => diff_counter
- diff_counter += 1
=render 'projects/git/commits/commits_small', :commits => commits_queue if commits_queue.present?

View File

@ -32,7 +32,7 @@
%div{:class => @pull.ready? ? 'notice' : 'alert'}
=pull_status @pull
.both
- if can?(:write, @pull.to_project)
.leftlist.big-list= t('activerecord.attributes.issue.assignee') + ':'
#assigned-container.rightlist
=render 'projects/issues/user_container', :user => @pull.assignee

View File

@ -17,7 +17,11 @@
.both
.both
- if presenter.content?
%div
=presenter.issue_referenced_state if presenter.issue_referenced_state?
.fulltext{:class => "#{presenter.expandable? ? "hidden" : ''} #{presenter.caption? ? "" : "alone"}",
:id => presenter.expandable? ? "content-expand#{item_no}" : ''}
.cm-s-default.md_and_cm=markdown presenter.content
.md_and_cm{:class => presenter.is_reference_to_issue ? '' : 'cm-s-default'}
=presenter.is_reference_to_issue ? presenter.content : markdown(presenter.content)
.both

View File

@ -10,3 +10,6 @@ Rosa::Application.config.middleware.insert_after ::Rails::Rack::Logger, ::Grack:
Rosa::Application.config.middleware.insert_before ::Grack::Handler, ::Grack::Auth
Rosa::Application.config.action_mailer.default_url_options = { :host => APP_CONFIG['action_mailer_host'] } if APP_CONFIG['action_mailer_host']
# Workaround for https://github.com/github/gemoji/pull/18
Rosa::Application.config.assets.paths << Emoji.images_path

View File

@ -7,3 +7,6 @@ en:
commit: commit
commits: commits
commits2: commits
reference: "%{committer} referenced this issue from a commit %{commit}"
deleted: Commit has since been removed from the git-repository and is no longer available.
unknown_committer: Unknown

View File

@ -7,3 +7,6 @@ ru:
commit: коммит
commits: коммита
commits2: коммитов
reference: "%{committer} ссылается на данную задачу в коммите %{commit}"
deleted: Коммит был удален и более недоступен в git-репозитории.
unknown_committer: Неизвестный

View File

@ -25,6 +25,22 @@ en:
syntax_highlighting: Syntax highlighting
indent_code: indent your code 4 spaces
inline_code: Inline code for comments
emoji_header: Emoji Cheat Sheet
reference_format: Reference Format
reference_format_example: |
for members: @abf
for issues:
#123 abf#123 abf/rosa-build#123
for pull requests:
!123 abf!123 abf/rosa-build!123
for commits: 123456
issues: Issues
pull_requests: Pull Requests
members: Members
commits: Commits
reference: "%{user} referenced this issue"
removed: Comment has since been removed and is no longer available.
flash:
comment:

View File

@ -25,6 +25,21 @@ ru:
syntax_highlighting: Подсветка синтаксиса
indent_code: Отступ кода на 4 пробела
inline_code: Встроенный код в строке
emoji_header: Шпаргалка по Emoji
reference_format: Формат ссылок
reference_format_example: |
для участников: @abf
для задач:
#123 abf#123 abf/rosa-build#123
для пул реквестов:
!123 abf!123 abf/rosa-build!123
для коммитов: 123456
issues: Задачи
pull_requests: Пул реквесты
members: Участники
commits: Коммиты
reference: "%{user} ссылается на данную задачу"
removed: Комментарий был удален и более недоступен.
flash:
comment:

View File

@ -1,5 +1,7 @@
en:
activerecord:
models:
issue: Issue
attributes:
issue:
title: Name

View File

@ -1,5 +1,7 @@
ru:
activerecord:
models:
issue: Задача
attributes:
issue:
title: Название

View File

@ -45,6 +45,8 @@ en:
save_error: Unable to save pull request
activerecord:
models:
pull_request: Pull Request
attributes:
pull_requests:
to_ref: Source

View File

@ -46,6 +46,8 @@ ru:
save_error: Не удалось сохранить пул реквест
activerecord:
models:
pull_request: Пул реквест
attributes:
pull_requests:
to_ref: Приемник

View File

@ -0,0 +1,9 @@
class AddAutomaticToComments < ActiveRecord::Migration
def change
add_column :comments, :automatic, :boolean, :default => false
add_index :comments, :commentable_type
add_index :comments, :automatic
add_index :comments, :commentable_id
end
end

View File

@ -0,0 +1,9 @@
class AddAddInfoToComments < ActiveRecord::Migration
def change
add_column :comments, :created_from_commit_hash, :decimal, :precision => 50, :scale => 0
add_column :comments, :created_from_issue_id, :integer
add_index :comments, :created_from_issue_id
add_index :comments, :created_from_commit_hash
end
end

View File

@ -12,18 +12,17 @@ class ReservedNameValidator < ActiveModel::EachValidator
jobs
login log-in log_in logout log-out log_out logs
map maps
new
new none
oauth oauth_clients openid
privacy
register remove replies rss root
save search sessions settings
signup sign-up sign_up signin sign-in sign_in signout sign-out sign_out
sitemap ssl subscribe
teams terms test trends tree
teams terms test tour trends tree
unfollow unsubscribe url user
widget widgets wiki
xfn xmpp
tour
}
def reserved_names

View File

@ -0,0 +1,199 @@
# This module is based on
# https://github.com/gitlabhq/gitlabhq/blob/397c3da9758c03a215a308c011f94261d9c61cfa/lib/gitlab/markdown.rb
module Modules
module Models
# Custom parser for GitLab-flavored Markdown
#
# It replaces references in the text with links to the appropriate items in
# GitLab.
#
# Supported reference formats are:
# * @foo for team members
# * for issues:
# * #123
# * abf#123
# * abf/rosa-build#123
# * for pull requests:
# * !123
# * abf!123
# * abf/rosa-build!123
# * 123456 for commits
#
# It also parses Emoji codes to insert images. See
# http://www.emoji-cheat-sheet.com/ for a list of the supported icons.
#
# Examples
#
# >> gfm("Hey @david, can you fix this?")
# => "Hey <a href="/users/david">@david</a>, can you fix this?"
#
# >> gfm("Commit 35d5f7c closes #1234")
# => "Commit <a href="/gitlab/commits/35d5f7c">35d5f7c</a> closes <a href="/gitlab/issues/1234">#1234</a>"
#
# >> gfm(":trollface:")
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
module Markdown
include IssuesHelper
attr_reader :html_options
# Public: Parse the provided text with GitLab-Flavored Markdown
#
# text - the source text
# html_options - extra options for the reference links as given to link_to
#
# Note: reference links will only be generated if @project is set
def gfm(text, html_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
@html_options = html_options
# Extract pre blocks so they are not altered
# from http://github.github.com/github-flavored-markdown/
text.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) { |match| extract_piece(match) }
# Extract links with probably parsable hrefs
text.gsub!(%r{<a.*?>.*?</a>}m) { |match| extract_piece(match) }
# Extract images with probably parsable src
text.gsub!(%r{<img.*?>}m) { |match| extract_piece(match) }
# TODO: add popups with additional information
text = parse(text)
# Insert pre block extractions
text.gsub!(/\{gfm-extraction-(\h{32})\}/) do
insert_piece($1)
end
sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class)
end
private
def extract_piece(text)
@extractions ||= {}
md5 = Digest::MD5.hexdigest(text)
@extractions[md5] = text
"{gfm-extraction-#{md5}}"
end
def insert_piece(id)
@extractions[id]
end
# Private: Parses text for references and emoji
#
# text - Text to parse
#
# Note: reference links will only be generated if @project is set
#
# Returns parsed text
def parse(text)
parse_references(text) if @project
parse_emoji(text)
text
end
REFERENCE_PATTERN = %r{
(?<prefix>[\W\/])? # Prefix
( # Reference
@(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User uname
|(?<issue>(?:[a-zA-Z0-9\-_]*\/)?(?:[a-zA-Z0-9\-_]*)?\#[0-9]+) # Issue ID
|(?<pull_request>(?:[a-zA-Z0-9\-_]*\/)?(?:[a-zA-Z0-9\-_]*)?\![0-9]+) # PR ID
|(?<commit>[\h]{6,40}) # Commit ID
)
(?<suffix>\W)? # Suffix
}x.freeze
TYPES = [:user, :issue, :pull_request, :commit].freeze
def parse_references(text)
# parse reference links
text.gsub!(REFERENCE_PATTERN) do |match|
prefix = $~[:prefix]
suffix = $~[:suffix]
type = TYPES.select{|t| !$~[t].nil?}.first
identifier = $~[type]
# Avoid HTML entities
if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
match
elsif ref_link = reference_link(type, identifier)
"#{prefix}#{ref_link}#{suffix}"
else
match
end
end
end
EMOJI_PATTERN = %r{(:(\S+):)}.freeze
def parse_emoji(text)
# parse emoji
text.gsub!(EMOJI_PATTERN) do |match|
if valid_emoji?($2)
image_tag(image_path("emoji/#{$2}.png"), class: 'emoji', title: $1, alt: $1, size: "20x20")
else
match
end
end
end
# Private: Checks if an emoji icon exists in the image asset directory
#
# emoji - Identifier of the emoji as a string (e.g., "+1", "heart")
#
# Returns boolean
def valid_emoji?(emoji)
Emoji.names.include? emoji
end
# Private: Dispatches to a dedicated processing method based on reference
#
# reference - Object reference ("@1234", "!567", etc.)
# identifier - Object identifier (Issue ID, SHA hash, etc.)
#
# Returns string rendered by the processing method
def reference_link(type, identifier)
send("reference_#{type}", identifier)
end
def reference_user(identifier)
if member = @project.all_members.select {|u| u.uname == identifier} #.joins(:user).where(users: {uname: identifier}).first
link_to("@#{identifier}", user_path(identifier), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member
end
end
def reference_issue(identifier)
if issue = Issue.find_by_hash_tag(identifier, current_ability, @project)
url = project_issue_path(issue.project.owner, issue.project.name, issue.serial_id)
title = "#{Issue.model_name.human}: #{issue.title}"
link_to(identifier, url, html_options.merge(title: title, class: "gfm gfm-issue #{html_options[:class]}"))
end
end
def reference_pull_request(identifier)
issue = Issue.find_by_hash_tag(identifier, current_ability, @project, '!')
if pull_request = issue.pull_request
title = "#{PullRequest.model_name.human}: #{pull_request.title}"
link_to(identifier, project_pull_request_path(pull_request.to_project, pull_request), html_options.merge(title: title, class: "gfm gfm-pull_request #{html_options[:class]}"))
end
end
def reference_commit(identifier)
if commit = @project.repo.commit(identifier)
link_to shortest_hash_id(@commit.id), commit_path(options[:project], @commit.id)
title = GitPresenters::CommitAsMessagePresenter.present(commit, :project => @project).caption
link_to(identifier, commit_path(@project, commit), html_options.merge(title: title, class: "gfm gfm-commit #{html_options[:class]}"))
end
end
end
end
end

View File

@ -0,0 +1,37 @@
# This class is based on
# https://github.com/gitlabhq/gitlabhq/blob/2bc78739a7aa9d7e5109281fc45dbd41a1a576d4/lib/gitlab/markdown.rb
class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
attr_reader :template
alias_method :h, :template
def initialize(template, options = {})
@template = template
@project = @template.instance_variable_get("@project")
super options
end
def block_code(code, language)
# New lines are placed to fix an rendering issue
# with code wrapped inside <h1> tag for next case:
#
# # Title kinda h1
#
# ruby code here
#
code_class = "class=\"#{language.downcase}\"" if language.present?
<<-HTML
<pre><code #{code_class}">#{code}</code></pre>
HTML
end
def link(link, title, content)
h.link_to_gfm(content, link, title: title)
end
def postprocess(full_document)
h.gfm(full_document)
end
end

View File

@ -2,13 +2,14 @@
require 'spec_helper'
shared_context "issues controller" do
before(:each) do
before do
stub_symlink_methods
@project = FactoryGirl.create(:project)
@issue_user = FactoryGirl.create(:user)
@issue = FactoryGirl.create(:issue, :project_id => @project.id, :assignee_id => @issue_user.id)
@label = FactoryGirl.create(:label, :project_id => @project.id)
@project_with_turned_off_issues = FactoryGirl.create(:project, :has_issues => false)
@turned_of_issue = FactoryGirl.create(:issue, :project_id => @project_with_turned_off_issues.id, :assignee_id => @issue_user.id)
@ -20,10 +21,10 @@ shared_context "issues controller" do
:owner_name => @project.owner.uname, :project_name => @project.name,
:issue => {
:title => "issue1",
:body => "issue body"
},
:assignee_id => @issue_user.id,
:assignee_uname => @issue_user.uname
:body => "issue body",
:labelings_attributes => { @label.id => {:label_id => @label.id}},
:assignee_id => @issue_user.id
}
}
@update_params = {
@ -56,9 +57,7 @@ shared_examples_for 'issue user with project reader rights' do
get :index, :owner_name => @project.owner.uname, :project_name => @project.name
response.should render_template(:index)
end
end
shared_examples_for 'issue user with project writer rights' do
it 'should be able to perform create action' do
post :create, @create_params
response.should redirect_to(project_issues_path(@project))
@ -69,6 +68,30 @@ shared_examples_for 'issue user with project writer rights' do
end
end
shared_examples_for 'issue user with project writer rights' do
it 'should be able to perform index action on hidden project' do
@project.update_attributes(:visibility => 'hidden')
get :index, :owner_name => @project.owner.uname, :project_name => @project.name
response.should render_template(:index)
end
it 'should create issue object into db' do
lambda{ post :create, @create_params }.should change{ Issue.count }.by(1)
end
context 'perform create action' do
before { post :create, @create_params }
it 'user should be assigned to issue' do
@project.issues.last.assignee_id.should_not be_nil
end
it 'label should be attached to issue' do
@project.issues.last.labels.should have(1).item
end
end
end
shared_examples_for 'user with issue update rights' do
it 'should be able to perform update action' do
put :update, {:id => @issue.serial_id}.merge(@update_params)
@ -167,11 +190,22 @@ describe Projects::IssuesController do
it_should_behave_like 'issue user with project guest rights'
it_should_behave_like 'issue user with project reader rights'
it_should_behave_like 'issue user with project writer rights'
it_should_behave_like 'user without issue update rights'
it_should_behave_like 'project with issues turned off'
it_should_behave_like 'user without issue destroy rights'
context 'perform create action' do
before { post :create, @create_params }
it 'user should not be assigned to issue' do
@project.issues.last.assignee_id.should be_nil
end
it 'label should not be attached to issue' do
@project.issues.last.labels.should have(:no).items
end
end
# it 'should not be able to perform create action on project' do
# post :create, @create_params
# response.should redirect_to(forbidden_path)

8
spec/factories/label.rb Normal file
View File

@ -0,0 +1,8 @@
# -*- encoding : utf-8 -*-
FactoryGirl.define do
factory :label do
name { FactoryGirl.generate(:string) }
color 'FFF'
association :project, :factory => :project
end
end

View File

@ -0,0 +1,7 @@
# -*- encoding : utf-8 -*-
FactoryGirl.define do
factory :labeling do
association :project, :factory => :project
association :label, :factory => :label
end
end

View File

@ -6,6 +6,10 @@ def create_comment user
FactoryGirl.create(:comment, :user => user, :commentable => @commit, :project => @project)
end
def create_comment_in_commit commit, project, body
FactoryGirl.create(:comment, :user => @user, :commentable => commit, :project => project, :body => body)
end
def set_comments_data_for_commit
@ability = Ability.new(@user)
@ -263,5 +267,63 @@ describe Comment do
should_not_send_email(commentor: @user)
end
end
context 'automatic issue linking' do
before(:each) do
@same_name_project = FactoryGirl.create(:project, :name => @project.name)
@issue_in_same_name_project = FactoryGirl.create(:issue, :project => @same_name_project, :user => @same_name_project.owner)
@another_project = FactoryGirl.create(:project, :owner => @user)
@other_user_project = FactoryGirl.create(:project)
@issue = FactoryGirl.create(:issue, :project => @project, :user => @user)
@second_issue = FactoryGirl.create(:issue, :project => @project, :user => @user)
@issue_in_another_project = FactoryGirl.create(:issue, :project => @another_project, :user => @user)
@issue_in_other_user_project = FactoryGirl.create(:issue, :project => @other_user_project, :user => @other_user_project.owner)
end
it 'should create automatic comment' do
create_comment_in_commit(@commit, @project, "test link to ##{@issue.serial_id}; [##{@second_issue.serial_id}]")
Comment.where(:automatic => true, :commentable_type => 'Issue',
:commentable_id => @second_issue.id,
:created_from_commit_hash => @commit.id.hex).count.should == 1
end
it 'should create automatic comment in the another project issue' do
body = "[#{@another_project.name_with_owner}##{@issue_in_another_project.serial_id}]"
create_comment_in_commit(@commit, @project, body)
Comment.where(:automatic => true, :commentable_type => 'Issue',
:commentable_id => @issue_in_another_project.id,
:created_from_commit_hash => @commit.id.hex).count.should == 1
end
it 'should create automatic comment in the same name project issue' do
body = "[#{@same_name_project.owner.uname}##{@issue_in_same_name_project.serial_id}]"
create_comment_in_commit(@commit, @project, body)
Comment.where(:automatic => true, :commentable_type => 'Issue',
:commentable_id => @issue_in_same_name_project.id,
:created_from_commit_hash => @commit.id.hex).count.should == 1
end
it 'should not create duplicate automatic comment' do
create_comment_in_commit(@commit, @project, "test link to [##{@second_issue.serial_id}]")
create_comment_in_commit(@commit, @project, "test duplicate link to [##{@second_issue.serial_id}]")
Comment.where(:automatic => true, :commentable_type => 'Issue',
:commentable_id => @second_issue.id,
:created_from_commit_hash => @commit.id.hex).count.should == 1
end
it 'should not create duplicate automatic comment from one' do
create_comment_in_commit(@commit, @project, "test link to [##{@second_issue.serial_id}]; ##{@second_issue.serial_id}")
Comment.where(:automatic => true, :commentable_type => 'Issue',
:commentable_id => @second_issue.id,
:created_from_commit_hash => @commit.id.hex).count.should == 1
end
it 'should create two automatic comment' do
body = "test ##{@second_issue.serial_id}" +
" && [#{@another_project.name_with_owner}##{@issue_in_another_project.serial_id}]"
create_comment_in_commit(@commit, @project, body)
Comment.where(:automatic => true,
:created_from_commit_hash => @commit.id.hex).count.should == 2
end
end
end
end

View File

@ -14,6 +14,10 @@ def set_commentable_data
any_instance_of(Project, :versions => ['v1.0', 'v2.0'])
end
def create_comment_in_issue issue, body
FactoryGirl.create(:comment, :user => issue.user, :commentable => issue, :project => issue.project, :body => body)
end
describe Comment do
before { stub_symlink_methods }
context 'for global admin user' do
@ -100,5 +104,69 @@ describe Comment do
@comment.should_not allow_mass_assignment_of :project_id
end
end
context 'automatic issue linking' do
before(:each) do
@same_name_project = FactoryGirl.create(:project, :name => @project.name)
@issue_in_same_name_project = FactoryGirl.create(:issue, :project => @same_name_project, :user => @same_name_project.owner)
@another_project = FactoryGirl.create(:project, :owner => @user)
@other_user_project = FactoryGirl.create(:project)
@issue = FactoryGirl.create(:issue, :project => @project, :user => @user)
@second_issue = FactoryGirl.create(:issue, :project => @project, :user => @user)
@issue_in_another_project = FactoryGirl.create(:issue, :project => @another_project, :user => @user)
@issue_in_other_user_project = FactoryGirl.create(:issue, :project => @other_user_project, :user => @other_user_project.owner)
end
it 'should create automatic comment' do
create_comment_in_issue(@issue, "test link to ##{@issue.serial_id}; [##{@second_issue.serial_id}]")
Comment.where(:automatic => true, :commentable_type => 'Issue',
:commentable_id => @second_issue.id,
:created_from_issue_id => @issue.id).count.should == 1
end
it 'should not create automatic comment to the same issue' do
create_comment_in_issue(@issue, "test link to ##{@issue.serial_id}; [##{@second_issue.serial_id}]")
Comment.where(:automatic => true,
:created_from_issue_id => @issue.id).count.should == 1
end
it 'should create automatic comment in the another project issue' do
body = "[#{@another_project.name_with_owner}##{@issue_in_another_project.serial_id}]"
create_comment_in_issue(@issue, body)
Comment.where(:automatic => true, :commentable_type => 'Issue',
:commentable_id => @issue_in_another_project.id,
:created_from_issue_id => @issue.id).count.should == 1
end
it 'should create automatic comment in the same name project issue' do
body = "[#{@same_name_project.owner.uname}##{@issue_in_same_name_project.serial_id}]"
create_comment_in_issue(@issue, body)
Comment.where(:automatic => true, :commentable_type => 'Issue',
:commentable_id => @issue_in_same_name_project.id,
:created_from_issue_id => @issue.id).count.should == 1
end
it 'should not create duplicate automatic comment' do
create_comment_in_issue(@issue, "test link to [##{@second_issue.serial_id}]")
create_comment_in_issue(@issue, "test duplicate link to [##{@second_issue.serial_id}]")
Comment.where(:automatic => true, :commentable_type => 'Issue',
:commentable_id => @second_issue.id,
:created_from_issue_id => @issue.id).count.should == 1
end
it 'should not create duplicate automatic comment from one' do
create_comment_in_issue(@issue, "test link to [##{@second_issue.serial_id}]; ##{@second_issue.serial_id}")
Comment.where(:automatic => true, :commentable_type => 'Issue',
:commentable_id => @second_issue.id,
:created_from_issue_id => @issue.id).count.should == 1
end
it 'should create two automatic comment' do
body = "test ##{@second_issue.serial_id}" +
" && [#{@another_project.name_with_owner}##{@issue_in_another_project.serial_id}]"
create_comment_in_issue(@issue, body)
Comment.where(:automatic => true,
:created_from_issue_id => @issue.id).count.should == 2
end
end
end
end