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