##// END OF EJS Templates
scm: replace invalid utf-8 sequences in comments instead of stripping on Ruby 1.8....
Toshi MARUYAMA -
r5253:6536c53e0973
parent child
Show More
@@ -1,288 +1,287
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'iconv'
18 require 'iconv'
19
19
20 class Changeset < ActiveRecord::Base
20 class Changeset < ActiveRecord::Base
21 belongs_to :repository
21 belongs_to :repository
22 belongs_to :user
22 belongs_to :user
23 has_many :changes, :dependent => :delete_all
23 has_many :changes, :dependent => :delete_all
24 has_and_belongs_to_many :issues
24 has_and_belongs_to_many :issues
25
25
26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
27 :description => :long_comments,
27 :description => :long_comments,
28 :datetime => :committed_on,
28 :datetime => :committed_on,
29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
30
30
31 acts_as_searchable :columns => 'comments',
31 acts_as_searchable :columns => 'comments',
32 :include => {:repository => :project},
32 :include => {:repository => :project},
33 :project_key => "#{Repository.table_name}.project_id",
33 :project_key => "#{Repository.table_name}.project_id",
34 :date_column => 'committed_on'
34 :date_column => 'committed_on'
35
35
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
37 :author_key => :user_id,
37 :author_key => :user_id,
38 :find_options => {:include => [:user, {:repository => :project}]}
38 :find_options => {:include => [:user, {:repository => :project}]}
39
39
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
41 validates_uniqueness_of :revision, :scope => :repository_id
41 validates_uniqueness_of :revision, :scope => :repository_id
42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
43
43
44 named_scope :visible, lambda {|*args| { :include => {:repository => :project},
44 named_scope :visible, lambda {|*args| { :include => {:repository => :project},
45 :conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } }
45 :conditions => Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args) } }
46
46
47 def revision=(r)
47 def revision=(r)
48 write_attribute :revision, (r.nil? ? nil : r.to_s)
48 write_attribute :revision, (r.nil? ? nil : r.to_s)
49 end
49 end
50
50
51 # Returns the identifier of this changeset; depending on repository backends
51 # Returns the identifier of this changeset; depending on repository backends
52 def identifier
52 def identifier
53 if repository.class.respond_to? :changeset_identifier
53 if repository.class.respond_to? :changeset_identifier
54 repository.class.changeset_identifier self
54 repository.class.changeset_identifier self
55 else
55 else
56 revision.to_s
56 revision.to_s
57 end
57 end
58 end
58 end
59
59
60 def committed_on=(date)
60 def committed_on=(date)
61 self.commit_date = date
61 self.commit_date = date
62 super
62 super
63 end
63 end
64
64
65 # Returns the readable identifier
65 # Returns the readable identifier
66 def format_identifier
66 def format_identifier
67 if repository.class.respond_to? :format_changeset_identifier
67 if repository.class.respond_to? :format_changeset_identifier
68 repository.class.format_changeset_identifier self
68 repository.class.format_changeset_identifier self
69 else
69 else
70 identifier
70 identifier
71 end
71 end
72 end
72 end
73
73
74 def project
74 def project
75 repository.project
75 repository.project
76 end
76 end
77
77
78 def author
78 def author
79 user || committer.to_s.split('<').first
79 user || committer.to_s.split('<').first
80 end
80 end
81
81
82 def before_create
82 def before_create
83 self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
83 self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
84 self.comments = self.class.normalize_comments(
84 self.comments = self.class.normalize_comments(
85 self.comments, repository.repo_log_encoding)
85 self.comments, repository.repo_log_encoding)
86 self.user = repository.find_committer_user(self.committer)
86 self.user = repository.find_committer_user(self.committer)
87 end
87 end
88
88
89 def after_create
89 def after_create
90 scan_comment_for_issue_ids
90 scan_comment_for_issue_ids
91 end
91 end
92
92
93 TIMELOG_RE = /
93 TIMELOG_RE = /
94 (
94 (
95 ((\d+)(h|hours?))((\d+)(m|min)?)?
95 ((\d+)(h|hours?))((\d+)(m|min)?)?
96 |
96 |
97 ((\d+)(h|hours?|m|min))
97 ((\d+)(h|hours?|m|min))
98 |
98 |
99 (\d+):(\d+)
99 (\d+):(\d+)
100 |
100 |
101 (\d+([\.,]\d+)?)h?
101 (\d+([\.,]\d+)?)h?
102 )
102 )
103 /x
103 /x
104
104
105 def scan_comment_for_issue_ids
105 def scan_comment_for_issue_ids
106 return if comments.blank?
106 return if comments.blank?
107 # keywords used to reference issues
107 # keywords used to reference issues
108 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
108 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
109 ref_keywords_any = ref_keywords.delete('*')
109 ref_keywords_any = ref_keywords.delete('*')
110 # keywords used to fix issues
110 # keywords used to fix issues
111 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
111 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
112
112
113 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
113 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
114
114
115 referenced_issues = []
115 referenced_issues = []
116
116
117 comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
117 comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
118 action, refs = match[2], match[3]
118 action, refs = match[2], match[3]
119 next unless action.present? || ref_keywords_any
119 next unless action.present? || ref_keywords_any
120
120
121 refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
121 refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
122 issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
122 issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
123 if issue
123 if issue
124 referenced_issues << issue
124 referenced_issues << issue
125 fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
125 fix_issue(issue) if fix_keywords.include?(action.to_s.downcase)
126 log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
126 log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
127 end
127 end
128 end
128 end
129 end
129 end
130
130
131 referenced_issues.uniq!
131 referenced_issues.uniq!
132 self.issues = referenced_issues unless referenced_issues.empty?
132 self.issues = referenced_issues unless referenced_issues.empty?
133 end
133 end
134
134
135 def short_comments
135 def short_comments
136 @short_comments || split_comments.first
136 @short_comments || split_comments.first
137 end
137 end
138
138
139 def long_comments
139 def long_comments
140 @long_comments || split_comments.last
140 @long_comments || split_comments.last
141 end
141 end
142
142
143 def text_tag
143 def text_tag
144 if scmid?
144 if scmid?
145 "commit:#{scmid}"
145 "commit:#{scmid}"
146 else
146 else
147 "r#{revision}"
147 "r#{revision}"
148 end
148 end
149 end
149 end
150
150
151 # Returns the previous changeset
151 # Returns the previous changeset
152 def previous
152 def previous
153 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
153 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
154 end
154 end
155
155
156 # Returns the next changeset
156 # Returns the next changeset
157 def next
157 def next
158 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
158 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
159 end
159 end
160
160
161 # Creates a new Change from it's common parameters
161 # Creates a new Change from it's common parameters
162 def create_change(change)
162 def create_change(change)
163 Change.create(:changeset => self,
163 Change.create(:changeset => self,
164 :action => change[:action],
164 :action => change[:action],
165 :path => change[:path],
165 :path => change[:path],
166 :from_path => change[:from_path],
166 :from_path => change[:from_path],
167 :from_revision => change[:from_revision])
167 :from_revision => change[:from_revision])
168 end
168 end
169
169
170 private
170 private
171
171
172 # Finds an issue that can be referenced by the commit message
172 # Finds an issue that can be referenced by the commit message
173 # i.e. an issue that belong to the repository project, a subproject or a parent project
173 # i.e. an issue that belong to the repository project, a subproject or a parent project
174 def find_referenced_issue_by_id(id)
174 def find_referenced_issue_by_id(id)
175 return nil if id.blank?
175 return nil if id.blank?
176 issue = Issue.find_by_id(id.to_i, :include => :project)
176 issue = Issue.find_by_id(id.to_i, :include => :project)
177 if issue
177 if issue
178 unless issue.project &&
178 unless issue.project &&
179 (project == issue.project || project.is_ancestor_of?(issue.project) ||
179 (project == issue.project || project.is_ancestor_of?(issue.project) ||
180 project.is_descendant_of?(issue.project))
180 project.is_descendant_of?(issue.project))
181 issue = nil
181 issue = nil
182 end
182 end
183 end
183 end
184 issue
184 issue
185 end
185 end
186
186
187 def fix_issue(issue)
187 def fix_issue(issue)
188 status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
188 status = IssueStatus.find_by_id(Setting.commit_fix_status_id.to_i)
189 if status.nil?
189 if status.nil?
190 logger.warn("No status macthes commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
190 logger.warn("No status macthes commit_fix_status_id setting (#{Setting.commit_fix_status_id})") if logger
191 return issue
191 return issue
192 end
192 end
193
193
194 # the issue may have been updated by the closure of another one (eg. duplicate)
194 # the issue may have been updated by the closure of another one (eg. duplicate)
195 issue.reload
195 issue.reload
196 # don't change the status is the issue is closed
196 # don't change the status is the issue is closed
197 return if issue.status && issue.status.is_closed?
197 return if issue.status && issue.status.is_closed?
198
198
199 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
199 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag))
200 issue.status = status
200 issue.status = status
201 unless Setting.commit_fix_done_ratio.blank?
201 unless Setting.commit_fix_done_ratio.blank?
202 issue.done_ratio = Setting.commit_fix_done_ratio.to_i
202 issue.done_ratio = Setting.commit_fix_done_ratio.to_i
203 end
203 end
204 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
204 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
205 { :changeset => self, :issue => issue })
205 { :changeset => self, :issue => issue })
206 unless issue.save
206 unless issue.save
207 logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
207 logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
208 end
208 end
209 issue
209 issue
210 end
210 end
211
211
212 def log_time(issue, hours)
212 def log_time(issue, hours)
213 time_entry = TimeEntry.new(
213 time_entry = TimeEntry.new(
214 :user => user,
214 :user => user,
215 :hours => hours,
215 :hours => hours,
216 :issue => issue,
216 :issue => issue,
217 :spent_on => commit_date,
217 :spent_on => commit_date,
218 :comments => l(:text_time_logged_by_changeset, :value => text_tag,
218 :comments => l(:text_time_logged_by_changeset, :value => text_tag,
219 :locale => Setting.default_language)
219 :locale => Setting.default_language)
220 )
220 )
221 time_entry.activity = log_time_activity unless log_time_activity.nil?
221 time_entry.activity = log_time_activity unless log_time_activity.nil?
222
222
223 unless time_entry.save
223 unless time_entry.save
224 logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
224 logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
225 end
225 end
226 time_entry
226 time_entry
227 end
227 end
228
228
229 def log_time_activity
229 def log_time_activity
230 if Setting.commit_logtime_activity_id.to_i > 0
230 if Setting.commit_logtime_activity_id.to_i > 0
231 TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
231 TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
232 end
232 end
233 end
233 end
234
234
235 def split_comments
235 def split_comments
236 comments =~ /\A(.+?)\r?\n(.*)$/m
236 comments =~ /\A(.+?)\r?\n(.*)$/m
237 @short_comments = $1 || comments
237 @short_comments = $1 || comments
238 @long_comments = $2.to_s.strip
238 @long_comments = $2.to_s.strip
239 return @short_comments, @long_comments
239 return @short_comments, @long_comments
240 end
240 end
241
241
242 public
242 public
243
243
244 # Strips and reencodes a commit log before insertion into the database
244 # Strips and reencodes a commit log before insertion into the database
245 def self.normalize_comments(str, encoding)
245 def self.normalize_comments(str, encoding)
246 Changeset.to_utf8(str.to_s.strip, encoding)
246 Changeset.to_utf8(str.to_s.strip, encoding)
247 end
247 end
248
248
249 private
249 private
250
250
251 def self.to_utf8(str, encoding)
251 def self.to_utf8(str, encoding)
252 return str if str.nil?
252 return str if str.nil?
253 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
253 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
254 if str.empty?
254 if str.empty?
255 str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
255 str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
256 return str
256 return str
257 end
257 end
258 if str.respond_to?(:force_encoding)
259 enc = encoding.blank? ? "UTF-8" : encoding
258 enc = encoding.blank? ? "UTF-8" : encoding
259 if str.respond_to?(:force_encoding)
260 if enc != "UTF-8"
260 if enc != "UTF-8"
261 str.force_encoding(enc)
261 str.force_encoding(enc)
262 str = str.encode("UTF-8", :invalid => :replace,
262 str = str.encode("UTF-8", :invalid => :replace,
263 :undef => :replace, :replace => '?')
263 :undef => :replace, :replace => '?')
264 else
264 else
265 str.force_encoding("UTF-8")
265 str.force_encoding("UTF-8")
266 if ! str.valid_encoding?
266 if ! str.valid_encoding?
267 str = str.encode("US-ASCII", :invalid => :replace,
267 str = str.encode("US-ASCII", :invalid => :replace,
268 :undef => :replace, :replace => '?').encode("UTF-8")
268 :undef => :replace, :replace => '?').encode("UTF-8")
269 end
269 end
270 end
270 end
271 else
271 else
272 unless encoding.blank? || encoding == 'UTF-8'
272 ic = Iconv.new('UTF-8', enc)
273 begin
273 txtar = ""
274 str = Iconv.conv('UTF-8', encoding, str)
275 rescue Iconv::Failure
276 # do nothing here
277 end
278 end
279 # removes invalid UTF8 sequences
280 begin
274 begin
281 str = Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
275 txtar += ic.iconv(str)
282 rescue Iconv::InvalidEncoding
276 rescue Iconv::IllegalSequence
283 # "UTF-8//IGNORE" is not supported on some OS
277 txtar += $!.success
284 end
278 str = '?' + $!.failed[1,$!.failed.length]
279 retry
280 rescue
281 txtar += $!.success
282 end
283 str = txtar
285 end
284 end
286 str
285 str
287 end
286 end
288 end
287 end
@@ -1,365 +1,359
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2010 Jean-Philippe Lang
4 # Copyright (C) 2006-2010 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.expand_path('../../test_helper', __FILE__)
20 require File.expand_path('../../test_helper', __FILE__)
21
21
22 class ChangesetTest < ActiveSupport::TestCase
22 class ChangesetTest < ActiveSupport::TestCase
23 fixtures :projects, :repositories, :issues, :issue_statuses,
23 fixtures :projects, :repositories, :issues, :issue_statuses,
24 :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :users, :members, :member_roles, :trackers
24 :changesets, :changes, :issue_categories, :enumerations,
25 :custom_fields, :custom_values, :users, :members, :member_roles, :trackers
25
26
26 def setup
27 def setup
27 end
28 end
28
29
29 def test_ref_keywords_any
30 def test_ref_keywords_any
30 ActionMailer::Base.deliveries.clear
31 ActionMailer::Base.deliveries.clear
31 Setting.commit_fix_status_id = IssueStatus.find(
32 Setting.commit_fix_status_id = IssueStatus.find(
32 :first, :conditions => ["is_closed = ?", true]).id
33 :first, :conditions => ["is_closed = ?", true]).id
33 Setting.commit_fix_done_ratio = '90'
34 Setting.commit_fix_done_ratio = '90'
34 Setting.commit_ref_keywords = '*'
35 Setting.commit_ref_keywords = '*'
35 Setting.commit_fix_keywords = 'fixes , closes'
36 Setting.commit_fix_keywords = 'fixes , closes'
36
37
37 c = Changeset.new(:repository => Project.find(1).repository,
38 c = Changeset.new(:repository => Project.find(1).repository,
38 :committed_on => Time.now,
39 :committed_on => Time.now,
39 :comments => 'New commit (#2). Fixes #1')
40 :comments => 'New commit (#2). Fixes #1')
40 c.scan_comment_for_issue_ids
41 c.scan_comment_for_issue_ids
41
42
42 assert_equal [1, 2], c.issue_ids.sort
43 assert_equal [1, 2], c.issue_ids.sort
43 fixed = Issue.find(1)
44 fixed = Issue.find(1)
44 assert fixed.closed?
45 assert fixed.closed?
45 assert_equal 90, fixed.done_ratio
46 assert_equal 90, fixed.done_ratio
46 assert_equal 1, ActionMailer::Base.deliveries.size
47 assert_equal 1, ActionMailer::Base.deliveries.size
47 end
48 end
48
49
49 def test_ref_keywords
50 def test_ref_keywords
50 Setting.commit_ref_keywords = 'refs'
51 Setting.commit_ref_keywords = 'refs'
51 Setting.commit_fix_keywords = ''
52 Setting.commit_fix_keywords = ''
52
53
53 c = Changeset.new(:repository => Project.find(1).repository,
54 c = Changeset.new(:repository => Project.find(1).repository,
54 :committed_on => Time.now,
55 :committed_on => Time.now,
55 :comments => 'Ignores #2. Refs #1')
56 :comments => 'Ignores #2. Refs #1')
56 c.scan_comment_for_issue_ids
57 c.scan_comment_for_issue_ids
57
58
58 assert_equal [1], c.issue_ids.sort
59 assert_equal [1], c.issue_ids.sort
59 end
60 end
60
61
61 def test_ref_keywords_any_only
62 def test_ref_keywords_any_only
62 Setting.commit_ref_keywords = '*'
63 Setting.commit_ref_keywords = '*'
63 Setting.commit_fix_keywords = ''
64 Setting.commit_fix_keywords = ''
64
65
65 c = Changeset.new(:repository => Project.find(1).repository,
66 c = Changeset.new(:repository => Project.find(1).repository,
66 :committed_on => Time.now,
67 :committed_on => Time.now,
67 :comments => 'Ignores #2. Refs #1')
68 :comments => 'Ignores #2. Refs #1')
68 c.scan_comment_for_issue_ids
69 c.scan_comment_for_issue_ids
69
70
70 assert_equal [1, 2], c.issue_ids.sort
71 assert_equal [1, 2], c.issue_ids.sort
71 end
72 end
72
73
73 def test_ref_keywords_any_with_timelog
74 def test_ref_keywords_any_with_timelog
74 Setting.commit_ref_keywords = '*'
75 Setting.commit_ref_keywords = '*'
75 Setting.commit_logtime_enabled = '1'
76 Setting.commit_logtime_enabled = '1'
76
77
77 {
78 {
78 '2' => 2.0,
79 '2' => 2.0,
79 '2h' => 2.0,
80 '2h' => 2.0,
80 '2hours' => 2.0,
81 '2hours' => 2.0,
81 '15m' => 0.25,
82 '15m' => 0.25,
82 '15min' => 0.25,
83 '15min' => 0.25,
83 '3h15' => 3.25,
84 '3h15' => 3.25,
84 '3h15m' => 3.25,
85 '3h15m' => 3.25,
85 '3h15min' => 3.25,
86 '3h15min' => 3.25,
86 '3:15' => 3.25,
87 '3:15' => 3.25,
87 '3.25' => 3.25,
88 '3.25' => 3.25,
88 '3.25h' => 3.25,
89 '3.25h' => 3.25,
89 '3,25' => 3.25,
90 '3,25' => 3.25,
90 '3,25h' => 3.25,
91 '3,25h' => 3.25,
91 }.each do |syntax, expected_hours|
92 }.each do |syntax, expected_hours|
92 c = Changeset.new(:repository => Project.find(1).repository,
93 c = Changeset.new(:repository => Project.find(1).repository,
93 :committed_on => 24.hours.ago,
94 :committed_on => 24.hours.ago,
94 :comments => "Worked on this issue #1 @#{syntax}",
95 :comments => "Worked on this issue #1 @#{syntax}",
95 :revision => '520',
96 :revision => '520',
96 :user => User.find(2))
97 :user => User.find(2))
97 assert_difference 'TimeEntry.count' do
98 assert_difference 'TimeEntry.count' do
98 c.scan_comment_for_issue_ids
99 c.scan_comment_for_issue_ids
99 end
100 end
100 assert_equal [1], c.issue_ids.sort
101 assert_equal [1], c.issue_ids.sort
101
102
102 time = TimeEntry.first(:order => 'id desc')
103 time = TimeEntry.first(:order => 'id desc')
103 assert_equal 1, time.issue_id
104 assert_equal 1, time.issue_id
104 assert_equal 1, time.project_id
105 assert_equal 1, time.project_id
105 assert_equal 2, time.user_id
106 assert_equal 2, time.user_id
106 assert_equal expected_hours, time.hours,
107 assert_equal expected_hours, time.hours,
107 "@#{syntax} should be logged as #{expected_hours} hours but was #{time.hours}"
108 "@#{syntax} should be logged as #{expected_hours} hours but was #{time.hours}"
108 assert_equal Date.yesterday, time.spent_on
109 assert_equal Date.yesterday, time.spent_on
109 assert time.activity.is_default?
110 assert time.activity.is_default?
110 assert time.comments.include?('r520'),
111 assert time.comments.include?('r520'),
111 "r520 was expected in time_entry comments: #{time.comments}"
112 "r520 was expected in time_entry comments: #{time.comments}"
112 end
113 end
113 end
114 end
114
115
115 def test_ref_keywords_closing_with_timelog
116 def test_ref_keywords_closing_with_timelog
116 Setting.commit_fix_status_id = IssueStatus.find(
117 Setting.commit_fix_status_id = IssueStatus.find(
117 :first, :conditions => ["is_closed = ?", true]).id
118 :first, :conditions => ["is_closed = ?", true]).id
118 Setting.commit_ref_keywords = '*'
119 Setting.commit_ref_keywords = '*'
119 Setting.commit_fix_keywords = 'fixes , closes'
120 Setting.commit_fix_keywords = 'fixes , closes'
120 Setting.commit_logtime_enabled = '1'
121 Setting.commit_logtime_enabled = '1'
121
122
122 c = Changeset.new(:repository => Project.find(1).repository,
123 c = Changeset.new(:repository => Project.find(1).repository,
123 :committed_on => Time.now,
124 :committed_on => Time.now,
124 :comments => 'This is a comment. Fixes #1 @4.5, #2 @1',
125 :comments => 'This is a comment. Fixes #1 @4.5, #2 @1',
125 :user => User.find(2))
126 :user => User.find(2))
126 assert_difference 'TimeEntry.count', 2 do
127 assert_difference 'TimeEntry.count', 2 do
127 c.scan_comment_for_issue_ids
128 c.scan_comment_for_issue_ids
128 end
129 end
129
130
130 assert_equal [1, 2], c.issue_ids.sort
131 assert_equal [1, 2], c.issue_ids.sort
131 assert Issue.find(1).closed?
132 assert Issue.find(1).closed?
132 assert Issue.find(2).closed?
133 assert Issue.find(2).closed?
133
134
134 times = TimeEntry.all(:order => 'id desc', :limit => 2)
135 times = TimeEntry.all(:order => 'id desc', :limit => 2)
135 assert_equal [1, 2], times.collect(&:issue_id).sort
136 assert_equal [1, 2], times.collect(&:issue_id).sort
136 end
137 end
137
138
138 def test_ref_keywords_any_line_start
139 def test_ref_keywords_any_line_start
139 Setting.commit_ref_keywords = '*'
140 Setting.commit_ref_keywords = '*'
140
141
141 c = Changeset.new(:repository => Project.find(1).repository,
142 c = Changeset.new(:repository => Project.find(1).repository,
142 :committed_on => Time.now,
143 :committed_on => Time.now,
143 :comments => '#1 is the reason of this commit')
144 :comments => '#1 is the reason of this commit')
144 c.scan_comment_for_issue_ids
145 c.scan_comment_for_issue_ids
145
146
146 assert_equal [1], c.issue_ids.sort
147 assert_equal [1], c.issue_ids.sort
147 end
148 end
148
149
149 def test_ref_keywords_allow_brackets_around_a_issue_number
150 def test_ref_keywords_allow_brackets_around_a_issue_number
150 Setting.commit_ref_keywords = '*'
151 Setting.commit_ref_keywords = '*'
151
152
152 c = Changeset.new(:repository => Project.find(1).repository,
153 c = Changeset.new(:repository => Project.find(1).repository,
153 :committed_on => Time.now,
154 :committed_on => Time.now,
154 :comments => '[#1] Worked on this issue')
155 :comments => '[#1] Worked on this issue')
155 c.scan_comment_for_issue_ids
156 c.scan_comment_for_issue_ids
156
157
157 assert_equal [1], c.issue_ids.sort
158 assert_equal [1], c.issue_ids.sort
158 end
159 end
159
160
160 def test_ref_keywords_allow_brackets_around_multiple_issue_numbers
161 def test_ref_keywords_allow_brackets_around_multiple_issue_numbers
161 Setting.commit_ref_keywords = '*'
162 Setting.commit_ref_keywords = '*'
162
163
163 c = Changeset.new(:repository => Project.find(1).repository,
164 c = Changeset.new(:repository => Project.find(1).repository,
164 :committed_on => Time.now,
165 :committed_on => Time.now,
165 :comments => '[#1 #2, #3] Worked on these')
166 :comments => '[#1 #2, #3] Worked on these')
166 c.scan_comment_for_issue_ids
167 c.scan_comment_for_issue_ids
167
168
168 assert_equal [1,2,3], c.issue_ids.sort
169 assert_equal [1,2,3], c.issue_ids.sort
169 end
170 end
170
171
171 def test_commit_referencing_a_subproject_issue
172 def test_commit_referencing_a_subproject_issue
172 c = Changeset.new(:repository => Project.find(1).repository,
173 c = Changeset.new(:repository => Project.find(1).repository,
173 :committed_on => Time.now,
174 :committed_on => Time.now,
174 :comments => 'refs #5, a subproject issue')
175 :comments => 'refs #5, a subproject issue')
175 c.scan_comment_for_issue_ids
176 c.scan_comment_for_issue_ids
176
177
177 assert_equal [5], c.issue_ids.sort
178 assert_equal [5], c.issue_ids.sort
178 assert c.issues.first.project != c.project
179 assert c.issues.first.project != c.project
179 end
180 end
180
181
181 def test_commit_referencing_a_parent_project_issue
182 def test_commit_referencing_a_parent_project_issue
182 # repository of child project
183 # repository of child project
183 r = Repository::Subversion.create!(
184 r = Repository::Subversion.create!(
184 :project => Project.find(3),
185 :project => Project.find(3),
185 :url => 'svn://localhost/test')
186 :url => 'svn://localhost/test')
186
187
187 c = Changeset.new(:repository => r,
188 c = Changeset.new(:repository => r,
188 :committed_on => Time.now,
189 :committed_on => Time.now,
189 :comments => 'refs #2, an issue of a parent project')
190 :comments => 'refs #2, an issue of a parent project')
190 c.scan_comment_for_issue_ids
191 c.scan_comment_for_issue_ids
191
192
192 assert_equal [2], c.issue_ids.sort
193 assert_equal [2], c.issue_ids.sort
193 assert c.issues.first.project != c.project
194 assert c.issues.first.project != c.project
194 end
195 end
195
196
196 def test_text_tag_revision
197 def test_text_tag_revision
197 c = Changeset.new(:revision => '520')
198 c = Changeset.new(:revision => '520')
198 assert_equal 'r520', c.text_tag
199 assert_equal 'r520', c.text_tag
199 end
200 end
200
201
201 def test_text_tag_hash
202 def test_text_tag_hash
202 c = Changeset.new(
203 c = Changeset.new(
203 :scmid => '7234cb2750b63f47bff735edc50a1c0a433c2518',
204 :scmid => '7234cb2750b63f47bff735edc50a1c0a433c2518',
204 :revision => '7234cb2750b63f47bff735edc50a1c0a433c2518')
205 :revision => '7234cb2750b63f47bff735edc50a1c0a433c2518')
205 assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag
206 assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag
206 end
207 end
207
208
208 def test_text_tag_hash_all_number
209 def test_text_tag_hash_all_number
209 c = Changeset.new(:scmid => '0123456789', :revision => '0123456789')
210 c = Changeset.new(:scmid => '0123456789', :revision => '0123456789')
210 assert_equal 'commit:0123456789', c.text_tag
211 assert_equal 'commit:0123456789', c.text_tag
211 end
212 end
212
213
213 def test_previous
214 def test_previous
214 changeset = Changeset.find_by_revision('3')
215 changeset = Changeset.find_by_revision('3')
215 assert_equal Changeset.find_by_revision('2'), changeset.previous
216 assert_equal Changeset.find_by_revision('2'), changeset.previous
216 end
217 end
217
218
218 def test_previous_nil
219 def test_previous_nil
219 changeset = Changeset.find_by_revision('1')
220 changeset = Changeset.find_by_revision('1')
220 assert_nil changeset.previous
221 assert_nil changeset.previous
221 end
222 end
222
223
223 def test_next
224 def test_next
224 changeset = Changeset.find_by_revision('2')
225 changeset = Changeset.find_by_revision('2')
225 assert_equal Changeset.find_by_revision('3'), changeset.next
226 assert_equal Changeset.find_by_revision('3'), changeset.next
226 end
227 end
227
228
228 def test_next_nil
229 def test_next_nil
229 changeset = Changeset.find_by_revision('10')
230 changeset = Changeset.find_by_revision('10')
230 assert_nil changeset.next
231 assert_nil changeset.next
231 end
232 end
232
233
233 def test_comments_should_be_converted_to_utf8
234 def test_comments_should_be_converted_to_utf8
234 proj = Project.find(3)
235 proj = Project.find(3)
235 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
236 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
236 str = "Texte encod\xe9 en ISO-8859-1."
237 str = "Texte encod\xe9 en ISO-8859-1."
237 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
238 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
238 r = Repository::Bazaar.create!(
239 r = Repository::Bazaar.create!(
239 :project => proj, :url => '/tmp/test/bazaar',
240 :project => proj, :url => '/tmp/test/bazaar',
240 :log_encoding => 'ISO-8859-1' )
241 :log_encoding => 'ISO-8859-1' )
241 assert r
242 assert r
242 c = Changeset.new(:repository => r,
243 c = Changeset.new(:repository => r,
243 :committed_on => Time.now,
244 :committed_on => Time.now,
244 :revision => '123',
245 :revision => '123',
245 :scmid => '12345',
246 :scmid => '12345',
246 :comments => str)
247 :comments => str)
247 assert( c.save )
248 assert( c.save )
248 str_utf8 = "Texte encod\xc3\xa9 en ISO-8859-1."
249 str_utf8 = "Texte encod\xc3\xa9 en ISO-8859-1."
249 str_utf8.force_encoding("UTF-8") if str_utf8.respond_to?(:force_encoding)
250 str_utf8.force_encoding("UTF-8") if str_utf8.respond_to?(:force_encoding)
250 assert_equal str_utf8, c.comments
251 assert_equal str_utf8, c.comments
251 end
252 end
252
253
253 def test_invalid_utf8_sequences_in_comments_should_be_stripped
254 def test_invalid_utf8_sequences_in_comments_should_be_replaced_latin1
254 proj = Project.find(3)
255 proj = Project.find(3)
255 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
256 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
256 str = "Texte encod\xe9 en ISO-8859-1."
257 str = "Texte encod\xe9 en ISO-8859-1."
257 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
258 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
258 r = Repository::Bazaar.create!(
259 r = Repository::Bazaar.create!(
259 :project => proj, :url => '/tmp/test/bazaar',
260 :project => proj,
261 :url => '/tmp/test/bazaar',
260 :log_encoding => 'UTF-8' )
262 :log_encoding => 'UTF-8' )
261 assert r
263 assert r
262 c = Changeset.new(:repository => r,
264 c = Changeset.new(:repository => r,
263 :committed_on => Time.now,
265 :committed_on => Time.now,
264 :revision => '123',
266 :revision => '123',
265 :scmid => '12345',
267 :scmid => '12345',
266 :comments => str)
268 :comments => str)
267 assert( c.save )
269 assert( c.save )
268 if str.respond_to?(:force_encoding)
269 assert_equal "Texte encod? en ISO-8859-1.", c.comments
270 assert_equal "Texte encod? en ISO-8859-1.", c.comments
270 else
271 assert_equal "Texte encod en ISO-8859-1.", c.comments
272 end
273 end
271 end
274
272
275 def test_invalid_utf8_sequences_in_comments_should_be_stripped_ja_jis
273 def test_invalid_utf8_sequences_in_comments_should_be_replaced_ja_jis
276 proj = Project.find(3)
274 proj = Project.find(3)
277 str = "test\xb5\xfetest\xb5\xfe"
275 str = "test\xb5\xfetest\xb5\xfe"
278 if str.respond_to?(:force_encoding)
276 if str.respond_to?(:force_encoding)
279 str.force_encoding('ASCII-8BIT')
277 str.force_encoding('ASCII-8BIT')
280 end
278 end
281 r = Repository::Bazaar.create!(
279 r = Repository::Bazaar.create!(
282 :project => proj,
280 :project => proj,
283 :url => '/tmp/test/bazaar',
281 :url => '/tmp/test/bazaar',
284 :log_encoding => 'ISO-2022-JP' )
282 :log_encoding => 'ISO-2022-JP' )
285 assert r
283 assert r
286 c = Changeset.new(:repository => r,
284 c = Changeset.new(:repository => r,
287 :committed_on => Time.now,
285 :committed_on => Time.now,
288 :revision => '123',
286 :revision => '123',
289 :scmid => '12345',
287 :scmid => '12345',
290 :comments => str)
288 :comments => str)
291 assert( c.save )
289 assert( c.save )
292 if str.respond_to?(:force_encoding)
293 assert_equal "test??test??", c.comments
290 assert_equal "test??test??", c.comments
294 else
295 assert_equal "testtest", c.comments
296 end
297 end
291 end
298
292
299 def test_comments_should_be_converted_all_latin1_to_utf8
293 def test_comments_should_be_converted_all_latin1_to_utf8
300 s1 = "\xC2\x80"
294 s1 = "\xC2\x80"
301 s2 = "\xc3\x82\xc2\x80"
295 s2 = "\xc3\x82\xc2\x80"
302 s4 = s2.dup
296 s4 = s2.dup
303 if s1.respond_to?(:force_encoding)
297 if s1.respond_to?(:force_encoding)
304 s3 = s1.dup
298 s3 = s1.dup
305 s1.force_encoding('ASCII-8BIT')
299 s1.force_encoding('ASCII-8BIT')
306 s2.force_encoding('ASCII-8BIT')
300 s2.force_encoding('ASCII-8BIT')
307 s3.force_encoding('ISO-8859-1')
301 s3.force_encoding('ISO-8859-1')
308 s4.force_encoding('UTF-8')
302 s4.force_encoding('UTF-8')
309 assert_equal s3.encode('UTF-8'), s4
303 assert_equal s3.encode('UTF-8'), s4
310 end
304 end
311 proj = Project.find(3)
305 proj = Project.find(3)
312 r = Repository::Bazaar.create!(
306 r = Repository::Bazaar.create!(
313 :project => proj, :url => '/tmp/test/bazaar',
307 :project => proj, :url => '/tmp/test/bazaar',
314 :log_encoding => 'ISO-8859-1' )
308 :log_encoding => 'ISO-8859-1' )
315 assert r
309 assert r
316 c = Changeset.new(:repository => r,
310 c = Changeset.new(:repository => r,
317 :committed_on => Time.now,
311 :committed_on => Time.now,
318 :revision => '123',
312 :revision => '123',
319 :scmid => '12345',
313 :scmid => '12345',
320 :comments => s1)
314 :comments => s1)
321 assert( c.save )
315 assert( c.save )
322 assert_equal s4, c.comments
316 assert_equal s4, c.comments
323 end
317 end
324
318
325 def test_comments_nil
319 def test_comments_nil
326 proj = Project.find(3)
320 proj = Project.find(3)
327 r = Repository::Bazaar.create!(
321 r = Repository::Bazaar.create!(
328 :project => proj, :url => '/tmp/test/bazaar',
322 :project => proj, :url => '/tmp/test/bazaar',
329 :log_encoding => 'ISO-8859-1' )
323 :log_encoding => 'ISO-8859-1' )
330 assert r
324 assert r
331 c = Changeset.new(:repository => r,
325 c = Changeset.new(:repository => r,
332 :committed_on => Time.now,
326 :committed_on => Time.now,
333 :revision => '123',
327 :revision => '123',
334 :scmid => '12345',
328 :scmid => '12345',
335 :comments => nil)
329 :comments => nil)
336 assert( c.save )
330 assert( c.save )
337 assert_equal "", c.comments
331 assert_equal "", c.comments
338 if c.comments.respond_to?(:force_encoding)
332 if c.comments.respond_to?(:force_encoding)
339 assert_equal "UTF-8", c.comments.encoding.to_s
333 assert_equal "UTF-8", c.comments.encoding.to_s
340 end
334 end
341 end
335 end
342
336
343 def test_comments_empty
337 def test_comments_empty
344 proj = Project.find(3)
338 proj = Project.find(3)
345 r = Repository::Bazaar.create!(
339 r = Repository::Bazaar.create!(
346 :project => proj, :url => '/tmp/test/bazaar',
340 :project => proj, :url => '/tmp/test/bazaar',
347 :log_encoding => 'ISO-8859-1' )
341 :log_encoding => 'ISO-8859-1' )
348 assert r
342 assert r
349 c = Changeset.new(:repository => r,
343 c = Changeset.new(:repository => r,
350 :committed_on => Time.now,
344 :committed_on => Time.now,
351 :revision => '123',
345 :revision => '123',
352 :scmid => '12345',
346 :scmid => '12345',
353 :comments => "")
347 :comments => "")
354 assert( c.save )
348 assert( c.save )
355 assert_equal "", c.comments
349 assert_equal "", c.comments
356 if c.comments.respond_to?(:force_encoding)
350 if c.comments.respond_to?(:force_encoding)
357 assert_equal "UTF-8", c.comments.encoding.to_s
351 assert_equal "UTF-8", c.comments.encoding.to_s
358 end
352 end
359 end
353 end
360
354
361 def test_identifier
355 def test_identifier
362 c = Changeset.find_by_revision('1')
356 c = Changeset.find_by_revision('1')
363 assert_equal c.revision, c.identifier
357 assert_equal c.revision, c.identifier
364 end
358 end
365 end
359 end
General Comments 0
You need to be logged in to leave comments. Login now