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