##// END OF EJS Templates
Merged r14758 (#21071)....
Jean-Philippe Lang -
r14421:a483b19a95f6
parent child
Show More
@@ -1,294 +1,294
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 class Changeset < ActiveRecord::Base
18 class Changeset < ActiveRecord::Base
19 belongs_to :repository
19 belongs_to :repository
20 belongs_to :user
20 belongs_to :user
21 has_many :filechanges, :class_name => 'Change', :dependent => :delete_all
21 has_many :filechanges, :class_name => 'Change', :dependent => :delete_all
22 has_and_belongs_to_many :issues
22 has_and_belongs_to_many :issues
23 has_and_belongs_to_many :parents,
23 has_and_belongs_to_many :parents,
24 :class_name => "Changeset",
24 :class_name => "Changeset",
25 :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
25 :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
26 :association_foreign_key => 'parent_id', :foreign_key => 'changeset_id'
26 :association_foreign_key => 'parent_id', :foreign_key => 'changeset_id'
27 has_and_belongs_to_many :children,
27 has_and_belongs_to_many :children,
28 :class_name => "Changeset",
28 :class_name => "Changeset",
29 :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
29 :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
30 :association_foreign_key => 'changeset_id', :foreign_key => 'parent_id'
30 :association_foreign_key => 'changeset_id', :foreign_key => 'parent_id'
31
31
32 acts_as_event :title => Proc.new {|o| o.title},
32 acts_as_event :title => Proc.new {|o| o.title},
33 :description => :long_comments,
33 :description => :long_comments,
34 :datetime => :committed_on,
34 :datetime => :committed_on,
35 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}}
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 acts_as_searchable :columns => 'comments',
37 acts_as_searchable :columns => 'comments',
38 :preload => {:repository => :project},
38 :preload => {:repository => :project},
39 :project_key => "#{Repository.table_name}.project_id",
39 :project_key => "#{Repository.table_name}.project_id",
40 :date_column => :committed_on
40 :date_column => :committed_on
41
41
42 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
42 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
43 :author_key => :user_id,
43 :author_key => :user_id,
44 :scope => preload(:user, {:repository => :project})
44 :scope => preload(:user, {:repository => :project})
45
45
46 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
46 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
47 validates_uniqueness_of :revision, :scope => :repository_id
47 validates_uniqueness_of :revision, :scope => :repository_id
48 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
48 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
49 attr_protected :id
49 attr_protected :id
50
50
51 scope :visible, lambda {|*args|
51 scope :visible, lambda {|*args|
52 joins(:repository => :project).
52 joins(:repository => :project).
53 where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args))
53 where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args))
54 }
54 }
55
55
56 after_create :scan_for_issues
56 after_create :scan_for_issues
57 before_create :before_create_cs
57 before_create :before_create_cs
58
58
59 def revision=(r)
59 def revision=(r)
60 write_attribute :revision, (r.nil? ? nil : r.to_s)
60 write_attribute :revision, (r.nil? ? nil : r.to_s)
61 end
61 end
62
62
63 # Returns the identifier of this changeset; depending on repository backends
63 # Returns the identifier of this changeset; depending on repository backends
64 def identifier
64 def identifier
65 if repository.class.respond_to? :changeset_identifier
65 if repository.class.respond_to? :changeset_identifier
66 repository.class.changeset_identifier self
66 repository.class.changeset_identifier self
67 else
67 else
68 revision.to_s
68 revision.to_s
69 end
69 end
70 end
70 end
71
71
72 def committed_on=(date)
72 def committed_on=(date)
73 self.commit_date = date
73 self.commit_date = date
74 super
74 super
75 end
75 end
76
76
77 # Returns the readable identifier
77 # Returns the readable identifier
78 def format_identifier
78 def format_identifier
79 if repository.class.respond_to? :format_changeset_identifier
79 if repository.class.respond_to? :format_changeset_identifier
80 repository.class.format_changeset_identifier self
80 repository.class.format_changeset_identifier self
81 else
81 else
82 identifier
82 identifier
83 end
83 end
84 end
84 end
85
85
86 def project
86 def project
87 repository.project
87 repository.project
88 end
88 end
89
89
90 def author
90 def author
91 user || committer.to_s.split('<').first
91 user || committer.to_s.split('<').first
92 end
92 end
93
93
94 def before_create_cs
94 def before_create_cs
95 self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
95 self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
96 self.comments = self.class.normalize_comments(
96 self.comments = self.class.normalize_comments(
97 self.comments, repository.repo_log_encoding)
97 self.comments, repository.repo_log_encoding)
98 self.user = repository.find_committer_user(self.committer)
98 self.user = repository.find_committer_user(self.committer)
99 end
99 end
100
100
101 def scan_for_issues
101 def scan_for_issues
102 scan_comment_for_issue_ids
102 scan_comment_for_issue_ids
103 end
103 end
104
104
105 TIMELOG_RE = /
105 TIMELOG_RE = /
106 (
106 (
107 ((\d+)(h|hours?))((\d+)(m|min)?)?
107 ((\d+)(h|hours?))((\d+)(m|min)?)?
108 |
108 |
109 ((\d+)(h|hours?|m|min))
109 ((\d+)(h|hours?|m|min))
110 |
110 |
111 (\d+):(\d+)
111 (\d+):(\d+)
112 |
112 |
113 (\d+([\.,]\d+)?)h?
113 (\d+([\.,]\d+)?)h?
114 )
114 )
115 /x
115 /x
116
116
117 def scan_comment_for_issue_ids
117 def scan_comment_for_issue_ids
118 return if comments.blank?
118 return if comments.blank?
119 # keywords used to reference issues
119 # keywords used to reference issues
120 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
120 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
121 ref_keywords_any = ref_keywords.delete('*')
121 ref_keywords_any = ref_keywords.delete('*')
122 # keywords used to fix issues
122 # keywords used to fix issues
123 fix_keywords = Setting.commit_update_keywords_array.map {|r| r['keywords']}.flatten.compact
123 fix_keywords = Setting.commit_update_keywords_array.map {|r| r['keywords']}.flatten.compact
124
124
125 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
125 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
126
126
127 referenced_issues = []
127 referenced_issues = []
128
128
129 comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
129 comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
130 action, refs = match[2].to_s.downcase, match[3]
130 action, refs = match[2].to_s.downcase, match[3]
131 next unless action.present? || ref_keywords_any
131 next unless action.present? || ref_keywords_any
132
132
133 refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
133 refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
134 issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
134 issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
135 if issue && !issue_linked_to_same_commit?(issue)
135 if issue && !issue_linked_to_same_commit?(issue)
136 referenced_issues << issue
136 referenced_issues << issue
137 # Don't update issues or log time when importing old commits
137 # Don't update issues or log time when importing old commits
138 unless repository.created_on && committed_on && committed_on < repository.created_on
138 unless repository.created_on && committed_on && committed_on < repository.created_on
139 fix_issue(issue, action) if fix_keywords.include?(action)
139 fix_issue(issue, action) if fix_keywords.include?(action)
140 log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
140 log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
141 end
141 end
142 end
142 end
143 end
143 end
144 end
144 end
145
145
146 referenced_issues.uniq!
146 referenced_issues.uniq!
147 self.issues = referenced_issues unless referenced_issues.empty?
147 self.issues = referenced_issues unless referenced_issues.empty?
148 end
148 end
149
149
150 def short_comments
150 def short_comments
151 @short_comments || split_comments.first
151 @short_comments || split_comments.first
152 end
152 end
153
153
154 def long_comments
154 def long_comments
155 @long_comments || split_comments.last
155 @long_comments || split_comments.last
156 end
156 end
157
157
158 def text_tag(ref_project=nil)
158 def text_tag(ref_project=nil)
159 repo = ""
159 repo = ""
160 if repository && repository.identifier.present?
160 if repository && repository.identifier.present?
161 repo = "#{repository.identifier}|"
161 repo = "#{repository.identifier}|"
162 end
162 end
163 tag = if scmid?
163 tag = if scmid?
164 "commit:#{repo}#{scmid}"
164 "commit:#{repo}#{scmid}"
165 else
165 else
166 "#{repo}r#{revision}"
166 "#{repo}r#{revision}"
167 end
167 end
168 if ref_project && project && ref_project != project
168 if ref_project && project && ref_project != project
169 tag = "#{project.identifier}:#{tag}"
169 tag = "#{project.identifier}:#{tag}"
170 end
170 end
171 tag
171 tag
172 end
172 end
173
173
174 # Returns the title used for the changeset in the activity/search results
174 # Returns the title used for the changeset in the activity/search results
175 def title
175 def title
176 repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : ''
176 repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : ''
177 comm = short_comments.blank? ? '' : (': ' + short_comments)
177 comm = short_comments.blank? ? '' : (': ' + short_comments)
178 "#{l(:label_revision)} #{format_identifier}#{repo}#{comm}"
178 "#{l(:label_revision)} #{format_identifier}#{repo}#{comm}"
179 end
179 end
180
180
181 # Returns the previous changeset
181 # Returns the previous changeset
182 def previous
182 def previous
183 @previous ||= Changeset.where(["id < ? AND repository_id = ?", id, repository_id]).order('id DESC').first
183 @previous ||= Changeset.where(["id < ? AND repository_id = ?", id, repository_id]).order('id DESC').first
184 end
184 end
185
185
186 # Returns the next changeset
186 # Returns the next changeset
187 def next
187 def next
188 @next ||= Changeset.where(["id > ? AND repository_id = ?", id, repository_id]).order('id ASC').first
188 @next ||= Changeset.where(["id > ? AND repository_id = ?", id, repository_id]).order('id ASC').first
189 end
189 end
190
190
191 # Creates a new Change from it's common parameters
191 # Creates a new Change from it's common parameters
192 def create_change(change)
192 def create_change(change)
193 Change.create(:changeset => self,
193 Change.create(:changeset => self,
194 :action => change[:action],
194 :action => change[:action],
195 :path => change[:path],
195 :path => change[:path],
196 :from_path => change[:from_path],
196 :from_path => change[:from_path],
197 :from_revision => change[:from_revision])
197 :from_revision => change[:from_revision])
198 end
198 end
199
199
200 # Finds an issue that can be referenced by the commit message
200 # Finds an issue that can be referenced by the commit message
201 def find_referenced_issue_by_id(id)
201 def find_referenced_issue_by_id(id)
202 return nil if id.blank?
202 return nil if id.blank?
203 issue = Issue.includes(:project).where(:id => id.to_i).first
203 issue = Issue.find_by_id(id.to_i)
204 if Setting.commit_cross_project_ref?
204 if Setting.commit_cross_project_ref?
205 # all issues can be referenced/fixed
205 # all issues can be referenced/fixed
206 elsif issue
206 elsif issue
207 # issue that belong to the repository project, a subproject or a parent project only
207 # issue that belong to the repository project, a subproject or a parent project only
208 unless issue.project &&
208 unless issue.project &&
209 (project == issue.project || project.is_ancestor_of?(issue.project) ||
209 (project == issue.project || project.is_ancestor_of?(issue.project) ||
210 project.is_descendant_of?(issue.project))
210 project.is_descendant_of?(issue.project))
211 issue = nil
211 issue = nil
212 end
212 end
213 end
213 end
214 issue
214 issue
215 end
215 end
216
216
217 private
217 private
218
218
219 # Returns true if the issue is already linked to the same commit
219 # Returns true if the issue is already linked to the same commit
220 # from a different repository
220 # from a different repository
221 def issue_linked_to_same_commit?(issue)
221 def issue_linked_to_same_commit?(issue)
222 repository.same_commits_in_scope(issue.changesets, self).any?
222 repository.same_commits_in_scope(issue.changesets, self).any?
223 end
223 end
224
224
225 # Updates the +issue+ according to +action+
225 # Updates the +issue+ according to +action+
226 def fix_issue(issue, action)
226 def fix_issue(issue, action)
227 # the issue may have been updated by the closure of another one (eg. duplicate)
227 # the issue may have been updated by the closure of another one (eg. duplicate)
228 issue.reload
228 issue.reload
229 # don't change the status is the issue is closed
229 # don't change the status is the issue is closed
230 return if issue.closed?
230 return if issue.closed?
231
231
232 journal = issue.init_journal(user || User.anonymous,
232 journal = issue.init_journal(user || User.anonymous,
233 ll(Setting.default_language,
233 ll(Setting.default_language,
234 :text_status_changed_by_changeset,
234 :text_status_changed_by_changeset,
235 text_tag(issue.project)))
235 text_tag(issue.project)))
236 rule = Setting.commit_update_keywords_array.detect do |rule|
236 rule = Setting.commit_update_keywords_array.detect do |rule|
237 rule['keywords'].include?(action) &&
237 rule['keywords'].include?(action) &&
238 (rule['if_tracker_id'].blank? || rule['if_tracker_id'] == issue.tracker_id.to_s)
238 (rule['if_tracker_id'].blank? || rule['if_tracker_id'] == issue.tracker_id.to_s)
239 end
239 end
240 if rule
240 if rule
241 issue.assign_attributes rule.slice(*Issue.attribute_names)
241 issue.assign_attributes rule.slice(*Issue.attribute_names)
242 end
242 end
243 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
243 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
244 { :changeset => self, :issue => issue, :action => action })
244 { :changeset => self, :issue => issue, :action => action })
245
245
246 if issue.changes.any?
246 if issue.changes.any?
247 unless issue.save
247 unless issue.save
248 logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
248 logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
249 end
249 end
250 end
250 end
251 issue
251 issue
252 end
252 end
253
253
254 def log_time(issue, hours)
254 def log_time(issue, hours)
255 time_entry = TimeEntry.new(
255 time_entry = TimeEntry.new(
256 :user => user,
256 :user => user,
257 :hours => hours,
257 :hours => hours,
258 :issue => issue,
258 :issue => issue,
259 :spent_on => commit_date,
259 :spent_on => commit_date,
260 :comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project),
260 :comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project),
261 :locale => Setting.default_language)
261 :locale => Setting.default_language)
262 )
262 )
263 time_entry.activity = log_time_activity unless log_time_activity.nil?
263 time_entry.activity = log_time_activity unless log_time_activity.nil?
264
264
265 unless time_entry.save
265 unless time_entry.save
266 logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
266 logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
267 end
267 end
268 time_entry
268 time_entry
269 end
269 end
270
270
271 def log_time_activity
271 def log_time_activity
272 if Setting.commit_logtime_activity_id.to_i > 0
272 if Setting.commit_logtime_activity_id.to_i > 0
273 TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
273 TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
274 end
274 end
275 end
275 end
276
276
277 def split_comments
277 def split_comments
278 comments =~ /\A(.+?)\r?\n(.*)$/m
278 comments =~ /\A(.+?)\r?\n(.*)$/m
279 @short_comments = $1 || comments
279 @short_comments = $1 || comments
280 @long_comments = $2.to_s.strip
280 @long_comments = $2.to_s.strip
281 return @short_comments, @long_comments
281 return @short_comments, @long_comments
282 end
282 end
283
283
284 public
284 public
285
285
286 # Strips and reencodes a commit log before insertion into the database
286 # Strips and reencodes a commit log before insertion into the database
287 def self.normalize_comments(str, encoding)
287 def self.normalize_comments(str, encoding)
288 Changeset.to_utf8(str.to_s.strip, encoding)
288 Changeset.to_utf8(str.to_s.strip, encoding)
289 end
289 end
290
290
291 def self.to_utf8(str, encoding)
291 def self.to_utf8(str, encoding)
292 Redmine::CodesetUtil.to_utf8(str, encoding)
292 Redmine::CodesetUtil.to_utf8(str, encoding)
293 end
293 end
294 end
294 end
@@ -1,578 +1,590
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 # Copyright (C) 2006-2015 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,
23 fixtures :projects, :repositories,
24 :issues, :issue_statuses, :issue_categories,
24 :issues, :issue_statuses, :issue_categories,
25 :journals, :journal_details,
25 :journals, :journal_details,
26 :workflows,
26 :workflows,
27 :changesets, :changes,
27 :changesets, :changes,
28 :enumerations,
28 :enumerations,
29 :custom_fields, :custom_values,
29 :custom_fields, :custom_values,
30 :users, :members, :member_roles,
30 :users, :members, :member_roles,
31 :email_addresses,
31 :email_addresses,
32 :trackers, :projects_trackers,
32 :trackers, :projects_trackers,
33 :enabled_modules, :roles
33 :enabled_modules, :roles
34
34
35 def test_ref_keywords_any
35 def test_ref_keywords_any
36 ActionMailer::Base.deliveries.clear
36 ActionMailer::Base.deliveries.clear
37 Setting.commit_ref_keywords = '*'
37 Setting.commit_ref_keywords = '*'
38 Setting.commit_update_keywords = [{'keywords' => 'fixes , closes', 'status_id' => '5', 'done_ratio' => '90'}]
38 Setting.commit_update_keywords = [{'keywords' => 'fixes , closes', 'status_id' => '5', 'done_ratio' => '90'}]
39
39
40 c = Changeset.new(:repository => Project.find(1).repository,
40 c = Changeset.new(:repository => Project.find(1).repository,
41 :committed_on => Time.now,
41 :committed_on => Time.now,
42 :comments => 'New commit (#2). Fixes #1',
42 :comments => 'New commit (#2). Fixes #1',
43 :revision => '12345')
43 :revision => '12345')
44 assert c.save
44 assert c.save
45 assert_equal [1, 2], c.issue_ids.sort
45 assert_equal [1, 2], c.issue_ids.sort
46 fixed = Issue.find(1)
46 fixed = Issue.find(1)
47 assert fixed.closed?
47 assert fixed.closed?
48 assert_equal 90, fixed.done_ratio
48 assert_equal 90, fixed.done_ratio
49 assert_equal 1, ActionMailer::Base.deliveries.size
49 assert_equal 1, ActionMailer::Base.deliveries.size
50 end
50 end
51
51
52 def test_ref_keywords
52 def test_ref_keywords
53 Setting.commit_ref_keywords = 'refs'
53 Setting.commit_ref_keywords = 'refs'
54 Setting.commit_update_keywords = ''
54 Setting.commit_update_keywords = ''
55 c = Changeset.new(:repository => Project.find(1).repository,
55 c = Changeset.new(:repository => Project.find(1).repository,
56 :committed_on => Time.now,
56 :committed_on => Time.now,
57 :comments => 'Ignores #2. Refs #1',
57 :comments => 'Ignores #2. Refs #1',
58 :revision => '12345')
58 :revision => '12345')
59 assert c.save
59 assert c.save
60 assert_equal [1], c.issue_ids.sort
60 assert_equal [1], c.issue_ids.sort
61 end
61 end
62
62
63 def test_ref_keywords_any_only
63 def test_ref_keywords_any_only
64 Setting.commit_ref_keywords = '*'
64 Setting.commit_ref_keywords = '*'
65 Setting.commit_update_keywords = ''
65 Setting.commit_update_keywords = ''
66 c = Changeset.new(:repository => Project.find(1).repository,
66 c = Changeset.new(:repository => Project.find(1).repository,
67 :committed_on => Time.now,
67 :committed_on => Time.now,
68 :comments => 'Ignores #2. Refs #1',
68 :comments => 'Ignores #2. Refs #1',
69 :revision => '12345')
69 :revision => '12345')
70 assert c.save
70 assert c.save
71 assert_equal [1, 2], c.issue_ids.sort
71 assert_equal [1, 2], c.issue_ids.sort
72 end
72 end
73
73
74 def test_ref_keywords_any_with_timelog
74 def test_ref_keywords_any_with_timelog
75 Setting.commit_ref_keywords = '*'
75 Setting.commit_ref_keywords = '*'
76 Setting.commit_logtime_enabled = '1'
76 Setting.commit_logtime_enabled = '1'
77
77
78 {
78 {
79 '2' => 2.0,
79 '2' => 2.0,
80 '2h' => 2.0,
80 '2h' => 2.0,
81 '2hours' => 2.0,
81 '2hours' => 2.0,
82 '15m' => 0.25,
82 '15m' => 0.25,
83 '15min' => 0.25,
83 '15min' => 0.25,
84 '3h15' => 3.25,
84 '3h15' => 3.25,
85 '3h15m' => 3.25,
85 '3h15m' => 3.25,
86 '3h15min' => 3.25,
86 '3h15min' => 3.25,
87 '3:15' => 3.25,
87 '3:15' => 3.25,
88 '3.25' => 3.25,
88 '3.25' => 3.25,
89 '3.25h' => 3.25,
89 '3.25h' => 3.25,
90 '3,25' => 3.25,
90 '3,25' => 3.25,
91 '3,25h' => 3.25,
91 '3,25h' => 3.25,
92 }.each do |syntax, expected_hours|
92 }.each do |syntax, expected_hours|
93 c = Changeset.new(:repository => Project.find(1).repository,
93 c = Changeset.new(:repository => Project.find(1).repository,
94 :committed_on => 24.hours.ago,
94 :committed_on => 24.hours.ago,
95 :comments => "Worked on this issue #1 @#{syntax}",
95 :comments => "Worked on this issue #1 @#{syntax}",
96 :revision => '520',
96 :revision => '520',
97 :user => User.find(2))
97 :user => User.find(2))
98 assert_difference 'TimeEntry.count' do
98 assert_difference 'TimeEntry.count' do
99 c.scan_comment_for_issue_ids
99 c.scan_comment_for_issue_ids
100 end
100 end
101 assert_equal [1], c.issue_ids.sort
101 assert_equal [1], c.issue_ids.sort
102
102
103 time = TimeEntry.order('id desc').first
103 time = TimeEntry.order('id desc').first
104 assert_equal 1, time.issue_id
104 assert_equal 1, time.issue_id
105 assert_equal 1, time.project_id
105 assert_equal 1, time.project_id
106 assert_equal 2, time.user_id
106 assert_equal 2, time.user_id
107 assert_equal expected_hours, time.hours,
107 assert_equal expected_hours, time.hours,
108 "@#{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}"
109 assert_equal Date.yesterday, time.spent_on
109 assert_equal Date.yesterday, time.spent_on
110 assert time.activity.is_default?
110 assert time.activity.is_default?
111 assert time.comments.include?('r520'),
111 assert time.comments.include?('r520'),
112 "r520 was expected in time_entry comments: #{time.comments}"
112 "r520 was expected in time_entry comments: #{time.comments}"
113 end
113 end
114 end
114 end
115
115
116 def test_ref_keywords_closing_with_timelog
116 def test_ref_keywords_closing_with_timelog
117 Setting.commit_ref_keywords = '*'
117 Setting.commit_ref_keywords = '*'
118 Setting.commit_update_keywords = [{'keywords' => 'fixes , closes',
118 Setting.commit_update_keywords = [{'keywords' => 'fixes , closes',
119 'status_id' => IssueStatus.where(:is_closed => true).first.id.to_s}]
119 'status_id' => IssueStatus.where(:is_closed => true).first.id.to_s}]
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.order('id desc').limit(2)
134 times = TimeEntry.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 c = Changeset.new(:repository => Project.find(1).repository,
140 c = Changeset.new(:repository => Project.find(1).repository,
141 :committed_on => Time.now,
141 :committed_on => Time.now,
142 :comments => '#1 is the reason of this commit',
142 :comments => '#1 is the reason of this commit',
143 :revision => '12345')
143 :revision => '12345')
144 assert c.save
144 assert c.save
145 assert_equal [1], c.issue_ids.sort
145 assert_equal [1], c.issue_ids.sort
146 end
146 end
147
147
148 def test_ref_keywords_allow_brackets_around_a_issue_number
148 def test_ref_keywords_allow_brackets_around_a_issue_number
149 Setting.commit_ref_keywords = '*'
149 Setting.commit_ref_keywords = '*'
150 c = Changeset.new(:repository => Project.find(1).repository,
150 c = Changeset.new(:repository => Project.find(1).repository,
151 :committed_on => Time.now,
151 :committed_on => Time.now,
152 :comments => '[#1] Worked on this issue',
152 :comments => '[#1] Worked on this issue',
153 :revision => '12345')
153 :revision => '12345')
154 assert c.save
154 assert c.save
155 assert_equal [1], c.issue_ids.sort
155 assert_equal [1], c.issue_ids.sort
156 end
156 end
157
157
158 def test_ref_keywords_allow_brackets_around_multiple_issue_numbers
158 def test_ref_keywords_allow_brackets_around_multiple_issue_numbers
159 Setting.commit_ref_keywords = '*'
159 Setting.commit_ref_keywords = '*'
160 c = Changeset.new(:repository => Project.find(1).repository,
160 c = Changeset.new(:repository => Project.find(1).repository,
161 :committed_on => Time.now,
161 :committed_on => Time.now,
162 :comments => '[#1 #2, #3] Worked on these',
162 :comments => '[#1 #2, #3] Worked on these',
163 :revision => '12345')
163 :revision => '12345')
164 assert c.save
164 assert c.save
165 assert_equal [1,2,3], c.issue_ids.sort
165 assert_equal [1,2,3], c.issue_ids.sort
166 end
166 end
167
167
168 def test_ref_keywords_with_large_number_should_not_error
169 Setting.commit_ref_keywords = '*'
170 c = Changeset.new(:repository => Project.find(1).repository,
171 :committed_on => Time.now,
172 :comments => 'Out of range #2010021810000121',
173 :revision => '12345')
174 assert_nothing_raised do
175 assert c.save
176 end
177 assert_equal [], c.issue_ids.sort
178 end
179
168 def test_update_keywords_with_changes_should_create_journal
180 def test_update_keywords_with_changes_should_create_journal
169 issue = Issue.generate!(:project_id => 1, :status_id => 1)
181 issue = Issue.generate!(:project_id => 1, :status_id => 1)
170
182
171 with_settings :commit_update_keywords => [{'keywords' => 'fixes', 'status_id' => '3'}] do
183 with_settings :commit_update_keywords => [{'keywords' => 'fixes', 'status_id' => '3'}] do
172 assert_difference 'Journal.count' do
184 assert_difference 'Journal.count' do
173 c = Changeset.generate!(:repository => Project.find(1).repository,:comments => "Fixes ##{issue.id}")
185 c = Changeset.generate!(:repository => Project.find(1).repository,:comments => "Fixes ##{issue.id}")
174 assert_include c.id, issue.reload.changeset_ids
186 assert_include c.id, issue.reload.changeset_ids
175 journal = Journal.order('id DESC').first
187 journal = Journal.order('id DESC').first
176 assert_equal 1, journal.details.count
188 assert_equal 1, journal.details.count
177 end
189 end
178 end
190 end
179 end
191 end
180
192
181 def test_update_keywords_without_change_should_not_create_journal
193 def test_update_keywords_without_change_should_not_create_journal
182 issue = Issue.generate!(:project_id => 1, :status_id => 3)
194 issue = Issue.generate!(:project_id => 1, :status_id => 3)
183
195
184 with_settings :commit_update_keywords => [{'keywords' => 'fixes', 'status_id' => '3'}] do
196 with_settings :commit_update_keywords => [{'keywords' => 'fixes', 'status_id' => '3'}] do
185 assert_no_difference 'Journal.count' do
197 assert_no_difference 'Journal.count' do
186 c = Changeset.generate!(:repository => Project.find(1).repository,:comments => "Fixes ##{issue.id}")
198 c = Changeset.generate!(:repository => Project.find(1).repository,:comments => "Fixes ##{issue.id}")
187 assert_include c.id, issue.reload.changeset_ids
199 assert_include c.id, issue.reload.changeset_ids
188 end
200 end
189 end
201 end
190 end
202 end
191
203
192 def test_update_keywords_with_multiple_rules
204 def test_update_keywords_with_multiple_rules
193 with_settings :commit_update_keywords => [
205 with_settings :commit_update_keywords => [
194 {'keywords' => 'fixes, closes', 'status_id' => '5'},
206 {'keywords' => 'fixes, closes', 'status_id' => '5'},
195 {'keywords' => 'resolves', 'status_id' => '3'}
207 {'keywords' => 'resolves', 'status_id' => '3'}
196 ] do
208 ] do
197
209
198 issue1 = Issue.generate!
210 issue1 = Issue.generate!
199 issue2 = Issue.generate!
211 issue2 = Issue.generate!
200 Changeset.generate!(:comments => "Closes ##{issue1.id}\nResolves ##{issue2.id}")
212 Changeset.generate!(:comments => "Closes ##{issue1.id}\nResolves ##{issue2.id}")
201 assert_equal 5, issue1.reload.status_id
213 assert_equal 5, issue1.reload.status_id
202 assert_equal 3, issue2.reload.status_id
214 assert_equal 3, issue2.reload.status_id
203 end
215 end
204 end
216 end
205
217
206 def test_update_keywords_with_multiple_rules_should_match_tracker
218 def test_update_keywords_with_multiple_rules_should_match_tracker
207 with_settings :commit_update_keywords => [
219 with_settings :commit_update_keywords => [
208 {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'},
220 {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'},
209 {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => ''}
221 {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => ''}
210 ] do
222 ] do
211
223
212 issue1 = Issue.generate!(:tracker_id => 2)
224 issue1 = Issue.generate!(:tracker_id => 2)
213 issue2 = Issue.generate!
225 issue2 = Issue.generate!
214 Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}")
226 Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}")
215 assert_equal 5, issue1.reload.status_id
227 assert_equal 5, issue1.reload.status_id
216 assert_equal 3, issue2.reload.status_id
228 assert_equal 3, issue2.reload.status_id
217 end
229 end
218 end
230 end
219
231
220 def test_update_keywords_with_multiple_rules_and_no_match
232 def test_update_keywords_with_multiple_rules_and_no_match
221 with_settings :commit_update_keywords => [
233 with_settings :commit_update_keywords => [
222 {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'},
234 {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'},
223 {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => '3'}
235 {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => '3'}
224 ] do
236 ] do
225
237
226 issue1 = Issue.generate!(:tracker_id => 2)
238 issue1 = Issue.generate!(:tracker_id => 2)
227 issue2 = Issue.generate!
239 issue2 = Issue.generate!
228 Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}")
240 Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}")
229 assert_equal 5, issue1.reload.status_id
241 assert_equal 5, issue1.reload.status_id
230 assert_equal 1, issue2.reload.status_id # no updates
242 assert_equal 1, issue2.reload.status_id # no updates
231 end
243 end
232 end
244 end
233
245
234 def test_commit_referencing_a_subproject_issue
246 def test_commit_referencing_a_subproject_issue
235 c = Changeset.new(:repository => Project.find(1).repository,
247 c = Changeset.new(:repository => Project.find(1).repository,
236 :committed_on => Time.now,
248 :committed_on => Time.now,
237 :comments => 'refs #5, a subproject issue',
249 :comments => 'refs #5, a subproject issue',
238 :revision => '12345')
250 :revision => '12345')
239 assert c.save
251 assert c.save
240 assert_equal [5], c.issue_ids.sort
252 assert_equal [5], c.issue_ids.sort
241 assert c.issues.first.project != c.project
253 assert c.issues.first.project != c.project
242 end
254 end
243
255
244 def test_commit_closing_a_subproject_issue
256 def test_commit_closing_a_subproject_issue
245 with_settings :commit_update_keywords => [{'keywords' => 'closes', 'status_id' => '5'}],
257 with_settings :commit_update_keywords => [{'keywords' => 'closes', 'status_id' => '5'}],
246 :default_language => 'en' do
258 :default_language => 'en' do
247 issue = Issue.find(5)
259 issue = Issue.find(5)
248 assert !issue.closed?
260 assert !issue.closed?
249 assert_difference 'Journal.count' do
261 assert_difference 'Journal.count' do
250 c = Changeset.new(:repository => Project.find(1).repository,
262 c = Changeset.new(:repository => Project.find(1).repository,
251 :committed_on => Time.now,
263 :committed_on => Time.now,
252 :comments => 'closes #5, a subproject issue',
264 :comments => 'closes #5, a subproject issue',
253 :revision => '12345')
265 :revision => '12345')
254 assert c.save
266 assert c.save
255 end
267 end
256 assert issue.reload.closed?
268 assert issue.reload.closed?
257 journal = Journal.order('id DESC').first
269 journal = Journal.order('id DESC').first
258 assert_equal issue, journal.issue
270 assert_equal issue, journal.issue
259 assert_include "Applied in changeset ecookbook:r12345.", journal.notes
271 assert_include "Applied in changeset ecookbook:r12345.", journal.notes
260 end
272 end
261 end
273 end
262
274
263 def test_commit_referencing_a_parent_project_issue
275 def test_commit_referencing_a_parent_project_issue
264 # repository of child project
276 # repository of child project
265 r = Repository::Subversion.create!(
277 r = Repository::Subversion.create!(
266 :project => Project.find(3),
278 :project => Project.find(3),
267 :url => 'svn://localhost/test')
279 :url => 'svn://localhost/test')
268 c = Changeset.new(:repository => r,
280 c = Changeset.new(:repository => r,
269 :committed_on => Time.now,
281 :committed_on => Time.now,
270 :comments => 'refs #2, an issue of a parent project',
282 :comments => 'refs #2, an issue of a parent project',
271 :revision => '12345')
283 :revision => '12345')
272 assert c.save
284 assert c.save
273 assert_equal [2], c.issue_ids.sort
285 assert_equal [2], c.issue_ids.sort
274 assert c.issues.first.project != c.project
286 assert c.issues.first.project != c.project
275 end
287 end
276
288
277 def test_commit_referencing_a_project_with_commit_cross_project_ref_disabled
289 def test_commit_referencing_a_project_with_commit_cross_project_ref_disabled
278 r = Repository::Subversion.create!(
290 r = Repository::Subversion.create!(
279 :project => Project.find(3),
291 :project => Project.find(3),
280 :url => 'svn://localhost/test')
292 :url => 'svn://localhost/test')
281 with_settings :commit_cross_project_ref => '0' do
293 with_settings :commit_cross_project_ref => '0' do
282 c = Changeset.new(:repository => r,
294 c = Changeset.new(:repository => r,
283 :committed_on => Time.now,
295 :committed_on => Time.now,
284 :comments => 'refs #4, an issue of a different project',
296 :comments => 'refs #4, an issue of a different project',
285 :revision => '12345')
297 :revision => '12345')
286 assert c.save
298 assert c.save
287 assert_equal [], c.issue_ids
299 assert_equal [], c.issue_ids
288 end
300 end
289 end
301 end
290
302
291 def test_commit_referencing_a_project_with_commit_cross_project_ref_enabled
303 def test_commit_referencing_a_project_with_commit_cross_project_ref_enabled
292 r = Repository::Subversion.create!(
304 r = Repository::Subversion.create!(
293 :project => Project.find(3),
305 :project => Project.find(3),
294 :url => 'svn://localhost/test')
306 :url => 'svn://localhost/test')
295 with_settings :commit_cross_project_ref => '1' do
307 with_settings :commit_cross_project_ref => '1' do
296 c = Changeset.new(:repository => r,
308 c = Changeset.new(:repository => r,
297 :committed_on => Time.now,
309 :committed_on => Time.now,
298 :comments => 'refs #4, an issue of a different project',
310 :comments => 'refs #4, an issue of a different project',
299 :revision => '12345')
311 :revision => '12345')
300 assert c.save
312 assert c.save
301 assert_equal [4], c.issue_ids
313 assert_equal [4], c.issue_ids
302 end
314 end
303 end
315 end
304
316
305 def test_old_commits_should_not_update_issues_nor_log_time
317 def test_old_commits_should_not_update_issues_nor_log_time
306 Setting.commit_ref_keywords = '*'
318 Setting.commit_ref_keywords = '*'
307 Setting.commit_update_keywords = {'fixes , closes' => {'status_id' => '5', 'done_ratio' => '90'}}
319 Setting.commit_update_keywords = {'fixes , closes' => {'status_id' => '5', 'done_ratio' => '90'}}
308 Setting.commit_logtime_enabled = '1'
320 Setting.commit_logtime_enabled = '1'
309
321
310 repository = Project.find(1).repository
322 repository = Project.find(1).repository
311 repository.created_on = Time.now
323 repository.created_on = Time.now
312 repository.save!
324 repository.save!
313
325
314 c = Changeset.new(:repository => repository,
326 c = Changeset.new(:repository => repository,
315 :committed_on => 1.month.ago,
327 :committed_on => 1.month.ago,
316 :comments => 'New commit (#2). Fixes #1 @1h',
328 :comments => 'New commit (#2). Fixes #1 @1h',
317 :revision => '12345')
329 :revision => '12345')
318 assert_no_difference 'TimeEntry.count' do
330 assert_no_difference 'TimeEntry.count' do
319 assert c.save
331 assert c.save
320 end
332 end
321 assert_equal [1, 2], c.issue_ids.sort
333 assert_equal [1, 2], c.issue_ids.sort
322 issue = Issue.find(1)
334 issue = Issue.find(1)
323 assert_equal 1, issue.status_id
335 assert_equal 1, issue.status_id
324 assert_equal 0, issue.done_ratio
336 assert_equal 0, issue.done_ratio
325 end
337 end
326
338
327 def test_2_repositories_with_same_backend_should_not_link_issue_multiple_times
339 def test_2_repositories_with_same_backend_should_not_link_issue_multiple_times
328 Setting.commit_ref_keywords = '*'
340 Setting.commit_ref_keywords = '*'
329 r1 = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///svn1')
341 r1 = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///svn1')
330 r2 = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn2', :url => 'file:///svn1')
342 r2 = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn2', :url => 'file:///svn1')
331 now = Time.now
343 now = Time.now
332 assert_difference 'Issue.find(1).changesets.count' do
344 assert_difference 'Issue.find(1).changesets.count' do
333 c1 = Changeset.create!(:repository => r1, :committed_on => now, :comments => 'Fixes #1', :revision => '12345')
345 c1 = Changeset.create!(:repository => r1, :committed_on => now, :comments => 'Fixes #1', :revision => '12345')
334 c1 = Changeset.create!(:repository => r2, :committed_on => now, :comments => 'Fixes #1', :revision => '12345')
346 c1 = Changeset.create!(:repository => r2, :committed_on => now, :comments => 'Fixes #1', :revision => '12345')
335 end
347 end
336 end
348 end
337
349
338 def test_text_tag_revision
350 def test_text_tag_revision
339 c = Changeset.new(:revision => '520')
351 c = Changeset.new(:revision => '520')
340 assert_equal 'r520', c.text_tag
352 assert_equal 'r520', c.text_tag
341 end
353 end
342
354
343 def test_text_tag_revision_with_same_project
355 def test_text_tag_revision_with_same_project
344 c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
356 c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
345 assert_equal 'r520', c.text_tag(Project.find(1))
357 assert_equal 'r520', c.text_tag(Project.find(1))
346 end
358 end
347
359
348 def test_text_tag_revision_with_different_project
360 def test_text_tag_revision_with_different_project
349 c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
361 c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
350 assert_equal 'ecookbook:r520', c.text_tag(Project.find(2))
362 assert_equal 'ecookbook:r520', c.text_tag(Project.find(2))
351 end
363 end
352
364
353 def test_text_tag_revision_with_repository_identifier
365 def test_text_tag_revision_with_repository_identifier
354 r = Repository::Subversion.create!(
366 r = Repository::Subversion.create!(
355 :project_id => 1,
367 :project_id => 1,
356 :url => 'svn://localhost/test',
368 :url => 'svn://localhost/test',
357 :identifier => 'documents')
369 :identifier => 'documents')
358 c = Changeset.new(:revision => '520', :repository => r)
370 c = Changeset.new(:revision => '520', :repository => r)
359 assert_equal 'documents|r520', c.text_tag
371 assert_equal 'documents|r520', c.text_tag
360 assert_equal 'ecookbook:documents|r520', c.text_tag(Project.find(2))
372 assert_equal 'ecookbook:documents|r520', c.text_tag(Project.find(2))
361 end
373 end
362
374
363 def test_text_tag_hash
375 def test_text_tag_hash
364 c = Changeset.new(
376 c = Changeset.new(
365 :scmid => '7234cb2750b63f47bff735edc50a1c0a433c2518',
377 :scmid => '7234cb2750b63f47bff735edc50a1c0a433c2518',
366 :revision => '7234cb2750b63f47bff735edc50a1c0a433c2518')
378 :revision => '7234cb2750b63f47bff735edc50a1c0a433c2518')
367 assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag
379 assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag
368 end
380 end
369
381
370 def test_text_tag_hash_with_same_project
382 def test_text_tag_hash_with_same_project
371 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
383 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
372 assert_equal 'commit:7234cb27', c.text_tag(Project.find(1))
384 assert_equal 'commit:7234cb27', c.text_tag(Project.find(1))
373 end
385 end
374
386
375 def test_text_tag_hash_with_different_project
387 def test_text_tag_hash_with_different_project
376 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
388 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
377 assert_equal 'ecookbook:commit:7234cb27', c.text_tag(Project.find(2))
389 assert_equal 'ecookbook:commit:7234cb27', c.text_tag(Project.find(2))
378 end
390 end
379
391
380 def test_text_tag_hash_all_number
392 def test_text_tag_hash_all_number
381 c = Changeset.new(:scmid => '0123456789', :revision => '0123456789')
393 c = Changeset.new(:scmid => '0123456789', :revision => '0123456789')
382 assert_equal 'commit:0123456789', c.text_tag
394 assert_equal 'commit:0123456789', c.text_tag
383 end
395 end
384
396
385 def test_text_tag_hash_with_repository_identifier
397 def test_text_tag_hash_with_repository_identifier
386 r = Repository::Subversion.new(
398 r = Repository::Subversion.new(
387 :project_id => 1,
399 :project_id => 1,
388 :url => 'svn://localhost/test',
400 :url => 'svn://localhost/test',
389 :identifier => 'documents')
401 :identifier => 'documents')
390 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => r)
402 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => r)
391 assert_equal 'commit:documents|7234cb27', c.text_tag
403 assert_equal 'commit:documents|7234cb27', c.text_tag
392 assert_equal 'ecookbook:commit:documents|7234cb27', c.text_tag(Project.find(2))
404 assert_equal 'ecookbook:commit:documents|7234cb27', c.text_tag(Project.find(2))
393 end
405 end
394
406
395 def test_previous
407 def test_previous
396 changeset = Changeset.find_by_revision('3')
408 changeset = Changeset.find_by_revision('3')
397 assert_equal Changeset.find_by_revision('2'), changeset.previous
409 assert_equal Changeset.find_by_revision('2'), changeset.previous
398 end
410 end
399
411
400 def test_previous_nil
412 def test_previous_nil
401 changeset = Changeset.find_by_revision('1')
413 changeset = Changeset.find_by_revision('1')
402 assert_nil changeset.previous
414 assert_nil changeset.previous
403 end
415 end
404
416
405 def test_next
417 def test_next
406 changeset = Changeset.find_by_revision('2')
418 changeset = Changeset.find_by_revision('2')
407 assert_equal Changeset.find_by_revision('3'), changeset.next
419 assert_equal Changeset.find_by_revision('3'), changeset.next
408 end
420 end
409
421
410 def test_next_nil
422 def test_next_nil
411 changeset = Changeset.find_by_revision('10')
423 changeset = Changeset.find_by_revision('10')
412 assert_nil changeset.next
424 assert_nil changeset.next
413 end
425 end
414
426
415 def test_comments_should_be_converted_to_utf8
427 def test_comments_should_be_converted_to_utf8
416 proj = Project.find(3)
428 proj = Project.find(3)
417 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
429 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
418 str = "Texte encod\xe9 en ISO-8859-1.".force_encoding("ASCII-8BIT")
430 str = "Texte encod\xe9 en ISO-8859-1.".force_encoding("ASCII-8BIT")
419 r = Repository::Bazaar.create!(
431 r = Repository::Bazaar.create!(
420 :project => proj,
432 :project => proj,
421 :url => '/tmp/test/bazaar',
433 :url => '/tmp/test/bazaar',
422 :log_encoding => 'ISO-8859-1' )
434 :log_encoding => 'ISO-8859-1' )
423 assert r
435 assert r
424 c = Changeset.new(:repository => r,
436 c = Changeset.new(:repository => r,
425 :committed_on => Time.now,
437 :committed_on => Time.now,
426 :revision => '123',
438 :revision => '123',
427 :scmid => '12345',
439 :scmid => '12345',
428 :comments => str)
440 :comments => str)
429 assert( c.save )
441 assert( c.save )
430 str_utf8 = "Texte encod\xc3\xa9 en ISO-8859-1.".force_encoding("UTF-8")
442 str_utf8 = "Texte encod\xc3\xa9 en ISO-8859-1.".force_encoding("UTF-8")
431 assert_equal str_utf8, c.comments
443 assert_equal str_utf8, c.comments
432 end
444 end
433
445
434 def test_invalid_utf8_sequences_in_comments_should_be_replaced_latin1
446 def test_invalid_utf8_sequences_in_comments_should_be_replaced_latin1
435 proj = Project.find(3)
447 proj = Project.find(3)
436 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
448 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
437 str1 = "Texte encod\xe9 en ISO-8859-1.".force_encoding("UTF-8")
449 str1 = "Texte encod\xe9 en ISO-8859-1.".force_encoding("UTF-8")
438 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test".force_encoding("ASCII-8BIT")
450 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test".force_encoding("ASCII-8BIT")
439 r = Repository::Bazaar.create!(
451 r = Repository::Bazaar.create!(
440 :project => proj,
452 :project => proj,
441 :url => '/tmp/test/bazaar',
453 :url => '/tmp/test/bazaar',
442 :log_encoding => 'UTF-8' )
454 :log_encoding => 'UTF-8' )
443 assert r
455 assert r
444 c = Changeset.new(:repository => r,
456 c = Changeset.new(:repository => r,
445 :committed_on => Time.now,
457 :committed_on => Time.now,
446 :revision => '123',
458 :revision => '123',
447 :scmid => '12345',
459 :scmid => '12345',
448 :comments => str1,
460 :comments => str1,
449 :committer => str2)
461 :committer => str2)
450 assert( c.save )
462 assert( c.save )
451 assert_equal "Texte encod? en ISO-8859-1.", c.comments
463 assert_equal "Texte encod? en ISO-8859-1.", c.comments
452 assert_equal "?a?b?c?d?e test", c.committer
464 assert_equal "?a?b?c?d?e test", c.committer
453 end
465 end
454
466
455 def test_invalid_utf8_sequences_in_comments_should_be_replaced_ja_jis
467 def test_invalid_utf8_sequences_in_comments_should_be_replaced_ja_jis
456 proj = Project.find(3)
468 proj = Project.find(3)
457 str = "test\xb5\xfetest\xb5\xfe".force_encoding('ASCII-8BIT')
469 str = "test\xb5\xfetest\xb5\xfe".force_encoding('ASCII-8BIT')
458 r = Repository::Bazaar.create!(
470 r = Repository::Bazaar.create!(
459 :project => proj,
471 :project => proj,
460 :url => '/tmp/test/bazaar',
472 :url => '/tmp/test/bazaar',
461 :log_encoding => 'ISO-2022-JP' )
473 :log_encoding => 'ISO-2022-JP' )
462 assert r
474 assert r
463 c = Changeset.new(:repository => r,
475 c = Changeset.new(:repository => r,
464 :committed_on => Time.now,
476 :committed_on => Time.now,
465 :revision => '123',
477 :revision => '123',
466 :scmid => '12345',
478 :scmid => '12345',
467 :comments => str)
479 :comments => str)
468 assert( c.save )
480 assert( c.save )
469 assert_equal "test??test??", c.comments
481 assert_equal "test??test??", c.comments
470 end
482 end
471
483
472 def test_comments_should_be_converted_all_latin1_to_utf8
484 def test_comments_should_be_converted_all_latin1_to_utf8
473 s1 = "\xC2\x80"
485 s1 = "\xC2\x80"
474 s2 = "\xc3\x82\xc2\x80"
486 s2 = "\xc3\x82\xc2\x80"
475 s4 = s2.dup
487 s4 = s2.dup
476 s3 = s1.dup
488 s3 = s1.dup
477 s1.force_encoding('ASCII-8BIT')
489 s1.force_encoding('ASCII-8BIT')
478 s2.force_encoding('ASCII-8BIT')
490 s2.force_encoding('ASCII-8BIT')
479 s3.force_encoding('ISO-8859-1')
491 s3.force_encoding('ISO-8859-1')
480 s4.force_encoding('UTF-8')
492 s4.force_encoding('UTF-8')
481 assert_equal s3.encode('UTF-8'), s4
493 assert_equal s3.encode('UTF-8'), s4
482 proj = Project.find(3)
494 proj = Project.find(3)
483 r = Repository::Bazaar.create!(
495 r = Repository::Bazaar.create!(
484 :project => proj,
496 :project => proj,
485 :url => '/tmp/test/bazaar',
497 :url => '/tmp/test/bazaar',
486 :log_encoding => 'ISO-8859-1' )
498 :log_encoding => 'ISO-8859-1' )
487 assert r
499 assert r
488 c = Changeset.new(:repository => r,
500 c = Changeset.new(:repository => r,
489 :committed_on => Time.now,
501 :committed_on => Time.now,
490 :revision => '123',
502 :revision => '123',
491 :scmid => '12345',
503 :scmid => '12345',
492 :comments => s1)
504 :comments => s1)
493 assert( c.save )
505 assert( c.save )
494 assert_equal s4, c.comments
506 assert_equal s4, c.comments
495 end
507 end
496
508
497 def test_invalid_utf8_sequences_in_paths_should_be_replaced
509 def test_invalid_utf8_sequences_in_paths_should_be_replaced
498 proj = Project.find(3)
510 proj = Project.find(3)
499 str1 = "Texte encod\xe9 en ISO-8859-1".force_encoding("UTF-8")
511 str1 = "Texte encod\xe9 en ISO-8859-1".force_encoding("UTF-8")
500 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test".force_encoding("ASCII-8BIT")
512 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test".force_encoding("ASCII-8BIT")
501 r = Repository::Bazaar.create!(
513 r = Repository::Bazaar.create!(
502 :project => proj,
514 :project => proj,
503 :url => '/tmp/test/bazaar',
515 :url => '/tmp/test/bazaar',
504 :log_encoding => 'UTF-8' )
516 :log_encoding => 'UTF-8' )
505 assert r
517 assert r
506 cs = Changeset.new(
518 cs = Changeset.new(
507 :repository => r,
519 :repository => r,
508 :committed_on => Time.now,
520 :committed_on => Time.now,
509 :revision => '123',
521 :revision => '123',
510 :scmid => '12345',
522 :scmid => '12345',
511 :comments => "test")
523 :comments => "test")
512 assert(cs.save)
524 assert(cs.save)
513 ch = Change.new(
525 ch = Change.new(
514 :changeset => cs,
526 :changeset => cs,
515 :action => "A",
527 :action => "A",
516 :path => str1,
528 :path => str1,
517 :from_path => str2,
529 :from_path => str2,
518 :from_revision => "345")
530 :from_revision => "345")
519 assert(ch.save)
531 assert(ch.save)
520 assert_equal "Texte encod? en ISO-8859-1", ch.path
532 assert_equal "Texte encod? en ISO-8859-1", ch.path
521 assert_equal "?a?b?c?d?e test", ch.from_path
533 assert_equal "?a?b?c?d?e test", ch.from_path
522 end
534 end
523
535
524 def test_comments_nil
536 def test_comments_nil
525 proj = Project.find(3)
537 proj = Project.find(3)
526 r = Repository::Bazaar.create!(
538 r = Repository::Bazaar.create!(
527 :project => proj,
539 :project => proj,
528 :url => '/tmp/test/bazaar',
540 :url => '/tmp/test/bazaar',
529 :log_encoding => 'ISO-8859-1' )
541 :log_encoding => 'ISO-8859-1' )
530 assert r
542 assert r
531 c = Changeset.new(:repository => r,
543 c = Changeset.new(:repository => r,
532 :committed_on => Time.now,
544 :committed_on => Time.now,
533 :revision => '123',
545 :revision => '123',
534 :scmid => '12345',
546 :scmid => '12345',
535 :comments => nil,
547 :comments => nil,
536 :committer => nil)
548 :committer => nil)
537 assert( c.save )
549 assert( c.save )
538 assert_equal "", c.comments
550 assert_equal "", c.comments
539 assert_equal nil, c.committer
551 assert_equal nil, c.committer
540 assert_equal "UTF-8", c.comments.encoding.to_s
552 assert_equal "UTF-8", c.comments.encoding.to_s
541 end
553 end
542
554
543 def test_comments_empty
555 def test_comments_empty
544 proj = Project.find(3)
556 proj = Project.find(3)
545 r = Repository::Bazaar.create!(
557 r = Repository::Bazaar.create!(
546 :project => proj,
558 :project => proj,
547 :url => '/tmp/test/bazaar',
559 :url => '/tmp/test/bazaar',
548 :log_encoding => 'ISO-8859-1' )
560 :log_encoding => 'ISO-8859-1' )
549 assert r
561 assert r
550 c = Changeset.new(:repository => r,
562 c = Changeset.new(:repository => r,
551 :committed_on => Time.now,
563 :committed_on => Time.now,
552 :revision => '123',
564 :revision => '123',
553 :scmid => '12345',
565 :scmid => '12345',
554 :comments => "",
566 :comments => "",
555 :committer => "")
567 :committer => "")
556 assert( c.save )
568 assert( c.save )
557 assert_equal "", c.comments
569 assert_equal "", c.comments
558 assert_equal "", c.committer
570 assert_equal "", c.committer
559 assert_equal "UTF-8", c.comments.encoding.to_s
571 assert_equal "UTF-8", c.comments.encoding.to_s
560 assert_equal "UTF-8", c.committer.encoding.to_s
572 assert_equal "UTF-8", c.committer.encoding.to_s
561 end
573 end
562
574
563 def test_comments_should_accept_more_than_64k
575 def test_comments_should_accept_more_than_64k
564 c = Changeset.new(:repository => Repository.first,
576 c = Changeset.new(:repository => Repository.first,
565 :committed_on => Time.now,
577 :committed_on => Time.now,
566 :revision => '123',
578 :revision => '123',
567 :scmid => '12345',
579 :scmid => '12345',
568 :comments => "a" * 500.kilobyte)
580 :comments => "a" * 500.kilobyte)
569 assert c.save
581 assert c.save
570 c.reload
582 c.reload
571 assert_equal 500.kilobyte, c.comments.size
583 assert_equal 500.kilobyte, c.comments.size
572 end
584 end
573
585
574 def test_identifier
586 def test_identifier
575 c = Changeset.find_by_revision('1')
587 c = Changeset.find_by_revision('1')
576 assert_equal c.revision, c.identifier
588 assert_equal c.revision, c.identifier
577 end
589 end
578 end
590 end
General Comments 0
You need to be logged in to leave comments. Login now