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