##// END OF EJS Templates
Merged r10856 and r10857 from trunk to 2.1-stable (#12409)...
Toshi MARUYAMA -
r10631:06b68f194811
parent child
Show More
@@ -1,431 +1,435
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class ScmFetchError < Exception; end
18 class ScmFetchError < Exception; end
19
19
20 class Repository < ActiveRecord::Base
20 class Repository < ActiveRecord::Base
21 include Redmine::Ciphering
21 include Redmine::Ciphering
22 include Redmine::SafeAttributes
22 include Redmine::SafeAttributes
23
23
24 # Maximum length for repository identifiers
24 # Maximum length for repository identifiers
25 IDENTIFIER_MAX_LENGTH = 255
25 IDENTIFIER_MAX_LENGTH = 255
26
26
27 belongs_to :project
27 belongs_to :project
28 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
28 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
29 has_many :filechanges, :class_name => 'Change', :through => :changesets
29 has_many :filechanges, :class_name => 'Change', :through => :changesets
30
30
31 serialize :extra_info
31 serialize :extra_info
32
32
33 before_save :check_default
33 before_save :check_default
34
34
35 # Raw SQL to delete changesets and changes in the database
35 # Raw SQL to delete changesets and changes in the database
36 # has_many :changesets, :dependent => :destroy is too slow for big repositories
36 # has_many :changesets, :dependent => :destroy is too slow for big repositories
37 before_destroy :clear_changesets
37 before_destroy :clear_changesets
38
38
39 validates_length_of :password, :maximum => 255, :allow_nil => true
39 validates_length_of :password, :maximum => 255, :allow_nil => true
40 validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true
40 validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true
41 validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
41 validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
42 validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
42 validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
43 validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph)
43 validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph)
44 # donwcase letters, digits, dashes, underscores but not digits only
44 # donwcase letters, digits, dashes, underscores but not digits only
45 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-_]*$/, :allow_blank => true
45 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-_]*$/, :allow_blank => true
46 # Checks if the SCM is enabled when creating a repository
46 # Checks if the SCM is enabled when creating a repository
47 validate :repo_create_validation, :on => :create
47 validate :repo_create_validation, :on => :create
48
48
49 safe_attributes 'identifier',
49 safe_attributes 'identifier',
50 'login',
50 'login',
51 'password',
51 'password',
52 'path_encoding',
52 'path_encoding',
53 'log_encoding',
53 'log_encoding',
54 'is_default'
54 'is_default'
55
55
56 safe_attributes 'url',
56 safe_attributes 'url',
57 :if => lambda {|repository, user| repository.new_record?}
57 :if => lambda {|repository, user| repository.new_record?}
58
58
59 def repo_create_validation
59 def repo_create_validation
60 unless Setting.enabled_scm.include?(self.class.name.demodulize)
60 unless Setting.enabled_scm.include?(self.class.name.demodulize)
61 errors.add(:type, :invalid)
61 errors.add(:type, :invalid)
62 end
62 end
63 end
63 end
64
64
65 def self.human_attribute_name(attribute_key_name, *args)
65 def self.human_attribute_name(attribute_key_name, *args)
66 attr_name = attribute_key_name.to_s
66 attr_name = attribute_key_name.to_s
67 if attr_name == "log_encoding"
67 if attr_name == "log_encoding"
68 attr_name = "commit_logs_encoding"
68 attr_name = "commit_logs_encoding"
69 end
69 end
70 super(attr_name, *args)
70 super(attr_name, *args)
71 end
71 end
72
72
73 # Removes leading and trailing whitespace
73 # Removes leading and trailing whitespace
74 def url=(arg)
74 def url=(arg)
75 write_attribute(:url, arg ? arg.to_s.strip : nil)
75 write_attribute(:url, arg ? arg.to_s.strip : nil)
76 end
76 end
77
77
78 # Removes leading and trailing whitespace
78 # Removes leading and trailing whitespace
79 def root_url=(arg)
79 def root_url=(arg)
80 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
80 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
81 end
81 end
82
82
83 def password
83 def password
84 read_ciphered_attribute(:password)
84 read_ciphered_attribute(:password)
85 end
85 end
86
86
87 def password=(arg)
87 def password=(arg)
88 write_ciphered_attribute(:password, arg)
88 write_ciphered_attribute(:password, arg)
89 end
89 end
90
90
91 def scm_adapter
91 def scm_adapter
92 self.class.scm_adapter_class
92 self.class.scm_adapter_class
93 end
93 end
94
94
95 def scm
95 def scm
96 unless @scm
96 unless @scm
97 @scm = self.scm_adapter.new(url, root_url,
97 @scm = self.scm_adapter.new(url, root_url,
98 login, password, path_encoding)
98 login, password, path_encoding)
99 if root_url.blank? && @scm.root_url.present?
99 if root_url.blank? && @scm.root_url.present?
100 update_attribute(:root_url, @scm.root_url)
100 update_attribute(:root_url, @scm.root_url)
101 end
101 end
102 end
102 end
103 @scm
103 @scm
104 end
104 end
105
105
106 def scm_name
106 def scm_name
107 self.class.scm_name
107 self.class.scm_name
108 end
108 end
109
109
110 def name
110 def name
111 if identifier.present?
111 if identifier.present?
112 identifier
112 identifier
113 elsif is_default?
113 elsif is_default?
114 l(:field_repository_is_default)
114 l(:field_repository_is_default)
115 else
115 else
116 scm_name
116 scm_name
117 end
117 end
118 end
118 end
119
119
120 def identifier=(identifier)
120 def identifier=(identifier)
121 super unless identifier_frozen?
121 super unless identifier_frozen?
122 end
122 end
123
123
124 def identifier_frozen?
124 def identifier_frozen?
125 errors[:identifier].blank? && !(new_record? || identifier.blank?)
125 errors[:identifier].blank? && !(new_record? || identifier.blank?)
126 end
126 end
127
127
128 def identifier_param
128 def identifier_param
129 if is_default?
129 if is_default?
130 nil
130 nil
131 elsif identifier.present?
131 elsif identifier.present?
132 identifier
132 identifier
133 else
133 else
134 id.to_s
134 id.to_s
135 end
135 end
136 end
136 end
137
137
138 def <=>(repository)
138 def <=>(repository)
139 if is_default?
139 if is_default?
140 -1
140 -1
141 elsif repository.is_default?
141 elsif repository.is_default?
142 1
142 1
143 else
143 else
144 identifier.to_s <=> repository.identifier.to_s
144 identifier.to_s <=> repository.identifier.to_s
145 end
145 end
146 end
146 end
147
147
148 def self.find_by_identifier_param(param)
148 def self.find_by_identifier_param(param)
149 if param.to_s =~ /^\d+$/
149 if param.to_s =~ /^\d+$/
150 find_by_id(param)
150 find_by_id(param)
151 else
151 else
152 find_by_identifier(param)
152 find_by_identifier(param)
153 end
153 end
154 end
154 end
155
155
156 def merge_extra_info(arg)
156 def merge_extra_info(arg)
157 h = extra_info || {}
157 h = extra_info || {}
158 return h if arg.nil?
158 return h if arg.nil?
159 h.merge!(arg)
159 h.merge!(arg)
160 write_attribute(:extra_info, h)
160 write_attribute(:extra_info, h)
161 end
161 end
162
162
163 def report_last_commit
163 def report_last_commit
164 true
164 true
165 end
165 end
166
166
167 def supports_cat?
167 def supports_cat?
168 scm.supports_cat?
168 scm.supports_cat?
169 end
169 end
170
170
171 def supports_annotate?
171 def supports_annotate?
172 scm.supports_annotate?
172 scm.supports_annotate?
173 end
173 end
174
174
175 def supports_all_revisions?
175 def supports_all_revisions?
176 true
176 true
177 end
177 end
178
178
179 def supports_directory_revisions?
179 def supports_directory_revisions?
180 false
180 false
181 end
181 end
182
182
183 def supports_revision_graph?
183 def supports_revision_graph?
184 false
184 false
185 end
185 end
186
186
187 def entry(path=nil, identifier=nil)
187 def entry(path=nil, identifier=nil)
188 scm.entry(path, identifier)
188 scm.entry(path, identifier)
189 end
189 end
190
190
191 def entries(path=nil, identifier=nil)
191 def entries(path=nil, identifier=nil)
192 entries = scm.entries(path, identifier)
192 entries = scm.entries(path, identifier)
193 load_entries_changesets(entries)
193 load_entries_changesets(entries)
194 entries
194 entries
195 end
195 end
196
196
197 def branches
197 def branches
198 scm.branches
198 scm.branches
199 end
199 end
200
200
201 def tags
201 def tags
202 scm.tags
202 scm.tags
203 end
203 end
204
204
205 def default_branch
205 def default_branch
206 nil
206 nil
207 end
207 end
208
208
209 def properties(path, identifier=nil)
209 def properties(path, identifier=nil)
210 scm.properties(path, identifier)
210 scm.properties(path, identifier)
211 end
211 end
212
212
213 def cat(path, identifier=nil)
213 def cat(path, identifier=nil)
214 scm.cat(path, identifier)
214 scm.cat(path, identifier)
215 end
215 end
216
216
217 def diff(path, rev, rev_to)
217 def diff(path, rev, rev_to)
218 scm.diff(path, rev, rev_to)
218 scm.diff(path, rev, rev_to)
219 end
219 end
220
220
221 def diff_format_revisions(cs, cs_to, sep=':')
221 def diff_format_revisions(cs, cs_to, sep=':')
222 text = ""
222 text = ""
223 text << cs_to.format_identifier + sep if cs_to
223 text << cs_to.format_identifier + sep if cs_to
224 text << cs.format_identifier if cs
224 text << cs.format_identifier if cs
225 text
225 text
226 end
226 end
227
227
228 # Returns a path relative to the url of the repository
228 # Returns a path relative to the url of the repository
229 def relative_path(path)
229 def relative_path(path)
230 path
230 path
231 end
231 end
232
232
233 # Finds and returns a revision with a number or the beginning of a hash
233 # Finds and returns a revision with a number or the beginning of a hash
234 def find_changeset_by_name(name)
234 def find_changeset_by_name(name)
235 return nil if name.blank?
235 return nil if name.blank?
236 s = name.to_s
236 s = name.to_s
237 changesets.find(:first, :conditions => (s.match(/^\d*$/) ?
237 changesets.find(:first, :conditions => (s.match(/^\d*$/) ?
238 ["revision = ?", s] : ["revision LIKE ?", s + '%']))
238 ["revision = ?", s] : ["revision LIKE ?", s + '%']))
239 end
239 end
240
240
241 def latest_changeset
241 def latest_changeset
242 @latest_changeset ||= changesets.find(:first)
242 @latest_changeset ||= changesets.find(:first)
243 end
243 end
244
244
245 # Returns the latest changesets for +path+
245 # Returns the latest changesets for +path+
246 # Default behaviour is to search in cached changesets
246 # Default behaviour is to search in cached changesets
247 def latest_changesets(path, rev, limit=10)
247 def latest_changesets(path, rev, limit=10)
248 if path.blank?
248 if path.blank?
249 changesets.find(
249 changesets.find(
250 :all,
250 :all,
251 :include => :user,
251 :include => :user,
252 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
252 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
253 :limit => limit)
253 :limit => limit)
254 else
254 else
255 filechanges.find(
255 filechanges.find(
256 :all,
256 :all,
257 :include => {:changeset => :user},
257 :include => {:changeset => :user},
258 :conditions => ["path = ?", path.with_leading_slash],
258 :conditions => ["path = ?", path.with_leading_slash],
259 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
259 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
260 :limit => limit
260 :limit => limit
261 ).collect(&:changeset)
261 ).collect(&:changeset)
262 end
262 end
263 end
263 end
264
264
265 def scan_changesets_for_issue_ids
265 def scan_changesets_for_issue_ids
266 self.changesets.each(&:scan_comment_for_issue_ids)
266 self.changesets.each(&:scan_comment_for_issue_ids)
267 end
267 end
268
268
269 # Returns an array of committers usernames and associated user_id
269 # Returns an array of committers usernames and associated user_id
270 def committers
270 def committers
271 @committers ||= Changeset.connection.select_rows(
271 @committers ||= Changeset.connection.select_rows(
272 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
272 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
273 end
273 end
274
274
275 # Maps committers username to a user ids
275 # Maps committers username to a user ids
276 def committer_ids=(h)
276 def committer_ids=(h)
277 if h.is_a?(Hash)
277 if h.is_a?(Hash)
278 committers.each do |committer, user_id|
278 committers.each do |committer, user_id|
279 new_user_id = h[committer]
279 new_user_id = h[committer]
280 if new_user_id && (new_user_id.to_i != user_id.to_i)
280 if new_user_id && (new_user_id.to_i != user_id.to_i)
281 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
281 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
282 Changeset.update_all(
282 Changeset.update_all(
283 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
283 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
284 ["repository_id = ? AND committer = ?", id, committer])
284 ["repository_id = ? AND committer = ?", id, committer])
285 end
285 end
286 end
286 end
287 @committers = nil
287 @committers = nil
288 @found_committer_users = nil
288 @found_committer_users = nil
289 true
289 true
290 else
290 else
291 false
291 false
292 end
292 end
293 end
293 end
294
294
295 # Returns the Redmine User corresponding to the given +committer+
295 # Returns the Redmine User corresponding to the given +committer+
296 # It will return nil if the committer is not yet mapped and if no User
296 # It will return nil if the committer is not yet mapped and if no User
297 # with the same username or email was found
297 # with the same username or email was found
298 def find_committer_user(committer)
298 def find_committer_user(committer)
299 unless committer.blank?
299 unless committer.blank?
300 @found_committer_users ||= {}
300 @found_committer_users ||= {}
301 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
301 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
302
302
303 user = nil
303 user = nil
304 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
304 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
305 if c && c.user
305 if c && c.user
306 user = c.user
306 user = c.user
307 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
307 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
308 username, email = $1.strip, $3
308 username, email = $1.strip, $3
309 u = User.find_by_login(username)
309 u = User.find_by_login(username)
310 u ||= User.find_by_mail(email) unless email.blank?
310 u ||= User.find_by_mail(email) unless email.blank?
311 user = u
311 user = u
312 end
312 end
313 @found_committer_users[committer] = user
313 @found_committer_users[committer] = user
314 user
314 user
315 end
315 end
316 end
316 end
317
317
318 def repo_log_encoding
318 def repo_log_encoding
319 encoding = log_encoding.to_s.strip
319 encoding = log_encoding.to_s.strip
320 encoding.blank? ? 'UTF-8' : encoding
320 encoding.blank? ? 'UTF-8' : encoding
321 end
321 end
322
322
323 # Fetches new changesets for all repositories of active projects
323 # Fetches new changesets for all repositories of active projects
324 # Can be called periodically by an external script
324 # Can be called periodically by an external script
325 # eg. ruby script/runner "Repository.fetch_changesets"
325 # eg. ruby script/runner "Repository.fetch_changesets"
326 def self.fetch_changesets
326 def self.fetch_changesets
327 Project.active.has_module(:repository).all.each do |project|
327 Project.active.has_module(:repository).all.each do |project|
328 project.repositories.each do |repository|
328 project.repositories.each do |repository|
329 begin
329 begin
330 repository.fetch_changesets
330 repository.fetch_changesets
331 rescue Redmine::Scm::Adapters::CommandFailed => e
331 rescue Redmine::Scm::Adapters::CommandFailed => e
332 logger.error "scm: error during fetching changesets: #{e.message}"
332 logger.error "scm: error during fetching changesets: #{e.message}"
333 end
333 end
334 end
334 end
335 end
335 end
336 end
336 end
337
337
338 # scan changeset comments to find related and fixed issues for all repositories
338 # scan changeset comments to find related and fixed issues for all repositories
339 def self.scan_changesets_for_issue_ids
339 def self.scan_changesets_for_issue_ids
340 find(:all).each(&:scan_changesets_for_issue_ids)
340 find(:all).each(&:scan_changesets_for_issue_ids)
341 end
341 end
342
342
343 def self.scm_name
343 def self.scm_name
344 'Abstract'
344 'Abstract'
345 end
345 end
346
346
347 def self.available_scm
347 def self.available_scm
348 subclasses.collect {|klass| [klass.scm_name, klass.name]}
348 subclasses.collect {|klass| [klass.scm_name, klass.name]}
349 end
349 end
350
350
351 def self.factory(klass_name, *args)
351 def self.factory(klass_name, *args)
352 klass = "Repository::#{klass_name}".constantize
352 klass = "Repository::#{klass_name}".constantize
353 klass.new(*args)
353 klass.new(*args)
354 rescue
354 rescue
355 nil
355 nil
356 end
356 end
357
357
358 def self.scm_adapter_class
358 def self.scm_adapter_class
359 nil
359 nil
360 end
360 end
361
361
362 def self.scm_command
362 def self.scm_command
363 ret = ""
363 ret = ""
364 begin
364 begin
365 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
365 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
366 rescue Exception => e
366 rescue Exception => e
367 logger.error "scm: error during get command: #{e.message}"
367 logger.error "scm: error during get command: #{e.message}"
368 end
368 end
369 ret
369 ret
370 end
370 end
371
371
372 def self.scm_version_string
372 def self.scm_version_string
373 ret = ""
373 ret = ""
374 begin
374 begin
375 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
375 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
376 rescue Exception => e
376 rescue Exception => e
377 logger.error "scm: error during get version string: #{e.message}"
377 logger.error "scm: error during get version string: #{e.message}"
378 end
378 end
379 ret
379 ret
380 end
380 end
381
381
382 def self.scm_available
382 def self.scm_available
383 ret = false
383 ret = false
384 begin
384 begin
385 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
385 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
386 rescue Exception => e
386 rescue Exception => e
387 logger.error "scm: error during get scm available: #{e.message}"
387 logger.error "scm: error during get scm available: #{e.message}"
388 end
388 end
389 ret
389 ret
390 end
390 end
391
391
392 def set_as_default?
392 def set_as_default?
393 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
393 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
394 end
394 end
395
395
396 protected
396 protected
397
397
398 def check_default
398 def check_default
399 if !is_default? && set_as_default?
399 if !is_default? && set_as_default?
400 self.is_default = true
400 self.is_default = true
401 end
401 end
402 if is_default? && is_default_changed?
402 if is_default? && is_default_changed?
403 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
403 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
404 end
404 end
405 end
405 end
406
406
407 def load_entries_changesets(entries)
407 def load_entries_changesets(entries)
408 if entries
408 if entries
409 entries.each do |entry|
409 entries.each do |entry|
410 if entry.lastrev && entry.lastrev.identifier
410 if entry.lastrev && entry.lastrev.identifier
411 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
411 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
412 end
412 end
413 end
413 end
414 end
414 end
415 end
415 end
416
416
417 private
417 private
418
418
419 # Deletes repository data
419 # Deletes repository data
420 def clear_changesets
420 def clear_changesets
421 cs = Changeset.table_name
421 cs = Changeset.table_name
422 ch = Change.table_name
422 ch = Change.table_name
423 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
423 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
424 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
424 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
425
425
426 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
426 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
427 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
427 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
428 connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
428 connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
429 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
429 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
430 clear_extra_info_of_changesets
431 end
432
433 def clear_extra_info_of_changesets
430 end
434 end
431 end
435 end
@@ -1,258 +1,269
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 # Copyright (C) 2007 Patrick Aljord patcito@Ε‹mail.com
3 # Copyright (C) 2007 Patrick Aljord patcito@Ε‹mail.com
4 #
4 #
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
8 # of the License, or (at your option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
18
19 require 'redmine/scm/adapters/git_adapter'
19 require 'redmine/scm/adapters/git_adapter'
20
20
21 class Repository::Git < Repository
21 class Repository::Git < Repository
22 attr_protected :root_url
22 attr_protected :root_url
23 validates_presence_of :url
23 validates_presence_of :url
24
24
25 def self.human_attribute_name(attribute_key_name, *args)
25 def self.human_attribute_name(attribute_key_name, *args)
26 attr_name = attribute_key_name.to_s
26 attr_name = attribute_key_name.to_s
27 if attr_name == "url"
27 if attr_name == "url"
28 attr_name = "path_to_repository"
28 attr_name = "path_to_repository"
29 end
29 end
30 super(attr_name, *args)
30 super(attr_name, *args)
31 end
31 end
32
32
33 def self.scm_adapter_class
33 def self.scm_adapter_class
34 Redmine::Scm::Adapters::GitAdapter
34 Redmine::Scm::Adapters::GitAdapter
35 end
35 end
36
36
37 def self.scm_name
37 def self.scm_name
38 'Git'
38 'Git'
39 end
39 end
40
40
41 def report_last_commit
41 def report_last_commit
42 extra_report_last_commit
42 extra_report_last_commit
43 end
43 end
44
44
45 def extra_report_last_commit
45 def extra_report_last_commit
46 return false if extra_info.nil?
46 return false if extra_info.nil?
47 v = extra_info["extra_report_last_commit"]
47 v = extra_info["extra_report_last_commit"]
48 return false if v.nil?
48 return false if v.nil?
49 v.to_s != '0'
49 v.to_s != '0'
50 end
50 end
51
51
52 def supports_directory_revisions?
52 def supports_directory_revisions?
53 true
53 true
54 end
54 end
55
55
56 def supports_revision_graph?
56 def supports_revision_graph?
57 true
57 true
58 end
58 end
59
59
60 def repo_log_encoding
60 def repo_log_encoding
61 'UTF-8'
61 'UTF-8'
62 end
62 end
63
63
64 # Returns the identifier for the given git changeset
64 # Returns the identifier for the given git changeset
65 def self.changeset_identifier(changeset)
65 def self.changeset_identifier(changeset)
66 changeset.scmid
66 changeset.scmid
67 end
67 end
68
68
69 # Returns the readable identifier for the given git changeset
69 # Returns the readable identifier for the given git changeset
70 def self.format_changeset_identifier(changeset)
70 def self.format_changeset_identifier(changeset)
71 changeset.revision[0, 8]
71 changeset.revision[0, 8]
72 end
72 end
73
73
74 def branches
74 def branches
75 scm.branches
75 scm.branches
76 end
76 end
77
77
78 def tags
78 def tags
79 scm.tags
79 scm.tags
80 end
80 end
81
81
82 def default_branch
82 def default_branch
83 scm.default_branch
83 scm.default_branch
84 rescue Exception => e
84 rescue Exception => e
85 logger.error "git: error during get default branch: #{e.message}"
85 logger.error "git: error during get default branch: #{e.message}"
86 nil
86 nil
87 end
87 end
88
88
89 def find_changeset_by_name(name)
89 def find_changeset_by_name(name)
90 if name.present?
90 if name.present?
91 changesets.where(:revision => name.to_s).first ||
91 changesets.where(:revision => name.to_s).first ||
92 changesets.where('scmid LIKE ?', "#{name}%").first
92 changesets.where('scmid LIKE ?', "#{name}%").first
93 end
93 end
94 end
94 end
95
95
96 def entries(path=nil, identifier=nil)
96 def entries(path=nil, identifier=nil)
97 entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
97 entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
98 load_entries_changesets(entries)
98 load_entries_changesets(entries)
99 entries
99 entries
100 end
100 end
101
101
102 # With SCMs that have a sequential commit numbering,
102 # With SCMs that have a sequential commit numbering,
103 # such as Subversion and Mercurial,
103 # such as Subversion and Mercurial,
104 # Redmine is able to be clever and only fetch changesets
104 # Redmine is able to be clever and only fetch changesets
105 # going forward from the most recent one it knows about.
105 # going forward from the most recent one it knows about.
106 #
106 #
107 # However, Git does not have a sequential commit numbering.
107 # However, Git does not have a sequential commit numbering.
108 #
108 #
109 # In order to fetch only new adding revisions,
109 # In order to fetch only new adding revisions,
110 # Redmine needs to save "heads".
110 # Redmine needs to save "heads".
111 #
111 #
112 # In Git and Mercurial, revisions are not in date order.
112 # In Git and Mercurial, revisions are not in date order.
113 # Redmine Mercurial fixed issues.
113 # Redmine Mercurial fixed issues.
114 # * Redmine Takes Too Long On Large Mercurial Repository
114 # * Redmine Takes Too Long On Large Mercurial Repository
115 # http://www.redmine.org/issues/3449
115 # http://www.redmine.org/issues/3449
116 # * Sorting for changesets might go wrong on Mercurial repos
116 # * Sorting for changesets might go wrong on Mercurial repos
117 # http://www.redmine.org/issues/3567
117 # http://www.redmine.org/issues/3567
118 #
118 #
119 # Database revision column is text, so Redmine can not sort by revision.
119 # Database revision column is text, so Redmine can not sort by revision.
120 # Mercurial has revision number, and revision number guarantees revision order.
120 # Mercurial has revision number, and revision number guarantees revision order.
121 # Redmine Mercurial model stored revisions ordered by database id to database.
121 # Redmine Mercurial model stored revisions ordered by database id to database.
122 # So, Redmine Mercurial model can use correct ordering revisions.
122 # So, Redmine Mercurial model can use correct ordering revisions.
123 #
123 #
124 # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
124 # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
125 # to get limited revisions from old to new.
125 # to get limited revisions from old to new.
126 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
126 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
127 #
127 #
128 # The repository can still be fully reloaded by calling #clear_changesets
128 # The repository can still be fully reloaded by calling #clear_changesets
129 # before fetching changesets (eg. for offline resync)
129 # before fetching changesets (eg. for offline resync)
130 def fetch_changesets
130 def fetch_changesets
131 scm_brs = branches
131 scm_brs = branches
132 return if scm_brs.nil? || scm_brs.empty?
132 return if scm_brs.nil? || scm_brs.empty?
133
133
134 h1 = extra_info || {}
134 h1 = extra_info || {}
135 h = h1.dup
135 h = h1.dup
136 repo_heads = scm_brs.map{ |br| br.scmid }
136 repo_heads = scm_brs.map{ |br| br.scmid }
137 h["heads"] ||= []
137 h["heads"] ||= []
138 prev_db_heads = h["heads"].dup
138 prev_db_heads = h["heads"].dup
139 if prev_db_heads.empty?
139 if prev_db_heads.empty?
140 prev_db_heads += heads_from_branches_hash
140 prev_db_heads += heads_from_branches_hash
141 end
141 end
142 return if prev_db_heads.sort == repo_heads.sort
142 return if prev_db_heads.sort == repo_heads.sort
143
143
144 h["db_consistent"] ||= {}
144 h["db_consistent"] ||= {}
145 if changesets.count == 0
145 if changesets.count == 0
146 h["db_consistent"]["ordering"] = 1
146 h["db_consistent"]["ordering"] = 1
147 merge_extra_info(h)
147 merge_extra_info(h)
148 self.save
148 self.save
149 elsif ! h["db_consistent"].has_key?("ordering")
149 elsif ! h["db_consistent"].has_key?("ordering")
150 h["db_consistent"]["ordering"] = 0
150 h["db_consistent"]["ordering"] = 0
151 merge_extra_info(h)
151 merge_extra_info(h)
152 self.save
152 self.save
153 end
153 end
154 save_revisions(prev_db_heads, repo_heads)
154 save_revisions(prev_db_heads, repo_heads)
155 end
155 end
156
156
157 def save_revisions(prev_db_heads, repo_heads)
157 def save_revisions(prev_db_heads, repo_heads)
158 h = {}
158 h = {}
159 opts = {}
159 opts = {}
160 opts[:reverse] = true
160 opts[:reverse] = true
161 opts[:excludes] = prev_db_heads
161 opts[:excludes] = prev_db_heads
162 opts[:includes] = repo_heads
162 opts[:includes] = repo_heads
163
163
164 revisions = scm.revisions('', nil, nil, opts)
164 revisions = scm.revisions('', nil, nil, opts)
165 return if revisions.blank?
165 return if revisions.blank?
166
166
167 # Make the search for existing revisions in the database in a more sufficient manner
167 # Make the search for existing revisions in the database in a more sufficient manner
168 #
168 #
169 # Git branch is the reference to the specific revision.
169 # Git branch is the reference to the specific revision.
170 # Git can *delete* remote branch and *re-push* branch.
170 # Git can *delete* remote branch and *re-push* branch.
171 #
171 #
172 # $ git push remote :branch
172 # $ git push remote :branch
173 # $ git push remote branch
173 # $ git push remote branch
174 #
174 #
175 # After deleting branch, revisions remain in repository until "git gc".
175 # After deleting branch, revisions remain in repository until "git gc".
176 # On git 1.7.2.3, default pruning date is 2 weeks.
176 # On git 1.7.2.3, default pruning date is 2 weeks.
177 # So, "git log --not deleted_branch_head_revision" return code is 0.
177 # So, "git log --not deleted_branch_head_revision" return code is 0.
178 #
178 #
179 # After re-pushing branch, "git log" returns revisions which are saved in database.
179 # After re-pushing branch, "git log" returns revisions which are saved in database.
180 # So, Redmine needs to scan revisions and database every time.
180 # So, Redmine needs to scan revisions and database every time.
181 #
181 #
182 # This is replacing the one-after-one queries.
182 # This is replacing the one-after-one queries.
183 # Find all revisions, that are in the database, and then remove them from the revision array.
183 # Find all revisions, that are in the database, and then remove them from the revision array.
184 # Then later we won't need any conditions for db existence.
184 # Then later we won't need any conditions for db existence.
185 # Query for several revisions at once, and remove them from the revisions array, if they are there.
185 # Query for several revisions at once, and remove them from the revisions array, if they are there.
186 # Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits).
186 # Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits).
187 # If there are no revisions (because the original code's algorithm filtered them),
187 # If there are no revisions (because the original code's algorithm filtered them),
188 # then this part will be stepped over.
188 # then this part will be stepped over.
189 # We make queries, just if there is any revision.
189 # We make queries, just if there is any revision.
190 limit = 100
190 limit = 100
191 offset = 0
191 offset = 0
192 revisions_copy = revisions.clone # revisions will change
192 revisions_copy = revisions.clone # revisions will change
193 while offset < revisions_copy.size
193 while offset < revisions_copy.size
194 recent_changesets_slice = changesets.find(
194 recent_changesets_slice = changesets.find(
195 :all,
195 :all,
196 :conditions => [
196 :conditions => [
197 'scmid IN (?)',
197 'scmid IN (?)',
198 revisions_copy.slice(offset, limit).map{|x| x.scmid}
198 revisions_copy.slice(offset, limit).map{|x| x.scmid}
199 ]
199 ]
200 )
200 )
201 # Subtract revisions that redmine already knows about
201 # Subtract revisions that redmine already knows about
202 recent_revisions = recent_changesets_slice.map{|c| c.scmid}
202 recent_revisions = recent_changesets_slice.map{|c| c.scmid}
203 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
203 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
204 offset += limit
204 offset += limit
205 end
205 end
206
206
207 revisions.each do |rev|
207 revisions.each do |rev|
208 transaction do
208 transaction do
209 # There is no search in the db for this revision, because above we ensured,
209 # There is no search in the db for this revision, because above we ensured,
210 # that it's not in the db.
210 # that it's not in the db.
211 save_revision(rev)
211 save_revision(rev)
212 end
212 end
213 end
213 end
214 h["heads"] = repo_heads.dup
214 h["heads"] = repo_heads.dup
215 merge_extra_info(h)
215 merge_extra_info(h)
216 self.save
216 self.save
217 end
217 end
218 private :save_revisions
218 private :save_revisions
219
219
220 def save_revision(rev)
220 def save_revision(rev)
221 parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
221 parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
222 changeset = Changeset.create(
222 changeset = Changeset.create(
223 :repository => self,
223 :repository => self,
224 :revision => rev.identifier,
224 :revision => rev.identifier,
225 :scmid => rev.scmid,
225 :scmid => rev.scmid,
226 :committer => rev.author,
226 :committer => rev.author,
227 :committed_on => rev.time,
227 :committed_on => rev.time,
228 :comments => rev.message,
228 :comments => rev.message,
229 :parents => parents
229 :parents => parents
230 )
230 )
231 unless changeset.new_record?
231 unless changeset.new_record?
232 rev.paths.each { |change| changeset.create_change(change) }
232 rev.paths.each { |change| changeset.create_change(change) }
233 end
233 end
234 changeset
234 changeset
235 end
235 end
236 private :save_revision
236 private :save_revision
237
237
238 def heads_from_branches_hash
238 def heads_from_branches_hash
239 h1 = extra_info || {}
239 h1 = extra_info || {}
240 h = h1.dup
240 h = h1.dup
241 h["branches"] ||= {}
241 h["branches"] ||= {}
242 h['branches'].map{|br, hs| hs['last_scmid']}
242 h['branches'].map{|br, hs| hs['last_scmid']}
243 end
243 end
244
244
245 def latest_changesets(path,rev,limit=10)
245 def latest_changesets(path,rev,limit=10)
246 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
246 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
247 return [] if revisions.nil? || revisions.empty?
247 return [] if revisions.nil? || revisions.empty?
248
248
249 changesets.find(
249 changesets.find(
250 :all,
250 :all,
251 :conditions => [
251 :conditions => [
252 "scmid IN (?)",
252 "scmid IN (?)",
253 revisions.map!{|c| c.scmid}
253 revisions.map!{|c| c.scmid}
254 ],
254 ],
255 :order => 'committed_on DESC'
255 :order => 'committed_on DESC'
256 )
256 )
257 end
257 end
258
259 def clear_extra_info_of_changesets
260 return if extra_info.nil?
261 v = extra_info["extra_report_last_commit"]
262 write_attribute(:extra_info, nil)
263 h = {}
264 h["extra_report_last_commit"] = v
265 merge_extra_info(h)
266 self.save
267 end
268 private :clear_extra_info_of_changesets
258 end
269 end
@@ -1,567 +1,601
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class RepositoryGitTest < ActiveSupport::TestCase
20 class RepositoryGitTest < ActiveSupport::TestCase
21 fixtures :projects, :repositories, :enabled_modules, :users, :roles
21 fixtures :projects, :repositories, :enabled_modules, :users, :roles
22
22
23 include Redmine::I18n
23 include Redmine::I18n
24
24
25 REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
25 REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
26 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
26 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
27
27
28 NUM_REV = 28
28 NUM_REV = 28
29 NUM_HEAD = 6
29 NUM_HEAD = 6
30
30
31 FELIX_HEX = "Felix Sch\xC3\xA4fer"
31 FELIX_HEX = "Felix Sch\xC3\xA4fer"
32 CHAR_1_HEX = "\xc3\x9c"
32 CHAR_1_HEX = "\xc3\x9c"
33
33
34 ## Git, Mercurial and CVS path encodings are binary.
34 ## Git, Mercurial and CVS path encodings are binary.
35 ## Subversion supports URL encoding for path.
35 ## Subversion supports URL encoding for path.
36 ## Redmine Mercurial adapter and extension use URL encoding.
36 ## Redmine Mercurial adapter and extension use URL encoding.
37 ## Git accepts only binary path in command line parameter.
37 ## Git accepts only binary path in command line parameter.
38 ## So, there is no way to use binary command line parameter in JRuby.
38 ## So, there is no way to use binary command line parameter in JRuby.
39 JRUBY_SKIP = (RUBY_PLATFORM == 'java')
39 JRUBY_SKIP = (RUBY_PLATFORM == 'java')
40 JRUBY_SKIP_STR = "TODO: This test fails in JRuby"
40 JRUBY_SKIP_STR = "TODO: This test fails in JRuby"
41
41
42 def setup
42 def setup
43 @project = Project.find(3)
43 @project = Project.find(3)
44 @repository = Repository::Git.create(
44 @repository = Repository::Git.create(
45 :project => @project,
45 :project => @project,
46 :url => REPOSITORY_PATH,
46 :url => REPOSITORY_PATH,
47 :path_encoding => 'ISO-8859-1'
47 :path_encoding => 'ISO-8859-1'
48 )
48 )
49 assert @repository
49 assert @repository
50 @char_1 = CHAR_1_HEX.dup
50 @char_1 = CHAR_1_HEX.dup
51 if @char_1.respond_to?(:force_encoding)
51 if @char_1.respond_to?(:force_encoding)
52 @char_1.force_encoding('UTF-8')
52 @char_1.force_encoding('UTF-8')
53 end
53 end
54 end
54 end
55
55
56 def test_blank_path_to_repository_error_message
56 def test_blank_path_to_repository_error_message
57 set_language_if_valid 'en'
57 set_language_if_valid 'en'
58 repo = Repository::Git.new(
58 repo = Repository::Git.new(
59 :project => @project,
59 :project => @project,
60 :identifier => 'test'
60 :identifier => 'test'
61 )
61 )
62 assert !repo.save
62 assert !repo.save
63 assert_include "Path to repository can't be blank",
63 assert_include "Path to repository can't be blank",
64 repo.errors.full_messages
64 repo.errors.full_messages
65 end
65 end
66
66
67 def test_blank_path_to_repository_error_message_fr
67 def test_blank_path_to_repository_error_message_fr
68 set_language_if_valid 'fr'
68 set_language_if_valid 'fr'
69 str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)"
69 str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)"
70 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
70 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
71 repo = Repository::Git.new(
71 repo = Repository::Git.new(
72 :project => @project,
72 :project => @project,
73 :url => "",
73 :url => "",
74 :identifier => 'test',
74 :identifier => 'test',
75 :path_encoding => ''
75 :path_encoding => ''
76 )
76 )
77 assert !repo.save
77 assert !repo.save
78 assert_include str, repo.errors.full_messages
78 assert_include str, repo.errors.full_messages
79 end
79 end
80
80
81 if File.directory?(REPOSITORY_PATH)
81 if File.directory?(REPOSITORY_PATH)
82 ## Ruby uses ANSI api to fork a process on Windows.
82 ## Ruby uses ANSI api to fork a process on Windows.
83 ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem
83 ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem
84 ## and these are incompatible with ASCII.
84 ## and these are incompatible with ASCII.
85 ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10
85 ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10
86 ## http://code.google.com/p/msysgit/issues/detail?id=80
86 ## http://code.google.com/p/msysgit/issues/detail?id=80
87 ## So, Latin-1 path tests fail on Japanese Windows
87 ## So, Latin-1 path tests fail on Japanese Windows
88 WINDOWS_PASS = (Redmine::Platform.mswin? &&
88 WINDOWS_PASS = (Redmine::Platform.mswin? &&
89 Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10]))
89 Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10]))
90 WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10"
90 WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10"
91
91
92 def test_scm_available
92 def test_scm_available
93 klass = Repository::Git
93 klass = Repository::Git
94 assert_equal "Git", klass.scm_name
94 assert_equal "Git", klass.scm_name
95 assert klass.scm_adapter_class
95 assert klass.scm_adapter_class
96 assert_not_equal "", klass.scm_command
96 assert_not_equal "", klass.scm_command
97 assert_equal true, klass.scm_available
97 assert_equal true, klass.scm_available
98 end
98 end
99
99
100 def test_entries
100 def test_entries
101 entries = @repository.entries
101 entries = @repository.entries
102 assert_kind_of Redmine::Scm::Adapters::Entries, entries
102 assert_kind_of Redmine::Scm::Adapters::Entries, entries
103 end
103 end
104
104
105 def test_fetch_changesets_from_scratch
105 def test_fetch_changesets_from_scratch
106 assert_nil @repository.extra_info
106 assert_nil @repository.extra_info
107
107
108 assert_equal 0, @repository.changesets.count
108 assert_equal 0, @repository.changesets.count
109 @repository.fetch_changesets
109 @repository.fetch_changesets
110 @project.reload
110 @project.reload
111
111
112 assert_equal NUM_REV, @repository.changesets.count
112 assert_equal NUM_REV, @repository.changesets.count
113 assert_equal 39, @repository.filechanges.count
113 assert_equal 39, @repository.filechanges.count
114
114
115 commit = @repository.changesets.find_by_revision("7234cb2750b63f47bff735edc50a1c0a433c2518")
115 commit = @repository.changesets.find_by_revision("7234cb2750b63f47bff735edc50a1c0a433c2518")
116 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.scmid
116 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.scmid
117 assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments
117 assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments
118 assert_equal "jsmith <jsmith@foo.bar>", commit.committer
118 assert_equal "jsmith <jsmith@foo.bar>", commit.committer
119 assert_equal User.find_by_login('jsmith'), commit.user
119 assert_equal User.find_by_login('jsmith'), commit.user
120 # TODO: add a commit with commit time <> author time to the test repository
120 # TODO: add a commit with commit time <> author time to the test repository
121 assert_equal "2007-12-14 09:22:52".to_time, commit.committed_on
121 assert_equal "2007-12-14 09:22:52".to_time, commit.committed_on
122 assert_equal "2007-12-14".to_date, commit.commit_date
122 assert_equal "2007-12-14".to_date, commit.commit_date
123 assert_equal 3, commit.filechanges.count
123 assert_equal 3, commit.filechanges.count
124 change = commit.filechanges.sort_by(&:path).first
124 change = commit.filechanges.sort_by(&:path).first
125 assert_equal "README", change.path
125 assert_equal "README", change.path
126 assert_equal nil, change.from_path
126 assert_equal nil, change.from_path
127 assert_equal "A", change.action
127 assert_equal "A", change.action
128
128
129 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
129 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
130 end
130 end
131
131
132 def test_fetch_changesets_incremental
132 def test_fetch_changesets_incremental
133 assert_equal 0, @repository.changesets.count
133 assert_equal 0, @repository.changesets.count
134 @repository.fetch_changesets
134 @repository.fetch_changesets
135 @project.reload
135 @project.reload
136 assert_equal NUM_REV, @repository.changesets.count
136 assert_equal NUM_REV, @repository.changesets.count
137 extra_info_heads = @repository.extra_info["heads"].dup
137 extra_info_heads = @repository.extra_info["heads"].dup
138 assert_equal NUM_HEAD, extra_info_heads.size
138 assert_equal NUM_HEAD, extra_info_heads.size
139 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
139 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
140 assert_equal 4, extra_info_heads.size
140 assert_equal 4, extra_info_heads.size
141
141
142 del_revs = [
142 del_revs = [
143 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
143 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
144 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
144 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
145 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
145 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
146 "deff712f05a90d96edbd70facc47d944be5897e3",
146 "deff712f05a90d96edbd70facc47d944be5897e3",
147 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
147 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
148 "7e61ac704deecde634b51e59daa8110435dcb3da",
148 "7e61ac704deecde634b51e59daa8110435dcb3da",
149 ]
149 ]
150 @repository.changesets.each do |rev|
150 @repository.changesets.each do |rev|
151 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
151 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
152 end
152 end
153 @project.reload
153 @project.reload
154 cs1 = @repository.changesets
154 cs1 = @repository.changesets
155 assert_equal NUM_REV - 6, cs1.count
155 assert_equal NUM_REV - 6, cs1.count
156 extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8"
156 extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8"
157 h = {}
157 h = {}
158 h["heads"] = extra_info_heads
158 h["heads"] = extra_info_heads
159 @repository.merge_extra_info(h)
159 @repository.merge_extra_info(h)
160 @repository.save
160 @repository.save
161 @project.reload
161 @project.reload
162 assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8")
162 assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8")
163 @repository.fetch_changesets
163 @repository.fetch_changesets
164 @project.reload
164 @project.reload
165 assert_equal NUM_REV, @repository.changesets.count
165 assert_equal NUM_REV, @repository.changesets.count
166 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
166 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
167 assert @repository.extra_info["heads"].index("83ca5fd546063a3c7dc2e568ba3355661a9e2b2c")
167 assert @repository.extra_info["heads"].index("83ca5fd546063a3c7dc2e568ba3355661a9e2b2c")
168 end
168 end
169
169
170 def test_fetch_changesets_history_editing
170 def test_fetch_changesets_history_editing
171 assert_equal 0, @repository.changesets.count
171 assert_equal 0, @repository.changesets.count
172 @repository.fetch_changesets
172 @repository.fetch_changesets
173 @project.reload
173 @project.reload
174 assert_equal NUM_REV, @repository.changesets.count
174 assert_equal NUM_REV, @repository.changesets.count
175 extra_info_heads = @repository.extra_info["heads"].dup
175 extra_info_heads = @repository.extra_info["heads"].dup
176 assert_equal NUM_HEAD, extra_info_heads.size
176 assert_equal NUM_HEAD, extra_info_heads.size
177 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
177 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
178 assert_equal 4, extra_info_heads.size
178 assert_equal 4, extra_info_heads.size
179
179
180 del_revs = [
180 del_revs = [
181 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
181 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
182 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
182 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
183 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
183 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
184 "deff712f05a90d96edbd70facc47d944be5897e3",
184 "deff712f05a90d96edbd70facc47d944be5897e3",
185 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
185 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
186 "7e61ac704deecde634b51e59daa8110435dcb3da",
186 "7e61ac704deecde634b51e59daa8110435dcb3da",
187 ]
187 ]
188 @repository.changesets.each do |rev|
188 @repository.changesets.each do |rev|
189 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
189 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
190 end
190 end
191 @project.reload
191 @project.reload
192 assert_equal NUM_REV - 6, @repository.changesets.count
192 assert_equal NUM_REV - 6, @repository.changesets.count
193
193
194 c = Changeset.new(:repository => @repository,
194 c = Changeset.new(:repository => @repository,
195 :committed_on => Time.now,
195 :committed_on => Time.now,
196 :revision => "abcd1234efgh",
196 :revision => "abcd1234efgh",
197 :scmid => "abcd1234efgh",
197 :scmid => "abcd1234efgh",
198 :comments => 'test')
198 :comments => 'test')
199 assert c.save
199 assert c.save
200 @project.reload
200 @project.reload
201 assert_equal NUM_REV - 5, @repository.changesets.count
201 assert_equal NUM_REV - 5, @repository.changesets.count
202
202
203 extra_info_heads << "1234abcd5678"
203 extra_info_heads << "1234abcd5678"
204 h = {}
204 h = {}
205 h["heads"] = extra_info_heads
205 h["heads"] = extra_info_heads
206 @repository.merge_extra_info(h)
206 @repository.merge_extra_info(h)
207 @repository.save
207 @repository.save
208 @project.reload
208 @project.reload
209 h1 = @repository.extra_info["heads"].dup
209 h1 = @repository.extra_info["heads"].dup
210 assert h1.index("1234abcd5678")
210 assert h1.index("1234abcd5678")
211 assert_equal 5, h1.size
211 assert_equal 5, h1.size
212
212
213 @repository.fetch_changesets
213 @repository.fetch_changesets
214 @project.reload
214 @project.reload
215 assert_equal NUM_REV - 5, @repository.changesets.count
215 assert_equal NUM_REV - 5, @repository.changesets.count
216 h2 = @repository.extra_info["heads"].dup
216 h2 = @repository.extra_info["heads"].dup
217 assert_equal h1, h2
217 assert_equal h1, h2
218 end
218 end
219
219
220 def test_keep_extra_report_last_commit_in_clear_changesets
221 assert_nil @repository.extra_info
222 h = {}
223 h["extra_report_last_commit"] = 1
224 @repository.merge_extra_info(h)
225 @repository.save
226 @project.reload
227
228 assert_equal 0, @repository.changesets.count
229 @repository.fetch_changesets
230 @project.reload
231
232 assert_equal NUM_REV, @repository.changesets.count
233 @repository.send(:clear_changesets)
234 assert_equal 1, @repository.extra_info.size
235 assert_equal 1, @repository.extra_info["extra_report_last_commit"]
236 end
237
238 def test_refetch_after_clear_changesets
239 assert_nil @repository.extra_info
240 assert_equal 0, @repository.changesets.count
241 @repository.fetch_changesets
242 @project.reload
243 assert_equal NUM_REV, @repository.changesets.count
244
245 @repository.send(:clear_changesets)
246 @project.reload
247 assert_equal 0, @repository.changesets.count
248
249 @repository.fetch_changesets
250 @project.reload
251 assert_equal NUM_REV, @repository.changesets.count
252 end
253
220 def test_parents
254 def test_parents
221 assert_equal 0, @repository.changesets.count
255 assert_equal 0, @repository.changesets.count
222 @repository.fetch_changesets
256 @repository.fetch_changesets
223 @project.reload
257 @project.reload
224 assert_equal NUM_REV, @repository.changesets.count
258 assert_equal NUM_REV, @repository.changesets.count
225 r1 = @repository.find_changeset_by_name("7234cb2750b63")
259 r1 = @repository.find_changeset_by_name("7234cb2750b63")
226 assert_equal [], r1.parents
260 assert_equal [], r1.parents
227 r2 = @repository.find_changeset_by_name("899a15dba03a3")
261 r2 = @repository.find_changeset_by_name("899a15dba03a3")
228 assert_equal 1, r2.parents.length
262 assert_equal 1, r2.parents.length
229 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518",
263 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518",
230 r2.parents[0].identifier
264 r2.parents[0].identifier
231 r3 = @repository.find_changeset_by_name("32ae898b720c2")
265 r3 = @repository.find_changeset_by_name("32ae898b720c2")
232 assert_equal 2, r3.parents.length
266 assert_equal 2, r3.parents.length
233 r4 = [r3.parents[0].identifier, r3.parents[1].identifier].sort
267 r4 = [r3.parents[0].identifier, r3.parents[1].identifier].sort
234 assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", r4[0]
268 assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", r4[0]
235 assert_equal "7e61ac704deecde634b51e59daa8110435dcb3da", r4[1]
269 assert_equal "7e61ac704deecde634b51e59daa8110435dcb3da", r4[1]
236 end
270 end
237
271
238 def test_db_consistent_ordering_init
272 def test_db_consistent_ordering_init
239 assert_nil @repository.extra_info
273 assert_nil @repository.extra_info
240 assert_equal 0, @repository.changesets.count
274 assert_equal 0, @repository.changesets.count
241 @repository.fetch_changesets
275 @repository.fetch_changesets
242 @project.reload
276 @project.reload
243 assert_equal 1, @repository.extra_info["db_consistent"]["ordering"]
277 assert_equal 1, @repository.extra_info["db_consistent"]["ordering"]
244 end
278 end
245
279
246 def test_db_consistent_ordering_before_1_2
280 def test_db_consistent_ordering_before_1_2
247 assert_nil @repository.extra_info
281 assert_nil @repository.extra_info
248 assert_equal 0, @repository.changesets.count
282 assert_equal 0, @repository.changesets.count
249 @repository.fetch_changesets
283 @repository.fetch_changesets
250 @project.reload
284 @project.reload
251 assert_equal NUM_REV, @repository.changesets.count
285 assert_equal NUM_REV, @repository.changesets.count
252 assert_not_nil @repository.extra_info
286 assert_not_nil @repository.extra_info
253 h = {}
287 h = {}
254 h["heads"] = []
288 h["heads"] = []
255 h["branches"] = {}
289 h["branches"] = {}
256 h["db_consistent"] = {}
290 h["db_consistent"] = {}
257 @repository.merge_extra_info(h)
291 @repository.merge_extra_info(h)
258 @repository.save
292 @repository.save
259 assert_equal NUM_REV, @repository.changesets.count
293 assert_equal NUM_REV, @repository.changesets.count
260 @repository.fetch_changesets
294 @repository.fetch_changesets
261 @project.reload
295 @project.reload
262 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
296 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
263
297
264 extra_info_heads = @repository.extra_info["heads"].dup
298 extra_info_heads = @repository.extra_info["heads"].dup
265 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
299 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
266 del_revs = [
300 del_revs = [
267 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
301 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
268 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
302 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
269 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
303 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
270 "deff712f05a90d96edbd70facc47d944be5897e3",
304 "deff712f05a90d96edbd70facc47d944be5897e3",
271 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
305 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
272 "7e61ac704deecde634b51e59daa8110435dcb3da",
306 "7e61ac704deecde634b51e59daa8110435dcb3da",
273 ]
307 ]
274 @repository.changesets.each do |rev|
308 @repository.changesets.each do |rev|
275 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
309 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
276 end
310 end
277 @project.reload
311 @project.reload
278 cs1 = @repository.changesets
312 cs1 = @repository.changesets
279 assert_equal NUM_REV - 6, cs1.count
313 assert_equal NUM_REV - 6, cs1.count
280 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
314 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
281
315
282 extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8"
316 extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8"
283 h = {}
317 h = {}
284 h["heads"] = extra_info_heads
318 h["heads"] = extra_info_heads
285 @repository.merge_extra_info(h)
319 @repository.merge_extra_info(h)
286 @repository.save
320 @repository.save
287 @project.reload
321 @project.reload
288 assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8")
322 assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8")
289 @repository.fetch_changesets
323 @repository.fetch_changesets
290 @project.reload
324 @project.reload
291 assert_equal NUM_REV, @repository.changesets.count
325 assert_equal NUM_REV, @repository.changesets.count
292 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
326 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
293
327
294 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
328 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
295 end
329 end
296
330
297 def test_heads_from_branches_hash
331 def test_heads_from_branches_hash
298 assert_nil @repository.extra_info
332 assert_nil @repository.extra_info
299 assert_equal 0, @repository.changesets.count
333 assert_equal 0, @repository.changesets.count
300 assert_equal [], @repository.heads_from_branches_hash
334 assert_equal [], @repository.heads_from_branches_hash
301 h = {}
335 h = {}
302 h["branches"] = {}
336 h["branches"] = {}
303 h["branches"]["test1"] = {}
337 h["branches"]["test1"] = {}
304 h["branches"]["test1"]["last_scmid"] = "1234abcd"
338 h["branches"]["test1"]["last_scmid"] = "1234abcd"
305 h["branches"]["test2"] = {}
339 h["branches"]["test2"] = {}
306 h["branches"]["test2"]["last_scmid"] = "abcd1234"
340 h["branches"]["test2"]["last_scmid"] = "abcd1234"
307 @repository.merge_extra_info(h)
341 @repository.merge_extra_info(h)
308 @repository.save
342 @repository.save
309 @project.reload
343 @project.reload
310 assert_equal ["1234abcd", "abcd1234"], @repository.heads_from_branches_hash.sort
344 assert_equal ["1234abcd", "abcd1234"], @repository.heads_from_branches_hash.sort
311 end
345 end
312
346
313 def test_latest_changesets
347 def test_latest_changesets
314 assert_equal 0, @repository.changesets.count
348 assert_equal 0, @repository.changesets.count
315 @repository.fetch_changesets
349 @repository.fetch_changesets
316 @project.reload
350 @project.reload
317 assert_equal NUM_REV, @repository.changesets.count
351 assert_equal NUM_REV, @repository.changesets.count
318 # with limit
352 # with limit
319 changesets = @repository.latest_changesets('', 'master', 2)
353 changesets = @repository.latest_changesets('', 'master', 2)
320 assert_equal 2, changesets.size
354 assert_equal 2, changesets.size
321
355
322 # with path
356 # with path
323 changesets = @repository.latest_changesets('images', 'master')
357 changesets = @repository.latest_changesets('images', 'master')
324 assert_equal [
358 assert_equal [
325 'deff712f05a90d96edbd70facc47d944be5897e3',
359 'deff712f05a90d96edbd70facc47d944be5897e3',
326 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
360 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
327 '7234cb2750b63f47bff735edc50a1c0a433c2518',
361 '7234cb2750b63f47bff735edc50a1c0a433c2518',
328 ], changesets.collect(&:revision)
362 ], changesets.collect(&:revision)
329
363
330 changesets = @repository.latest_changesets('README', nil)
364 changesets = @repository.latest_changesets('README', nil)
331 assert_equal [
365 assert_equal [
332 '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf',
366 '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf',
333 '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8',
367 '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8',
334 '713f4944648826f558cf548222f813dabe7cbb04',
368 '713f4944648826f558cf548222f813dabe7cbb04',
335 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
369 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
336 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
370 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
337 '7234cb2750b63f47bff735edc50a1c0a433c2518',
371 '7234cb2750b63f47bff735edc50a1c0a433c2518',
338 ], changesets.collect(&:revision)
372 ], changesets.collect(&:revision)
339
373
340 # with path, revision and limit
374 # with path, revision and limit
341 changesets = @repository.latest_changesets('images', '899a15dba')
375 changesets = @repository.latest_changesets('images', '899a15dba')
342 assert_equal [
376 assert_equal [
343 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
377 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
344 '7234cb2750b63f47bff735edc50a1c0a433c2518',
378 '7234cb2750b63f47bff735edc50a1c0a433c2518',
345 ], changesets.collect(&:revision)
379 ], changesets.collect(&:revision)
346
380
347 changesets = @repository.latest_changesets('images', '899a15dba', 1)
381 changesets = @repository.latest_changesets('images', '899a15dba', 1)
348 assert_equal [
382 assert_equal [
349 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
383 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
350 ], changesets.collect(&:revision)
384 ], changesets.collect(&:revision)
351
385
352 changesets = @repository.latest_changesets('README', '899a15dba')
386 changesets = @repository.latest_changesets('README', '899a15dba')
353 assert_equal [
387 assert_equal [
354 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
388 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
355 '7234cb2750b63f47bff735edc50a1c0a433c2518',
389 '7234cb2750b63f47bff735edc50a1c0a433c2518',
356 ], changesets.collect(&:revision)
390 ], changesets.collect(&:revision)
357
391
358 changesets = @repository.latest_changesets('README', '899a15dba', 1)
392 changesets = @repository.latest_changesets('README', '899a15dba', 1)
359 assert_equal [
393 assert_equal [
360 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
394 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
361 ], changesets.collect(&:revision)
395 ], changesets.collect(&:revision)
362
396
363 # with path, tag and limit
397 # with path, tag and limit
364 changesets = @repository.latest_changesets('images', 'tag01.annotated')
398 changesets = @repository.latest_changesets('images', 'tag01.annotated')
365 assert_equal [
399 assert_equal [
366 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
400 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
367 '7234cb2750b63f47bff735edc50a1c0a433c2518',
401 '7234cb2750b63f47bff735edc50a1c0a433c2518',
368 ], changesets.collect(&:revision)
402 ], changesets.collect(&:revision)
369
403
370 changesets = @repository.latest_changesets('images', 'tag01.annotated', 1)
404 changesets = @repository.latest_changesets('images', 'tag01.annotated', 1)
371 assert_equal [
405 assert_equal [
372 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
406 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
373 ], changesets.collect(&:revision)
407 ], changesets.collect(&:revision)
374
408
375 changesets = @repository.latest_changesets('README', 'tag01.annotated')
409 changesets = @repository.latest_changesets('README', 'tag01.annotated')
376 assert_equal [
410 assert_equal [
377 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
411 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
378 '7234cb2750b63f47bff735edc50a1c0a433c2518',
412 '7234cb2750b63f47bff735edc50a1c0a433c2518',
379 ], changesets.collect(&:revision)
413 ], changesets.collect(&:revision)
380
414
381 changesets = @repository.latest_changesets('README', 'tag01.annotated', 1)
415 changesets = @repository.latest_changesets('README', 'tag01.annotated', 1)
382 assert_equal [
416 assert_equal [
383 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
417 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
384 ], changesets.collect(&:revision)
418 ], changesets.collect(&:revision)
385
419
386 # with path, branch and limit
420 # with path, branch and limit
387 changesets = @repository.latest_changesets('images', 'test_branch')
421 changesets = @repository.latest_changesets('images', 'test_branch')
388 assert_equal [
422 assert_equal [
389 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
423 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
390 '7234cb2750b63f47bff735edc50a1c0a433c2518',
424 '7234cb2750b63f47bff735edc50a1c0a433c2518',
391 ], changesets.collect(&:revision)
425 ], changesets.collect(&:revision)
392
426
393 changesets = @repository.latest_changesets('images', 'test_branch', 1)
427 changesets = @repository.latest_changesets('images', 'test_branch', 1)
394 assert_equal [
428 assert_equal [
395 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
429 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
396 ], changesets.collect(&:revision)
430 ], changesets.collect(&:revision)
397
431
398 changesets = @repository.latest_changesets('README', 'test_branch')
432 changesets = @repository.latest_changesets('README', 'test_branch')
399 assert_equal [
433 assert_equal [
400 '713f4944648826f558cf548222f813dabe7cbb04',
434 '713f4944648826f558cf548222f813dabe7cbb04',
401 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
435 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
402 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
436 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
403 '7234cb2750b63f47bff735edc50a1c0a433c2518',
437 '7234cb2750b63f47bff735edc50a1c0a433c2518',
404 ], changesets.collect(&:revision)
438 ], changesets.collect(&:revision)
405
439
406 changesets = @repository.latest_changesets('README', 'test_branch', 2)
440 changesets = @repository.latest_changesets('README', 'test_branch', 2)
407 assert_equal [
441 assert_equal [
408 '713f4944648826f558cf548222f813dabe7cbb04',
442 '713f4944648826f558cf548222f813dabe7cbb04',
409 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
443 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
410 ], changesets.collect(&:revision)
444 ], changesets.collect(&:revision)
411
445
412 if WINDOWS_PASS
446 if WINDOWS_PASS
413 puts WINDOWS_SKIP_STR
447 puts WINDOWS_SKIP_STR
414 elsif JRUBY_SKIP
448 elsif JRUBY_SKIP
415 puts JRUBY_SKIP_STR
449 puts JRUBY_SKIP_STR
416 else
450 else
417 # latin-1 encoding path
451 # latin-1 encoding path
418 changesets = @repository.latest_changesets(
452 changesets = @repository.latest_changesets(
419 "latin-1-dir/test-#{@char_1}-2.txt", '64f1f3e89')
453 "latin-1-dir/test-#{@char_1}-2.txt", '64f1f3e89')
420 assert_equal [
454 assert_equal [
421 '64f1f3e89ad1cb57976ff0ad99a107012ba3481d',
455 '64f1f3e89ad1cb57976ff0ad99a107012ba3481d',
422 '4fc55c43bf3d3dc2efb66145365ddc17639ce81e',
456 '4fc55c43bf3d3dc2efb66145365ddc17639ce81e',
423 ], changesets.collect(&:revision)
457 ], changesets.collect(&:revision)
424
458
425 changesets = @repository.latest_changesets(
459 changesets = @repository.latest_changesets(
426 "latin-1-dir/test-#{@char_1}-2.txt", '64f1f3e89', 1)
460 "latin-1-dir/test-#{@char_1}-2.txt", '64f1f3e89', 1)
427 assert_equal [
461 assert_equal [
428 '64f1f3e89ad1cb57976ff0ad99a107012ba3481d',
462 '64f1f3e89ad1cb57976ff0ad99a107012ba3481d',
429 ], changesets.collect(&:revision)
463 ], changesets.collect(&:revision)
430 end
464 end
431 end
465 end
432
466
433 def test_latest_changesets_latin_1_dir
467 def test_latest_changesets_latin_1_dir
434 if WINDOWS_PASS
468 if WINDOWS_PASS
435 puts WINDOWS_SKIP_STR
469 puts WINDOWS_SKIP_STR
436 elsif JRUBY_SKIP
470 elsif JRUBY_SKIP
437 puts JRUBY_SKIP_STR
471 puts JRUBY_SKIP_STR
438 else
472 else
439 assert_equal 0, @repository.changesets.count
473 assert_equal 0, @repository.changesets.count
440 @repository.fetch_changesets
474 @repository.fetch_changesets
441 @project.reload
475 @project.reload
442 assert_equal NUM_REV, @repository.changesets.count
476 assert_equal NUM_REV, @repository.changesets.count
443 changesets = @repository.latest_changesets(
477 changesets = @repository.latest_changesets(
444 "latin-1-dir/test-#{@char_1}-subdir", '1ca7f5ed')
478 "latin-1-dir/test-#{@char_1}-subdir", '1ca7f5ed')
445 assert_equal [
479 assert_equal [
446 '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127',
480 '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127',
447 ], changesets.collect(&:revision)
481 ], changesets.collect(&:revision)
448 end
482 end
449 end
483 end
450
484
451 def test_find_changeset_by_name
485 def test_find_changeset_by_name
452 assert_equal 0, @repository.changesets.count
486 assert_equal 0, @repository.changesets.count
453 @repository.fetch_changesets
487 @repository.fetch_changesets
454 @project.reload
488 @project.reload
455 assert_equal NUM_REV, @repository.changesets.count
489 assert_equal NUM_REV, @repository.changesets.count
456 ['7234cb2750b63f47bff735edc50a1c0a433c2518', '7234cb2750b'].each do |r|
490 ['7234cb2750b63f47bff735edc50a1c0a433c2518', '7234cb2750b'].each do |r|
457 assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518',
491 assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518',
458 @repository.find_changeset_by_name(r).revision
492 @repository.find_changeset_by_name(r).revision
459 end
493 end
460 end
494 end
461
495
462 def test_find_changeset_by_empty_name
496 def test_find_changeset_by_empty_name
463 assert_equal 0, @repository.changesets.count
497 assert_equal 0, @repository.changesets.count
464 @repository.fetch_changesets
498 @repository.fetch_changesets
465 @project.reload
499 @project.reload
466 assert_equal NUM_REV, @repository.changesets.count
500 assert_equal NUM_REV, @repository.changesets.count
467 ['', ' ', nil].each do |r|
501 ['', ' ', nil].each do |r|
468 assert_nil @repository.find_changeset_by_name(r)
502 assert_nil @repository.find_changeset_by_name(r)
469 end
503 end
470 end
504 end
471
505
472 def test_identifier
506 def test_identifier
473 assert_equal 0, @repository.changesets.count
507 assert_equal 0, @repository.changesets.count
474 @repository.fetch_changesets
508 @repository.fetch_changesets
475 @project.reload
509 @project.reload
476 assert_equal NUM_REV, @repository.changesets.count
510 assert_equal NUM_REV, @repository.changesets.count
477 c = @repository.changesets.find_by_revision(
511 c = @repository.changesets.find_by_revision(
478 '7234cb2750b63f47bff735edc50a1c0a433c2518')
512 '7234cb2750b63f47bff735edc50a1c0a433c2518')
479 assert_equal c.scmid, c.identifier
513 assert_equal c.scmid, c.identifier
480 end
514 end
481
515
482 def test_format_identifier
516 def test_format_identifier
483 assert_equal 0, @repository.changesets.count
517 assert_equal 0, @repository.changesets.count
484 @repository.fetch_changesets
518 @repository.fetch_changesets
485 @project.reload
519 @project.reload
486 assert_equal NUM_REV, @repository.changesets.count
520 assert_equal NUM_REV, @repository.changesets.count
487 c = @repository.changesets.find_by_revision(
521 c = @repository.changesets.find_by_revision(
488 '7234cb2750b63f47bff735edc50a1c0a433c2518')
522 '7234cb2750b63f47bff735edc50a1c0a433c2518')
489 assert_equal '7234cb27', c.format_identifier
523 assert_equal '7234cb27', c.format_identifier
490 end
524 end
491
525
492 def test_activities
526 def test_activities
493 c = Changeset.new(:repository => @repository,
527 c = Changeset.new(:repository => @repository,
494 :committed_on => Time.now,
528 :committed_on => Time.now,
495 :revision => 'abc7234cb2750b63f47bff735edc50a1c0a433c2',
529 :revision => 'abc7234cb2750b63f47bff735edc50a1c0a433c2',
496 :scmid => 'abc7234cb2750b63f47bff735edc50a1c0a433c2',
530 :scmid => 'abc7234cb2750b63f47bff735edc50a1c0a433c2',
497 :comments => 'test')
531 :comments => 'test')
498 assert c.event_title.include?('abc7234c:')
532 assert c.event_title.include?('abc7234c:')
499 assert_equal 'abc7234cb2750b63f47bff735edc50a1c0a433c2', c.event_url[:rev]
533 assert_equal 'abc7234cb2750b63f47bff735edc50a1c0a433c2', c.event_url[:rev]
500 end
534 end
501
535
502 def test_log_utf8
536 def test_log_utf8
503 assert_equal 0, @repository.changesets.count
537 assert_equal 0, @repository.changesets.count
504 @repository.fetch_changesets
538 @repository.fetch_changesets
505 @project.reload
539 @project.reload
506 assert_equal NUM_REV, @repository.changesets.count
540 assert_equal NUM_REV, @repository.changesets.count
507 str_felix_hex = FELIX_HEX.dup
541 str_felix_hex = FELIX_HEX.dup
508 if str_felix_hex.respond_to?(:force_encoding)
542 if str_felix_hex.respond_to?(:force_encoding)
509 str_felix_hex.force_encoding('UTF-8')
543 str_felix_hex.force_encoding('UTF-8')
510 end
544 end
511 c = @repository.changesets.find_by_revision(
545 c = @repository.changesets.find_by_revision(
512 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b')
546 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b')
513 assert_equal "#{str_felix_hex} <felix@fachschaften.org>", c.committer
547 assert_equal "#{str_felix_hex} <felix@fachschaften.org>", c.committer
514 end
548 end
515
549
516 def test_previous
550 def test_previous
517 assert_equal 0, @repository.changesets.count
551 assert_equal 0, @repository.changesets.count
518 @repository.fetch_changesets
552 @repository.fetch_changesets
519 @project.reload
553 @project.reload
520 assert_equal NUM_REV, @repository.changesets.count
554 assert_equal NUM_REV, @repository.changesets.count
521 %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1|
555 %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1|
522 changeset = @repository.find_changeset_by_name(r1)
556 changeset = @repository.find_changeset_by_name(r1)
523 %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2|
557 %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2|
524 assert_equal @repository.find_changeset_by_name(r2), changeset.previous
558 assert_equal @repository.find_changeset_by_name(r2), changeset.previous
525 end
559 end
526 end
560 end
527 end
561 end
528
562
529 def test_previous_nil
563 def test_previous_nil
530 assert_equal 0, @repository.changesets.count
564 assert_equal 0, @repository.changesets.count
531 @repository.fetch_changesets
565 @repository.fetch_changesets
532 @project.reload
566 @project.reload
533 assert_equal NUM_REV, @repository.changesets.count
567 assert_equal NUM_REV, @repository.changesets.count
534 %w|7234cb2750b63f47bff735edc50a1c0a433c2518 7234cb275|.each do |r1|
568 %w|7234cb2750b63f47bff735edc50a1c0a433c2518 7234cb275|.each do |r1|
535 changeset = @repository.find_changeset_by_name(r1)
569 changeset = @repository.find_changeset_by_name(r1)
536 assert_nil changeset.previous
570 assert_nil changeset.previous
537 end
571 end
538 end
572 end
539
573
540 def test_next
574 def test_next
541 assert_equal 0, @repository.changesets.count
575 assert_equal 0, @repository.changesets.count
542 @repository.fetch_changesets
576 @repository.fetch_changesets
543 @project.reload
577 @project.reload
544 assert_equal NUM_REV, @repository.changesets.count
578 assert_equal NUM_REV, @repository.changesets.count
545 %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2|
579 %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2|
546 changeset = @repository.find_changeset_by_name(r2)
580 changeset = @repository.find_changeset_by_name(r2)
547 %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1|
581 %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1|
548 assert_equal @repository.find_changeset_by_name(r1), changeset.next
582 assert_equal @repository.find_changeset_by_name(r1), changeset.next
549 end
583 end
550 end
584 end
551 end
585 end
552
586
553 def test_next_nil
587 def test_next_nil
554 assert_equal 0, @repository.changesets.count
588 assert_equal 0, @repository.changesets.count
555 @repository.fetch_changesets
589 @repository.fetch_changesets
556 @project.reload
590 @project.reload
557 assert_equal NUM_REV, @repository.changesets.count
591 assert_equal NUM_REV, @repository.changesets.count
558 %w|2a682156a3b6e77a8bf9cd4590e8db757f3c6c78 2a682156a3b6e77a|.each do |r1|
592 %w|2a682156a3b6e77a8bf9cd4590e8db757f3c6c78 2a682156a3b6e77a|.each do |r1|
559 changeset = @repository.find_changeset_by_name(r1)
593 changeset = @repository.find_changeset_by_name(r1)
560 assert_nil changeset.next
594 assert_nil changeset.next
561 end
595 end
562 end
596 end
563 else
597 else
564 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
598 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
565 def test_fake; assert true end
599 def test_fake; assert true end
566 end
600 end
567 end
601 end
General Comments 0
You need to be logged in to leave comments. Login now