##// END OF EJS Templates
Rails4: replace deprecated find_by_id at Changeset class...
Toshi MARUYAMA -
r12198:3ace0292c2e4
parent child
Show More
@@ -1,282 +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 issue = Issue.find_by_id(id.to_i, :include => :project)
200 issue = Issue.includes(:project).where(:id => id.to_i).first
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 223 journal = issue.init_journal(user || User.anonymous,
224 224 ll(Setting.default_language,
225 225 :text_status_changed_by_changeset,
226 226 text_tag(issue.project)))
227 227 rule = Setting.commit_update_keywords_array.detect do |rule|
228 228 rule['keywords'].include?(action) &&
229 229 (rule['if_tracker_id'].blank? || rule['if_tracker_id'] == issue.tracker_id.to_s)
230 230 end
231 231 if rule
232 232 issue.assign_attributes rule.slice(*Issue.attribute_names)
233 233 end
234 234 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
235 235 { :changeset => self, :issue => issue, :action => action })
236 236 unless issue.save
237 237 logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
238 238 end
239 239 issue
240 240 end
241 241
242 242 def log_time(issue, hours)
243 243 time_entry = TimeEntry.new(
244 244 :user => user,
245 245 :hours => hours,
246 246 :issue => issue,
247 247 :spent_on => commit_date,
248 248 :comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project),
249 249 :locale => Setting.default_language)
250 250 )
251 251 time_entry.activity = log_time_activity unless log_time_activity.nil?
252 252
253 253 unless time_entry.save
254 254 logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
255 255 end
256 256 time_entry
257 257 end
258 258
259 259 def log_time_activity
260 260 if Setting.commit_logtime_activity_id.to_i > 0
261 261 TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
262 262 end
263 263 end
264 264
265 265 def split_comments
266 266 comments =~ /\A(.+?)\r?\n(.*)$/m
267 267 @short_comments = $1 || comments
268 268 @long_comments = $2.to_s.strip
269 269 return @short_comments, @long_comments
270 270 end
271 271
272 272 public
273 273
274 274 # Strips and reencodes a commit log before insertion into the database
275 275 def self.normalize_comments(str, encoding)
276 276 Changeset.to_utf8(str.to_s.strip, encoding)
277 277 end
278 278
279 279 def self.to_utf8(str, encoding)
280 280 Redmine::CodesetUtil.to_utf8(str, encoding)
281 281 end
282 282 end
General Comments 0
You need to be logged in to leave comments. Login now