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