From c18b71bf0b9971c0571df95bbf67468264bf7139 Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Thu, 12 Jan 2012 00:10:23 +0600 Subject: [PATCH 01/16] [refs #18] changed refs id to string --- app/models/issue.rb | 6 +++++- .../20120111080234_change_commentable_id.rb | 9 +++++++++ db/schema.rb | 19 +++++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20120111080234_change_commentable_id.rb diff --git a/app/models/issue.rb b/app/models/issue.rb index 9ae96f439..04ec41356 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -4,7 +4,11 @@ class Issue < ActiveRecord::Base belongs_to :project belongs_to :user - has_many :comments, :as => :commentable + has_many :comments, :as => :commentable, + :finder_sql => 'SELECT comments.* FROM comments ' + + 'WHERE comments.commentable_id = \'#{self.id}\' ' + + ' AND comments.commentable_type = \'#{self.class.name}\' ' + + 'ORDER BY comments.created_at' has_many :subscribes, :as => :subscribeable validates :title, :body, :project_id, :presence => true diff --git a/db/migrate/20120111080234_change_commentable_id.rb b/db/migrate/20120111080234_change_commentable_id.rb new file mode 100644 index 000000000..60c3c3268 --- /dev/null +++ b/db/migrate/20120111080234_change_commentable_id.rb @@ -0,0 +1,9 @@ +class ChangeCommentableId < ActiveRecord::Migration + def self.up + change_column :comments, :commentable_id, :string + end + + def self.down + change_column :comments, :commentable_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index c4eb51827..a943f2d61 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20111228182425) do +ActiveRecord::Schema.define(:version => 20120111080234) do create_table "arches", :force => true do |t| t.string "name", :null => false @@ -87,7 +87,7 @@ ActiveRecord::Schema.define(:version => 20111228182425) do end create_table "comments", :force => true do |t| - t.integer "commentable_id" + t.string "commentable_id" t.string "commentable_type" t.integer "user_id" t.text "body" @@ -165,6 +165,13 @@ ActiveRecord::Schema.define(:version => 20111228182425) do add_index "issues", ["project_id", "serial_id"], :name => "index_issues_on_project_id_and_serial_id", :unique => true + create_table "permissions", :force => true do |t| + t.integer "right_id" + t.integer "role_id" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "platforms", :force => true do |t| t.string "description" t.string "name" @@ -262,6 +269,14 @@ ActiveRecord::Schema.define(:version => 20111228182425) do t.string "owner_type" end + create_table "rights", :force => true do |t| + t.string "name", :null => false + t.string "controller", :null => false + t.string "action", :null => false + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "rpms", :force => true do |t| t.string "name", :null => false t.integer "arch_id", :null => false From 41933604449dd22ff8993fc983ad6dfa0d7b1d5d Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Thu, 12 Jan 2012 00:15:35 +0600 Subject: [PATCH 02/16] [refs #18] add comments to commit --- app/controllers/comments_controller.rb | 17 ++++++++++++----- app/models/comment.rb | 3 ++- app/models/project.rb | 14 ++++++++++++-- app/models/repository.rb | 2 +- app/views/comments/_form.html.haml | 2 +- app/views/comments/edit.html.haml | 4 ++-- app/views/git/blobs/show.html.haml | 19 ++++++++++++++++++- app/views/git/commits/show.html.haml | 18 ++++++++++++++++++ config/routes.rb | 17 ++++++++++------- 9 files changed, 76 insertions(+), 20 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 4b70d8127..c295d39f0 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,7 +1,7 @@ class CommentsController < ApplicationController before_filter :authenticate_user! before_filter :set_commentable, :only => [:index, :edit, :create] - before_filter :find_project, :only => [:index] + before_filter :find_project, :only => [:index, :edit] before_filter :find_comment, :only => [:edit, :update, :destroy] authorize_resource :only => [:show, :edit, :update, :destroy] @@ -24,8 +24,14 @@ class CommentsController < ApplicationController end def edit - @issue = @commentable - @project = @issue.project + if @commentable + @issue = @commentable + @update_url = project_issue_comment_path(@project, @issue.id, @comment) + end + if @comment.commentable_type == 'Commit' + @commit = @project.git_repository.commit(@comment.commentable_id) + @update_url = project_commit_comment_path(@project, @commit, @comment) + end end def update @@ -55,7 +61,8 @@ class CommentsController < ApplicationController # end #end #nil - return Issue.find(params[:issue_id]) + return nil if params[:issue_id].nil? + Issue.find(params[:issue_id]) end def set_commentable @@ -67,6 +74,6 @@ class CommentsController < ApplicationController end def find_project - @project = @comment.commentable.project + @project = Project.find(params[:project_id]) end end diff --git a/app/models/comment.rb b/app/models/comment.rb index aceb80a7a..aa9dcf11a 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -9,7 +9,8 @@ class Comment < ActiveRecord::Base protected def deliver_new_comment_notification - subscribes = self.commentable.subscribes + return if self.commentable_type = 'Commit' + subscribes = self.commentable.try(:subscribes) subscribes.each do |subscribe| recipient = subscribe.user UserMailer.delay.new_comment_notification(self, recipient) diff --git a/app/models/project.rb b/app/models/project.rb index 8ef6d8753..428384b27 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -66,7 +66,7 @@ class Project < ActiveRecord::Base res = tags.select{|tag| tag.name =~ /^v\./} return res if res and res.size > 0 tags - end + end def collected_project_versions project_versions.collect{|tag| tag.name.gsub(/^\w+\./, "")} end @@ -118,7 +118,7 @@ class Project < ActiveRecord::Base return true else raise "Failed to create project #{name} (repo #{repository.name}) inside platform #{repository.platform.name} in path #{path} with code #{result}." - end + end end def xml_rpc_destroy(repository) @@ -134,6 +134,16 @@ class Project < ActiveRecord::Base @platforms ||= repositories.map(&:platform).uniq end + class << self + def commit_comments(commit) + Comment.where(:commentable_id => commit.id, :commentable_type => 'Commit') + end + + def build_commit_comment(commit) + Comment.new(:commentable_id => commit.id, :commentable_type => "Commit") + end + end + protected def build_path(dir) diff --git a/app/models/repository.rb b/app/models/repository.rb index 13c4e2a02..b9246f86a 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -48,7 +48,7 @@ class Repository < ActiveRecord::Base return true else raise "Failed to create repository #{name} inside platform #{platform.name} with code #{result}." - end + end end def xml_rpc_destroy diff --git a/app/views/comments/_form.html.haml b/app/views/comments/_form.html.haml index 4078f495e..03d6ce434 100644 --- a/app/views/comments/_form.html.haml +++ b/app/views/comments/_form.html.haml @@ -7,4 +7,4 @@ = image_tag("web-app-theme/icons/tick.png", :alt => t("layout.save")) = t("layout.save") %span.text_button_padding= t("layout.or") - = link_to t("layout.cancel"), [@issue.project, @issue], :class => "text_button_padding link_button" + = link_to t("layout.cancel"), [@project, @issue], :class => "text_button_padding link_button" diff --git a/app/views/comments/edit.html.haml b/app/views/comments/edit.html.haml index 5f2dc3ffd..5b039f318 100644 --- a/app/views/comments/edit.html.haml +++ b/app/views/comments/edit.html.haml @@ -1,10 +1,10 @@ .block .secondary-navigation %ul.wat-cf - %li.first= link_to t("layout.issues.list"), project_issue_path(@project, @issue) + %li.first= link_to t("layout.issues.list"), @issue ? project_issue_path(@project, @issue) : commit_path(@project, @commit) .content %h2.title = t("layout.issues.edit_header") .inner - = form_for @comment, :url => project_issue_comment_path(@project, @issue.id, @comment), :html => { :class => :form } do |f| + = form_for @comment, :url => @update_url, :html => { :class => :form } do |f| = render :partial => "form", :locals => {:f => f} diff --git a/app/views/git/blobs/show.html.haml b/app/views/git/blobs/show.html.haml index 2059f0a03..b9c8e79a8 100644 --- a/app/views/git/blobs/show.html.haml +++ b/app/views/git/blobs/show.html.haml @@ -31,4 +31,21 @@ :plain
#{render_blob(@blob)}
-- content_for :sidebar, render(:partial => 'git/shared/sidebar') \ No newline at end of file +- content_for :sidebar, render(:partial => 'git/shared/sidebar') + +%a{ :name => "comments" } +.block#block-list + .content + %h2.title + = t("layout.issues.comments_header") + .inner + %ul.list + - @project.commit_comments(@commit).each do |comment| + %li + .left + = link_to comment.user.uname, user_path(comment.user.uname) + .item + = comment.body + %br + %br + diff --git a/app/views/git/commits/show.html.haml b/app/views/git/commits/show.html.haml index e5d39833e..bb45ade99 100644 --- a/app/views/git/commits/show.html.haml +++ b/app/views/git/commits/show.html.haml @@ -26,3 +26,21 @@ %p= t 'layout.git.repositories.commit_diff_too_big' - content_for :sidebar, render(:partial => 'git/shared/sidebar') + +%a{ :name => "comments" } +.block#block-list + .content + %h2.title + = t("layout.issues.comments_header") + .inner + %ul.list + - Project.commit_comments(@commit).each do |comment| + %li + .left + = link_to comment.user.uname, user_path(comment.user.uname) + .item + = comment.body + %br + %br + = link_to t("layout.edit"), edit_project_commit_comment_path(@project, @commit, comment) if can? :update, comment + = link_to image_tag("web-app-theme/icons/cross.png", :alt => t("layout.delete")) + " " + t("layout.delete"), project_commit_comment_path(@project, @commit, comment), :method => "delete", :class => "button", :confirm => t("layout.comments.confirm_delete") if can? :delete, comment diff --git a/config/routes.rb b/config/routes.rb index 7f3449cde..253b886f7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,11 +1,11 @@ Rosa::Application.routes.draw do # XML RPC match 'api/xmlrpc' => 'rpc#xe_index' - + devise_for :users, :controllers => {:omniauth_callbacks => 'users/omniauth_callbacks'} do get '/users/auth/:provider' => 'users/omniauth_callbacks#passthru' end - + resources :users do resources :groups, :only => [:new, :create, :index] get :autocomplete_user_uname, :on => :collection @@ -142,20 +142,23 @@ Rosa::Application.routes.draw do # Tree match '/projects/:project_id/git/tree/:treeish(/*path)', :controller => "git/trees", :action => :show, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :tree - + # Commits match '/projects/:project_id/git/commits/:treeish(/*path)', :controller => "git/commits", :action => :index, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :commits match '/projects/:project_id/git/commit/:id(.:format)', :controller => "git/commits", :action => :show, :defaults => { :format => :html }, :as => :commit - + # Commit Comments + match '/projects/:project_id/git/commit/:commit_id/:id(.:format)', :controller => "comments", :action => :edit, :as => :edit_project_commit_comment, :via => :get + match '/projects/:project_id/git/commit/:commit_id/:id(.:format)', :controller => "comments", :action => :update, :as => :project_commit_comment, :via => :put + match '/projects/:project_id/git/commit/:commit_id/:id(.:format)', :controller => "comments", :action => :delete, :via => :delete # Blobs match '/projects/:project_id/git/blob/:treeish/*path', :controller => "git/blobs", :action => :show, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :blob match '/projects/:project_id/git/commit/blob/:commit_hash/*path', :controller => "git/blobs", :action => :show, :project_name => /[0-9a-zA-Z_.\-]*/, :as => :blob_commit - + # Blame match '/projects/:project_id/git/blame/:treeish/*path', :controller => "git/blobs", :action => :blame, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :blame match '/projects/:project_id/git/commit/blame/:commit_hash/*path', :controller => "git/blobs", :action => :blame, :as => :blame_commit - - # Raw + + # Raw match '/projects/:project_id/git/raw/:treeish/*path', :controller => "git/blobs", :action => :raw, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :raw match '/projects/:project_id/git/commit/raw/:commit_hash/*path', :controller => "git/blobs", :action => :raw, :as => :raw_commit From 07dc7a48007ef111839063805f387d630105a2e1 Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Thu, 12 Jan 2012 00:15:35 +0600 Subject: [PATCH 03/16] [refs #18] add comments to commit --- app/controllers/comments_controller.rb | 29 ++++++++++++++++------- app/controllers/git/commits_controller.rb | 2 +- app/models/comment.rb | 3 ++- app/models/project.rb | 10 ++++++-- app/models/repository.rb | 2 +- app/views/comments/_form.html.haml | 2 +- app/views/comments/edit.html.haml | 4 ++-- app/views/git/blobs/show.html.haml | 19 ++++++++++++++- app/views/git/commits/show.html.haml | 26 ++++++++++++++++++++ config/routes.rb | 18 ++++++++------ 10 files changed, 90 insertions(+), 25 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 4b70d8127..da4bfc127 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,8 +1,8 @@ class CommentsController < ApplicationController before_filter :authenticate_user! - before_filter :set_commentable, :only => [:index, :edit, :create] - before_filter :find_project, :only => [:index] before_filter :find_comment, :only => [:edit, :update, :destroy] + before_filter :set_commentable, :only => [:index, :edit, :create, :update] + #before_filter :find_project, :only => [:index, :edit] authorize_resource :only => [:show, :edit, :update, :destroy] authorize_resource :project, :only => [:index] @@ -12,11 +12,12 @@ class CommentsController < ApplicationController end def create - @comment = @commentable.comments.build(params[:comment]) + @comment = @commentable.comments.build(params[:comment]) if @commentable.class == Issue + @comment = Comment.new(params[:comment].merge(:commentable_id => @commentable.id, :commentable_type => @commentable.class.name)) if @commentable.class == Grit::Commit @comment.user = current_user if @comment.save flash[:notice] = I18n.t("flash.comment.saved") - redirect_to [@commentable.project, @commentable] + redirect_to :back else flash[:error] = I18n.t("flash.comment.save_error") render :action => 'new' @@ -24,15 +25,19 @@ class CommentsController < ApplicationController end def edit - @issue = @commentable - @project = @issue.project + @update_url = case @commentable.class.name + when "Issue" + project_issue_comment_path(@project, @commentable, @comment) + when "Grit::Commit" + project_commit_comment_path(@project, @commentable.id, @comment) + end end def update if @comment.update_attributes(params[:comment]) flash[:notice] = I18n.t("flash.comment.saved") #redirect_to :back - redirect_to [@comment.commentable.project, @comment.commentable] + redirect_to @commentable_path else flash[:error] = I18n.t("flash.comment.save_error") render :action => 'new' @@ -55,11 +60,17 @@ class CommentsController < ApplicationController # end #end #nil - return Issue.find(params[:issue_id]) + if params[:issue_id].present? + return Issue.find(params[:issue_id]) + elsif params[:commit_id].present? + return @project.git_repository.commit(params[:commit_id]) + end end def set_commentable + find_project @commentable = find_commentable + @commentable_path = @commentable.class == Issue ? project_issue_path(@project, @commentable) : commit_path(@project.id, @commentable.id) end def find_comment @@ -67,6 +78,6 @@ class CommentsController < ApplicationController end def find_project - @project = @comment.commentable.project + @project = Project.find(params[:project_id]) end end diff --git a/app/controllers/git/commits_controller.rb b/app/controllers/git/commits_controller.rb index 04b1a94dc..021d60cee 100644 --- a/app/controllers/git/commits_controller.rb +++ b/app/controllers/git/commits_controller.rb @@ -23,4 +23,4 @@ class Git::CommitsController < Git::BaseController end end -end \ No newline at end of file +end diff --git a/app/models/comment.rb b/app/models/comment.rb index aceb80a7a..aa9dcf11a 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -9,7 +9,8 @@ class Comment < ActiveRecord::Base protected def deliver_new_comment_notification - subscribes = self.commentable.subscribes + return if self.commentable_type = 'Commit' + subscribes = self.commentable.try(:subscribes) subscribes.each do |subscribe| recipient = subscribe.user UserMailer.delay.new_comment_notification(self, recipient) diff --git a/app/models/project.rb b/app/models/project.rb index 8ef6d8753..82dd4f563 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -66,7 +66,7 @@ class Project < ActiveRecord::Base res = tags.select{|tag| tag.name =~ /^v\./} return res if res and res.size > 0 tags - end + end def collected_project_versions project_versions.collect{|tag| tag.name.gsub(/^\w+\./, "")} end @@ -118,7 +118,7 @@ class Project < ActiveRecord::Base return true else raise "Failed to create project #{name} (repo #{repository.name}) inside platform #{repository.platform.name} in path #{path} with code #{result}." - end + end end def xml_rpc_destroy(repository) @@ -134,6 +134,12 @@ class Project < ActiveRecord::Base @platforms ||= repositories.map(&:platform).uniq end + class << self + def commit_comments(commit) + Comment.where(:commentable_id => commit.id, :commentable_type => 'Commit') + end + end + protected def build_path(dir) diff --git a/app/models/repository.rb b/app/models/repository.rb index 13c4e2a02..b9246f86a 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -48,7 +48,7 @@ class Repository < ActiveRecord::Base return true else raise "Failed to create repository #{name} inside platform #{platform.name} with code #{result}." - end + end end def xml_rpc_destroy diff --git a/app/views/comments/_form.html.haml b/app/views/comments/_form.html.haml index 4078f495e..03d6ce434 100644 --- a/app/views/comments/_form.html.haml +++ b/app/views/comments/_form.html.haml @@ -7,4 +7,4 @@ = image_tag("web-app-theme/icons/tick.png", :alt => t("layout.save")) = t("layout.save") %span.text_button_padding= t("layout.or") - = link_to t("layout.cancel"), [@issue.project, @issue], :class => "text_button_padding link_button" + = link_to t("layout.cancel"), [@project, @issue], :class => "text_button_padding link_button" diff --git a/app/views/comments/edit.html.haml b/app/views/comments/edit.html.haml index 5f2dc3ffd..b5bbe01c4 100644 --- a/app/views/comments/edit.html.haml +++ b/app/views/comments/edit.html.haml @@ -1,10 +1,10 @@ .block .secondary-navigation %ul.wat-cf - %li.first= link_to t("layout.issues.list"), project_issue_path(@project, @issue) + %li.first= link_to t("layout.issues.list"), @commentable_path .content %h2.title = t("layout.issues.edit_header") .inner - = form_for @comment, :url => project_issue_comment_path(@project, @issue.id, @comment), :html => { :class => :form } do |f| + = form_for @comment, :url => @update_url, :html => { :class => :form } do |f| = render :partial => "form", :locals => {:f => f} diff --git a/app/views/git/blobs/show.html.haml b/app/views/git/blobs/show.html.haml index 2059f0a03..b9c8e79a8 100644 --- a/app/views/git/blobs/show.html.haml +++ b/app/views/git/blobs/show.html.haml @@ -31,4 +31,21 @@ :plain
#{render_blob(@blob)}
-- content_for :sidebar, render(:partial => 'git/shared/sidebar') \ No newline at end of file +- content_for :sidebar, render(:partial => 'git/shared/sidebar') + +%a{ :name => "comments" } +.block#block-list + .content + %h2.title + = t("layout.issues.comments_header") + .inner + %ul.list + - @project.commit_comments(@commit).each do |comment| + %li + .left + = link_to comment.user.uname, user_path(comment.user.uname) + .item + = comment.body + %br + %br + diff --git a/app/views/git/commits/show.html.haml b/app/views/git/commits/show.html.haml index e5d39833e..25269c581 100644 --- a/app/views/git/commits/show.html.haml +++ b/app/views/git/commits/show.html.haml @@ -26,3 +26,29 @@ %p= t 'layout.git.repositories.commit_diff_too_big' - content_for :sidebar, render(:partial => 'git/shared/sidebar') + +%a{ :name => "comments" } +.block#block-list + .content + %h2.title + = t("layout.issues.comments_header") + .inner + %ul.list + - Project.commit_comments(@commit).each do |comment| + %li + .left + = link_to comment.user.uname, user_path(comment.user.uname) + .item + = comment.body + %br + %br + = link_to t("layout.edit"), edit_project_commit_comment_path(@project, @commit.id, comment) if can? :update, comment + = link_to image_tag("web-app-theme/icons/cross.png", :alt => t("layout.delete")) + " " + t("layout.delete"), project_commit_comment_path(@project, @commit.id, comment), :method => "delete", :class => "button", :confirm => t("layout.comments.confirm_delete") if can? :delete, comment + +.block + .content + %h2.title + = t("layout.comments.new_header") + .inner + = form_for :comment, :url => project_commit_comments_path(@project, @commit.id), :method => :post, :html => { :class => :form } do |f| + = render :partial => "comments/form", :locals => {:f => f} diff --git a/config/routes.rb b/config/routes.rb index 7f3449cde..5cd3cc3c1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,11 +1,11 @@ Rosa::Application.routes.draw do # XML RPC match 'api/xmlrpc' => 'rpc#xe_index' - + devise_for :users, :controllers => {:omniauth_callbacks => 'users/omniauth_callbacks'} do get '/users/auth/:provider' => 'users/omniauth_callbacks#passthru' end - + resources :users do resources :groups, :only => [:new, :create, :index] get :autocomplete_user_uname, :on => :collection @@ -142,20 +142,24 @@ Rosa::Application.routes.draw do # Tree match '/projects/:project_id/git/tree/:treeish(/*path)', :controller => "git/trees", :action => :show, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :tree - + # Commits match '/projects/:project_id/git/commits/:treeish(/*path)', :controller => "git/commits", :action => :index, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :commits match '/projects/:project_id/git/commit/:id(.:format)', :controller => "git/commits", :action => :show, :defaults => { :format => :html }, :as => :commit - + # Commit Comments + match '/projects/:project_id/git/commit/:commit_id/comments/:id(.:format)', :controller => "comments", :action => :edit, :as => :edit_project_commit_comment, :via => :get + match '/projects/:project_id/git/commit/:commit_id/comments/:id(.:format)', :controller => "comments", :action => :update, :as => :project_commit_comment, :via => :put + match '/projects/:project_id/git/commit/:commit_id/comments/:id(.:format)', :controller => "comments", :action => :destroy, :via => :delete + match '/projects/:project_id/git/commit/:commit_id/comments(.:format)', :controller => "comments", :action => :create, :as => :project_commit_comments, :via => :post # Blobs match '/projects/:project_id/git/blob/:treeish/*path', :controller => "git/blobs", :action => :show, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :blob match '/projects/:project_id/git/commit/blob/:commit_hash/*path', :controller => "git/blobs", :action => :show, :project_name => /[0-9a-zA-Z_.\-]*/, :as => :blob_commit - + # Blame match '/projects/:project_id/git/blame/:treeish/*path', :controller => "git/blobs", :action => :blame, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :blame match '/projects/:project_id/git/commit/blame/:commit_hash/*path', :controller => "git/blobs", :action => :blame, :as => :blame_commit - - # Raw + + # Raw match '/projects/:project_id/git/raw/:treeish/*path', :controller => "git/blobs", :action => :raw, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :raw match '/projects/:project_id/git/commit/raw/:commit_hash/*path', :controller => "git/blobs", :action => :raw, :as => :raw_commit From f8d00f52353b0ad4412c0c6111ff367e96fc0dd3 Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Thu, 12 Jan 2012 17:22:12 +0600 Subject: [PATCH 04/16] [refs #18] Fix cancan ability for commit comments --- app/models/ability.rb | 11 ++++++----- app/models/comment.rb | 1 + app/models/project.rb | 5 +++-- app/views/git/commits/show.html.haml | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 1b7509a2b..0c1684db0 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,8 +1,8 @@ # If rules goes one by one CanCan joins them by 'OR' sql operator # If rule has multiple conditions CanCan joins them by 'AND' sql operator -# WARNING: +# WARNING: # - put cannot rules _after_ can rules and not before! -# - beware inner joins. Use sub queries against them! +# - beware inner joins. Use sub queries against them! class Ability include CanCan::Ability @@ -89,9 +89,10 @@ class Ability can([:update, :destroy], Issue) {|issue| issue.user_id == user.id or local_admin?(issue.project)} cannot :manage, Issue, :project => {:has_issues => false} # switch off issues - can(:create, Comment) {|comment| can? :read, comment.commentable.project} - can(:update, Comment) {|comment| comment.user_id == user.id or local_admin?(comment.commentable.project)} - cannot :manage, Comment, :commentable => {:project => {:has_issues => false}} # switch off issues + can(:create, Comment) {|comment| can? :read, comment.project || comment.commentable.project} + can([:update, :delete], Comment) {|comment| comment.user_id == user.id or local_admin?(comment.project || comment.commentable.project)} + #cannot :manage, Comment, :commentable => {:project => {:has_issues => false}} # switch off issues + cannot(:manage, Comment) {|comment| comment.commentable_type == 'Issue' && !comment.commentable.project.has_issues} # switch off issues end end diff --git a/app/models/comment.rb b/app/models/comment.rb index aa9dcf11a..6925a28d7 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,6 +1,7 @@ class Comment < ActiveRecord::Base belongs_to :commentable, :polymorphic => true belongs_to :user + attr_accessor :project validates :body, :user_id, :commentable_id, :commentable_type, :presence => true diff --git a/app/models/project.rb b/app/models/project.rb index 82dd4f563..4cc5c143c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -135,8 +135,9 @@ class Project < ActiveRecord::Base end class << self - def commit_comments(commit) - Comment.where(:commentable_id => commit.id, :commentable_type => 'Commit') + def commit_comments(commit, project) + comments = Comment.where(:commentable_id => commit.id, :commentable_type => 'Grit::Commit') + comments.each {|x| x.project = project} end end diff --git a/app/views/git/commits/show.html.haml b/app/views/git/commits/show.html.haml index 25269c581..421c5d2e2 100644 --- a/app/views/git/commits/show.html.haml +++ b/app/views/git/commits/show.html.haml @@ -34,7 +34,7 @@ = t("layout.issues.comments_header") .inner %ul.list - - Project.commit_comments(@commit).each do |comment| + - Project.commit_comments(@commit, @project).each do |comment| %li .left = link_to comment.user.uname, user_path(comment.user.uname) From 636c90e6e42d8ea5416ce6b50449b9a515b22669 Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Thu, 12 Jan 2012 20:27:33 +0600 Subject: [PATCH 05/16] [refs #18] add some I18n --- app/views/comments/edit.html.haml | 4 ++-- config/locales/ru.yml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/comments/edit.html.haml b/app/views/comments/edit.html.haml index b5bbe01c4..eb586af80 100644 --- a/app/views/comments/edit.html.haml +++ b/app/views/comments/edit.html.haml @@ -1,10 +1,10 @@ .block .secondary-navigation %ul.wat-cf - %li.first= link_to t("layout.issues.list"), @commentable_path + %li.first= link_to t(@comment.commentable_type == 'Grit::Commit' ? "layout.git.repositories.commits" : "layout.issues.list"), @commentable_path .content %h2.title - = t("layout.issues.edit_header") + = t("layout.#{@comment.commentable_type == 'Grit::Commit' ? 'comments' : 'issues'}.edit_header") .inner = form_for @comment, :url => @update_url, :html => { :class => :form } do |f| = render :partial => "form", :locals => {:f => f} diff --git a/config/locales/ru.yml b/config/locales/ru.yml index ef20defa1..dfa655fc9 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -114,6 +114,7 @@ ru: comments: confirm_delete: Вы уверены, что хотите удалить комментарий? new_header: Новый комментарий + edit_header: Редактирование комментария platforms: admin_id: Владелец From 630346d2e30dc284d4ae08702aabad2df9f2a675 Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Thu, 12 Jan 2012 20:48:08 +0600 Subject: [PATCH 06/16] [refs #18] fix cancel button --- app/views/comments/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/comments/_form.html.haml b/app/views/comments/_form.html.haml index 03d6ce434..462aca983 100644 --- a/app/views/comments/_form.html.haml +++ b/app/views/comments/_form.html.haml @@ -7,4 +7,4 @@ = image_tag("web-app-theme/icons/tick.png", :alt => t("layout.save")) = t("layout.save") %span.text_button_padding= t("layout.or") - = link_to t("layout.cancel"), [@project, @issue], :class => "text_button_padding link_button" + = link_to t("layout.cancel"), :back , :class => "text_button_padding link_button" From caf7adef70cbf62889220c1f63045c4be1f7592f Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Thu, 12 Jan 2012 20:50:27 +0600 Subject: [PATCH 07/16] [refs #18] FIXME subscribe don't work --- app/models/comment.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/comment.rb b/app/models/comment.rb index 6925a28d7..6f3c6045a 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -10,8 +10,8 @@ class Comment < ActiveRecord::Base protected def deliver_new_comment_notification - return if self.commentable_type = 'Commit' - subscribes = self.commentable.try(:subscribes) + return if self.commentable_type == 'Grit::Commit' # FIXME + subscribes = self.commentable.subscribes subscribes.each do |subscribe| recipient = subscribe.user UserMailer.delay.new_comment_notification(self, recipient) From 825236753720164e5ba1df7da877ebc909281552 Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Fri, 13 Jan 2012 21:58:48 +0600 Subject: [PATCH 08/16] some fix --- app/controllers/comments_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index da4bfc127..d280e0ea6 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,8 +1,8 @@ class CommentsController < ApplicationController before_filter :authenticate_user! - before_filter :find_comment, :only => [:edit, :update, :destroy] before_filter :set_commentable, :only => [:index, :edit, :create, :update] #before_filter :find_project, :only => [:index, :edit] + before_filter :find_comment, :only => [:edit, :update, :destroy] authorize_resource :only => [:show, :edit, :update, :destroy] authorize_resource :project, :only => [:index] @@ -75,6 +75,7 @@ class CommentsController < ApplicationController def find_comment @comment = Comment.find(params[:id]) + @comment.project = @project if @comment.commentable_type == 'Grit::Commit' end def find_project From 2b1fe79adc12869913b66cee0582946b1174d913 Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Fri, 13 Jan 2012 22:00:28 +0600 Subject: [PATCH 09/16] [refs #18] controller tests --- .../comments_controller_for_commit_spec.rb | 149 +++++++++++++++ spec/tests.git/HEAD | 1 + spec/tests.git/config | 11 ++ spec/tests.git/description | 1 + spec/tests.git/hooks/applypatch-msg.sample | 15 ++ spec/tests.git/hooks/commit-msg.sample | 24 +++ spec/tests.git/hooks/post-commit.sample | 8 + spec/tests.git/hooks/post-receive.sample | 15 ++ spec/tests.git/hooks/post-update.sample | 8 + spec/tests.git/hooks/pre-applypatch.sample | 14 ++ spec/tests.git/hooks/pre-commit.sample | 46 +++++ spec/tests.git/hooks/pre-rebase.sample | 172 ++++++++++++++++++ .../tests.git/hooks/prepare-commit-msg.sample | 36 ++++ spec/tests.git/hooks/update.sample | 128 +++++++++++++ spec/tests.git/index | Bin 0 -> 192 bytes spec/tests.git/info/exclude | 6 + spec/tests.git/logs/HEAD | 2 + spec/tests.git/logs/refs/heads/master | 1 + ...85f74f028bf49d2611c9fea56570138a196143.idx | Bin 0 -> 3844 bytes ...5f74f028bf49d2611c9fea56570138a196143.pack | Bin 0 -> 24472 bytes spec/tests.git/packed-refs | 5 + spec/tests.git/refs/heads/master | 1 + spec/tests.git/refs/remotes/origin/HEAD | 1 + 23 files changed, 644 insertions(+) create mode 100644 spec/controllers/comments_controller_for_commit_spec.rb create mode 100644 spec/tests.git/HEAD create mode 100644 spec/tests.git/config create mode 100644 spec/tests.git/description create mode 100755 spec/tests.git/hooks/applypatch-msg.sample create mode 100755 spec/tests.git/hooks/commit-msg.sample create mode 100755 spec/tests.git/hooks/post-commit.sample create mode 100755 spec/tests.git/hooks/post-receive.sample create mode 100755 spec/tests.git/hooks/post-update.sample create mode 100755 spec/tests.git/hooks/pre-applypatch.sample create mode 100755 spec/tests.git/hooks/pre-commit.sample create mode 100755 spec/tests.git/hooks/pre-rebase.sample create mode 100755 spec/tests.git/hooks/prepare-commit-msg.sample create mode 100755 spec/tests.git/hooks/update.sample create mode 100644 spec/tests.git/index create mode 100644 spec/tests.git/info/exclude create mode 100644 spec/tests.git/logs/HEAD create mode 100644 spec/tests.git/logs/refs/heads/master create mode 100644 spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.idx create mode 100644 spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.pack create mode 100644 spec/tests.git/packed-refs create mode 100644 spec/tests.git/refs/heads/master create mode 100644 spec/tests.git/refs/remotes/origin/HEAD diff --git a/spec/controllers/comments_controller_for_commit_spec.rb b/spec/controllers/comments_controller_for_commit_spec.rb new file mode 100644 index 000000000..fccd9c57b --- /dev/null +++ b/spec/controllers/comments_controller_for_commit_spec.rb @@ -0,0 +1,149 @@ +require 'spec_helper' + +shared_examples_for 'user with create comment rights' do + it 'should be able to perform create action' do + post :create, @create_params + response.should redirect_to(commit_path(@project, @commit.id)) + end + + it 'should create subscribe object into db' do + lambda{ post :create, @create_params }.should change{ Comment.count }.by(1) + end +end + +shared_examples_for 'user with update own comment rights' do + it 'should be able to perform update action' do + put :update, {:id => @own_comment.id}.merge(@update_params) + response.should redirect_to(commit_path(@project, @commit.id)) + end + + it 'should update subscribe body' do + put :update, {:id => @own_comment.id}.merge(@update_params) + @own_comment.reload.body.should == 'updated' + end +end + +shared_examples_for 'user with update stranger comment rights' do + it 'should be able to perform update action' do + put :update, {:id => @comment.id}.merge(@update_params) + response.should redirect_to(commit_path(@project, @commit.id)) + end + + it 'should update comment title' do + put :update, {:id => @comment.id}.merge(@update_params) + @comment.reload.body.should == 'updated' + end +end + +shared_examples_for 'user without update stranger comment rights' do + it 'should not be able to perform update action' do + put :update, {:id => @comment.id}.merge(@update_params) + response.should redirect_to(forbidden_path) + end + + it 'should not update comment title' do + put :update, {:id => @comment.id}.merge(@update_params) + @comment.reload.body.should_not == 'updated' + end +end + +shared_examples_for 'user without destroy comment rights' do + it 'should not be able to perform destroy action' do + delete :destroy, :id => @comment.id, :commit_id => @commit.id, :project_id => @project.id + response.should redirect_to(forbidden_path) + end +end + +#shared_examples_for 'user with destroy rights' do +# it 'should be able to perform destroy action' do +# delete :destroy, :id => @comment.id, :issue_id => @issue.id, :project_id => @project.id +# response.should redirect_to([@project, @issue]) +# end +# +# it 'should reduce comments count' do +# lambda{ delete :destroy, :id => @comment.id, :issue_id => @issue.id, :project_id => @project.id }.should change{ Comment.count }.by(-1) +# end +#end + +describe CommentsController do + before(:each) do + stub_rsync_methods + + @project = Factory(:project) + %x(cp -Rf #{Rails.root}/spec/tests.git/* #{@project.git_repository.path}) # maybe FIXME ? + @commit = @project.git_repository.commits.first + + @comment = Factory(:comment) + @comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) + @create_params = {:comment => {:body => 'I am a comment!'}, :project_id => @project.id, :commit_id => @commit.id} + @update_params = {:comment => {:body => 'updated'}, :project_id => @project.id, :commit_id => @commit.id} + + any_instance_of(Project, :versions => ['v1.0', 'v2.0']) + + @request.env['HTTP_REFERER'] = commit_path(@project, @commit.id) + end + + context 'for project admin user' do + before(:each) do + @user = Factory(:user) + set_session_for(@user) + @project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin') + @own_comment = Factory(:comment, :user => @user) + @own_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) + end + + it_should_behave_like 'user with create comment rights' + it_should_behave_like 'user with update stranger comment rights' + it_should_behave_like 'user with update own comment rights' + it_should_behave_like 'user without destroy comment rights' + end + + #~ context 'for project owner user' do + #~ before(:each) do + #~ @user = Factory(:user) + #~ set_session_for(@user) + #~ @project.update_attribute(:owner, @user) + #~ @project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin') + + #~ @own_comment = Factory(:comment, :user => @user) + #~ @own_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) + #~ end + + #~ it_should_behave_like 'user with create comment rights' + #~ it_should_behave_like 'user with update stranger comment rights' + #~ it_should_behave_like 'user with update own comment rights' + #~ it_should_behave_like 'user without destroy comment rights' + #~ end + + context 'for project reader user' do + before(:each) do + @user = Factory(:user) + set_session_for(@user) + @project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'reader') + + @own_comment = Factory(:comment, :user => @user) + @own_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) + end + + it_should_behave_like 'user with create comment rights' + it_should_behave_like 'user without update stranger comment rights' + it_should_behave_like 'user with update own comment rights' + it_should_behave_like 'user without destroy comment rights' + end + + context 'for project writer user' do + before(:each) do + @user = Factory(:user) + set_session_for(@user) + @project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'writer') + + @own_comment = Factory(:comment, :user => @user) + @own_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) + end + + it_should_behave_like 'user with create comment rights' + it_should_behave_like 'user without update stranger comment rights' + it_should_behave_like 'user with update own comment rights' + it_should_behave_like 'user without destroy comment rights' + end +end \ No newline at end of file diff --git a/spec/tests.git/HEAD b/spec/tests.git/HEAD new file mode 100644 index 000000000..df9ee5473 --- /dev/null +++ b/spec/tests.git/HEAD @@ -0,0 +1 @@ +bdc8b580b5b583aeb43efb19aac2ab8ce5566dff diff --git a/spec/tests.git/config b/spec/tests.git/config new file mode 100644 index 000000000..d38f8c6fe --- /dev/null +++ b/spec/tests.git/config @@ -0,0 +1,11 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = git://github.com/tpope/vim-ragtag.git +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/spec/tests.git/description b/spec/tests.git/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/spec/tests.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/spec/tests.git/hooks/applypatch-msg.sample b/spec/tests.git/hooks/applypatch-msg.sample new file mode 100755 index 000000000..8b2a2fe84 --- /dev/null +++ b/spec/tests.git/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/spec/tests.git/hooks/commit-msg.sample b/spec/tests.git/hooks/commit-msg.sample new file mode 100755 index 000000000..b58d1184a --- /dev/null +++ b/spec/tests.git/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/spec/tests.git/hooks/post-commit.sample b/spec/tests.git/hooks/post-commit.sample new file mode 100755 index 000000000..22668216a --- /dev/null +++ b/spec/tests.git/hooks/post-commit.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script that is called after a successful +# commit is made. +# +# To enable this hook, rename this file to "post-commit". + +: Nothing diff --git a/spec/tests.git/hooks/post-receive.sample b/spec/tests.git/hooks/post-receive.sample new file mode 100755 index 000000000..7a83e17ab --- /dev/null +++ b/spec/tests.git/hooks/post-receive.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script for the "post-receive" event. +# +# The "post-receive" script is run after receive-pack has accepted a pack +# and the repository has been updated. It is passed arguments in through +# stdin in the form +# +# For example: +# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master +# +# see contrib/hooks/ for a sample, or uncomment the next line and +# rename the file to "post-receive". + +#. /usr/share/doc/git-core/contrib/hooks/post-receive-email diff --git a/spec/tests.git/hooks/post-update.sample b/spec/tests.git/hooks/post-update.sample new file mode 100755 index 000000000..ec17ec193 --- /dev/null +++ b/spec/tests.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/spec/tests.git/hooks/pre-applypatch.sample b/spec/tests.git/hooks/pre-applypatch.sample new file mode 100755 index 000000000..b1f187c2e --- /dev/null +++ b/spec/tests.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} +: diff --git a/spec/tests.git/hooks/pre-commit.sample b/spec/tests.git/hooks/pre-commit.sample new file mode 100755 index 000000000..b187c4bb1 --- /dev/null +++ b/spec/tests.git/hooks/pre-commit.sample @@ -0,0 +1,46 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ascii filenames set this variable to true. +allownonascii=$(git config hooks.allownonascii) + +# Cross platform projects tend to avoid non-ascii filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test "$(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0')" +then + echo "Error: Attempt to add a non-ascii file name." + echo + echo "This can cause problems if you want to work" + echo "with people on other platforms." + echo + echo "To be portable it is advisable to rename the file ..." + echo + echo "If you know what you are doing you can disable this" + echo "check using:" + echo + echo " git config hooks.allownonascii true" + echo + exit 1 +fi + +exec git diff-index --check --cached $against -- diff --git a/spec/tests.git/hooks/pre-rebase.sample b/spec/tests.git/hooks/pre-rebase.sample new file mode 100755 index 000000000..f0f6da314 --- /dev/null +++ b/spec/tests.git/hooks/pre-rebase.sample @@ -0,0 +1,172 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +<<\DOC_END +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/spec/tests.git/hooks/prepare-commit-msg.sample b/spec/tests.git/hooks/prepare-commit-msg.sample new file mode 100755 index 000000000..f093a02ec --- /dev/null +++ b/spec/tests.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/spec/tests.git/hooks/update.sample b/spec/tests.git/hooks/update.sample new file mode 100755 index 000000000..71ab04edc --- /dev/null +++ b/spec/tests.git/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to blocks unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/spec/tests.git/index b/spec/tests.git/index new file mode 100644 index 0000000000000000000000000000000000000000..29cca27d49d6f2554fc33e2e5e1a79a665961080 GIT binary patch literal 192 zcmZ?q402{*U|<4af0l-MK$-zYGca&4GvqkvGcYtRVPIf>0l|Ds5ug6KrR<(*ZXfz= z&fKOt!(Y)gYz%xU`N{f4iRmSY>3Ss 1325695134 +0600 clone: from git://github.com/tpope/vim-ragtag.git +bdc8b580b5b583aeb43efb19aac2ab8ce5566dff bdc8b580b5b583aeb43efb19aac2ab8ce5566dff Alexander 1325695134 +0600 checkout: moving from master to bdc8b580b5b583aeb43efb19aac2ab8ce5566dff diff --git a/spec/tests.git/logs/refs/heads/master b/spec/tests.git/logs/refs/heads/master new file mode 100644 index 000000000..9219de536 --- /dev/null +++ b/spec/tests.git/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 bdc8b580b5b583aeb43efb19aac2ab8ce5566dff Alexander 1325695134 +0600 clone: from git://github.com/tpope/vim-ragtag.git diff --git a/spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.idx b/spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.idx new file mode 100644 index 0000000000000000000000000000000000000000..46bd6036da21943b3ae5a3be1b6c0de1b3c0973f GIT binary patch literal 3844 zcmbuCc`(&~7soGIQnD{sq13g;MadqQYbWAbZq_U%)X&yMwya4=gc6cU*|YCkvhNWs zWZx-SBjPz`m@&WUY37;d&b;P*KIgNX@40`y&-P6%O9TQz4D9@N^ly;B{@);l{r!-^ z`hS5O&hKz=7Yfil4ns`&UpNAJDuCt>&_a$LVAzEbbU#e6-h&z9KY@h&epq0AbeChG ze}WbAY`d_79^c0ai2no!M%(;M?Ui=nnk*5P%r{F9cz| zhY-X&2=8(RbceIRxm`p-#deVZ-9ZXCzl#j$4zd8|FUY~3{2#zVZVv^B6#=DPltFh; z-9;Vr!Y-PiT7TdodADC3`qV$75LsRkD&VB-={B{dUEQTm{={41y1G^%T@oOV9YSBR&A_Mh75M>1AIkNhnY zsyL&vXOpM}8O5cf81Vry^%j2f+vfbf(sEwg9A_O@uYPvsiaqD-8#^63z-JIjf67$B zn&zhURz@8uuIyWqY213E0q1FMs$;I!MJV9|HRqT&sU8#>>hb6)jt<5+c)ROSeWtxr zNl?T@_3)T{44!4b>L@+8l)vVhAtB&7{SU9O%h_d3=KeEg7sqC5Z8y~$Qbq-p1ixP= zE)=z3l29*~Gh4**Ts|w5wUvPo$eTO3%^mX5;(?W}jXAq8i%y)hUs!5UiKWNWNLd0! zS$2W&Dp`ZlF)i_!ku}j%!Td9wB{VCd@2X^&+gI-!yvI8Jmi3hGq;PT*Lp{B*p`i7M zA9H@#4Tp*_3DaW~3-PAo=civn)4Z=H5o+!7OzK(`$F@aeUP~M9Oiw`Zyc+p|VLfdn zIWxPYkQz2lt>C4ya)p=y!|E?6b8;@#7-LFX^yccR*Ata)xVz!P-qz-6|~iahdaQUr6V=MzrN_H&WDV9gb>daT|&WKA-**`!&XC95q_t)7(lr;g0=o zYP?)CtKps9u*zlMsZ^Z7?p3-?H9u33(LS4|z_z3*6KEc*?JZ$;@TyYvVcrWZrQiJt zo{HwoPJS!Lhp~R#SW+W&lH0khZ+E*>nge5lqz~XZ2L@8V_42;Y>i(^G=TOiK_Ya%% zcT-m9^#`+cx3dY))s%zP5$G-kd7127(#jLfJlX1RRv1Hy@vP1yDDobP(9Q>Ri%Jga z^>LwMQ|@I|nR15w_?tQg+vc3| z=vR72Ki!uQE8@uT^%7wbm5?=|mJbnD=ba}%5o{$R^eQU(z9_Oc*;#h5`ru5-KPg#7 zm^?!S9Yy|@O{I&TwQ<)~Bsy^U;b|4J+9-Ch<|vPtZnN`<3%rxxA9m-HIMyEz$*>`- z6vW3LeN>CniK5&1W@*!MBZK62$*U#?YX`+iNFeuA^37&2g!PeCzWqkQxg=4o1i5;g$Oqrqf~*bA<|&o;^Le6s)gfSB_;}eX~*T+-F(c+zoWI=QA35$Vb|= zn1p7zCcQsr%6pv3ljuIt%bnBAZxK$`(aY{@N)b*-%3#l6TMBsp!czYX0M#@n1;kgacdXEff(9%!{;d?W90<9L$?yD)2an3l2$`r=3$XVYvwEc z+Nhct#d@NM$+fBsHeA}!N)N^LfUc9Tsf#B}^fs95Y;oeZObNey(JSg@?M56uv69iDm?-$PV`^y7wU|4i{y1Qk@)oR zEL6-`2Rk;Yzj$8D8bx1k=70-EBxkoH1!t~{u()w(`!ML#Lt2L*rVi!k3AGKT>s1w@@-Ce+pL3QDt-xng zkFAr6ToKL8mApveMO8Ubvcbz!ab#st6}>idJ`#<+{(e4x)^44gRq_VDBV@U57hua5o+9 z!Hi%CeKEtE)T0Q*0r1~)P0$lHjB~!w3;1+-IA;L=iNP<}gIUfr5PBsb5F#{CBMf@Y z0Z$5fij&X-^i0DG>ugY{g@gaq2y%D~yuAuN%)_^UUpHKaxBQ`Z=n;P!t}nq`j8Ln; t)taI4gVd$h#Zjdmv9^eU6yXMB(#h@n4CR$h%!^MvCV~$sXECMJ{09@dM(_Xt literal 0 HcmV?d00001 diff --git a/spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.pack b/spec/tests.git/objects/pack/pack-5185f74f028bf49d2611c9fea56570138a196143.pack new file mode 100644 index 0000000000000000000000000000000000000000..59398297275807cd2dc4c0a68892e2a03581f68b GIT binary patch literal 24472 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 Date: Sat, 14 Jan 2012 18:16:42 +0600 Subject: [PATCH 10/16] [refs #18] controller tests --- app/controllers/comments_controller.rb | 2 +- app/models/ability.rb | 2 +- .../comments_controller_for_commit_spec.rb | 31 ++++++++++--------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index d280e0ea6..8d950b18a 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -1,6 +1,6 @@ class CommentsController < ApplicationController before_filter :authenticate_user! - before_filter :set_commentable, :only => [:index, :edit, :create, :update] + before_filter :set_commentable, :only => [:index, :edit, :create, :update, :destroy] #before_filter :find_project, :only => [:index, :edit] before_filter :find_comment, :only => [:edit, :update, :destroy] diff --git a/app/models/ability.rb b/app/models/ability.rb index 0c1684db0..611697588 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -90,7 +90,7 @@ class Ability cannot :manage, Issue, :project => {:has_issues => false} # switch off issues can(:create, Comment) {|comment| can? :read, comment.project || comment.commentable.project} - can([:update, :delete], Comment) {|comment| comment.user_id == user.id or local_admin?(comment.project || comment.commentable.project)} + can(:update, Comment) {|comment| comment.user_id == user.id or local_admin?(comment.project || comment.commentable.project)} #cannot :manage, Comment, :commentable => {:project => {:has_issues => false}} # switch off issues cannot(:manage, Comment) {|comment| comment.commentable_type == 'Issue' && !comment.commentable.project.has_issues} # switch off issues end diff --git a/spec/controllers/comments_controller_for_commit_spec.rb b/spec/controllers/comments_controller_for_commit_spec.rb index fccd9c57b..07bdea33c 100644 --- a/spec/controllers/comments_controller_for_commit_spec.rb +++ b/spec/controllers/comments_controller_for_commit_spec.rb @@ -52,6 +52,10 @@ shared_examples_for 'user without destroy comment rights' do delete :destroy, :id => @comment.id, :commit_id => @commit.id, :project_id => @project.id response.should redirect_to(forbidden_path) end + + it 'should not reduce comments count' do + lambda{ delete :destroy, :id => @comment.id, :commit_id => @commit.id, :project_id => @project.id }.should change{ Comment.count }.by(0) + end end #shared_examples_for 'user with destroy rights' do @@ -98,22 +102,21 @@ describe CommentsController do it_should_behave_like 'user without destroy comment rights' end - #~ context 'for project owner user' do - #~ before(:each) do - #~ @user = Factory(:user) - #~ set_session_for(@user) - #~ @project.update_attribute(:owner, @user) - #~ @project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin') + context 'for project owner user' do + before(:each) do + @user = @project.owner + set_session_for(@user) + @project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin') - #~ @own_comment = Factory(:comment, :user => @user) - #~ @own_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) - #~ end + @own_comment = Factory(:comment, :user => @user) + @own_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) + end - #~ it_should_behave_like 'user with create comment rights' - #~ it_should_behave_like 'user with update stranger comment rights' - #~ it_should_behave_like 'user with update own comment rights' - #~ it_should_behave_like 'user without destroy comment rights' - #~ end + it_should_behave_like 'user with create comment rights' + it_should_behave_like 'user with update stranger comment rights' + it_should_behave_like 'user with update own comment rights' + it_should_behave_like 'user without destroy comment rights' + end context 'for project reader user' do before(:each) do From 674d5f8e5689a051e6f90e29ddb596c1083bdf27 Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Sat, 14 Jan 2012 18:36:00 +0600 Subject: [PATCH 11/16] [refs #18] model tests --- spec/models/comment_for_commit_spec.rb | 135 +++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 spec/models/comment_for_commit_spec.rb diff --git a/spec/models/comment_for_commit_spec.rb b/spec/models/comment_for_commit_spec.rb new file mode 100644 index 000000000..adcc70967 --- /dev/null +++ b/spec/models/comment_for_commit_spec.rb @@ -0,0 +1,135 @@ +require 'spec_helper' +require "cancan/matchers" + +def set_comments_data + @ability = Ability.new(@user) + + @project = Factory(:project) + %x(cp -Rf #{Rails.root}/spec/tests.git/* #{@project.git_repository.path}) # maybe FIXME ? + @commit = @project.git_repository.commits.first + + @comment = Factory(:comment, :user => @user) + @comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) + + @stranger_comment = Factory(:comment, :user => @stranger) + @stranger_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id, :project => @project) + + @create_params = {:commentable_type => @commit.class.name, :commentable_id => @commit.id, :user => @user, :project => @project} + + any_instance_of(Project, :versions => ['v1.0', 'v2.0']) +end + +describe Comment do + context 'for global admin user' do + before(:each) do + @user = Factory(:admin) + @stranger = Factory(:user) + + set_comments_data + end + + it 'should create comment' do + @ability.should be_able_to(:create, Comment.new(@create_params)) + end + + pending "sends an e-mail" do + ActionMailer::Base.deliveries.last.to.include?(@stranger.email).should == true + end + + it 'should update comment' do + @ability.should be_able_to(:update, @comment) + end + + it 'should update stranger comment' do + @ability.should be_able_to(:update, @stranger_comment) + end + + it 'should destroy own comment' do + @ability.should be_able_to(:destroy, @comment) + end + + it 'should destroy stranger comment' do + @ability.should be_able_to(:destroy, @stranger_comment) + end + end + + context 'for project admin user' do + before(:each) do + @user = Factory(:user) + @stranger = Factory(:user) + + set_comments_data + + @project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin') + end + + it 'should create comment' do + @ability.should be_able_to(:create, Comment.new(@create_params)) + end + + it 'should update comment' do + @ability.should be_able_to(:update, @comment) + end + + it 'should update stranger comment' do + @ability.should be_able_to(:update, @stranger_comment) + end + + it 'should not destroy comment' do + @ability.should_not be_able_to(:destroy, @comment) + end + end + + context 'for project owner user' do + before(:each) do + @user = Factory(:user) + @stranger = Factory(:user) + + set_comments_data + + @project.update_attribute(:owner, @user) + @project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin') + end + + it 'should create comment' do + @ability.should be_able_to(:create, Comment.new(@create_params)) + end + + it 'should update comment' do + @ability.should be_able_to(:update, @comment) + end + + it 'should update stranger comment' do + @ability.should be_able_to(:update, @stranger_comment) + end + + it 'should not destroy comment' do + @ability.should_not be_able_to(:destroy, @comment) + end + end + + context 'for simple user' do + before(:each) do + @user = Factory(:user) + @stranger = Factory(:user) + + set_comments_data + end + + it 'should create comment' do + @ability.should be_able_to(:create, Comment.new(@create_params)) + end + + it 'should update comment' do + @ability.should be_able_to(:update, @comment) + end + + it 'should not update stranger comment' do + @ability.should_not be_able_to(:update, @stranger_comment) + end + + it 'should not destroy comment' do + @ability.should_not be_able_to(:destroy, @comment) + end + end +end From e10dd56dffc3a8aca504f30f05a9945d05cc963a Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Mon, 16 Jan 2012 00:10:50 +0600 Subject: [PATCH 12/16] [refs #18] refactoring --- app/views/comments/_list.html.haml | 33 ++++++++++++++++++++++++++++ app/views/git/commits/show.html.haml | 26 +--------------------- app/views/issues/show.html.haml | 26 +--------------------- 3 files changed, 35 insertions(+), 50 deletions(-) create mode 100644 app/views/comments/_list.html.haml diff --git a/app/views/comments/_list.html.haml b/app/views/comments/_list.html.haml new file mode 100644 index 000000000..0457e4c34 --- /dev/null +++ b/app/views/comments/_list.html.haml @@ -0,0 +1,33 @@ +%a{ :name => "comments" } +.block#block-list + .content + %h2.title + = t("layout.issues.comments_header") + .inner + %ul.list + - list.each do |comment| + %li + .left + = link_to comment.user.uname, user_path(comment.user.uname) + .item + = comment.body + %br + %br + - if commentable.class == Issue + - edit_path = edit_project_issue_comment_path(project, commentable.id, comment) + - delete_path = project_issue_comment_path(project, commentable.id, comment) + - elsif commentable.class == Grit::Commit + - edit_path = edit_project_commit_comment_path(project, commentable.id, comment) + - delete_path = project_commit_comment_path(project, commentable.id, comment) + = link_to t("layout.edit"), edit_path if can? :update, comment + = link_to image_tag("web-app-theme/icons/cross.png", :alt => t("layout.delete")) + " " + t("layout.delete"), delete_path, :method => "delete", :class => "button", :confirm => t("layout.comments.confirm_delete") if can? :delete, comment + +.block + .content + %h2.title + = t("layout.comments.new_header") + .inner + - new_path = project_issue_comments_path(project, commentable.id) if commentable.class == Issue + - new_path = project_commit_comments_path(project, commentable.id) if commentable.class == Grit::Commit + = form_for :comment, :url => new_path, :method => :post, :html => { :class => :form } do |f| + = render :partial => "comments/form", :locals => {:f => f} diff --git a/app/views/git/commits/show.html.haml b/app/views/git/commits/show.html.haml index 421c5d2e2..8f3ec67f8 100644 --- a/app/views/git/commits/show.html.haml +++ b/app/views/git/commits/show.html.haml @@ -27,28 +27,4 @@ - content_for :sidebar, render(:partial => 'git/shared/sidebar') -%a{ :name => "comments" } -.block#block-list - .content - %h2.title - = t("layout.issues.comments_header") - .inner - %ul.list - - Project.commit_comments(@commit, @project).each do |comment| - %li - .left - = link_to comment.user.uname, user_path(comment.user.uname) - .item - = comment.body - %br - %br - = link_to t("layout.edit"), edit_project_commit_comment_path(@project, @commit.id, comment) if can? :update, comment - = link_to image_tag("web-app-theme/icons/cross.png", :alt => t("layout.delete")) + " " + t("layout.delete"), project_commit_comment_path(@project, @commit.id, comment), :method => "delete", :class => "button", :confirm => t("layout.comments.confirm_delete") if can? :delete, comment - -.block - .content - %h2.title - = t("layout.comments.new_header") - .inner - = form_for :comment, :url => project_commit_comments_path(@project, @commit.id), :method => :post, :html => { :class => :form } do |f| - = render :partial => "comments/form", :locals => {:f => f} += render :partial => "comments/list", :locals => {:list => Project.commit_comments(@commit, @project), :project => @project, :commentable => @commit} diff --git a/app/views/issues/show.html.haml b/app/views/issues/show.html.haml index 2a79e66a3..f246dbfda 100644 --- a/app/views/issues/show.html.haml +++ b/app/views/issues/show.html.haml @@ -29,28 +29,4 @@ - else = link_to t('layout.issues.subscribe_btn'), project_issue_subscribes_path(@project, @issue), :method => :post -%a{ :name => "comments" } -.block#block-list - .content - %h2.title - = t("layout.issues.comments_header") - .inner - %ul.list - - @issue.comments.each do |comment| - %li - .left - = link_to comment.user.uname, user_path(comment.user.uname) - .item - = comment.body - %br - %br - = link_to t("layout.edit"), edit_project_issue_comment_path(@project, @issue.id, comment) if can? :update, comment - = link_to image_tag("web-app-theme/icons/cross.png", :alt => t("layout.delete")) + " " + t("layout.delete"), project_issue_comment_path(@project, @issue.id, comment), :method => "delete", :class => "button", :confirm => t("layout.comments.confirm_delete") if can? :delete, comment - -.block - .content - %h2.title - = t("layout.comments.new_header") - .inner - = form_for :comment, :url => project_issue_comments_path(@project, @issue.id), :method => :post, :html => { :class => :form } do |f| - = render :partial => "comments/form", :locals => {:f => f} += render :partial => "comments/list", :locals => {:list => @issue.comments, :project => @project, :commentable => @issue} From 69debdd83e6facd7dace9549db4a84912c1eca32 Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Mon, 16 Jan 2012 19:43:23 +0600 Subject: [PATCH 13/16] fix tests for shared elements --- .../comments_controller_for_commit_spec.rb | 44 +++++++++---------- spec/models/comment_for_commit_spec.rb | 10 ++--- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/spec/controllers/comments_controller_for_commit_spec.rb b/spec/controllers/comments_controller_for_commit_spec.rb index 07bdea33c..a8e82283b 100644 --- a/spec/controllers/comments_controller_for_commit_spec.rb +++ b/spec/controllers/comments_controller_for_commit_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -shared_examples_for 'user with create comment rights' do +shared_examples_for 'user with create comment rights for commits' do it 'should be able to perform create action' do post :create, @create_params response.should redirect_to(commit_path(@project, @commit.id)) @@ -11,7 +11,7 @@ shared_examples_for 'user with create comment rights' do end end -shared_examples_for 'user with update own comment rights' do +shared_examples_for 'user with update own comment rights for commits' do it 'should be able to perform update action' do put :update, {:id => @own_comment.id}.merge(@update_params) response.should redirect_to(commit_path(@project, @commit.id)) @@ -23,7 +23,7 @@ shared_examples_for 'user with update own comment rights' do end end -shared_examples_for 'user with update stranger comment rights' do +shared_examples_for 'user with update stranger comment rights for commits' do it 'should be able to perform update action' do put :update, {:id => @comment.id}.merge(@update_params) response.should redirect_to(commit_path(@project, @commit.id)) @@ -35,7 +35,7 @@ shared_examples_for 'user with update stranger comment rights' do end end -shared_examples_for 'user without update stranger comment rights' do +shared_examples_for 'user without update stranger comment rights for commits' do it 'should not be able to perform update action' do put :update, {:id => @comment.id}.merge(@update_params) response.should redirect_to(forbidden_path) @@ -47,7 +47,7 @@ shared_examples_for 'user without update stranger comment rights' do end end -shared_examples_for 'user without destroy comment rights' do +shared_examples_for 'user without destroy comment rights for commits' do it 'should not be able to perform destroy action' do delete :destroy, :id => @comment.id, :commit_id => @commit.id, :project_id => @project.id response.should redirect_to(forbidden_path) @@ -96,10 +96,10 @@ describe CommentsController do @own_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) end - it_should_behave_like 'user with create comment rights' - it_should_behave_like 'user with update stranger comment rights' - it_should_behave_like 'user with update own comment rights' - it_should_behave_like 'user without destroy comment rights' + it_should_behave_like 'user with create comment rights for commits' + it_should_behave_like 'user with update stranger comment rights for commits' + it_should_behave_like 'user with update own comment rights for commits' + it_should_behave_like 'user without destroy comment rights for commits' end context 'for project owner user' do @@ -112,10 +112,10 @@ describe CommentsController do @own_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) end - it_should_behave_like 'user with create comment rights' - it_should_behave_like 'user with update stranger comment rights' - it_should_behave_like 'user with update own comment rights' - it_should_behave_like 'user without destroy comment rights' + it_should_behave_like 'user with create comment rights for commits' + it_should_behave_like 'user with update stranger comment rights for commits' + it_should_behave_like 'user with update own comment rights for commits' + it_should_behave_like 'user without destroy comment rights for commits' end context 'for project reader user' do @@ -128,10 +128,10 @@ describe CommentsController do @own_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) end - it_should_behave_like 'user with create comment rights' - it_should_behave_like 'user without update stranger comment rights' - it_should_behave_like 'user with update own comment rights' - it_should_behave_like 'user without destroy comment rights' + it_should_behave_like 'user with create comment rights for commits' + it_should_behave_like 'user without update stranger comment rights for commits' + it_should_behave_like 'user with update own comment rights for commits' + it_should_behave_like 'user without destroy comment rights for commits' end context 'for project writer user' do @@ -144,9 +144,9 @@ describe CommentsController do @own_comment.update_attributes(:commentable_type => @commit.class.name, :commentable_id => @commit.id) end - it_should_behave_like 'user with create comment rights' - it_should_behave_like 'user without update stranger comment rights' - it_should_behave_like 'user with update own comment rights' - it_should_behave_like 'user without destroy comment rights' + it_should_behave_like 'user with create comment rights for commits' + it_should_behave_like 'user without update stranger comment rights for commits' + it_should_behave_like 'user with update own comment rights for commits' + it_should_behave_like 'user without destroy comment rights for commits' end -end \ No newline at end of file +end diff --git a/spec/models/comment_for_commit_spec.rb b/spec/models/comment_for_commit_spec.rb index adcc70967..938c4db7e 100644 --- a/spec/models/comment_for_commit_spec.rb +++ b/spec/models/comment_for_commit_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require "cancan/matchers" -def set_comments_data +def set_comments_data_for_commit @ability = Ability.new(@user) @project = Factory(:project) @@ -25,7 +25,7 @@ describe Comment do @user = Factory(:admin) @stranger = Factory(:user) - set_comments_data + set_comments_data_for_commit end it 'should create comment' do @@ -58,7 +58,7 @@ describe Comment do @user = Factory(:user) @stranger = Factory(:user) - set_comments_data + set_comments_data_for_commit @project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin') end @@ -85,7 +85,7 @@ describe Comment do @user = Factory(:user) @stranger = Factory(:user) - set_comments_data + set_comments_data_for_commit @project.update_attribute(:owner, @user) @project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin') @@ -113,7 +113,7 @@ describe Comment do @user = Factory(:user) @stranger = Factory(:user) - set_comments_data + set_comments_data_for_commit end it 'should create comment' do From 95b4e2557ce74653253574822a617e5cbdc3456c Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Tue, 17 Jan 2012 16:30:11 +0600 Subject: [PATCH 14/16] [refs #18] forgot comments order --- app/views/git/commits/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/git/commits/show.html.haml b/app/views/git/commits/show.html.haml index 8f3ec67f8..7f7cc2d58 100644 --- a/app/views/git/commits/show.html.haml +++ b/app/views/git/commits/show.html.haml @@ -27,4 +27,4 @@ - content_for :sidebar, render(:partial => 'git/shared/sidebar') -= render :partial => "comments/list", :locals => {:list => Project.commit_comments(@commit, @project), :project => @project, :commentable => @commit} += render :partial => "comments/list", :locals => {:list => Project.commit_comments(@commit, @project).order(:created_at), :project => @project, :commentable => @commit} From a0aad7103e5c699e9f9f9fd864a5d0b2362b95e1 Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Tue, 17 Jan 2012 17:17:27 +0600 Subject: [PATCH 15/16] some fixes --- app/models/project.rb | 2 +- app/views/git/blobs/show.html.haml | 17 ----------------- app/views/git/commits/show.html.haml | 2 +- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 4cc5c143c..5763fc071 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -136,7 +136,7 @@ class Project < ActiveRecord::Base class << self def commit_comments(commit, project) - comments = Comment.where(:commentable_id => commit.id, :commentable_type => 'Grit::Commit') + comments = Comment.where(:commentable_id => commit.id, :commentable_type => 'Grit::Commit').order(:created_at) comments.each {|x| x.project = project} end end diff --git a/app/views/git/blobs/show.html.haml b/app/views/git/blobs/show.html.haml index b9c8e79a8..a02bcd20c 100644 --- a/app/views/git/blobs/show.html.haml +++ b/app/views/git/blobs/show.html.haml @@ -32,20 +32,3 @@
#{render_blob(@blob)}
- content_for :sidebar, render(:partial => 'git/shared/sidebar') - -%a{ :name => "comments" } -.block#block-list - .content - %h2.title - = t("layout.issues.comments_header") - .inner - %ul.list - - @project.commit_comments(@commit).each do |comment| - %li - .left - = link_to comment.user.uname, user_path(comment.user.uname) - .item - = comment.body - %br - %br - diff --git a/app/views/git/commits/show.html.haml b/app/views/git/commits/show.html.haml index 7f7cc2d58..8f3ec67f8 100644 --- a/app/views/git/commits/show.html.haml +++ b/app/views/git/commits/show.html.haml @@ -27,4 +27,4 @@ - content_for :sidebar, render(:partial => 'git/shared/sidebar') -= render :partial => "comments/list", :locals => {:list => Project.commit_comments(@commit, @project).order(:created_at), :project => @project, :commentable => @commit} += render :partial => "comments/list", :locals => {:list => Project.commit_comments(@commit, @project), :project => @project, :commentable => @commit} From 310e11b666558ebac9273a51e03d047f0e2085bd Mon Sep 17 00:00:00 2001 From: Alexander Machehin Date: Wed, 18 Jan 2012 00:33:42 +0600 Subject: [PATCH 16/16] [refs #18] Fix if blocking referer --- app/controllers/comments_controller.rb | 15 +++++++++++---- app/views/comments/_form.html.haml | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index efceb8033..6f699a602 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -17,7 +17,7 @@ class CommentsController < ApplicationController @comment.user = current_user if @comment.save flash[:notice] = I18n.t("flash.comment.saved") - redirect_to :back + redirect_to commentable_path else flash[:error] = I18n.t("flash.comment.save_error") render :action => 'new' @@ -31,13 +31,13 @@ class CommentsController < ApplicationController when "Grit::Commit" project_commit_comment_path(@project, @commentable, @comment) end + @commentable_path = commentable_path end def update if @comment.update_attributes(params[:comment]) flash[:notice] = I18n.t("flash.comment.saved") - redirect_to :back - #redirect_to @commentable.class == Issue ? project_issue_path(@project, @commentable) : commit_path(@project.id, @commentable.id) + redirect_to commentable_path else flash[:error] = I18n.t("flash.comment.save_error") render :action => 'new' @@ -48,7 +48,7 @@ class CommentsController < ApplicationController @comment.destroy flash[:notice] = t("flash.comment.destroyed") - redirect_to :back + redirect_to commentable_path end private @@ -80,4 +80,11 @@ class CommentsController < ApplicationController def find_project @project = Project.find(params[:project_id]) end + + protected + + def commentable_path + @commentable.class == Issue ? [@project, @commentable] : commit_path(@project, @commentable.id) + end + end diff --git a/app/views/comments/_form.html.haml b/app/views/comments/_form.html.haml index 462aca983..3600e8d2b 100644 --- a/app/views/comments/_form.html.haml +++ b/app/views/comments/_form.html.haml @@ -7,4 +7,4 @@ = image_tag("web-app-theme/icons/tick.png", :alt => t("layout.save")) = t("layout.save") %span.text_button_padding= t("layout.or") - = link_to t("layout.cancel"), :back , :class => "text_button_padding link_button" + = link_to t("layout.cancel"), @commentable_path , :class => "text_button_padding link_button"