##// END OF EJS Templates
Adds accessors for report_last_commit....
Jean-Philippe Lang -
r15503:46044b48a3d7
parent child
Show More
@@ -1,455 +1,433
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 require 'SVG/Graph/Bar'
19 19 require 'SVG/Graph/BarHorizontal'
20 20 require 'digest/sha1'
21 21 require 'redmine/scm/adapters'
22 22
23 23 class ChangesetNotFound < Exception; end
24 24 class InvalidRevisionParam < Exception; end
25 25
26 26 class RepositoriesController < ApplicationController
27 27 menu_item :repository
28 28 menu_item :settings, :only => [:new, :create, :edit, :update, :destroy, :committers]
29 29 default_search_scope :changesets
30 30
31 31 before_action :find_project_by_project_id, :only => [:new, :create]
32 32 before_action :find_repository, :only => [:edit, :update, :destroy, :committers]
33 33 before_action :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
34 34 before_action :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue]
35 35 before_action :authorize
36 36 accept_rss_auth :revisions
37 37
38 38 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
39 39
40 40 def new
41 41 scm = params[:repository_scm] || (Redmine::Scm::Base.all & Setting.enabled_scm).first
42 42 @repository = Repository.factory(scm)
43 43 @repository.is_default = @project.repository.nil?
44 44 @repository.project = @project
45 45 end
46 46
47 47 def create
48 attrs = pickup_extra_info
49 48 @repository = Repository.factory(params[:repository_scm])
50 49 @repository.safe_attributes = params[:repository]
51 if attrs[:attrs_extra].keys.any?
52 @repository.merge_extra_info(attrs[:attrs_extra])
53 end
54 50 @repository.project = @project
55 51 if request.post? && @repository.save
56 52 redirect_to settings_project_path(@project, :tab => 'repositories')
57 53 else
58 54 render :action => 'new'
59 55 end
60 56 end
61 57
62 58 def edit
63 59 end
64 60
65 61 def update
66 attrs = pickup_extra_info
67 @repository.safe_attributes = attrs[:attrs]
68 if attrs[:attrs_extra].keys.any?
69 @repository.merge_extra_info(attrs[:attrs_extra])
70 end
62 @repository.safe_attributes = params[:repository]
71 63 @repository.project = @project
72 64 if @repository.save
73 65 redirect_to settings_project_path(@project, :tab => 'repositories')
74 66 else
75 67 render :action => 'edit'
76 68 end
77 69 end
78 70
79 def pickup_extra_info
80 p = {}
81 p_extra = {}
82 params[:repository].each do |k, v|
83 if k =~ /^extra_/
84 p_extra[k] = v
85 else
86 p[k] = v
87 end
88 end
89 {:attrs => p, :attrs_extra => p_extra}
90 end
91 private :pickup_extra_info
92
93 71 def committers
94 72 @committers = @repository.committers
95 73 @users = @project.users.to_a
96 74 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
97 75 @users += User.where(:id => additional_user_ids).to_a unless additional_user_ids.empty?
98 76 @users.compact!
99 77 @users.sort!
100 78 if request.post? && params[:committers].is_a?(Hash)
101 79 # Build a hash with repository usernames as keys and corresponding user ids as values
102 80 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
103 81 flash[:notice] = l(:notice_successful_update)
104 82 redirect_to settings_project_path(@project, :tab => 'repositories')
105 83 end
106 84 end
107 85
108 86 def destroy
109 87 @repository.destroy if request.delete?
110 88 redirect_to settings_project_path(@project, :tab => 'repositories')
111 89 end
112 90
113 91 def show
114 92 @repository.fetch_changesets if @project.active? && Setting.autofetch_changesets? && @path.empty?
115 93
116 94 @entries = @repository.entries(@path, @rev)
117 95 @changeset = @repository.find_changeset_by_name(@rev)
118 96 if request.xhr?
119 97 @entries ? render(:partial => 'dir_list_content') : head(200)
120 98 else
121 99 (show_error_not_found; return) unless @entries
122 100 @changesets = @repository.latest_changesets(@path, @rev)
123 101 @properties = @repository.properties(@path, @rev)
124 102 @repositories = @project.repositories
125 103 render :action => 'show'
126 104 end
127 105 end
128 106
129 107 alias_method :browse, :show
130 108
131 109 def changes
132 110 @entry = @repository.entry(@path, @rev)
133 111 (show_error_not_found; return) unless @entry
134 112 @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
135 113 @properties = @repository.properties(@path, @rev)
136 114 @changeset = @repository.find_changeset_by_name(@rev)
137 115 end
138 116
139 117 def revisions
140 118 @changeset_count = @repository.changesets.count
141 119 @changeset_pages = Paginator.new @changeset_count,
142 120 per_page_option,
143 121 params['page']
144 122 @changesets = @repository.changesets.
145 123 limit(@changeset_pages.per_page).
146 124 offset(@changeset_pages.offset).
147 125 includes(:user, :repository, :parents).
148 126 to_a
149 127
150 128 respond_to do |format|
151 129 format.html { render :layout => false if request.xhr? }
152 130 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
153 131 end
154 132 end
155 133
156 134 def raw
157 135 entry_and_raw(true)
158 136 end
159 137
160 138 def entry
161 139 entry_and_raw(false)
162 140 end
163 141
164 142 def entry_and_raw(is_raw)
165 143 @entry = @repository.entry(@path, @rev)
166 144 (show_error_not_found; return) unless @entry
167 145
168 146 # If the entry is a dir, show the browser
169 147 (show; return) if @entry.is_dir?
170 148
171 149 if is_raw
172 150 # Force the download
173 151 send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) }
174 152 send_type = Redmine::MimeType.of(@path)
175 153 send_opt[:type] = send_type.to_s if send_type
176 154 send_opt[:disposition] = disposition(@path)
177 155 send_data @repository.cat(@path, @rev), send_opt
178 156 else
179 157 if !@entry.size || @entry.size <= Setting.file_max_size_displayed.to_i.kilobyte
180 158 content = @repository.cat(@path, @rev)
181 159 (show_error_not_found; return) unless content
182 160
183 161 if content.size <= Setting.file_max_size_displayed.to_i.kilobyte &&
184 162 is_entry_text_data?(content, @path)
185 163 # TODO: UTF-16
186 164 # Prevent empty lines when displaying a file with Windows style eol
187 165 # Is this needed? AttachmentsController simply reads file.
188 166 @content = content.gsub("\r\n", "\n")
189 167 end
190 168 end
191 169 @changeset = @repository.find_changeset_by_name(@rev)
192 170 end
193 171 end
194 172 private :entry_and_raw
195 173
196 174 def is_entry_text_data?(ent, path)
197 175 # UTF-16 contains "\x00".
198 176 # It is very strict that file contains less than 30% of ascii symbols
199 177 # in non Western Europe.
200 178 return true if Redmine::MimeType.is_type?('text', path)
201 179 # Ruby 1.8.6 has a bug of integer divisions.
202 180 # http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F
203 181 return false if ent.is_binary_data?
204 182 true
205 183 end
206 184 private :is_entry_text_data?
207 185
208 186 def annotate
209 187 @entry = @repository.entry(@path, @rev)
210 188 (show_error_not_found; return) unless @entry
211 189
212 190 @annotate = @repository.scm.annotate(@path, @rev)
213 191 if @annotate.nil? || @annotate.empty?
214 192 @annotate = nil
215 193 @error_message = l(:error_scm_annotate)
216 194 else
217 195 ann_buf_size = 0
218 196 @annotate.lines.each do |buf|
219 197 ann_buf_size += buf.size
220 198 end
221 199 if ann_buf_size > Setting.file_max_size_displayed.to_i.kilobyte
222 200 @annotate = nil
223 201 @error_message = l(:error_scm_annotate_big_text_file)
224 202 end
225 203 end
226 204 @changeset = @repository.find_changeset_by_name(@rev)
227 205 end
228 206
229 207 def revision
230 208 respond_to do |format|
231 209 format.html
232 210 format.js {render :layout => false}
233 211 end
234 212 end
235 213
236 214 # Adds a related issue to a changeset
237 215 # POST /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues
238 216 def add_related_issue
239 217 issue_id = params[:issue_id].to_s.sub(/^#/,'')
240 218 @issue = @changeset.find_referenced_issue_by_id(issue_id)
241 219 if @issue && (!@issue.visible? || @changeset.issues.include?(@issue))
242 220 @issue = nil
243 221 end
244 222
245 223 if @issue
246 224 @changeset.issues << @issue
247 225 end
248 226 end
249 227
250 228 # Removes a related issue from a changeset
251 229 # DELETE /projects/:project_id/repository/(:repository_id/)revisions/:rev/issues/:issue_id
252 230 def remove_related_issue
253 231 @issue = Issue.visible.find_by_id(params[:issue_id])
254 232 if @issue
255 233 @changeset.issues.delete(@issue)
256 234 end
257 235 end
258 236
259 237 def diff
260 238 if params[:format] == 'diff'
261 239 @diff = @repository.diff(@path, @rev, @rev_to)
262 240 (show_error_not_found; return) unless @diff
263 241 filename = "changeset_r#{@rev}"
264 242 filename << "_r#{@rev_to}" if @rev_to
265 243 send_data @diff.join, :filename => "#{filename}.diff",
266 244 :type => 'text/x-patch',
267 245 :disposition => 'attachment'
268 246 else
269 247 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
270 248 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
271 249
272 250 # Save diff type as user preference
273 251 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
274 252 User.current.pref[:diff_type] = @diff_type
275 253 User.current.preference.save
276 254 end
277 255 @cache_key = "repositories/diff/#{@repository.id}/" +
278 256 Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}-#{current_language}")
279 257 unless read_fragment(@cache_key)
280 258 @diff = @repository.diff(@path, @rev, @rev_to)
281 259 show_error_not_found unless @diff
282 260 end
283 261
284 262 @changeset = @repository.find_changeset_by_name(@rev)
285 263 @changeset_to = @rev_to ? @repository.find_changeset_by_name(@rev_to) : nil
286 264 @diff_format_revisions = @repository.diff_format_revisions(@changeset, @changeset_to)
287 265 end
288 266 end
289 267
290 268 def stats
291 269 end
292 270
293 271 def graph
294 272 data = nil
295 273 case params[:graph]
296 274 when "commits_per_month"
297 275 data = graph_commits_per_month(@repository)
298 276 when "commits_per_author"
299 277 data = graph_commits_per_author(@repository)
300 278 end
301 279 if data
302 280 headers["Content-Type"] = "image/svg+xml"
303 281 send_data(data, :type => "image/svg+xml", :disposition => "inline")
304 282 else
305 283 render_404
306 284 end
307 285 end
308 286
309 287 private
310 288
311 289 def find_repository
312 290 @repository = Repository.find(params[:id])
313 291 @project = @repository.project
314 292 rescue ActiveRecord::RecordNotFound
315 293 render_404
316 294 end
317 295
318 296 REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
319 297
320 298 def find_project_repository
321 299 @project = Project.find(params[:id])
322 300 if params[:repository_id].present?
323 301 @repository = @project.repositories.find_by_identifier_param(params[:repository_id])
324 302 else
325 303 @repository = @project.repository
326 304 end
327 305 (render_404; return false) unless @repository
328 306 @path = params[:path].is_a?(Array) ? params[:path].join('/') : params[:path].to_s
329 307 @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
330 308 @rev_to = params[:rev_to]
331 309
332 310 unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE)
333 311 if @repository.branches.blank?
334 312 raise InvalidRevisionParam
335 313 end
336 314 end
337 315 rescue ActiveRecord::RecordNotFound
338 316 render_404
339 317 rescue InvalidRevisionParam
340 318 show_error_not_found
341 319 end
342 320
343 321 def find_changeset
344 322 if @rev.present?
345 323 @changeset = @repository.find_changeset_by_name(@rev)
346 324 end
347 325 show_error_not_found unless @changeset
348 326 end
349 327
350 328 def show_error_not_found
351 329 render_error :message => l(:error_scm_not_found), :status => 404
352 330 end
353 331
354 332 # Handler for Redmine::Scm::Adapters::CommandFailed exception
355 333 def show_error_command_failed(exception)
356 334 render_error l(:error_scm_command_failed, exception.message)
357 335 end
358 336
359 337 def graph_commits_per_month(repository)
360 338 @date_to = User.current.today
361 339 @date_from = @date_to << 11
362 340 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
363 341 commits_by_day = Changeset.
364 342 where("repository_id = ? AND commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
365 343 group(:commit_date).
366 344 count
367 345 commits_by_month = [0] * 12
368 346 commits_by_day.each {|c| commits_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
369 347
370 348 changes_by_day = Change.
371 349 joins(:changeset).
372 350 where("#{Changeset.table_name}.repository_id = ? AND #{Changeset.table_name}.commit_date BETWEEN ? AND ?", repository.id, @date_from, @date_to).
373 351 group(:commit_date).
374 352 count
375 353 changes_by_month = [0] * 12
376 354 changes_by_day.each {|c| changes_by_month[(@date_to.month - c.first.to_date.month) % 12] += c.last }
377 355
378 356 fields = []
379 357 today = User.current.today
380 358 12.times {|m| fields << month_name(((today.month - 1 - m) % 12) + 1)}
381 359
382 360 graph = SVG::Graph::Bar.new(
383 361 :height => 300,
384 362 :width => 800,
385 363 :fields => fields.reverse,
386 364 :stack => :side,
387 365 :scale_integers => true,
388 366 :step_x_labels => 2,
389 367 :show_data_values => false,
390 368 :graph_title => l(:label_commits_per_month),
391 369 :show_graph_title => true
392 370 )
393 371
394 372 graph.add_data(
395 373 :data => commits_by_month[0..11].reverse,
396 374 :title => l(:label_revision_plural)
397 375 )
398 376
399 377 graph.add_data(
400 378 :data => changes_by_month[0..11].reverse,
401 379 :title => l(:label_change_plural)
402 380 )
403 381
404 382 graph.burn
405 383 end
406 384
407 385 def graph_commits_per_author(repository)
408 386 #data
409 387 stats = repository.stats_by_author
410 388 fields, commits_data, changes_data = [], [], []
411 389 stats.each do |name, hsh|
412 390 fields << name
413 391 commits_data << hsh[:commits_count]
414 392 changes_data << hsh[:changes_count]
415 393 end
416 394
417 395 #expand to 10 values if needed
418 396 fields = fields + [""]*(10 - fields.length) if fields.length<10
419 397 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
420 398 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
421 399
422 400 # Remove email address in usernames
423 401 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
424 402
425 403 #prepare graph
426 404 graph = SVG::Graph::BarHorizontal.new(
427 405 :height => 30 * commits_data.length,
428 406 :width => 800,
429 407 :fields => fields,
430 408 :stack => :side,
431 409 :scale_integers => true,
432 410 :show_data_values => false,
433 411 :rotate_y_labels => false,
434 412 :graph_title => l(:label_commits_per_author),
435 413 :show_graph_title => true
436 414 )
437 415 graph.add_data(
438 416 :data => commits_data,
439 417 :title => l(:label_revision_plural)
440 418 )
441 419 graph.add_data(
442 420 :data => changes_data,
443 421 :title => l(:label_change_plural)
444 422 )
445 423 graph.burn
446 424 end
447 425
448 426 def disposition(path)
449 427 if Redmine::MimeType.is_type?('image', @path) || Redmine::MimeType.of(@path) == "application/pdf"
450 428 'inline'
451 429 else
452 430 'attachment'
453 431 end
454 432 end
455 433 end
@@ -1,310 +1,310
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2016 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 module RepositoriesHelper
21 21 def format_revision(revision)
22 22 if revision.respond_to? :format_identifier
23 23 revision.format_identifier
24 24 else
25 25 revision.to_s
26 26 end
27 27 end
28 28
29 29 def truncate_at_line_break(text, length = 255)
30 30 if text
31 31 text.gsub(%r{^(.{#{length}}[^\n]*)\n.+$}m, '\\1...')
32 32 end
33 33 end
34 34
35 35 def render_properties(properties)
36 36 unless properties.nil? || properties.empty?
37 37 content = ''
38 38 properties.keys.sort.each do |property|
39 39 content << content_tag('li', "<b>#{h property}</b>: <span>#{h properties[property]}</span>".html_safe)
40 40 end
41 41 content_tag('ul', content.html_safe, :class => 'properties')
42 42 end
43 43 end
44 44
45 45 def render_changeset_changes
46 46 changes = @changeset.filechanges.limit(1000).reorder('path').collect do |change|
47 47 case change.action
48 48 when 'A'
49 49 # Detects moved/copied files
50 50 if !change.from_path.blank?
51 51 change.action =
52 52 @changeset.filechanges.detect {|c| c.action == 'D' && c.path == change.from_path} ? 'R' : 'C'
53 53 end
54 54 change
55 55 when 'D'
56 56 @changeset.filechanges.detect {|c| c.from_path == change.path} ? nil : change
57 57 else
58 58 change
59 59 end
60 60 end.compact
61 61
62 62 tree = { }
63 63 changes.each do |change|
64 64 p = tree
65 65 dirs = change.path.to_s.split('/').select {|d| !d.blank?}
66 66 path = ''
67 67 dirs.each do |dir|
68 68 path += '/' + dir
69 69 p[:s] ||= {}
70 70 p = p[:s]
71 71 p[path] ||= {}
72 72 p = p[path]
73 73 end
74 74 p[:c] = change
75 75 end
76 76 render_changes_tree(tree[:s])
77 77 end
78 78
79 79 def render_changes_tree(tree)
80 80 return '' if tree.nil?
81 81 output = ''
82 82 output << '<ul>'
83 83 tree.keys.sort.each do |file|
84 84 style = 'change'
85 85 text = File.basename(h(file))
86 86 if s = tree[file][:s]
87 87 style << ' folder'
88 88 path_param = to_path_param(@repository.relative_path(file))
89 89 text = link_to(h(text), :controller => 'repositories',
90 90 :action => 'show',
91 91 :id => @project,
92 92 :repository_id => @repository.identifier_param,
93 93 :path => path_param,
94 94 :rev => @changeset.identifier)
95 95 output << "<li class='#{style}'>#{text}"
96 96 output << render_changes_tree(s)
97 97 output << "</li>"
98 98 elsif c = tree[file][:c]
99 99 style << " change-#{c.action}"
100 100 path_param = to_path_param(@repository.relative_path(c.path))
101 101 text = link_to(h(text), :controller => 'repositories',
102 102 :action => 'entry',
103 103 :id => @project,
104 104 :repository_id => @repository.identifier_param,
105 105 :path => path_param,
106 106 :rev => @changeset.identifier) unless c.action == 'D'
107 107 text << " - #{h(c.revision)}" unless c.revision.blank?
108 108 text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories',
109 109 :action => 'diff',
110 110 :id => @project,
111 111 :repository_id => @repository.identifier_param,
112 112 :path => path_param,
113 113 :rev => @changeset.identifier) + ') '.html_safe if c.action == 'M'
114 114 text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank?
115 115 output << "<li class='#{style}'>#{text}</li>"
116 116 end
117 117 end
118 118 output << '</ul>'
119 119 output.html_safe
120 120 end
121 121
122 122 def repository_field_tags(form, repository)
123 123 method = repository.class.name.demodulize.underscore + "_field_tags"
124 124 if repository.is_a?(Repository) &&
125 125 respond_to?(method) && method != 'repository_field_tags'
126 126 send(method, form, repository)
127 127 end
128 128 end
129 129
130 130 def scm_select_tag(repository)
131 131 scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
132 132 Redmine::Scm::Base.all.each do |scm|
133 133 if Setting.enabled_scm.include?(scm) ||
134 134 (repository && repository.class.name.demodulize == scm)
135 135 scm_options << ["Repository::#{scm}".constantize.scm_name, scm]
136 136 end
137 137 end
138 138 select_tag('repository_scm',
139 139 options_for_select(scm_options, repository.class.name.demodulize),
140 140 :disabled => (repository && !repository.new_record?),
141 141 :data => {:remote => true, :method => 'get'})
142 142 end
143 143
144 144 def with_leading_slash(path)
145 145 path.to_s.starts_with?('/') ? path : "/#{path}"
146 146 end
147 147
148 148 def subversion_field_tags(form, repository)
149 149 content_tag('p', form.text_field(:url, :size => 60, :required => true,
150 150 :disabled => !repository.safe_attribute?('url')) +
151 151 scm_path_info_tag(repository)) +
152 152 content_tag('p', form.text_field(:login, :size => 30)) +
153 153 content_tag('p', form.password_field(
154 154 :password, :size => 30, :name => 'ignore',
155 155 :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
156 156 :onfocus => "this.value=''; this.name='repository[password]';",
157 157 :onchange => "this.name='repository[password]';"))
158 158 end
159 159
160 160 def darcs_field_tags(form, repository)
161 161 content_tag('p', form.text_field(
162 162 :url, :label => l(:field_path_to_repository),
163 163 :size => 60, :required => true,
164 164 :disabled => !repository.safe_attribute?('url')) +
165 165 scm_path_info_tag(repository)) +
166 166 scm_log_encoding_tag(form, repository)
167 167 end
168 168
169 169 def mercurial_field_tags(form, repository)
170 170 content_tag('p', form.text_field(
171 171 :url, :label => l(:field_path_to_repository),
172 172 :size => 60, :required => true,
173 173 :disabled => !repository.safe_attribute?('url')
174 174 ) +
175 175 scm_path_info_tag(repository)) +
176 176 scm_path_encoding_tag(form, repository)
177 177 end
178 178
179 179 def git_field_tags(form, repository)
180 180 content_tag('p', form.text_field(
181 181 :url, :label => l(:field_path_to_repository),
182 182 :size => 60, :required => true,
183 183 :disabled => !repository.safe_attribute?('url')
184 184 ) +
185 185 scm_path_info_tag(repository)) +
186 186 scm_path_encoding_tag(form, repository) +
187 187 content_tag('p', form.check_box(
188 :extra_report_last_commit,
188 :report_last_commit,
189 189 :label => l(:label_git_report_last_commit)
190 190 ))
191 191 end
192 192
193 193 def cvs_field_tags(form, repository)
194 194 content_tag('p', form.text_field(
195 195 :root_url,
196 196 :label => l(:field_cvsroot),
197 197 :size => 60, :required => true,
198 198 :disabled => !repository.safe_attribute?('root_url')) +
199 199 scm_path_info_tag(repository)) +
200 200 content_tag('p', form.text_field(
201 201 :url,
202 202 :label => l(:field_cvs_module),
203 203 :size => 30, :required => true,
204 204 :disabled => !repository.safe_attribute?('url'))) +
205 205 scm_log_encoding_tag(form, repository) +
206 206 scm_path_encoding_tag(form, repository)
207 207 end
208 208
209 209 def bazaar_field_tags(form, repository)
210 210 content_tag('p', form.text_field(
211 211 :url, :label => l(:field_path_to_repository),
212 212 :size => 60, :required => true,
213 213 :disabled => !repository.safe_attribute?('url')) +
214 214 scm_path_info_tag(repository)) +
215 215 scm_log_encoding_tag(form, repository)
216 216 end
217 217
218 218 def filesystem_field_tags(form, repository)
219 219 content_tag('p', form.text_field(
220 220 :url, :label => l(:field_root_directory),
221 221 :size => 60, :required => true,
222 222 :disabled => !repository.safe_attribute?('url')) +
223 223 scm_path_info_tag(repository)) +
224 224 scm_path_encoding_tag(form, repository)
225 225 end
226 226
227 227 def scm_path_info_tag(repository)
228 228 text = scm_path_info(repository)
229 229 if text.present?
230 230 content_tag('em', text, :class => 'info')
231 231 else
232 232 ''
233 233 end
234 234 end
235 235
236 236 def scm_path_info(repository)
237 237 scm_name = repository.scm_name.to_s.downcase
238 238
239 239 info_from_config = Redmine::Configuration["scm_#{scm_name}_path_info"].presence
240 240 return info_from_config.html_safe if info_from_config
241 241
242 242 l("text_#{scm_name}_repository_note", :default => '')
243 243 end
244 244
245 245 def scm_log_encoding_tag(form, repository)
246 246 select = form.select(
247 247 :log_encoding,
248 248 [nil] + Setting::ENCODINGS,
249 249 :label => l(:field_commit_logs_encoding),
250 250 :required => true
251 251 )
252 252 content_tag('p', select)
253 253 end
254 254
255 255 def scm_path_encoding_tag(form, repository)
256 256 select = form.select(
257 257 :path_encoding,
258 258 [nil] + Setting::ENCODINGS,
259 259 :label => l(:field_scm_path_encoding)
260 260 )
261 261 content_tag('p', select + content_tag('em', l(:text_scm_path_encoding_note), :class => 'info'))
262 262 end
263 263
264 264 def index_commits(commits, heads)
265 265 return nil if commits.nil? or commits.first.parents.nil?
266 266 refs_map = {}
267 267 heads.each do |head|
268 268 refs_map[head.scmid] ||= []
269 269 refs_map[head.scmid] << head
270 270 end
271 271 commits_by_scmid = {}
272 272 commits.reverse.each_with_index do |commit, commit_index|
273 273 commits_by_scmid[commit.scmid] = {
274 274 :parent_scmids => commit.parents.collect { |parent| parent.scmid },
275 275 :rdmid => commit_index,
276 276 :refs => refs_map.include?(commit.scmid) ? refs_map[commit.scmid].join(" ") : nil,
277 277 :scmid => commit.scmid,
278 278 :href => block_given? ? yield(commit.scmid) : commit.scmid
279 279 }
280 280 end
281 281 heads.sort! { |head1, head2| head1.to_s <=> head2.to_s }
282 282 space = nil
283 283 heads.each do |head|
284 284 if commits_by_scmid.include? head.scmid
285 285 space = index_head((space || -1) + 1, head, commits_by_scmid)
286 286 end
287 287 end
288 288 # when no head matched anything use first commit
289 289 space ||= index_head(0, commits.first, commits_by_scmid)
290 290 return commits_by_scmid, space
291 291 end
292 292
293 293 def index_head(space, commit, commits_by_scmid)
294 294 stack = [[space, commits_by_scmid[commit.scmid]]]
295 295 max_space = space
296 296 until stack.empty?
297 297 space, commit = stack.pop
298 298 commit[:space] = space if commit[:space].nil?
299 299 space -= 1
300 300 commit[:parent_scmids].each_with_index do |parent_scmid, parent_index|
301 301 parent_commit = commits_by_scmid[parent_scmid]
302 302 if parent_commit and parent_commit[:space].nil?
303 303 stack.unshift [space += 1, parent_commit]
304 304 end
305 305 end
306 306 max_space = space if max_space < space
307 307 end
308 308 max_space
309 309 end
310 310 end
@@ -1,263 +1,265
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 # Copyright (C) 2007 Patrick Aljord patcito@Ε‹mail.com
4 4 #
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; either version 2
8 8 # of the License, or (at your option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 18
19 19 require 'redmine/scm/adapters/git_adapter'
20 20
21 21 class Repository::Git < Repository
22 22 attr_protected :root_url
23 23 validates_presence_of :url
24 24
25 safe_attributes 'report_last_commit'
26
25 27 def self.human_attribute_name(attribute_key_name, *args)
26 28 attr_name = attribute_key_name.to_s
27 29 if attr_name == "url"
28 30 attr_name = "path_to_repository"
29 31 end
30 32 super(attr_name, *args)
31 33 end
32 34
33 35 def self.scm_adapter_class
34 36 Redmine::Scm::Adapters::GitAdapter
35 37 end
36 38
37 39 def self.scm_name
38 40 'Git'
39 41 end
40 42
41 43 def report_last_commit
42 extra_report_last_commit
43 end
44
45 def extra_report_last_commit
46 44 return false if extra_info.nil?
47 45 v = extra_info["extra_report_last_commit"]
48 46 return false if v.nil?
49 47 v.to_s != '0'
50 48 end
49
50 def report_last_commit=(arg)
51 merge_extra_info "extra_report_last_commit" => arg
52 end
51 53
52 54 def supports_directory_revisions?
53 55 true
54 56 end
55 57
56 58 def supports_revision_graph?
57 59 true
58 60 end
59 61
60 62 def repo_log_encoding
61 63 'UTF-8'
62 64 end
63 65
64 66 # Returns the identifier for the given git changeset
65 67 def self.changeset_identifier(changeset)
66 68 changeset.scmid
67 69 end
68 70
69 71 # Returns the readable identifier for the given git changeset
70 72 def self.format_changeset_identifier(changeset)
71 73 changeset.revision[0, 8]
72 74 end
73 75
74 76 def branches
75 77 scm.branches
76 78 end
77 79
78 80 def tags
79 81 scm.tags
80 82 end
81 83
82 84 def default_branch
83 85 scm.default_branch
84 86 rescue Exception => e
85 87 logger.error "git: error during get default branch: #{e.message}"
86 88 nil
87 89 end
88 90
89 91 def find_changeset_by_name(name)
90 92 if name.present?
91 93 changesets.where(:revision => name.to_s).first ||
92 94 changesets.where('scmid LIKE ?', "#{name}%").first
93 95 end
94 96 end
95 97
96 98 def scm_entries(path=nil, identifier=nil)
97 scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
99 scm.entries(path, identifier, :report_last_commit => report_last_commit)
98 100 end
99 101 protected :scm_entries
100 102
101 103 # With SCMs that have a sequential commit numbering,
102 104 # such as Subversion and Mercurial,
103 105 # Redmine is able to be clever and only fetch changesets
104 106 # going forward from the most recent one it knows about.
105 107 #
106 108 # However, Git does not have a sequential commit numbering.
107 109 #
108 110 # In order to fetch only new adding revisions,
109 111 # Redmine needs to save "heads".
110 112 #
111 113 # In Git and Mercurial, revisions are not in date order.
112 114 # Redmine Mercurial fixed issues.
113 115 # * Redmine Takes Too Long On Large Mercurial Repository
114 116 # http://www.redmine.org/issues/3449
115 117 # * Sorting for changesets might go wrong on Mercurial repos
116 118 # http://www.redmine.org/issues/3567
117 119 #
118 120 # Database revision column is text, so Redmine can not sort by revision.
119 121 # Mercurial has revision number, and revision number guarantees revision order.
120 122 # Redmine Mercurial model stored revisions ordered by database id to database.
121 123 # So, Redmine Mercurial model can use correct ordering revisions.
122 124 #
123 125 # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
124 126 # to get limited revisions from old to new.
125 127 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
126 128 #
127 129 # The repository can still be fully reloaded by calling #clear_changesets
128 130 # before fetching changesets (eg. for offline resync)
129 131 def fetch_changesets
130 132 scm_brs = branches
131 133 return if scm_brs.nil? || scm_brs.empty?
132 134
133 135 h1 = extra_info || {}
134 136 h = h1.dup
135 137 repo_heads = scm_brs.map{ |br| br.scmid }
136 138 h["heads"] ||= []
137 139 prev_db_heads = h["heads"].dup
138 140 if prev_db_heads.empty?
139 141 prev_db_heads += heads_from_branches_hash
140 142 end
141 143 return if prev_db_heads.sort == repo_heads.sort
142 144
143 145 h["db_consistent"] ||= {}
144 146 if changesets.count == 0
145 147 h["db_consistent"]["ordering"] = 1
146 148 merge_extra_info(h)
147 149 self.save
148 150 elsif ! h["db_consistent"].has_key?("ordering")
149 151 h["db_consistent"]["ordering"] = 0
150 152 merge_extra_info(h)
151 153 self.save
152 154 end
153 155 save_revisions(prev_db_heads, repo_heads)
154 156 end
155 157
156 158 def save_revisions(prev_db_heads, repo_heads)
157 159 h = {}
158 160 opts = {}
159 161 opts[:reverse] = true
160 162 opts[:excludes] = prev_db_heads
161 163 opts[:includes] = repo_heads
162 164
163 165 revisions = scm.revisions('', nil, nil, opts)
164 166 return if revisions.blank?
165 167
166 168 # Make the search for existing revisions in the database in a more sufficient manner
167 169 #
168 170 # Git branch is the reference to the specific revision.
169 171 # Git can *delete* remote branch and *re-push* branch.
170 172 #
171 173 # $ git push remote :branch
172 174 # $ git push remote branch
173 175 #
174 176 # After deleting branch, revisions remain in repository until "git gc".
175 177 # On git 1.7.2.3, default pruning date is 2 weeks.
176 178 # So, "git log --not deleted_branch_head_revision" return code is 0.
177 179 #
178 180 # After re-pushing branch, "git log" returns revisions which are saved in database.
179 181 # So, Redmine needs to scan revisions and database every time.
180 182 #
181 183 # This is replacing the one-after-one queries.
182 184 # Find all revisions, that are in the database, and then remove them
183 185 # from the revision array.
184 186 # Then later we won't need any conditions for db existence.
185 187 # Query for several revisions at once, and remove them
186 188 # from the revisions array, if they are there.
187 189 # Do this in chunks, to avoid eventual memory problems
188 190 # (in case of tens of thousands of commits).
189 191 # If there are no revisions (because the original code's algorithm filtered them),
190 192 # then this part will be stepped over.
191 193 # We make queries, just if there is any revision.
192 194 limit = 100
193 195 offset = 0
194 196 revisions_copy = revisions.clone # revisions will change
195 197 while offset < revisions_copy.size
196 198 scmids = revisions_copy.slice(offset, limit).map{|x| x.scmid}
197 199 recent_changesets_slice = changesets.where(:scmid => scmids)
198 200 # Subtract revisions that redmine already knows about
199 201 recent_revisions = recent_changesets_slice.map{|c| c.scmid}
200 202 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
201 203 offset += limit
202 204 end
203 205 revisions.each do |rev|
204 206 transaction do
205 207 # There is no search in the db for this revision, because above we ensured,
206 208 # that it's not in the db.
207 209 save_revision(rev)
208 210 end
209 211 end
210 212 h["heads"] = repo_heads.dup
211 213 merge_extra_info(h)
212 214 save(:validate => false)
213 215 end
214 216 private :save_revisions
215 217
216 218 def save_revision(rev)
217 219 parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
218 220 changeset = Changeset.create(
219 221 :repository => self,
220 222 :revision => rev.identifier,
221 223 :scmid => rev.scmid,
222 224 :committer => rev.author,
223 225 :committed_on => rev.time,
224 226 :comments => rev.message,
225 227 :parents => parents
226 228 )
227 229 unless changeset.new_record?
228 230 rev.paths.each { |change| changeset.create_change(change) }
229 231 end
230 232 changeset
231 233 end
232 234 private :save_revision
233 235
234 236 def heads_from_branches_hash
235 237 h1 = extra_info || {}
236 238 h = h1.dup
237 239 h["branches"] ||= {}
238 240 h['branches'].map{|br, hs| hs['last_scmid']}
239 241 end
240 242
241 243 def latest_changesets(path,rev,limit=10)
242 244 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
243 245 return [] if revisions.nil? || revisions.empty?
244 246 changesets.where(:scmid => revisions.map {|c| c.scmid}).to_a
245 247 end
246 248
247 249 def clear_extra_info_of_changesets
248 250 return if extra_info.nil?
249 251 v = extra_info["extra_report_last_commit"]
250 252 write_attribute(:extra_info, nil)
251 253 h = {}
252 254 h["extra_report_last_commit"] = v
253 255 merge_extra_info(h)
254 256 save(:validate => false)
255 257 end
256 258 private :clear_extra_info_of_changesets
257 259
258 260 def clear_changesets
259 261 super
260 262 clear_extra_info_of_changesets
261 263 end
262 264 private :clear_changesets
263 265 end
@@ -1,292 +1,302
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class RepositoriesControllerTest < Redmine::ControllerTest
21 21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles, :enabled_modules,
22 22 :repositories, :issues, :issue_statuses, :changesets, :changes,
23 23 :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
24 24
25 25 def setup
26 26 User.current = nil
27 27 end
28 28
29 29 def test_new
30 30 @request.session[:user_id] = 1
31 31 get :new, :project_id => 'subproject1'
32 32 assert_response :success
33 33 assert_select 'select[name=?]', 'repository_scm' do
34 34 assert_select 'option[value=?][selected=selected]', 'Subversion'
35 35 end
36 36 assert_select 'input[name=?]:not([disabled])', 'repository[url]'
37 37 end
38 38
39 39 def test_new_should_propose_enabled_scm_only
40 40 @request.session[:user_id] = 1
41 41 with_settings :enabled_scm => ['Mercurial', 'Git'] do
42 42 get :new, :project_id => 'subproject1'
43 43 end
44 44 assert_response :success
45 45
46 46 assert_select 'select[name=repository_scm]' do
47 47 assert_select 'option', 3
48 48 assert_select 'option[value=Mercurial][selected=selected]'
49 49 assert_select 'option[value=Git]:not([selected])'
50 50 end
51 51 end
52
53 def test_get_new_with_type
54 @request.session[:user_id] = 1
55 get :new, :project_id => 'subproject1', :repository_scm => 'Git'
56 assert_response :success
57
58 assert_select 'select[name=?]', 'repository_scm' do
59 assert_select 'option[value=?][selected=selected]', 'Git'
60 end
61 end
52 62
53 63 def test_create
54 64 @request.session[:user_id] = 1
55 65 assert_difference 'Repository.count' do
56 66 post :create, :project_id => 'subproject1',
57 67 :repository_scm => 'Subversion',
58 68 :repository => {:url => 'file:///test', :is_default => '1', :identifier => ''}
59 69 end
60 70 assert_response 302
61 71 repository = Repository.order('id DESC').first
62 72 assert_kind_of Repository::Subversion, repository
63 73 assert_equal 'file:///test', repository.url
64 74 end
65 75
66 76 def test_create_with_failure
67 77 @request.session[:user_id] = 1
68 78 assert_no_difference 'Repository.count' do
69 79 post :create, :project_id => 'subproject1',
70 80 :repository_scm => 'Subversion',
71 81 :repository => {:url => 'invalid'}
72 82 end
73 83 assert_response :success
74 84 assert_select_error /URL is invalid/
75 85 assert_select 'select[name=?]', 'repository_scm' do
76 86 assert_select 'option[value=?][selected=selected]', 'Subversion'
77 87 end
78 88 end
79 89
80 90 def test_edit
81 91 @request.session[:user_id] = 1
82 92 get :edit, :id => 11
83 93 assert_response :success
84 94 assert_select 'input[name=?][value=?][disabled=disabled]', 'repository[url]', 'svn://localhost/test'
85 95 end
86 96
87 97 def test_update
88 98 @request.session[:user_id] = 1
89 99 put :update, :id => 11, :repository => {:password => 'test_update'}
90 100 assert_response 302
91 101 assert_equal 'test_update', Repository.find(11).password
92 102 end
93 103
94 104 def test_update_with_failure
95 105 @request.session[:user_id] = 1
96 106 put :update, :id => 11, :repository => {:password => 'x'*260}
97 107 assert_response :success
98 108 assert_select_error /Password is too long/
99 109 end
100 110
101 111 def test_destroy
102 112 @request.session[:user_id] = 1
103 113 assert_difference 'Repository.count', -1 do
104 114 delete :destroy, :id => 11
105 115 end
106 116 assert_response 302
107 117 assert_nil Repository.find_by_id(11)
108 118 end
109 119
110 120 def test_show_with_autofetch_changesets_enabled_should_fetch_changesets
111 121 Repository::Subversion.any_instance.expects(:fetch_changesets).once
112 122
113 123 with_settings :autofetch_changesets => '1' do
114 124 get :show, :id => 1
115 125 end
116 126 end
117 127
118 128 def test_show_with_autofetch_changesets_disabled_should_not_fetch_changesets
119 129 Repository::Subversion.any_instance.expects(:fetch_changesets).never
120 130
121 131 with_settings :autofetch_changesets => '0' do
122 132 get :show, :id => 1
123 133 end
124 134 end
125 135
126 136 def test_show_with_closed_project_should_not_fetch_changesets
127 137 Repository::Subversion.any_instance.expects(:fetch_changesets).never
128 138 Project.find(1).close
129 139
130 140 with_settings :autofetch_changesets => '1' do
131 141 get :show, :id => 1
132 142 end
133 143 end
134 144
135 145 def test_revisions
136 146 get :revisions, :id => 1
137 147 assert_response :success
138 148 assert_select 'table.changesets'
139 149 end
140 150
141 151 def test_revisions_for_other_repository
142 152 repository = Repository::Subversion.create!(:project_id => 1, :identifier => 'foo', :url => 'file:///foo')
143 153
144 154 get :revisions, :id => 1, :repository_id => 'foo'
145 155 assert_response :success
146 156 assert_select 'table.changesets'
147 157 end
148 158
149 159 def test_revisions_for_invalid_repository
150 160 get :revisions, :id => 1, :repository_id => 'foo'
151 161 assert_response 404
152 162 end
153 163
154 164 def test_revision
155 165 get :revision, :id => 1, :rev => 1
156 166 assert_response :success
157 167 assert_select 'h2', :text => 'Revision 1'
158 168 end
159 169
160 170 def test_revision_should_show_add_related_issue_form
161 171 Role.find(1).add_permission! :manage_related_issues
162 172 @request.session[:user_id] = 2
163 173
164 174 get :revision, :id => 1, :rev => 1
165 175 assert_response :success
166 176
167 177 assert_select 'form[action=?]', '/projects/ecookbook/repository/revisions/1/issues' do
168 178 assert_select 'input[name=?]', 'issue_id'
169 179 end
170 180 end
171 181
172 182 def test_revision_should_not_change_the_project_menu_link
173 183 get :revision, :id => 1, :rev => 1
174 184 assert_response :success
175 185
176 186 assert_select '#main-menu a.repository[href=?]', '/projects/ecookbook/repository'
177 187 end
178 188
179 189 def test_revision_with_before_nil_and_afer_normal
180 190 get :revision, {:id => 1, :rev => 1}
181 191 assert_response :success
182 192
183 193 assert_select 'div.contextual' do
184 194 assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/0', 0
185 195 assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/2'
186 196 end
187 197 end
188 198
189 199 def test_add_related_issue
190 200 @request.session[:user_id] = 2
191 201 assert_difference 'Changeset.find(103).issues.size' do
192 202 xhr :post, :add_related_issue, :id => 1, :rev => 4, :issue_id => 2, :format => 'js'
193 203 assert_response :success
194 204 assert_equal 'text/javascript', response.content_type
195 205 end
196 206 assert_equal [2], Changeset.find(103).issue_ids
197 207 assert_include 'related-issues', response.body
198 208 assert_include 'Feature request #2', response.body
199 209 end
200 210
201 211 def test_add_related_issue_should_accept_issue_id_with_sharp
202 212 @request.session[:user_id] = 2
203 213 assert_difference 'Changeset.find(103).issues.size' do
204 214 xhr :post, :add_related_issue, :id => 1, :rev => 4, :issue_id => "#2", :format => 'js'
205 215 end
206 216 assert_equal [2], Changeset.find(103).issue_ids
207 217 end
208 218
209 219 def test_add_related_issue_with_invalid_issue_id
210 220 @request.session[:user_id] = 2
211 221 assert_no_difference 'Changeset.find(103).issues.size' do
212 222 xhr :post, :add_related_issue, :id => 1, :rev => 4, :issue_id => 9999, :format => 'js'
213 223 assert_response :success
214 224 assert_equal 'text/javascript', response.content_type
215 225 end
216 226 assert_include 'alert("Issue is invalid")', response.body
217 227 end
218 228
219 229 def test_remove_related_issue
220 230 Changeset.find(103).issues << Issue.find(1)
221 231 Changeset.find(103).issues << Issue.find(2)
222 232
223 233 @request.session[:user_id] = 2
224 234 assert_difference 'Changeset.find(103).issues.size', -1 do
225 235 xhr :delete, :remove_related_issue, :id => 1, :rev => 4, :issue_id => 2, :format => 'js'
226 236 assert_response :success
227 237 assert_equal 'text/javascript', response.content_type
228 238 end
229 239 assert_equal [1], Changeset.find(103).issue_ids
230 240 assert_include 'related-issue-2', response.body
231 241 end
232 242
233 243 def test_graph_commits_per_month
234 244 # Make sure there's some data to display
235 245 latest = Project.find(1).repository.changesets.maximum(:commit_date)
236 246 assert_not_nil latest
237 247 Date.stubs(:today).returns(latest.to_date + 10)
238 248
239 249 get :graph, :id => 1, :graph => 'commits_per_month'
240 250 assert_response :success
241 251 assert_equal 'image/svg+xml', @response.content_type
242 252 end
243 253
244 254 def test_graph_commits_per_author
245 255 get :graph, :id => 1, :graph => 'commits_per_author'
246 256 assert_response :success
247 257 assert_equal 'image/svg+xml', @response.content_type
248 258 end
249 259
250 260 def test_get_committers
251 261 @request.session[:user_id] = 2
252 262 # add a commit with an unknown user
253 263 Changeset.create!(
254 264 :repository => Project.find(1).repository,
255 265 :committer => 'foo',
256 266 :committed_on => Time.now,
257 267 :revision => 100,
258 268 :comments => 'Committed by foo.'
259 269 )
260 270
261 271 get :committers, :id => 10
262 272 assert_response :success
263 273
264 274 assert_select 'input[value=dlopper] + select option[value="3"][selected=selected]', :text => 'Dave Lopper'
265 275 assert_select 'input[value=foo] + select option[selected=selected]', 0 # no option selected
266 276 end
267 277
268 278 def test_get_committers_without_changesets
269 279 Changeset.delete_all
270 280 @request.session[:user_id] = 2
271 281
272 282 get :committers, :id => 10
273 283 assert_response :success
274 284 end
275 285
276 286 def test_post_committers
277 287 @request.session[:user_id] = 2
278 288 # add a commit with an unknown user
279 289 c = Changeset.create!(
280 290 :repository => Project.find(1).repository,
281 291 :committer => 'foo',
282 292 :committed_on => Time.now,
283 293 :revision => 100,
284 294 :comments => 'Committed by foo.'
285 295 )
286 296 assert_no_difference "Changeset.where(:user_id => 3).count" do
287 297 post :committers, :id => 10, :committers => { '0' => ['foo', '2'], '1' => ['dlopper', '3']}
288 298 assert_response 302
289 299 assert_equal User.find(2), c.reload.user
290 300 end
291 301 end
292 302 end
@@ -1,585 +1,585
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class RepositoriesGitControllerTest < Redmine::ControllerTest
21 21 tests RepositoriesController
22 22
23 23 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
24 24 :repositories, :enabled_modules
25 25
26 26 REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
27 27 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
28 28 PRJ_ID = 3
29 29 CHAR_1_HEX = "\xc3\x9c".force_encoding('UTF-8')
30 30 FELIX_HEX = "Felix Sch\xC3\xA4fer".force_encoding('UTF-8')
31 31 NUM_REV = 28
32 32
33 33 ## Git, Mercurial and CVS path encodings are binary.
34 34 ## Subversion supports URL encoding for path.
35 35 ## Redmine Mercurial adapter and extension use URL encoding.
36 36 ## Git accepts only binary path in command line parameter.
37 37 ## So, there is no way to use binary command line parameter in JRuby.
38 38 JRUBY_SKIP = (RUBY_PLATFORM == 'java')
39 39 JRUBY_SKIP_STR = "TODO: This test fails in JRuby"
40 40
41 41 def setup
42 42 @ruby19_non_utf8_pass = Encoding.default_external.to_s != 'UTF-8'
43 43
44 44 User.current = nil
45 45 @project = Project.find(PRJ_ID)
46 46 @repository = Repository::Git.create(
47 47 :project => @project,
48 48 :url => REPOSITORY_PATH,
49 49 :path_encoding => 'ISO-8859-1'
50 50 )
51 51 assert @repository
52 52 end
53 53
54 54 def test_create_and_update
55 55 @request.session[:user_id] = 1
56 56 assert_difference 'Repository.count' do
57 57 post :create, :project_id => 'subproject1',
58 58 :repository_scm => 'Git',
59 59 :repository => {
60 60 :url => '/test',
61 61 :is_default => '0',
62 62 :identifier => 'test-create',
63 :extra_report_last_commit => '1',
63 :report_last_commit => '1',
64 64 }
65 65 end
66 66 assert_response 302
67 67 repository = Repository.order('id DESC').first
68 68 assert_kind_of Repository::Git, repository
69 69 assert_equal '/test', repository.url
70 assert_equal true, repository.extra_report_last_commit
70 assert_equal true, repository.report_last_commit
71 71
72 72 put :update, :id => repository.id,
73 73 :repository => {
74 :extra_report_last_commit => '0'
74 :report_last_commit => '0'
75 75 }
76 76 assert_response 302
77 77 repo2 = Repository.find(repository.id)
78 assert_equal false, repo2.extra_report_last_commit
78 assert_equal false, repo2.report_last_commit
79 79 end
80 80
81 81 if File.directory?(REPOSITORY_PATH)
82 82 ## Ruby uses ANSI api to fork a process on Windows.
83 83 ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem
84 84 ## and these are incompatible with ASCII.
85 85 ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10
86 86 ## http://code.google.com/p/msysgit/issues/detail?id=80
87 87 ## So, Latin-1 path tests fail on Japanese Windows
88 88 WINDOWS_PASS = (Redmine::Platform.mswin? &&
89 89 Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10]))
90 90 WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10"
91 91
92 92 def test_get_new
93 93 @request.session[:user_id] = 1
94 94 @project.repository.destroy
95 95 get :new, :project_id => 'subproject1', :repository_scm => 'Git'
96 96 assert_response :success
97 97 assert_select 'select[name=?]', 'repository_scm' do
98 98 assert_select 'option[value=?][selected=selected]', 'Git'
99 99 end
100 100 end
101 101
102 102 def test_browse_root
103 103 assert_equal 0, @repository.changesets.count
104 104 @repository.fetch_changesets
105 105 @project.reload
106 106 assert_equal NUM_REV, @repository.changesets.count
107 107
108 108 get :show, :id => PRJ_ID
109 109 assert_response :success
110 110
111 111 assert_select 'table.entries tbody' do
112 112 assert_select 'tr', 9
113 113 assert_select 'tr.dir td.filename_no_report a', :text => 'images'
114 114 assert_select 'tr.dir td.filename_no_report a', :text => 'this_is_a_really_long_and_verbose_directory_name'
115 115 assert_select 'tr.dir td.filename_no_report a', :text => 'sources'
116 116 assert_select 'tr.file td.filename_no_report a', :text => 'README'
117 117 assert_select 'tr.file td.filename_no_report a', :text => 'copied_README'
118 118 assert_select 'tr.file td.filename_no_report a', :text => 'new_file.txt'
119 119 assert_select 'tr.file td.filename_no_report a', :text => 'renamed_test.txt'
120 120 assert_select 'tr.file td.filename_no_report a', :text => 'filemane with spaces.txt'
121 121 assert_select 'tr.file td.filename_no_report a', :text => 'filename with a leading space.txt'
122 122 end
123 123
124 124 assert_select 'table.changesets tbody' do
125 125 assert_select 'tr'
126 126 end
127 127 end
128 128
129 129 def test_browse_branch
130 130 assert_equal 0, @repository.changesets.count
131 131 @repository.fetch_changesets
132 132 @project.reload
133 133 assert_equal NUM_REV, @repository.changesets.count
134 134 get :show, :id => PRJ_ID, :rev => 'test_branch'
135 135 assert_response :success
136 136
137 137 assert_select 'table.entries tbody' do
138 138 assert_select 'tr', 4
139 139 assert_select 'tr.dir td.filename_no_report a', :text => 'images'
140 140 assert_select 'tr.dir td.filename_no_report a', :text => 'sources'
141 141 assert_select 'tr.file td.filename_no_report a', :text => 'README'
142 142 assert_select 'tr.file td.filename_no_report a', :text => 'test.txt'
143 143 end
144 144
145 145 assert_select 'table.changesets tbody' do
146 146 assert_select 'tr'
147 147 end
148 148 end
149 149
150 150 def test_browse_tag
151 151 assert_equal 0, @repository.changesets.count
152 152 @repository.fetch_changesets
153 153 @project.reload
154 154 assert_equal NUM_REV, @repository.changesets.count
155 155 [
156 156 "tag00.lightweight",
157 157 "tag01.annotated",
158 158 ].each do |t1|
159 159 get :show, :id => PRJ_ID, :rev => t1
160 160 assert_response :success
161 161
162 162 assert_select 'table.entries tbody tr'
163 163 assert_select 'table.changesets tbody tr'
164 164 end
165 165 end
166 166
167 167 def test_browse_directory
168 168 assert_equal 0, @repository.changesets.count
169 169 @repository.fetch_changesets
170 170 @project.reload
171 171 assert_equal NUM_REV, @repository.changesets.count
172 172 get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param]
173 173 assert_response :success
174 174
175 175 assert_select 'table.entries tbody' do
176 176 assert_select 'tr', 1
177 177 assert_select 'tr.file td.filename_no_report a', :text => 'edit.png'
178 178 end
179 179 assert_select 'table.changesets tbody tr'
180 180 end
181 181
182 182 def test_browse_at_given_revision
183 183 assert_equal 0, @repository.changesets.count
184 184 @repository.fetch_changesets
185 185 @project.reload
186 186 assert_equal NUM_REV, @repository.changesets.count
187 187 get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param],
188 188 :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518'
189 189 assert_response :success
190 190
191 191 assert_select 'table.entries tbody' do
192 192 assert_select 'tr', 1
193 193 assert_select 'tr.file td.filename_no_report a', :text => 'delete.png'
194 194 end
195 195 end
196 196
197 197 def test_changes
198 198 get :changes, :id => PRJ_ID,
199 199 :path => repository_path_hash(['images', 'edit.png'])[:param]
200 200 assert_response :success
201 201 assert_select 'h2', :text => /edit.png/
202 202 end
203 203
204 204 def test_entry_show
205 205 get :entry, :id => PRJ_ID,
206 206 :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param]
207 207 assert_response :success
208 208 # Line 11
209 209 assert_select 'tr#L11 td.line-code', :text => /WITHOUT ANY WARRANTY/
210 210 end
211 211
212 212 def test_entry_show_latin_1
213 213 if @ruby19_non_utf8_pass
214 214 puts_ruby19_non_utf8_pass()
215 215 elsif WINDOWS_PASS
216 216 puts WINDOWS_SKIP_STR
217 217 elsif JRUBY_SKIP
218 218 puts JRUBY_SKIP_STR
219 219 else
220 220 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
221 221 ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1|
222 222 get :entry, :id => PRJ_ID,
223 223 :path => repository_path_hash(['latin-1-dir', "test-#{CHAR_1_HEX}.txt"])[:param],
224 224 :rev => r1
225 225 assert_response :success
226 226 assert_select 'tr#L1 td.line-code', :text => /test-#{CHAR_1_HEX}.txt/
227 227 end
228 228 end
229 229 end
230 230 end
231 231
232 232 def test_entry_download
233 233 get :entry, :id => PRJ_ID,
234 234 :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param],
235 235 :format => 'raw'
236 236 assert_response :success
237 237 # File content
238 238 assert @response.body.include?('WITHOUT ANY WARRANTY')
239 239 end
240 240
241 241 def test_directory_entry
242 242 get :entry, :id => PRJ_ID,
243 243 :path => repository_path_hash(['sources'])[:param]
244 244 assert_response :success
245 245 assert_select 'h2 a', :text => 'sources'
246 246 assert_select 'table.entries tbody'
247 247 end
248 248
249 249 def test_diff
250 250 assert_equal true, @repository.is_default
251 251 assert @repository.identifier.blank?
252 252 assert_equal 0, @repository.changesets.count
253 253 @repository.fetch_changesets
254 254 @project.reload
255 255 assert_equal NUM_REV, @repository.changesets.count
256 256 # Full diff of changeset 2f9c0091
257 257 ['inline', 'sbs'].each do |dt|
258 258 get :diff,
259 259 :id => PRJ_ID,
260 260 :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7',
261 261 :type => dt
262 262 assert_response :success
263 263 # Line 22 removed
264 264 assert_select 'th.line-num:contains(22) ~ td.diff_out', :text => /def remove/
265 265 assert_select 'h2', :text => /2f9c0091/
266 266 end
267 267 end
268 268
269 269 def test_diff_with_rev_and_path
270 270 assert_equal 0, @repository.changesets.count
271 271 @repository.fetch_changesets
272 272 @project.reload
273 273 assert_equal NUM_REV, @repository.changesets.count
274 274 with_settings :diff_max_lines_displayed => 1000 do
275 275 # Full diff of changeset 2f9c0091
276 276 ['inline', 'sbs'].each do |dt|
277 277 get :diff,
278 278 :id => PRJ_ID,
279 279 :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7',
280 280 :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param],
281 281 :type => dt
282 282 assert_response :success
283 283 # Line 22 removed
284 284 assert_select 'th.line-num:contains(22) ~ td.diff_out', :text => /def remove/
285 285 assert_select 'h2', :text => /2f9c0091/
286 286 end
287 287 end
288 288 end
289 289
290 290 def test_diff_truncated
291 291 assert_equal 0, @repository.changesets.count
292 292 @repository.fetch_changesets
293 293 @project.reload
294 294 assert_equal NUM_REV, @repository.changesets.count
295 295
296 296 with_settings :diff_max_lines_displayed => 5 do
297 297 # Truncated diff of changeset 2f9c0091
298 298 with_cache do
299 299 with_settings :default_language => 'en' do
300 300 get :diff, :id => PRJ_ID, :type => 'inline',
301 301 :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
302 302 assert_response :success
303 303 assert @response.body.include?("... This diff was truncated")
304 304 end
305 305 with_settings :default_language => 'fr' do
306 306 get :diff, :id => PRJ_ID, :type => 'inline',
307 307 :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
308 308 assert_response :success
309 309 assert ! @response.body.include?("... This diff was truncated")
310 310 assert @response.body.include?("... Ce diff")
311 311 end
312 312 end
313 313 end
314 314 end
315 315
316 316 def test_diff_two_revs
317 317 assert_equal 0, @repository.changesets.count
318 318 @repository.fetch_changesets
319 319 @project.reload
320 320 assert_equal NUM_REV, @repository.changesets.count
321 321 ['inline', 'sbs'].each do |dt|
322 322 get :diff,
323 323 :id => PRJ_ID,
324 324 :rev => '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
325 325 :rev_to => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7',
326 326 :type => dt
327 327 assert_response :success
328 328 assert_select 'h2', :text => /2f9c0091:61b685fb/
329 329 assert_select 'form[action=?]', '/projects/subproject1/repository/revisions/61b685fbe55ab05b5ac68402d5720c1a6ac973d1/diff'
330 330 assert_select 'input#rev_to[type=hidden][name=rev_to][value=?]', '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
331 331 end
332 332 end
333 333
334 334 def test_diff_path_in_subrepo
335 335 repo = Repository::Git.create(
336 336 :project => @project,
337 337 :url => REPOSITORY_PATH,
338 338 :identifier => 'test-diff-path',
339 339 :path_encoding => 'ISO-8859-1'
340 340 )
341 341 assert repo
342 342 assert_equal false, repo.is_default
343 343 assert_equal 'test-diff-path', repo.identifier
344 344 get :diff,
345 345 :id => PRJ_ID,
346 346 :repository_id => 'test-diff-path',
347 347 :rev => '61b685fbe55ab05b',
348 348 :rev_to => '2f9c0091c754a91a',
349 349 :type => 'inline'
350 350 assert_response :success
351 351 assert_select 'form[action=?]', '/projects/subproject1/repository/test-diff-path/revisions/61b685fbe55ab05b/diff'
352 352 assert_select 'input#rev_to[type=hidden][name=rev_to][value=?]', '2f9c0091c754a91a'
353 353 end
354 354
355 355 def test_diff_latin_1
356 356 if @ruby19_non_utf8_pass
357 357 puts_ruby19_non_utf8_pass()
358 358 else
359 359 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
360 360 ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1|
361 361 ['inline', 'sbs'].each do |dt|
362 362 get :diff, :id => PRJ_ID, :rev => r1, :type => dt
363 363 assert_response :success
364 364 assert_select 'table' do
365 365 assert_select 'thead th.filename', :text => /latin-1-dir\/test-#{CHAR_1_HEX}.txt/
366 366 assert_select 'tbody td.diff_in', :text => /test-#{CHAR_1_HEX}.txt/
367 367 end
368 368 end
369 369 end
370 370 end
371 371 end
372 372 end
373 373
374 374 def test_diff_should_show_filenames
375 375 get :diff, :id => PRJ_ID, :rev => 'deff712f05a90d96edbd70facc47d944be5897e3', :type => 'inline'
376 376 assert_response :success
377 377 # modified file
378 378 assert_select 'th.filename', :text => 'sources/watchers_controller.rb'
379 379 # deleted file
380 380 assert_select 'th.filename', :text => 'test.txt'
381 381 end
382 382
383 383 def test_save_diff_type
384 384 user1 = User.find(1)
385 385 user1.pref[:diff_type] = nil
386 386 user1.preference.save
387 387 user = User.find(1)
388 388 assert_nil user.pref[:diff_type]
389 389
390 390 @request.session[:user_id] = 1 # admin
391 391 get :diff,
392 392 :id => PRJ_ID,
393 393 :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
394 394 assert_response :success
395 395 user.reload
396 396 assert_equal "inline", user.pref[:diff_type]
397 397 get :diff,
398 398 :id => PRJ_ID,
399 399 :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7',
400 400 :type => 'sbs'
401 401 assert_response :success
402 402 user.reload
403 403 assert_equal "sbs", user.pref[:diff_type]
404 404 end
405 405
406 406 def test_annotate
407 407 get :annotate, :id => PRJ_ID,
408 408 :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param]
409 409 assert_response :success
410 410
411 411 # Line 23, changeset 2f9c0091
412 412 assert_select 'tr' do
413 413 assert_select 'th.line-num', :text => '23'
414 414 assert_select 'td.revision', :text => /2f9c0091/
415 415 assert_select 'td.author', :text => 'jsmith'
416 416 assert_select 'td', :text => /remove_watcher/
417 417 end
418 418 end
419 419
420 420 def test_annotate_at_given_revision
421 421 assert_equal 0, @repository.changesets.count
422 422 @repository.fetch_changesets
423 423 @project.reload
424 424 assert_equal NUM_REV, @repository.changesets.count
425 425 get :annotate, :id => PRJ_ID, :rev => 'deff7',
426 426 :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param]
427 427 assert_response :success
428 428 assert_select 'h2', :text => /@ deff712f/
429 429 end
430 430
431 431 def test_annotate_binary_file
432 432 with_settings :default_language => 'en' do
433 433 get :annotate, :id => PRJ_ID,
434 434 :path => repository_path_hash(['images', 'edit.png'])[:param]
435 435 assert_response :success
436 436 assert_select 'p#errorExplanation', :text => /cannot be annotated/
437 437 end
438 438 end
439 439
440 440 def test_annotate_error_when_too_big
441 441 with_settings :file_max_size_displayed => 1 do
442 442 get :annotate, :id => PRJ_ID,
443 443 :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param],
444 444 :rev => 'deff712f'
445 445 assert_response :success
446 446 assert_select 'p#errorExplanation', :text => /exceeds the maximum text file size/
447 447
448 448 get :annotate, :id => PRJ_ID,
449 449 :path => repository_path_hash(['README'])[:param],
450 450 :rev => '7234cb2'
451 451 assert_response :success
452 452 end
453 453 end
454 454
455 455 def test_annotate_latin_1
456 456 if @ruby19_non_utf8_pass
457 457 puts_ruby19_non_utf8_pass()
458 458 elsif WINDOWS_PASS
459 459 puts WINDOWS_SKIP_STR
460 460 elsif JRUBY_SKIP
461 461 puts JRUBY_SKIP_STR
462 462 else
463 463 with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
464 464 ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1|
465 465 get :annotate, :id => PRJ_ID,
466 466 :path => repository_path_hash(['latin-1-dir', "test-#{CHAR_1_HEX}.txt"])[:param],
467 467 :rev => r1
468 468 assert_select "th.line-num", :text => '1' do
469 469 assert_select "+ td.revision" do
470 470 assert_select "a", :text => '57ca437c'
471 471 assert_select "+ td.author", :text => "jsmith" do
472 472 assert_select "+ td",
473 473 :text => "test-#{CHAR_1_HEX}.txt"
474 474 end
475 475 end
476 476 end
477 477 end
478 478 end
479 479 end
480 480 end
481 481
482 482 def test_annotate_latin_1_author
483 483 ['83ca5fd546063a3c7dc2e568ba3355661a9e2b2c', '83ca5fd546063a'].each do |r1|
484 484 get :annotate, :id => PRJ_ID,
485 485 :path => repository_path_hash([" filename with a leading space.txt "])[:param],
486 486 :rev => r1
487 487 assert_select "th.line-num", :text => '1' do
488 488 assert_select "+ td.revision" do
489 489 assert_select "a", :text => '83ca5fd5'
490 490 assert_select "+ td.author", :text => FELIX_HEX do
491 491 assert_select "+ td",
492 492 :text => "And this is a file with a leading and trailing space..."
493 493 end
494 494 end
495 495 end
496 496 end
497 497 end
498 498
499 499 def test_revisions
500 500 assert_equal 0, @repository.changesets.count
501 501 @repository.fetch_changesets
502 502 @project.reload
503 503 assert_equal NUM_REV, @repository.changesets.count
504 504 get :revisions, :id => PRJ_ID
505 505 assert_select 'form[method=get][action=?]', '/projects/subproject1/repository/revision'
506 506 end
507 507
508 508 def test_revision
509 509 assert_equal 0, @repository.changesets.count
510 510 @repository.fetch_changesets
511 511 @project.reload
512 512 assert_equal NUM_REV, @repository.changesets.count
513 513 ['61b685fbe55ab05b5ac68402d5720c1a6ac973d1', '61b685f'].each do |r|
514 514 get :revision, :id => PRJ_ID, :rev => r
515 515 assert_response :success
516 516 end
517 517 end
518 518
519 519 def test_empty_revision
520 520 assert_equal 0, @repository.changesets.count
521 521 @repository.fetch_changesets
522 522 @project.reload
523 523 assert_equal NUM_REV, @repository.changesets.count
524 524 ['', ' ', nil].each do |r|
525 525 get :revision, :id => PRJ_ID, :rev => r
526 526 assert_response 404
527 527 assert_select_error /was not found/
528 528 end
529 529 end
530 530
531 531 def test_destroy_valid_repository
532 532 @request.session[:user_id] = 1 # admin
533 533 assert_equal 0, @repository.changesets.count
534 534 @repository.fetch_changesets
535 535 @project.reload
536 536 assert_equal NUM_REV, @repository.changesets.count
537 537
538 538 assert_difference 'Repository.count', -1 do
539 539 delete :destroy, :id => @repository.id
540 540 end
541 541 assert_response 302
542 542 @project.reload
543 543 assert_nil @project.repository
544 544 end
545 545
546 546 def test_destroy_invalid_repository
547 547 @request.session[:user_id] = 1 # admin
548 548 @project.repository.destroy
549 549 @repository = Repository::Git.create!(
550 550 :project => @project,
551 551 :url => "/invalid",
552 552 :path_encoding => 'ISO-8859-1'
553 553 )
554 554 @repository.fetch_changesets
555 555 @repository.reload
556 556 assert_equal 0, @repository.changesets.count
557 557
558 558 assert_difference 'Repository.count', -1 do
559 559 delete :destroy, :id => @repository.id
560 560 end
561 561 assert_response 302
562 562 @project.reload
563 563 assert_nil @project.repository
564 564 end
565 565
566 566 private
567 567
568 568 def puts_ruby19_non_utf8_pass
569 569 puts "TODO: This test fails " +
570 570 "when Encoding.default_external is not UTF-8. " +
571 571 "Current value is '#{Encoding.default_external.to_s}'"
572 572 end
573 573 else
574 574 puts "Git test repository NOT FOUND. Skipping functional tests !!!"
575 575 def test_fake; assert true end
576 576 end
577 577
578 578 private
579 579 def with_cache(&block)
580 580 before = ActionController::Base.perform_caching
581 581 ActionController::Base.perform_caching = true
582 582 block.call
583 583 ActionController::Base.perform_caching = before
584 584 end
585 585 end
@@ -1,623 +1,614
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class RepositoryGitTest < ActiveSupport::TestCase
21 21 fixtures :projects, :repositories, :enabled_modules, :users, :roles
22 22
23 23 include Redmine::I18n
24 24
25 25 REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
26 26 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
27 27
28 28 NUM_REV = 28
29 29 NUM_HEAD = 6
30 30
31 31 FELIX_HEX = "Felix Sch\xC3\xA4fer".force_encoding('UTF-8')
32 32 CHAR_1_HEX = "\xc3\x9c".force_encoding('UTF-8')
33 33
34 34 ## Git, Mercurial and CVS path encodings are binary.
35 35 ## Subversion supports URL encoding for path.
36 36 ## Redmine Mercurial adapter and extension use URL encoding.
37 37 ## Git accepts only binary path in command line parameter.
38 38 ## So, there is no way to use binary command line parameter in JRuby.
39 39 JRUBY_SKIP = (RUBY_PLATFORM == 'java')
40 40 JRUBY_SKIP_STR = "TODO: This test fails in JRuby"
41 41
42 42 def setup
43 43 @project = Project.find(3)
44 44 @repository = Repository::Git.create(
45 45 :project => @project,
46 46 :url => REPOSITORY_PATH,
47 47 :path_encoding => 'ISO-8859-1'
48 48 )
49 49 assert @repository
50 50 end
51 51
52 52 def test_nondefault_repo_with_blank_identifier_destruction
53 53 Repository.delete_all
54 54
55 55 repo1 = Repository::Git.new(
56 56 :project => @project,
57 57 :url => REPOSITORY_PATH,
58 58 :identifier => '',
59 59 :is_default => true
60 60 )
61 61 assert repo1.save
62 62 repo1.fetch_changesets
63 63
64 64 repo2 = Repository::Git.new(
65 65 :project => @project,
66 66 :url => REPOSITORY_PATH,
67 67 :identifier => 'repo2',
68 68 :is_default => true
69 69 )
70 70 assert repo2.save
71 71 repo2.fetch_changesets
72 72
73 73 repo1.reload
74 74 repo2.reload
75 75 assert !repo1.is_default?
76 76 assert repo2.is_default?
77 77
78 78 assert_difference 'Repository.count', -1 do
79 79 repo1.destroy
80 80 end
81 81 end
82 82
83 83 def test_blank_path_to_repository_error_message
84 84 set_language_if_valid 'en'
85 85 repo = Repository::Git.new(
86 86 :project => @project,
87 87 :identifier => 'test'
88 88 )
89 89 assert !repo.save
90 90 assert_include "Path to repository cannot be blank",
91 91 repo.errors.full_messages
92 92 end
93 93
94 94 def test_blank_path_to_repository_error_message_fr
95 95 set_language_if_valid 'fr'
96 96 str = "Chemin du d\xc3\xa9p\xc3\xb4t doit \xc3\xaatre renseign\xc3\xa9(e)".force_encoding('UTF-8')
97 97 repo = Repository::Git.new(
98 98 :project => @project,
99 99 :url => "",
100 100 :identifier => 'test',
101 101 :path_encoding => ''
102 102 )
103 103 assert !repo.save
104 104 assert_include str, repo.errors.full_messages
105 105 end
106 106
107 107 if File.directory?(REPOSITORY_PATH)
108 108 ## Ruby uses ANSI api to fork a process on Windows.
109 109 ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem
110 110 ## and these are incompatible with ASCII.
111 111 ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10
112 112 ## http://code.google.com/p/msysgit/issues/detail?id=80
113 113 ## So, Latin-1 path tests fail on Japanese Windows
114 114 WINDOWS_PASS = (Redmine::Platform.mswin? &&
115 115 Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10]))
116 116 WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10"
117 117
118 118 def test_scm_available
119 119 klass = Repository::Git
120 120 assert_equal "Git", klass.scm_name
121 121 assert klass.scm_adapter_class
122 122 assert_not_equal "", klass.scm_command
123 123 assert_equal true, klass.scm_available
124 124 end
125 125
126 126 def test_entries
127 127 entries = @repository.entries
128 128 assert_kind_of Redmine::Scm::Adapters::Entries, entries
129 129 end
130 130
131 131 def test_fetch_changesets_from_scratch
132 132 assert_nil @repository.extra_info
133 133
134 134 assert_equal 0, @repository.changesets.count
135 135 @repository.fetch_changesets
136 136 @project.reload
137 137
138 138 assert_equal NUM_REV, @repository.changesets.count
139 139 assert_equal 39, @repository.filechanges.count
140 140
141 141 commit = @repository.changesets.find_by_revision("7234cb2750b63f47bff735edc50a1c0a433c2518")
142 142 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.scmid
143 143 assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments
144 144 assert_equal "jsmith <jsmith@foo.bar>", commit.committer
145 145 assert_equal User.find_by_login('jsmith'), commit.user
146 146 # TODO: add a commit with commit time <> author time to the test repository
147 147 assert_equal Time.gm(2007, 12, 14, 9, 22, 52), commit.committed_on
148 148 assert_equal "2007-12-14".to_date, commit.commit_date
149 149 assert_equal 3, commit.filechanges.count
150 150 change = commit.filechanges.sort_by(&:path).first
151 151 assert_equal "README", change.path
152 152 assert_equal nil, change.from_path
153 153 assert_equal "A", change.action
154 154
155 155 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
156 156 end
157 157
158 158 def test_fetch_changesets_incremental
159 159 assert_equal 0, @repository.changesets.count
160 160 @repository.fetch_changesets
161 161 @project.reload
162 162 assert_equal NUM_REV, @repository.changesets.count
163 163 extra_info_heads = @repository.extra_info["heads"].dup
164 164 assert_equal NUM_HEAD, extra_info_heads.size
165 165 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
166 166 assert_equal 4, extra_info_heads.size
167 167
168 168 del_revs = [
169 169 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
170 170 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
171 171 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
172 172 "deff712f05a90d96edbd70facc47d944be5897e3",
173 173 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
174 174 "7e61ac704deecde634b51e59daa8110435dcb3da",
175 175 ]
176 176 @repository.changesets.each do |rev|
177 177 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
178 178 end
179 179 @project.reload
180 180 cs1 = @repository.changesets
181 181 assert_equal NUM_REV - 6, cs1.count
182 182 extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8"
183 183 h = {}
184 184 h["heads"] = extra_info_heads
185 185 @repository.merge_extra_info(h)
186 186 @repository.save
187 187 @project.reload
188 188 assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8")
189 189 @repository.fetch_changesets
190 190 @project.reload
191 191 assert_equal NUM_REV, @repository.changesets.count
192 192 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
193 193 assert @repository.extra_info["heads"].index("83ca5fd546063a3c7dc2e568ba3355661a9e2b2c")
194 194 end
195 195
196 196 def test_fetch_changesets_history_editing
197 197 assert_equal 0, @repository.changesets.count
198 198 @repository.fetch_changesets
199 199 @project.reload
200 200 assert_equal NUM_REV, @repository.changesets.count
201 201 extra_info_heads = @repository.extra_info["heads"].dup
202 202 assert_equal NUM_HEAD, extra_info_heads.size
203 203 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
204 204 assert_equal 4, extra_info_heads.size
205 205
206 206 del_revs = [
207 207 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
208 208 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
209 209 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
210 210 "deff712f05a90d96edbd70facc47d944be5897e3",
211 211 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
212 212 "7e61ac704deecde634b51e59daa8110435dcb3da",
213 213 ]
214 214 @repository.changesets.each do |rev|
215 215 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
216 216 end
217 217 @project.reload
218 218 assert_equal NUM_REV - 6, @repository.changesets.count
219 219
220 220 c = Changeset.new(:repository => @repository,
221 221 :committed_on => Time.now,
222 222 :revision => "abcd1234efgh",
223 223 :scmid => "abcd1234efgh",
224 224 :comments => 'test')
225 225 assert c.save
226 226 @project.reload
227 227 assert_equal NUM_REV - 5, @repository.changesets.count
228 228
229 229 extra_info_heads << "1234abcd5678"
230 230 h = {}
231 231 h["heads"] = extra_info_heads
232 232 @repository.merge_extra_info(h)
233 233 @repository.save
234 234 @project.reload
235 235 h1 = @repository.extra_info["heads"].dup
236 236 assert h1.index("1234abcd5678")
237 237 assert_equal 5, h1.size
238 238
239 239 @repository.fetch_changesets
240 240 @project.reload
241 241 assert_equal NUM_REV - 5, @repository.changesets.count
242 242 h2 = @repository.extra_info["heads"].dup
243 243 assert_equal h1, h2
244 244 end
245 245
246 def test_keep_extra_report_last_commit_in_clear_changesets
246 def test_clear_changesets_should_keep_report_last_commit
247 247 assert_nil @repository.extra_info
248 h = {}
249 h["extra_report_last_commit"] = "1"
250 @repository.merge_extra_info(h)
248 @repository.report_last_commit = "1"
251 249 @repository.save
252 @project.reload
253
254 assert_equal 0, @repository.changesets.count
255 @repository.fetch_changesets
256 @project.reload
257
258 assert_equal NUM_REV, @repository.changesets.count
259 250 @repository.send(:clear_changesets)
260 assert_equal 1, @repository.extra_info.size
261 assert_equal "1", @repository.extra_info["extra_report_last_commit"]
251
252 assert_equal true, @repository.report_last_commit
262 253 end
263 254
264 255 def test_refetch_after_clear_changesets
265 256 assert_nil @repository.extra_info
266 257 assert_equal 0, @repository.changesets.count
267 258 @repository.fetch_changesets
268 259 @project.reload
269 260 assert_equal NUM_REV, @repository.changesets.count
270 261
271 262 @repository.send(:clear_changesets)
272 263 @project.reload
273 264 assert_equal 0, @repository.changesets.count
274 265
275 266 @repository.fetch_changesets
276 267 @project.reload
277 268 assert_equal NUM_REV, @repository.changesets.count
278 269 end
279 270
280 271 def test_parents
281 272 assert_equal 0, @repository.changesets.count
282 273 @repository.fetch_changesets
283 274 @project.reload
284 275 assert_equal NUM_REV, @repository.changesets.count
285 276 r1 = @repository.find_changeset_by_name("7234cb2750b63")
286 277 assert_equal [], r1.parents
287 278 r2 = @repository.find_changeset_by_name("899a15dba03a3")
288 279 assert_equal 1, r2.parents.length
289 280 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518",
290 281 r2.parents[0].identifier
291 282 r3 = @repository.find_changeset_by_name("32ae898b720c2")
292 283 assert_equal 2, r3.parents.length
293 284 r4 = [r3.parents[0].identifier, r3.parents[1].identifier].sort
294 285 assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", r4[0]
295 286 assert_equal "7e61ac704deecde634b51e59daa8110435dcb3da", r4[1]
296 287 end
297 288
298 289 def test_db_consistent_ordering_init
299 290 assert_nil @repository.extra_info
300 291 assert_equal 0, @repository.changesets.count
301 292 @repository.fetch_changesets
302 293 @project.reload
303 294 assert_equal 1, @repository.extra_info["db_consistent"]["ordering"]
304 295 end
305 296
306 297 def test_db_consistent_ordering_before_1_2
307 298 assert_nil @repository.extra_info
308 299 assert_equal 0, @repository.changesets.count
309 300 @repository.fetch_changesets
310 301 @project.reload
311 302 assert_equal NUM_REV, @repository.changesets.count
312 303 assert_not_nil @repository.extra_info
313 304 h = {}
314 305 h["heads"] = []
315 306 h["branches"] = {}
316 307 h["db_consistent"] = {}
317 308 @repository.merge_extra_info(h)
318 309 @repository.save
319 310 assert_equal NUM_REV, @repository.changesets.count
320 311 @repository.fetch_changesets
321 312 @project.reload
322 313 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
323 314
324 315 extra_info_heads = @repository.extra_info["heads"].dup
325 316 extra_info_heads.delete_if { |x| x == "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c" }
326 317 del_revs = [
327 318 "83ca5fd546063a3c7dc2e568ba3355661a9e2b2c",
328 319 "ed5bb786bbda2dee66a2d50faf51429dbc043a7b",
329 320 "4f26664364207fa8b1af9f8722647ab2d4ac5d43",
330 321 "deff712f05a90d96edbd70facc47d944be5897e3",
331 322 "32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf",
332 323 "7e61ac704deecde634b51e59daa8110435dcb3da",
333 324 ]
334 325 @repository.changesets.each do |rev|
335 326 rev.destroy if del_revs.detect {|r| r == rev.scmid.to_s }
336 327 end
337 328 @project.reload
338 329 cs1 = @repository.changesets
339 330 assert_equal NUM_REV - 6, cs1.count
340 331 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
341 332
342 333 extra_info_heads << "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8"
343 334 h = {}
344 335 h["heads"] = extra_info_heads
345 336 @repository.merge_extra_info(h)
346 337 @repository.save
347 338 @project.reload
348 339 assert @repository.extra_info["heads"].index("4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8")
349 340 @repository.fetch_changesets
350 341 @project.reload
351 342 assert_equal NUM_REV, @repository.changesets.count
352 343 assert_equal NUM_HEAD, @repository.extra_info["heads"].size
353 344
354 345 assert_equal 0, @repository.extra_info["db_consistent"]["ordering"]
355 346 end
356 347
357 348 def test_heads_from_branches_hash
358 349 assert_nil @repository.extra_info
359 350 assert_equal 0, @repository.changesets.count
360 351 assert_equal [], @repository.heads_from_branches_hash
361 352 h = {}
362 353 h["branches"] = {}
363 354 h["branches"]["test1"] = {}
364 355 h["branches"]["test1"]["last_scmid"] = "1234abcd"
365 356 h["branches"]["test2"] = {}
366 357 h["branches"]["test2"]["last_scmid"] = "abcd1234"
367 358 @repository.merge_extra_info(h)
368 359 @repository.save
369 360 @project.reload
370 361 assert_equal ["1234abcd", "abcd1234"], @repository.heads_from_branches_hash.sort
371 362 end
372 363
373 364 def test_latest_changesets
374 365 assert_equal 0, @repository.changesets.count
375 366 @repository.fetch_changesets
376 367 @project.reload
377 368 assert_equal NUM_REV, @repository.changesets.count
378 369 # with limit
379 370 changesets = @repository.latest_changesets('', 'master', 2)
380 371 assert_equal 2, changesets.size
381 372
382 373 # with path
383 374 changesets = @repository.latest_changesets('images', 'master')
384 375 assert_equal [
385 376 'deff712f05a90d96edbd70facc47d944be5897e3',
386 377 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
387 378 '7234cb2750b63f47bff735edc50a1c0a433c2518',
388 379 ], changesets.collect(&:revision)
389 380
390 381 changesets = @repository.latest_changesets('README', nil)
391 382 assert_equal [
392 383 '32ae898b720c2f7eec2723d5bdd558b4cb2d3ddf',
393 384 '4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8',
394 385 '713f4944648826f558cf548222f813dabe7cbb04',
395 386 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
396 387 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
397 388 '7234cb2750b63f47bff735edc50a1c0a433c2518',
398 389 ], changesets.collect(&:revision)
399 390
400 391 # with path, revision and limit
401 392 changesets = @repository.latest_changesets('images', '899a15dba')
402 393 assert_equal [
403 394 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
404 395 '7234cb2750b63f47bff735edc50a1c0a433c2518',
405 396 ], changesets.collect(&:revision)
406 397
407 398 changesets = @repository.latest_changesets('images', '899a15dba', 1)
408 399 assert_equal [
409 400 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
410 401 ], changesets.collect(&:revision)
411 402
412 403 changesets = @repository.latest_changesets('README', '899a15dba')
413 404 assert_equal [
414 405 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
415 406 '7234cb2750b63f47bff735edc50a1c0a433c2518',
416 407 ], changesets.collect(&:revision)
417 408
418 409 changesets = @repository.latest_changesets('README', '899a15dba', 1)
419 410 assert_equal [
420 411 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
421 412 ], changesets.collect(&:revision)
422 413
423 414 # with path, tag and limit
424 415 changesets = @repository.latest_changesets('images', 'tag01.annotated')
425 416 assert_equal [
426 417 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
427 418 '7234cb2750b63f47bff735edc50a1c0a433c2518',
428 419 ], changesets.collect(&:revision)
429 420
430 421 changesets = @repository.latest_changesets('images', 'tag01.annotated', 1)
431 422 assert_equal [
432 423 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
433 424 ], changesets.collect(&:revision)
434 425
435 426 changesets = @repository.latest_changesets('README', 'tag01.annotated')
436 427 assert_equal [
437 428 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
438 429 '7234cb2750b63f47bff735edc50a1c0a433c2518',
439 430 ], changesets.collect(&:revision)
440 431
441 432 changesets = @repository.latest_changesets('README', 'tag01.annotated', 1)
442 433 assert_equal [
443 434 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
444 435 ], changesets.collect(&:revision)
445 436
446 437 # with path, branch and limit
447 438 changesets = @repository.latest_changesets('images', 'test_branch')
448 439 assert_equal [
449 440 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
450 441 '7234cb2750b63f47bff735edc50a1c0a433c2518',
451 442 ], changesets.collect(&:revision)
452 443
453 444 changesets = @repository.latest_changesets('images', 'test_branch', 1)
454 445 assert_equal [
455 446 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
456 447 ], changesets.collect(&:revision)
457 448
458 449 changesets = @repository.latest_changesets('README', 'test_branch')
459 450 assert_equal [
460 451 '713f4944648826f558cf548222f813dabe7cbb04',
461 452 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
462 453 '899a15dba03a3b350b89c3f537e4bbe02a03cdc9',
463 454 '7234cb2750b63f47bff735edc50a1c0a433c2518',
464 455 ], changesets.collect(&:revision)
465 456
466 457 changesets = @repository.latest_changesets('README', 'test_branch', 2)
467 458 assert_equal [
468 459 '713f4944648826f558cf548222f813dabe7cbb04',
469 460 '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
470 461 ], changesets.collect(&:revision)
471 462
472 463 if WINDOWS_PASS
473 464 puts WINDOWS_SKIP_STR
474 465 elsif JRUBY_SKIP
475 466 puts JRUBY_SKIP_STR
476 467 else
477 468 # latin-1 encoding path
478 469 changesets = @repository.latest_changesets(
479 470 "latin-1-dir/test-#{CHAR_1_HEX}-2.txt", '64f1f3e89')
480 471 assert_equal [
481 472 '64f1f3e89ad1cb57976ff0ad99a107012ba3481d',
482 473 '4fc55c43bf3d3dc2efb66145365ddc17639ce81e',
483 474 ], changesets.collect(&:revision)
484 475
485 476 changesets = @repository.latest_changesets(
486 477 "latin-1-dir/test-#{CHAR_1_HEX}-2.txt", '64f1f3e89', 1)
487 478 assert_equal [
488 479 '64f1f3e89ad1cb57976ff0ad99a107012ba3481d',
489 480 ], changesets.collect(&:revision)
490 481 end
491 482 end
492 483
493 484 def test_latest_changesets_latin_1_dir
494 485 if WINDOWS_PASS
495 486 puts WINDOWS_SKIP_STR
496 487 elsif JRUBY_SKIP
497 488 puts JRUBY_SKIP_STR
498 489 else
499 490 assert_equal 0, @repository.changesets.count
500 491 @repository.fetch_changesets
501 492 @project.reload
502 493 assert_equal NUM_REV, @repository.changesets.count
503 494 changesets = @repository.latest_changesets(
504 495 "latin-1-dir/test-#{CHAR_1_HEX}-subdir", '1ca7f5ed')
505 496 assert_equal [
506 497 '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127',
507 498 ], changesets.collect(&:revision)
508 499 end
509 500 end
510 501
511 502 def test_find_changeset_by_name
512 503 assert_equal 0, @repository.changesets.count
513 504 @repository.fetch_changesets
514 505 @project.reload
515 506 assert_equal NUM_REV, @repository.changesets.count
516 507 ['7234cb2750b63f47bff735edc50a1c0a433c2518', '7234cb2750b'].each do |r|
517 508 assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518',
518 509 @repository.find_changeset_by_name(r).revision
519 510 end
520 511 end
521 512
522 513 def test_find_changeset_by_empty_name
523 514 assert_equal 0, @repository.changesets.count
524 515 @repository.fetch_changesets
525 516 @project.reload
526 517 assert_equal NUM_REV, @repository.changesets.count
527 518 ['', ' ', nil].each do |r|
528 519 assert_nil @repository.find_changeset_by_name(r)
529 520 end
530 521 end
531 522
532 523 def test_identifier
533 524 assert_equal 0, @repository.changesets.count
534 525 @repository.fetch_changesets
535 526 @project.reload
536 527 assert_equal NUM_REV, @repository.changesets.count
537 528 c = @repository.changesets.find_by_revision(
538 529 '7234cb2750b63f47bff735edc50a1c0a433c2518')
539 530 assert_equal c.scmid, c.identifier
540 531 end
541 532
542 533 def test_format_identifier
543 534 assert_equal 0, @repository.changesets.count
544 535 @repository.fetch_changesets
545 536 @project.reload
546 537 assert_equal NUM_REV, @repository.changesets.count
547 538 c = @repository.changesets.find_by_revision(
548 539 '7234cb2750b63f47bff735edc50a1c0a433c2518')
549 540 assert_equal '7234cb27', c.format_identifier
550 541 end
551 542
552 543 def test_activities
553 544 c = Changeset.new(:repository => @repository,
554 545 :committed_on => Time.now,
555 546 :revision => 'abc7234cb2750b63f47bff735edc50a1c0a433c2',
556 547 :scmid => 'abc7234cb2750b63f47bff735edc50a1c0a433c2',
557 548 :comments => 'test')
558 549 assert c.event_title.include?('abc7234c:')
559 550 assert_equal 'abc7234cb2750b63f47bff735edc50a1c0a433c2', c.event_url[:rev]
560 551 end
561 552
562 553 def test_log_utf8
563 554 assert_equal 0, @repository.changesets.count
564 555 @repository.fetch_changesets
565 556 @project.reload
566 557 assert_equal NUM_REV, @repository.changesets.count
567 558 c = @repository.changesets.find_by_revision(
568 559 'ed5bb786bbda2dee66a2d50faf51429dbc043a7b')
569 560 assert_equal "#{FELIX_HEX} <felix@fachschaften.org>", c.committer
570 561 end
571 562
572 563 def test_previous
573 564 assert_equal 0, @repository.changesets.count
574 565 @repository.fetch_changesets
575 566 @project.reload
576 567 assert_equal NUM_REV, @repository.changesets.count
577 568 %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1|
578 569 changeset = @repository.find_changeset_by_name(r1)
579 570 %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2|
580 571 assert_equal @repository.find_changeset_by_name(r2), changeset.previous
581 572 end
582 573 end
583 574 end
584 575
585 576 def test_previous_nil
586 577 assert_equal 0, @repository.changesets.count
587 578 @repository.fetch_changesets
588 579 @project.reload
589 580 assert_equal NUM_REV, @repository.changesets.count
590 581 %w|7234cb2750b63f47bff735edc50a1c0a433c2518 7234cb275|.each do |r1|
591 582 changeset = @repository.find_changeset_by_name(r1)
592 583 assert_nil changeset.previous
593 584 end
594 585 end
595 586
596 587 def test_next
597 588 assert_equal 0, @repository.changesets.count
598 589 @repository.fetch_changesets
599 590 @project.reload
600 591 assert_equal NUM_REV, @repository.changesets.count
601 592 %w|64f1f3e89ad1cb57976ff0ad99a107012ba3481d 64f1f3e89ad1|.each do |r2|
602 593 changeset = @repository.find_changeset_by_name(r2)
603 594 %w|1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127 1ca7f5ed|.each do |r1|
604 595 assert_equal @repository.find_changeset_by_name(r1), changeset.next
605 596 end
606 597 end
607 598 end
608 599
609 600 def test_next_nil
610 601 assert_equal 0, @repository.changesets.count
611 602 @repository.fetch_changesets
612 603 @project.reload
613 604 assert_equal NUM_REV, @repository.changesets.count
614 605 %w|2a682156a3b6e77a8bf9cd4590e8db757f3c6c78 2a682156a3b6e77a|.each do |r1|
615 606 changeset = @repository.find_changeset_by_name(r1)
616 607 assert_nil changeset.next
617 608 end
618 609 end
619 610 else
620 611 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
621 612 def test_fake; assert true end
622 613 end
623 614 end
General Comments 0
You need to be logged in to leave comments. Login now