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