diff --git a/Gemfile b/Gemfile
index fb9224fac..49c6d12a4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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'
diff --git a/Gemfile.lock b/Gemfile.lock
index c73f08a63..2f620a232 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 432f4a399..c908583cb 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -3,4 +3,4 @@
@import "design/git";
@import "design/common";
@import "design/custom";
-@import "design/build_lists_monitoring";
\ No newline at end of file
+@import "design/build_lists_monitoring";
diff --git a/app/assets/stylesheets/design/custom.scss b/app/assets/stylesheets/design/custom.scss
index 078a44535..894c7097e 100644
--- a/app/assets/stylesheets/design/custom.scss
+++ b/app/assets/stylesheets/design/custom.scss
@@ -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;
+}
diff --git a/app/controllers/projects/comments_controller.rb b/app/controllers/projects/comments_controller.rb
index e1f7f1073..587d33c9a 100644
--- a/app/controllers/projects/comments_controller.rb
+++ b/app/controllers/projects/comments_controller.rb
@@ -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
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b4b03d4fd..0e11f2c38 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -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'
diff --git a/app/controllers/projects/pull_requests_controller.rb b/app/controllers/projects/pull_requests_controller.rb
index cce92b69e..c1d5e4d45 100644
--- a/app/controllers/projects/pull_requests_controller.rb
+++ b/app/controllers/projects/pull_requests_controller.rb
@@ -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
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index d2498da18..afacec351 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -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 = "
#{text}"
html << link_to('×', '#', :class => 'close close-alert', 'data-dismiss' => 'alert')
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
new file mode 100644
index 000000000..6a9ad45e9
--- /dev/null
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -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.
+ # "
outer text gfm ref more outer text"). This will not be
+ # interpreted as intended. Browsers will parse something like
+ # "
outer text gfm ref 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.
+ # "
outer text gfm ref more outer text").
+ def link_to_gfm(body, url, html_options = {})
+ return "" if body.blank?
+
+ escaped_body = if body =~ /^\
.*?}m) do |match|
+ "#{match}#{link_to("", url, html_options)[0..-5]}" # "".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
diff --git a/app/helpers/pull_request_helper.rb b/app/helpers/pull_request_helper.rb
index 0b9cc87d4..fe25b4b87 100644
--- a/app/helpers/pull_request_helper.rb
+++ b/app/helpers/pull_request_helper.rb
@@ -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'}
\
+ str = "#{t '.header'} #{t 'from'} \
#{show_ref pull, 'from'} \
#{t 'into'} \
#{show_ref pull, 'to'}"
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 1424c819f..37560e66a 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -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
diff --git a/app/models/activity_feed_observer.rb b/app/models/activity_feed_observer.rb
index 4d48d1572..4f8672e73 100644
--- a/app/models/activity_feed_observer.rb
+++ b/app/models/activity_feed_observer.rb
@@ -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|
@@ -67,15 +68,16 @@ class ActivityFeedObserver < ActiveRecord::Observer
(subscribe.user.committer?(record.commentable) && subscribe.user.notifier.new_comment_commit_owner) )
UserMailer.new_comment_notification(record, subscribe.user).deliver
end
- ActivityFeed.create(
- :user => subscribe.user,
- :kind => 'new_comment_commit_notification',
- :data => {:user_name => record.user.name, :user_email => record.user.email, :user_id => record.user_id, :comment_body => record.body,
- :commit_message => record.commentable.message, :commit_id => record.commentable.id,
- :project_id => record.project.id, :comment_id => record.id, :project_name => record.project.name, :project_owner => record.project.owner.uname}
- )
+ ActivityFeed.create(
+ :user => subscribe.user,
+ :kind => 'new_comment_commit_notification',
+ :data => {:user_name => record.user.name, :user_email => record.user.email, :user_id => record.user_id, :comment_body => record.body,
+ :commit_message => record.commentable.message, :commit_id => record.commentable.id,
+ :project_id => record.project.id, :comment_id => record.id, :project_name => record.project.name, :project_owner => record.project.owner.uname}
+ )
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
diff --git a/app/models/avatar.rb b/app/models/avatar.rb
index 2fbefa5e8..e4e4a4b7a 100644
--- a/app/models/avatar.rb
+++ b/app/models/avatar.rb
@@ -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
diff --git a/app/models/comment.rb b/app/models/comment.rb
index 61a2d1200..636d9e170 100644
--- a/app/models/comment.rb
+++ b/app/models/comment.rb
@@ -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
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 1d61be583..4e1ec3e1b 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -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
diff --git a/app/models/pull_request.rb b/app/models/pull_request.rb
index 47a2ff04d..9786b9dc0 100644
--- a/app/models/pull_request.rb
+++ b/app/models/pull_request.rb
@@ -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
diff --git a/app/presenters/comment_presenter.rb b/app/presenters/comment_presenter.rb
index ac9564c31..ddab23e53 100644
--- a/app/presenters/comment_presenter.rb
+++ b/app/presenters/comment_presenter.rb
@@ -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
- @content = @comment.body
+ 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 = "#{title}:"
+ issue_link = project_issue_path(issue.project, issue)
+ @content = "#{title} #{issue.title}".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
diff --git a/app/presenters/git_presenters/commit_as_message_presenter.rb b/app/presenters/git_presenters/commit_as_message_presenter.rb
index 1d2604f55..2d611d04e 100644
--- a/app/presenters/git_presenters/commit_as_message_presenter.rb
+++ b/app/presenters/git_presenters/commit_as_message_presenter.rb
@@ -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)
- end.html_safe
+ @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,33 +72,39 @@ class GitPresenters::CommitAsMessagePresenter < ApplicationPresenter
false
end
+ def issue_referenced_state?
+ false
+ end
+
protected
- def committer
- @committer ||= User.where(:email => @commit.committer.email).first || @commit.committer
+ def committer_link
+ @committer_link ||= if committer.is_a? User
+ link_to committer.uname, user_path(committer)
+ elsif committer.is_a? Grit::Actor
+ mail_to committer.email, committer.name
+ else # unknown committer
+ committer
end
+ end
- def committer_link
- @committer_link ||= if committer.is_a? User
- link_to committer.uname, user_path(committer)
- else
- mail_to committer.email, committer.name
- end
+ def commit_link
+ if @project
+ link_to shortest_hash_id(@commit_hash), commit_path(@project, @commit_hash)
+ else
+ shortest_hash_id(@commit_hash)
end
+ end
- def commit_link
- link_to shortest_hash_id(@commit.id), commit_path(options[:project], @commit.id)
+ def prepare_message
+ (@caption, @content) = @commit_message.split("\n\n", 2)
+ @caption = 'empty message' unless @caption.present?
+ if @caption.length > 72
+ tmp = '...' + @caption[69..-1]
+ @content = (@content.present?) ? tmp + @content : tmp
+ @caption = @caption[0..68] + '...'
end
-
- def prepare_message
- (@caption, @content) = @commit.message.split("\n\n", 2)
- @caption = 'empty message' unless @caption.present?
- if @caption.length > 72
- tmp = '...' + @caption[69..-1]
- @content = (@content.present?) ? tmp + @content : tmp
- @caption = @caption[0..68] + '...'
- end
# @content = @content.gsub("\n", "
").html_safe if @content
- @content = simple_format(@content, {}, :sanitize => true).html_safe if @content
- end
+ @content = simple_format(@content, {}, :sanitize => true).html_safe if @content
+ end
end
diff --git a/app/views/platforms/base/_sidebar.html.haml b/app/views/platforms/base/_sidebar.html.haml
index b5a857be5..4edefc28a 100644
--- a/app/views/platforms/base/_sidebar.html.haml
+++ b/app/views/platforms/base/_sidebar.html.haml
@@ -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
diff --git a/app/views/platforms/mass_builds/index.html.haml b/app/views/platforms/mass_builds/index.html.haml
index d965430c2..c82ed00b6 100644
--- a/app/views/platforms/mass_builds/index.html.haml
+++ b/app/views/platforms/mass_builds/index.html.haml
@@ -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
diff --git a/app/views/projects/comments/_comment.html.haml b/app/views/projects/comments/_comment.html.haml
index 0c25b2ac3..88c0e9f78 100644
--- a/app/views/projects/comments/_comment.html.haml
+++ b/app/views/projects/comments/_comment.html.haml
@@ -1,10 +1,11 @@
- CommentPresenter.present(comment, data) do |presenter|
= render 'shared/feed_message', :presenter => presenter
-#open-comment.comment.hidden{:class => "comment-#{comment.id}"}
- =render 'projects/comments/button_md_help'
- %h3.tmargin0= t("layout.comments.edit_header")
- = form_for comment, :url => project_commentable_comment_path(data[:project], data[:commentable], comment), :html => { :class => 'form edit_comment' } do |f|
- = render "projects/comments/form", :f => f, :id => "#{data[:add_id]}edit_#{comment.id}"
- .comment-left
- =link_to t('layout.cancel'), '#', :id => "comment-#{comment.id}", :class => 'cancel_edit_comment button'
- .both
+-unless comment.automatic
+ #open-comment.comment.hidden{:class => "comment-#{comment.id}"}
+ =render 'projects/comments/button_md_help'
+ %h3.tmargin0= t("layout.comments.edit_header")
+ = form_for comment, :url => project_commentable_comment_path(data[:project], data[:commentable], comment), :html => { :class => 'form edit_comment' } do |f|
+ = render "projects/comments/form", :f => f, :id => "#{data[:add_id]}edit_#{comment.id}"
+ .comment-left
+ =link_to t('layout.cancel'), '#', :id => "comment-#{comment.id}", :class => 'cancel_edit_comment button'
+ .both
diff --git a/app/views/projects/comments/_list.html.haml b/app/views/projects/comments/_list.html.haml
index e63505aa7..f7edaff1d 100644
--- a/app/views/projects/comments/_list.html.haml
+++ b/app/views/projects/comments/_list.html.haml
@@ -2,5 +2,9 @@
.hr
%h3#block-list= t("layout.comments.comments_header")
- list.each do |comment|
- = render 'projects/comments/comment', :comment => comment, :data => {:project => project, :commentable => commentable}
+ -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"
diff --git a/app/views/projects/comments/_markdown_help.html.haml b/app/views/projects/comments/_markdown_help.html.haml
index 28cf08dd8..3c9a27af4 100644
--- a/app/views/projects/comments/_markdown_help.html.haml
+++ b/app/views/projects/comments/_markdown_help.html.haml
@@ -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'
+ * 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
- 1. Item 1
- 2. Item 2
- 3. Item 3
- * Item 3a
- * Item 3b
+ =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
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index a0948aebb..d960bafa9 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,13 +1,14 @@
=render 'title_body', :f => f, :id => 'new'
-.leftlist= t('activerecord.attributes.issue.assignee') + ':'
-#assigned-container.rightlist
- =render 'user_container', :user => @issue.assignee
-.both
-.leftlist= t('layout.issues.labels') + ':'
-.rightlist
- %span#flag-span.small-text= t('layout.issues.choose_labels_on_left')
- #issue_labels
-.both
+- if can?(:write, @project)
+ .leftlist= t('activerecord.attributes.issue.assignee') + ':'
+ #assigned-container.rightlist
+ =render 'user_container', :user => @issue.assignee
+ .both
+ .leftlist= t('layout.issues.labels') + ':'
+ .rightlist
+ %span#flag-span.small-text= t('layout.issues.choose_labels_on_left')
+ #issue_labels
+ .both
.leftlist
.rightlist
%input{:type => "submit", :value => t(@issue.new_record? ? 'layout.create' : 'layout.update')}
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 2795d61c2..0154d5aa3 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -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
\ No newline at end of file
+ .count=issue.comments.where(:automatic => false).count
diff --git a/app/views/projects/issues/_manage_sidebar.html.haml b/app/views/projects/issues/_manage_sidebar.html.haml
index 98144b2d2..1ba6fa829 100644
--- a/app/views/projects/issues/_manage_sidebar.html.haml
+++ b/app/views/projects/issues/_manage_sidebar.html.haml
@@ -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')
diff --git a/app/views/projects/issues/_user_container.html.haml b/app/views/projects/issues/_user_container.html.haml
index 47b78037d..21c1ca3a6 100644
--- a/app/views/projects/issues/_user_container.html.haml
+++ b/app/views/projects/issues/_user_container.html.haml
@@ -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
\ No newline at end of file
diff --git a/app/views/projects/pull_requests/_activity.html.haml b/app/views/projects/pull_requests/_activity.html.haml
index cbde0e05b..7141c49db 100644
--- a/app/views/projects/pull_requests/_activity.html.haml
+++ b/app/views/projects/pull_requests/_activity.html.haml
@@ -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
- =render 'projects/comments/comment', :comment => item, :data => {:project => @project, :commentable => @commentable}
+ -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?
-
diff --git a/app/views/projects/pull_requests/new.html.haml b/app/views/projects/pull_requests/new.html.haml
index a4d30a2d8..9b148e318 100644
--- a/app/views/projects/pull_requests/new.html.haml
+++ b/app/views/projects/pull_requests/new.html.haml
@@ -32,11 +32,11 @@
%div{:class => @pull.ready? ? 'notice' : 'alert'}
=pull_status @pull
.both
-
- .leftlist.big-list= t('activerecord.attributes.issue.assignee') + ':'
- #assigned-container.rightlist
- =render 'projects/issues/user_container', :user => @pull.assignee
- .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
+ .both
.leftlist.big-list
.rightlist
=f.submit t('.submit'), :class => 'btn btn-primary disabled', 'data-loading-text' => t('layout.processing'), :id => 'create_pull' unless @pull.already?
diff --git a/app/views/shared/_feed_message.html.haml b/app/views/shared/_feed_message.html.haml
index 414b4cc9b..2caadaf11 100644
--- a/app/views/shared/_feed_message.html.haml
+++ b/app/views/shared/_feed_message.html.haml
@@ -17,7 +17,11 @@
.both
.both
- if presenter.content?
- .fulltext{:class => "#{presenter.expandable? ? "hidden" : ''} #{presenter.caption? ? "" : "alone"}",
- :id => presenter.expandable? ? "content-expand#{item_no}" : ''}
- .cm-s-default.md_and_cm=markdown presenter.content
- .both
+ %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}" : ''}
+ .md_and_cm{:class => presenter.is_reference_to_issue ? '' : 'cm-s-default'}
+ =presenter.is_reference_to_issue ? presenter.content : markdown(presenter.content)
+ .both
+
diff --git a/config/initializers/setup.rb b/config/initializers/setup.rb
index bb9d9b5c0..39f5bf3f3 100644
--- a/config/initializers/setup.rb
+++ b/config/initializers/setup.rb
@@ -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
\ No newline at end of file
diff --git a/config/locales/layout/commits.en.yml b/config/locales/layout/commits.en.yml
index 33b5b42c3..359d0f4d8 100644
--- a/config/locales/layout/commits.en.yml
+++ b/config/locales/layout/commits.en.yml
@@ -6,4 +6,7 @@ en:
pluralize:
commit: commit
commits: commits
- commits2: commits
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/config/locales/layout/commits.ru.yml b/config/locales/layout/commits.ru.yml
index 3e88b0da3..4f3f5d4a8 100644
--- a/config/locales/layout/commits.ru.yml
+++ b/config/locales/layout/commits.ru.yml
@@ -6,4 +6,7 @@ ru:
pluralize:
commit: коммит
commits: коммита
- commits2: коммитов
\ No newline at end of file
+ commits2: коммитов
+ reference: "%{committer} ссылается на данную задачу в коммите %{commit}"
+ deleted: Коммит был удален и более недоступен в git-репозитории.
+ unknown_committer: Неизвестный
\ No newline at end of file
diff --git a/config/locales/models/comment.en.yml b/config/locales/models/comment.en.yml
index 8f3e5a4a6..5def48633 100644
--- a/config/locales/models/comment.en.yml
+++ b/config/locales/models/comment.en.yml
@@ -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:
diff --git a/config/locales/models/comment.ru.yml b/config/locales/models/comment.ru.yml
index f481d904c..e4c5b0c4c 100644
--- a/config/locales/models/comment.ru.yml
+++ b/config/locales/models/comment.ru.yml
@@ -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:
diff --git a/config/locales/models/issue.en.yml b/config/locales/models/issue.en.yml
index c06dfd908..b9930d4e2 100644
--- a/config/locales/models/issue.en.yml
+++ b/config/locales/models/issue.en.yml
@@ -1,5 +1,7 @@
en:
activerecord:
+ models:
+ issue: Issue
attributes:
issue:
title: Name
diff --git a/config/locales/models/issue.ru.yml b/config/locales/models/issue.ru.yml
index 1e1e3c190..51bd8db5e 100644
--- a/config/locales/models/issue.ru.yml
+++ b/config/locales/models/issue.ru.yml
@@ -1,5 +1,7 @@
ru:
activerecord:
+ models:
+ issue: Задача
attributes:
issue:
title: Название
diff --git a/config/locales/models/pull_request.en.yml b/config/locales/models/pull_request.en.yml
index 156bf8192..a27122c72 100644
--- a/config/locales/models/pull_request.en.yml
+++ b/config/locales/models/pull_request.en.yml
@@ -45,6 +45,8 @@ en:
save_error: Unable to save pull request
activerecord:
+ models:
+ pull_request: Pull Request
attributes:
pull_requests:
to_ref: Source
diff --git a/config/locales/models/pull_request.ru.yml b/config/locales/models/pull_request.ru.yml
index 26dd0f761..e67132b06 100644
--- a/config/locales/models/pull_request.ru.yml
+++ b/config/locales/models/pull_request.ru.yml
@@ -46,6 +46,8 @@ ru:
save_error: Не удалось сохранить пул реквест
activerecord:
+ models:
+ pull_request: Пул реквест
attributes:
pull_requests:
to_ref: Приемник
diff --git a/db/migrate/20130319172358_add_automatic_to_comments.rb b/db/migrate/20130319172358_add_automatic_to_comments.rb
new file mode 100644
index 000000000..a3f2d24bc
--- /dev/null
+++ b/db/migrate/20130319172358_add_automatic_to_comments.rb
@@ -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
diff --git a/db/migrate/20130326165628_add_add_info_to_comments.rb b/db/migrate/20130326165628_add_add_info_to_comments.rb
new file mode 100644
index 000000000..5a88f8414
--- /dev/null
+++ b/db/migrate/20130326165628_add_add_info_to_comments.rb
@@ -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
diff --git a/lib/ext/rails/reserved_name_validator.rb b/lib/ext/rails/reserved_name_validator.rb
index 7d578e6c4..408c8b05f 100644
--- a/lib/ext/rails/reserved_name_validator.rb
+++ b/lib/ext/rails/reserved_name_validator.rb
@@ -3,7 +3,7 @@ class ReservedNameValidator < ActiveModel::EachValidator
about account add admin administrator api autocomplete_group_uname
app apps archive archives auth
blog
- config connect contact create commit commits
+ config connect contact create commit commits
dashboard delete direct_messages downloads
edit email
faq favorites feed feeds follow followers following
@@ -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
diff --git a/lib/modules/models/markdown.rb b/lib/modules/models/markdown.rb
new file mode 100644
index 000000000..9efb3eded
--- /dev/null
+++ b/lib/modules/models/markdown.rb
@@ -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 @david, can you fix this?"
+ #
+ # >> gfm("Commit 35d5f7c closes #1234")
+ # => "Commit 35d5f7c closes #1234"
+ #
+ # >> gfm(":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{.*?
|.*?
}m) { |match| extract_piece(match) }
+ # Extract links with probably parsable hrefs
+ text.gsub!(%r{.*?}m) { |match| extract_piece(match) }
+ # Extract images with probably parsable src
+ text.gsub!(%r{}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{
+ (?[\W\/])? # Prefix
+ ( # Reference
+ @(?[a-zA-Z][a-zA-Z0-9_\-\.]*) # User uname
+ |(?(?:[a-zA-Z0-9\-_]*\/)?(?:[a-zA-Z0-9\-_]*)?\#[0-9]+) # Issue ID
+ |(?(?:[a-zA-Z0-9\-_]*\/)?(?:[a-zA-Z0-9\-_]*)?\![0-9]+) # PR ID
+ |(?[\h]{6,40}) # Commit ID
+ )
+ (?\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
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
new file mode 100644
index 000000000..a3e3e6724
--- /dev/null
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -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 tag for next case:
+ #
+ # # Title kinda h1
+ #
+ # ruby code here
+ #
+ code_class = "class=\"#{language.downcase}\"" if language.present?
+ <<-HTML
+
+ #{code}
+
+ 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
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 65873a0a3..d6a7dd000 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -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)
diff --git a/spec/factories/label.rb b/spec/factories/label.rb
new file mode 100644
index 000000000..b985a2d98
--- /dev/null
+++ b/spec/factories/label.rb
@@ -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
\ No newline at end of file
diff --git a/spec/factories/labeling.rb b/spec/factories/labeling.rb
new file mode 100644
index 000000000..51d1f6d70
--- /dev/null
+++ b/spec/factories/labeling.rb
@@ -0,0 +1,7 @@
+# -*- encoding : utf-8 -*-
+FactoryGirl.define do
+ factory :labeling do
+ association :project, :factory => :project
+ association :label, :factory => :label
+ end
+end
\ No newline at end of file
diff --git a/spec/models/comment_for_commit_spec.rb b/spec/models/comment_for_commit_spec.rb
index a3b113a4a..9972a699a 100644
--- a/spec/models/comment_for_commit_spec.rb
+++ b/spec/models/comment_for_commit_spec.rb
@@ -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
diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb
index 93e1dfc35..ef67f810f 100644
--- a/spec/models/comment_spec.rb
+++ b/spec/models/comment_spec.rb
@@ -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