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