##// END OF EJS Templates
Fixed: using '*' as keyword for repository referencing keywords doesn't work when issue id is at the beginning of a line (#1253)....
Jean-Philippe Lang -
r1437:e8d092e46ad8
parent child
Show More
@@ -1,131 +1,131
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Changeset < ActiveRecord::Base
18 class Changeset < ActiveRecord::Base
19 belongs_to :repository
19 belongs_to :repository
20 has_many :changes, :dependent => :delete_all
20 has_many :changes, :dependent => :delete_all
21 has_and_belongs_to_many :issues
21 has_and_belongs_to_many :issues
22
22
23 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.comments.blank? ? '' : (': ' + o.comments))},
23 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.comments.blank? ? '' : (': ' + o.comments))},
24 :description => :comments,
24 :description => :comments,
25 :datetime => :committed_on,
25 :datetime => :committed_on,
26 :author => :committer,
26 :author => :committer,
27 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
27 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
28
28
29 acts_as_searchable :columns => 'comments',
29 acts_as_searchable :columns => 'comments',
30 :include => {:repository => :project},
30 :include => {:repository => :project},
31 :project_key => "#{Repository.table_name}.project_id",
31 :project_key => "#{Repository.table_name}.project_id",
32 :date_column => 'committed_on'
32 :date_column => 'committed_on'
33
33
34 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
34 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
35 validates_uniqueness_of :revision, :scope => :repository_id
35 validates_uniqueness_of :revision, :scope => :repository_id
36 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
36 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
37
37
38 def revision=(r)
38 def revision=(r)
39 write_attribute :revision, (r.nil? ? nil : r.to_s)
39 write_attribute :revision, (r.nil? ? nil : r.to_s)
40 end
40 end
41
41
42 def comments=(comment)
42 def comments=(comment)
43 write_attribute(:comments, comment.strip)
43 write_attribute(:comments, comment.strip)
44 end
44 end
45
45
46 def committed_on=(date)
46 def committed_on=(date)
47 self.commit_date = date
47 self.commit_date = date
48 super
48 super
49 end
49 end
50
50
51 def project
51 def project
52 repository.project
52 repository.project
53 end
53 end
54
54
55 def after_create
55 def after_create
56 scan_comment_for_issue_ids
56 scan_comment_for_issue_ids
57 end
57 end
58 require 'pp'
58 require 'pp'
59
59
60 def scan_comment_for_issue_ids
60 def scan_comment_for_issue_ids
61 return if comments.blank?
61 return if comments.blank?
62 # keywords used to reference issues
62 # keywords used to reference issues
63 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
63 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
64 # keywords used to fix issues
64 # keywords used to fix issues
65 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
65 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
66 # status and optional done ratio applied
66 # status and optional done ratio applied
67 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
67 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
68 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
68 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
69
69
70 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
70 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
71 return if kw_regexp.blank?
71 return if kw_regexp.blank?
72
72
73 referenced_issues = []
73 referenced_issues = []
74
74
75 if ref_keywords.delete('*')
75 if ref_keywords.delete('*')
76 # find any issue ID in the comments
76 # find any issue ID in the comments
77 target_issue_ids = []
77 target_issue_ids = []
78 comments.scan(%r{([\s\(,-^])#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
78 comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
79 referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
79 referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
80 end
80 end
81
81
82 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
82 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
83 action = match[0]
83 action = match[0]
84 target_issue_ids = match[1].scan(/\d+/)
84 target_issue_ids = match[1].scan(/\d+/)
85 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
85 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
86 if fix_status && fix_keywords.include?(action.downcase)
86 if fix_status && fix_keywords.include?(action.downcase)
87 # update status of issues
87 # update status of issues
88 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
88 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
89 target_issues.each do |issue|
89 target_issues.each do |issue|
90 # the issue may have been updated by the closure of another one (eg. duplicate)
90 # the issue may have been updated by the closure of another one (eg. duplicate)
91 issue.reload
91 issue.reload
92 # don't change the status is the issue is closed
92 # don't change the status is the issue is closed
93 next if issue.status.is_closed?
93 next if issue.status.is_closed?
94 user = committer_user || User.anonymous
94 user = committer_user || User.anonymous
95 csettext = "r#{self.revision}"
95 csettext = "r#{self.revision}"
96 if self.scmid && (! (csettext =~ /^r[0-9]+$/))
96 if self.scmid && (! (csettext =~ /^r[0-9]+$/))
97 csettext = "commit:\"#{self.scmid}\""
97 csettext = "commit:\"#{self.scmid}\""
98 end
98 end
99 journal = issue.init_journal(user, l(:text_status_changed_by_changeset, csettext))
99 journal = issue.init_journal(user, l(:text_status_changed_by_changeset, csettext))
100 issue.status = fix_status
100 issue.status = fix_status
101 issue.done_ratio = done_ratio if done_ratio
101 issue.done_ratio = done_ratio if done_ratio
102 issue.save
102 issue.save
103 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
103 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
104 end
104 end
105 end
105 end
106 referenced_issues += target_issues
106 referenced_issues += target_issues
107 end
107 end
108
108
109 self.issues = referenced_issues.uniq
109 self.issues = referenced_issues.uniq
110 end
110 end
111
111
112 # Returns the Redmine User corresponding to the committer
112 # Returns the Redmine User corresponding to the committer
113 def committer_user
113 def committer_user
114 if committer && committer.strip =~ /^([^<]+)(<(.*)>)?$/
114 if committer && committer.strip =~ /^([^<]+)(<(.*)>)?$/
115 username, email = $1.strip, $3
115 username, email = $1.strip, $3
116 u = User.find_by_login(username)
116 u = User.find_by_login(username)
117 u ||= User.find_by_mail(email) unless email.blank?
117 u ||= User.find_by_mail(email) unless email.blank?
118 u
118 u
119 end
119 end
120 end
120 end
121
121
122 # Returns the previous changeset
122 # Returns the previous changeset
123 def previous
123 def previous
124 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
124 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
125 end
125 end
126
126
127 # Returns the next changeset
127 # Returns the next changeset
128 def next
128 def next
129 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
129 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
130 end
130 end
131 end
131 end
@@ -1,62 +1,73
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class ChangesetTest < Test::Unit::TestCase
20 class ChangesetTest < Test::Unit::TestCase
21 fixtures :projects, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :users, :members, :trackers
21 fixtures :projects, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :users, :members, :trackers
22
22
23 def setup
23 def setup
24 end
24 end
25
25
26 def test_ref_keywords_any
26 def test_ref_keywords_any
27 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
27 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
28 Setting.commit_fix_done_ratio = '90'
28 Setting.commit_fix_done_ratio = '90'
29 Setting.commit_ref_keywords = '*'
29 Setting.commit_ref_keywords = '*'
30 Setting.commit_fix_keywords = 'fixes , closes'
30 Setting.commit_fix_keywords = 'fixes , closes'
31
31
32 c = Changeset.new(:repository => Project.find(1).repository,
32 c = Changeset.new(:repository => Project.find(1).repository,
33 :committed_on => Time.now,
33 :committed_on => Time.now,
34 :comments => 'New commit (#2). Fixes #1')
34 :comments => 'New commit (#2). Fixes #1')
35 c.scan_comment_for_issue_ids
35 c.scan_comment_for_issue_ids
36
36
37 assert_equal [1, 2], c.issue_ids.sort
37 assert_equal [1, 2], c.issue_ids.sort
38 fixed = Issue.find(1)
38 fixed = Issue.find(1)
39 assert fixed.closed?
39 assert fixed.closed?
40 assert_equal 90, fixed.done_ratio
40 assert_equal 90, fixed.done_ratio
41 end
41 end
42
42
43 def test_ref_keywords_any_line_start
44 Setting.commit_ref_keywords = '*'
45
46 c = Changeset.new(:repository => Project.find(1).repository,
47 :committed_on => Time.now,
48 :comments => '#1 is the reason of this commit')
49 c.scan_comment_for_issue_ids
50
51 assert_equal [1], c.issue_ids.sort
52 end
53
43 def test_previous
54 def test_previous
44 changeset = Changeset.find_by_revision('3')
55 changeset = Changeset.find_by_revision('3')
45 assert_equal Changeset.find_by_revision('2'), changeset.previous
56 assert_equal Changeset.find_by_revision('2'), changeset.previous
46 end
57 end
47
58
48 def test_previous_nil
59 def test_previous_nil
49 changeset = Changeset.find_by_revision('1')
60 changeset = Changeset.find_by_revision('1')
50 assert_nil changeset.previous
61 assert_nil changeset.previous
51 end
62 end
52
63
53 def test_next
64 def test_next
54 changeset = Changeset.find_by_revision('2')
65 changeset = Changeset.find_by_revision('2')
55 assert_equal Changeset.find_by_revision('3'), changeset.next
66 assert_equal Changeset.find_by_revision('3'), changeset.next
56 end
67 end
57
68
58 def test_next_nil
69 def test_next_nil
59 changeset = Changeset.find_by_revision('4')
70 changeset = Changeset.find_by_revision('4')
60 assert_nil changeset.next
71 assert_nil changeset.next
61 end
72 end
62 end
73 end
General Comments 0
You need to be logged in to leave comments. Login now