##// END OF EJS Templates
Fixed: fetch_changesets fails on commit comments that close 2 duplicates issues....
Jean-Philippe Lang -
r1169:7ee55562ffb8
parent child
Show More
@@ -1,118 +1,120
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,
30 :include => :repository,
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_numericality_of :revision, :only_integer => true
35 validates_numericality_of :revision, :only_integer => true
36 validates_uniqueness_of :revision, :scope => :repository_id
36 validates_uniqueness_of :revision, :scope => :repository_id
37 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
37 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
38
38
39 def comments=(comment)
39 def comments=(comment)
40 write_attribute(:comments, comment.strip)
40 write_attribute(:comments, comment.strip)
41 end
41 end
42
42
43 def committed_on=(date)
43 def committed_on=(date)
44 self.commit_date = date
44 self.commit_date = date
45 super
45 super
46 end
46 end
47
47
48 def after_create
48 def after_create
49 scan_comment_for_issue_ids
49 scan_comment_for_issue_ids
50 end
50 end
51 require 'pp'
51 require 'pp'
52
52
53 def scan_comment_for_issue_ids
53 def scan_comment_for_issue_ids
54 return if comments.blank?
54 return if comments.blank?
55 # keywords used to reference issues
55 # keywords used to reference issues
56 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
56 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
57 # keywords used to fix issues
57 # keywords used to fix issues
58 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
58 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
59 # status and optional done ratio applied
59 # status and optional done ratio applied
60 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
60 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
61 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
61 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
62
62
63 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
63 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
64 return if kw_regexp.blank?
64 return if kw_regexp.blank?
65
65
66 referenced_issues = []
66 referenced_issues = []
67
67
68 if ref_keywords.delete('*')
68 if ref_keywords.delete('*')
69 # find any issue ID in the comments
69 # find any issue ID in the comments
70 target_issue_ids = []
70 target_issue_ids = []
71 comments.scan(%r{([\s\(,-^])#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
71 comments.scan(%r{([\s\(,-^])#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
72 referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
72 referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
73 end
73 end
74
74
75 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
75 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
76 action = match[0]
76 action = match[0]
77 target_issue_ids = match[1].scan(/\d+/)
77 target_issue_ids = match[1].scan(/\d+/)
78 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
78 target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
79 if fix_status && fix_keywords.include?(action.downcase)
79 if fix_status && fix_keywords.include?(action.downcase)
80 # update status of issues
80 # update status of issues
81 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
81 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
82 target_issues.each do |issue|
82 target_issues.each do |issue|
83 # the issue may have been updated by the closure of another one (eg. duplicate)
84 issue.reload
83 # don't change the status is the issue is closed
85 # don't change the status is the issue is closed
84 next if issue.status.is_closed?
86 next if issue.status.is_closed?
85 user = committer_user || User.anonymous
87 user = committer_user || User.anonymous
86 journal = issue.init_journal(user, l(:text_status_changed_by_changeset, "r#{self.revision}"))
88 journal = issue.init_journal(user, l(:text_status_changed_by_changeset, "r#{self.revision}"))
87 issue.status = fix_status
89 issue.status = fix_status
88 issue.done_ratio = done_ratio if done_ratio
90 issue.done_ratio = done_ratio if done_ratio
89 issue.save
91 issue.save
90 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
92 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
91 end
93 end
92 end
94 end
93 referenced_issues += target_issues
95 referenced_issues += target_issues
94 end
96 end
95
97
96 self.issues = referenced_issues.uniq
98 self.issues = referenced_issues.uniq
97 end
99 end
98
100
99 # Returns the Redmine User corresponding to the committer
101 # Returns the Redmine User corresponding to the committer
100 def committer_user
102 def committer_user
101 if committer && committer.strip =~ /^([^<]+)(<(.*)>)?$/
103 if committer && committer.strip =~ /^([^<]+)(<(.*)>)?$/
102 username, email = $1.strip, $3
104 username, email = $1.strip, $3
103 u = User.find_by_login(username)
105 u = User.find_by_login(username)
104 u ||= User.find_by_mail(email) unless email.blank?
106 u ||= User.find_by_mail(email) unless email.blank?
105 u
107 u
106 end
108 end
107 end
109 end
108
110
109 # Returns the previous changeset
111 # Returns the previous changeset
110 def previous
112 def previous
111 @previous ||= Changeset.find(:first, :conditions => ['revision < ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision DESC')
113 @previous ||= Changeset.find(:first, :conditions => ['revision < ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision DESC')
112 end
114 end
113
115
114 # Returns the next changeset
116 # Returns the next changeset
115 def next
117 def next
116 @next ||= Changeset.find(:first, :conditions => ['revision > ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision ASC')
118 @next ||= Changeset.find(:first, :conditions => ['revision > ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision ASC')
117 end
119 end
118 end
120 end
General Comments 0
You need to be logged in to leave comments. Login now