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