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