##// END OF EJS Templates
Rails3: replace deprecated 'after_create' to declared method at Repository model....
Toshi MARUYAMA -
r6620:80c5f34c07a5
parent child
Show More
@@ -1,298 +1,300
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 'iconv'
18 require 'iconv'
19
19
20 class Changeset < ActiveRecord::Base
20 class Changeset < ActiveRecord::Base
21 belongs_to :repository
21 belongs_to :repository
22 belongs_to :user
22 belongs_to :user
23 has_many :changes, :dependent => :delete_all
23 has_many :changes, :dependent => :delete_all
24 has_and_belongs_to_many :issues
24 has_and_belongs_to_many :issues
25
25
26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
27 :description => :long_comments,
27 :description => :long_comments,
28 :datetime => :committed_on,
28 :datetime => :committed_on,
29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
30
30
31 acts_as_searchable :columns => 'comments',
31 acts_as_searchable :columns => 'comments',
32 :include => {:repository => :project},
32 :include => {:repository => :project},
33 :project_key => "#{Repository.table_name}.project_id",
33 :project_key => "#{Repository.table_name}.project_id",
34 :date_column => 'committed_on'
34 :date_column => 'committed_on'
35
35
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
37 :author_key => :user_id,
37 :author_key => :user_id,
38 :find_options => {:include => [:user, {:repository => :project}]}
38 :find_options => {:include => [:user, {:repository => :project}]}
39
39
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
41 validates_uniqueness_of :revision, :scope => :repository_id
41 validates_uniqueness_of :revision, :scope => :repository_id
42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
43
43
44 named_scope :visible, lambda {|*args| { :include => {:repository => :project},
44 named_scope :visible, lambda {|*args| { :include => {:repository => :project},
45 :conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } }
45 :conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } }
46
46
47 after_create :scan_for_issues
48
47 def revision=(r)
49 def revision=(r)
48 write_attribute :revision, (r.nil? ? nil : r.to_s)
50 write_attribute :revision, (r.nil? ? nil : r.to_s)
49 end
51 end
50
52
51 # Returns the identifier of this changeset; depending on repository backends
53 # Returns the identifier of this changeset; depending on repository backends
52 def identifier
54 def identifier
53 if repository.class.respond_to? :changeset_identifier
55 if repository.class.respond_to? :changeset_identifier
54 repository.class.changeset_identifier self
56 repository.class.changeset_identifier self
55 else
57 else
56 revision.to_s
58 revision.to_s
57 end
59 end
58 end
60 end
59
61
60 def committed_on=(date)
62 def committed_on=(date)
61 self.commit_date = date
63 self.commit_date = date
62 super
64 super
63 end
65 end
64
66
65 # Returns the readable identifier
67 # Returns the readable identifier
66 def format_identifier
68 def format_identifier
67 if repository.class.respond_to? :format_changeset_identifier
69 if repository.class.respond_to? :format_changeset_identifier
68 repository.class.format_changeset_identifier self
70 repository.class.format_changeset_identifier self
69 else
71 else
70 identifier
72 identifier
71 end
73 end
72 end
74 end
73
75
74 def project
76 def project
75 repository.project
77 repository.project
76 end
78 end
77
79
78 def author
80 def author
79 user || committer.to_s.split('<').first
81 user || committer.to_s.split('<').first
80 end
82 end
81
83
82 def before_create
84 def before_create
83 self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
85 self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
84 self.comments = self.class.normalize_comments(
86 self.comments = self.class.normalize_comments(
85 self.comments, repository.repo_log_encoding)
87 self.comments, repository.repo_log_encoding)
86 self.user = repository.find_committer_user(self.committer)
88 self.user = repository.find_committer_user(self.committer)
87 end
89 end
88
90
89 def after_create
91 def scan_for_issues
90 scan_comment_for_issue_ids
92 scan_comment_for_issue_ids
91 end
93 end
92
94
93 TIMELOG_RE = /
95 TIMELOG_RE = /
94 (
96 (
95 ((\d+)(h|hours?))((\d+)(m|min)?)?
97 ((\d+)(h|hours?))((\d+)(m|min)?)?
96 |
98 |
97 ((\d+)(h|hours?|m|min))
99 ((\d+)(h|hours?|m|min))
98 |
100 |
99 (\d+):(\d+)
101 (\d+):(\d+)
100 |
102 |
101 (\d+([\.,]\d+)?)h?
103 (\d+([\.,]\d+)?)h?
102 )
104 )
103 /x
105 /x
104
106
105 def scan_comment_for_issue_ids
107 def scan_comment_for_issue_ids
106 return if comments.blank?
108 return if comments.blank?
107 # keywords used to reference issues
109 # keywords used to reference issues
108 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
110 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
109 ref_keywords_any = ref_keywords.delete('*')
111 ref_keywords_any = ref_keywords.delete('*')
110 # keywords used to fix issues
112 # keywords used to fix issues
111 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
113 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
112
114
113 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
115 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
114
116
115 referenced_issues = []
117 referenced_issues = []
116
118
117 comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
119 comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
118 action, refs = match[2], match[3]
120 action, refs = match[2], match[3]
119 next unless action.present? || ref_keywords_any
121 next unless action.present? || ref_keywords_any
120
122
121 refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
123 refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
122 issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
124 issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
123 if issue
125 if issue
124 referenced_issues << issue
126 referenced_issues << issue
125 fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
127 fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
126 log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
128 log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
127 end
129 end
128 end
130 end
129 end
131 end
130
132
131 referenced_issues.uniq!
133 referenced_issues.uniq!
132 self.issues = referenced_issues unless referenced_issues.empty?
134 self.issues = referenced_issues unless referenced_issues.empty?
133 end
135 end
134
136
135 def short_comments
137 def short_comments
136 @short_comments || split_comments.first
138 @short_comments || split_comments.first
137 end
139 end
138
140
139 def long_comments
141 def long_comments
140 @long_comments || split_comments.last
142 @long_comments || split_comments.last
141 end
143 end
142
144
143 def text_tag
145 def text_tag
144 if scmid?
146 if scmid?
145 "commit:#{scmid}"
147 "commit:#{scmid}"
146 else
148 else
147 "r#{revision}"
149 "r#{revision}"
148 end
150 end
149 end
151 end
150
152
151 # Returns the previous changeset
153 # Returns the previous changeset
152 def previous
154 def previous
153 @previous ||= Changeset.find(:first,
155 @previous ||= Changeset.find(:first,
154 :conditions => ['id < ? AND repository_id = ?',
156 :conditions => ['id < ? AND repository_id = ?',
155 self.id, self.repository_id],
157 self.id, self.repository_id],
156 :order => 'id DESC')
158 :order => 'id DESC')
157 end
159 end
158
160
159 # Returns the next changeset
161 # Returns the next changeset
160 def next
162 def next
161 @next ||= Changeset.find(:first,
163 @next ||= Changeset.find(:first,
162 :conditions => ['id > ? AND repository_id = ?',
164 :conditions => ['id > ? AND repository_id = ?',
163 self.id, self.repository_id],
165 self.id, self.repository_id],
164 :order => 'id ASC')
166 :order => 'id ASC')
165 end
167 end
166
168
167 # Creates a new Change from it's common parameters
169 # Creates a new Change from it's common parameters
168 def create_change(change)
170 def create_change(change)
169 Change.create(:changeset => self,
171 Change.create(:changeset => self,
170 :action => change[:action],
172 :action => change[:action],
171 :path => change[:path],
173 :path => change[:path],
172 :from_path => change[:from_path],
174 :from_path => change[:from_path],
173 :from_revision => change[:from_revision])
175 :from_revision => change[:from_revision])
174 end
176 end
175
177
176 private
178 private
177
179
178 # Finds an issue that can be referenced by the commit message
180 # Finds an issue that can be referenced by the commit message
179 # i.e. an issue that belong to the repository project, a subproject or a parent project
181 # i.e. an issue that belong to the repository project, a subproject or a parent project
180 def find_referenced_issue_by_id(id)
182 def find_referenced_issue_by_id(id)
181 return nil if id.blank?
183 return nil if id.blank?
182 issue = Issue.find_by_id(id.to_i, :include => :project)
184 issue = Issue.find_by_id(id.to_i, :include => :project)
183 if issue
185 if issue
184 unless issue.project &&
186 unless issue.project &&
185 (project == issue.project || project.is_ancestor_of?(issue.project) ||
187 (project == issue.project || project.is_ancestor_of?(issue.project) ||
186 project.is_descendant_of?(issue.project))
188 project.is_descendant_of?(issue.project))
187 issue = nil
189 issue = nil
188 end
190 end
189 end
191 end
190 issue
192 issue
191 end
193 end
192
194
193 def fix_issue(issue)
195 def fix_issue(issue)
194 status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
196 status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
195 if status.nil?
197 if status.nil?
196 logger.warn("No status macthes commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
198 logger.warn("No status macthes commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
197 return issue
199 return issue
198 end
200 end
199
201
200 # the issue may have been updated by the closure of another one (eg. duplicate)
202 # the issue may have been updated by the closure of another one (eg. duplicate)
201 issue.reload
203 issue.reload
202 # don't change the status is the issue is closed
204 # don't change the status is the issue is closed
203 return if issue.status && issue.status.is_closed?
205 return if issue.status && issue.status.is_closed?
204
206
205 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
207 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
206 issue.status = status
208 issue.status = status
207 unless Setting.commit_fix_done_ratio.blank?
209 unless Setting.commit_fix_done_ratio.blank?
208 issue.done_ratio = Setting.commit_fix_done_ratio.to_i
210 issue.done_ratio = Setting.commit_fix_done_ratio.to_i
209 end
211 end
210 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
212 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
211 { :changeset => self, :issue => issue })
213 { :changeset => self, :issue => issue })
212 unless issue.save
214 unless issue.save
213 logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
215 logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
214 end
216 end
215 issue
217 issue
216 end
218 end
217
219
218 def log_time(issue, hours)
220 def log_time(issue, hours)
219 time_entry = TimeEntry.new(
221 time_entry = TimeEntry.new(
220 :user => user,
222 :user => user,
221 :hours => hours,
223 :hours => hours,
222 :issue => issue,
224 :issue => issue,
223 :spent_on => commit_date,
225 :spent_on => commit_date,
224 :comments => l(:text_time_logged_by_changeset, :value => text_tag,
226 :comments => l(:text_time_logged_by_changeset, :value => text_tag,
225 :locale => Setting.default_language)
227 :locale => Setting.default_language)
226 )
228 )
227 time_entry.activity = log_time_activity unless log_time_activity.nil?
229 time_entry.activity = log_time_activity unless log_time_activity.nil?
228
230
229 unless time_entry.save
231 unless time_entry.save
230 logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
232 logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
231 end
233 end
232 time_entry
234 time_entry
233 end
235 end
234
236
235 def log_time_activity
237 def log_time_activity
236 if Setting.commit_logtime_activity_id.to_i > 0
238 if Setting.commit_logtime_activity_id.to_i > 0
237 TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
239 TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
238 end
240 end
239 end
241 end
240
242
241 def split_comments
243 def split_comments
242 comments =~ /\A(.+?)\r?\n(.*)$/m
244 comments =~ /\A(.+?)\r?\n(.*)$/m
243 @short_comments = $1 || comments
245 @short_comments = $1 || comments
244 @long_comments = $2.to_s.strip
246 @long_comments = $2.to_s.strip
245 return @short_comments, @long_comments
247 return @short_comments, @long_comments
246 end
248 end
247
249
248 public
250 public
249
251
250 # Strips and reencodes a commit log before insertion into the database
252 # Strips and reencodes a commit log before insertion into the database
251 def self.normalize_comments(str, encoding)
253 def self.normalize_comments(str, encoding)
252 Changeset.to_utf8(str.to_s.strip, encoding)
254 Changeset.to_utf8(str.to_s.strip, encoding)
253 end
255 end
254
256
255 def self.to_utf8(str, encoding)
257 def self.to_utf8(str, encoding)
256 return str if str.nil?
258 return str if str.nil?
257 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
259 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
258 if str.empty?
260 if str.empty?
259 str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
261 str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
260 return str
262 return str
261 end
263 end
262 enc = encoding.blank? ? "UTF-8" : encoding
264 enc = encoding.blank? ? "UTF-8" : encoding
263 if str.respond_to?(:force_encoding)
265 if str.respond_to?(:force_encoding)
264 if enc.upcase != "UTF-8"
266 if enc.upcase != "UTF-8"
265 str.force_encoding(enc)
267 str.force_encoding(enc)
266 str = str.encode("UTF-8", :invalid => :replace,
268 str = str.encode("UTF-8", :invalid => :replace,
267 :undef => :replace, :replace => '?')
269 :undef => :replace, :replace => '?')
268 else
270 else
269 str.force_encoding("UTF-8")
271 str.force_encoding("UTF-8")
270 if ! str.valid_encoding?
272 if ! str.valid_encoding?
271 str = str.encode("US-ASCII", :invalid => :replace,
273 str = str.encode("US-ASCII", :invalid => :replace,
272 :undef => :replace, :replace => '?').encode("UTF-8")
274 :undef => :replace, :replace => '?').encode("UTF-8")
273 end
275 end
274 end
276 end
275 elsif RUBY_PLATFORM == 'java'
277 elsif RUBY_PLATFORM == 'java'
276 begin
278 begin
277 ic = Iconv.new('UTF-8', enc)
279 ic = Iconv.new('UTF-8', enc)
278 str = ic.iconv(str)
280 str = ic.iconv(str)
279 rescue
281 rescue
280 str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?')
282 str = str.gsub(%r{[^\r\n\t\x20-\x7e]}, '?')
281 end
283 end
282 else
284 else
283 ic = Iconv.new('UTF-8', enc)
285 ic = Iconv.new('UTF-8', enc)
284 txtar = ""
286 txtar = ""
285 begin
287 begin
286 txtar += ic.iconv(str)
288 txtar += ic.iconv(str)
287 rescue Iconv::IllegalSequence
289 rescue Iconv::IllegalSequence
288 txtar += $!.success
290 txtar += $!.success
289 str = '?' + $!.failed[1,$!.failed.length]
291 str = '?' + $!.failed[1,$!.failed.length]
290 retry
292 retry
291 rescue
293 rescue
292 txtar += $!.success
294 txtar += $!.success
293 end
295 end
294 str = txtar
296 str = txtar
295 end
297 end
296 str
298 str
297 end
299 end
298 end
300 end
General Comments 0
You need to be logged in to leave comments. Login now