@@ -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 |
: |
|
|
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 => |
|
|
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 |
: |
|
|
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. |
|
|
70 | assert_equal true, repository.report_last_commit | |
|
71 | 71 | |
|
72 | 72 | put :update, :id => repository.id, |
|
73 | 73 | :repository => { |
|
74 |
|
|
|
74 | :report_last_commit => '0' | |
|
75 | 75 | } |
|
76 | 76 | assert_response 302 |
|
77 | 77 | repo2 = Repository.find(repository.id) |
|
78 |
assert_equal false, repo2. |
|
|
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 |
|
|
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