Revision 1297:0a574315af3e .svn/pristine/74

View differences:

.svn/pristine/74/74843b67cd322d5f1fa051b32a2e14dd00a341ce.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require File.expand_path('../../test_helper', __FILE__)
19

  
20
class RepositoriesGitControllerTest < ActionController::TestCase
21
  tests RepositoriesController
22

  
23
  fixtures :projects, :users, :roles, :members, :member_roles,
24
           :repositories, :enabled_modules
25

  
26
  REPOSITORY_PATH = Rails.root.join('tmp/test/git_repository').to_s
27
  REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
28
  PRJ_ID     = 3
29
  CHAR_1_HEX = "\xc3\x9c"
30
  NUM_REV = 28
31

  
32
  ## Git, Mercurial and CVS path encodings are binary.
33
  ## Subversion supports URL encoding for path.
34
  ## Redmine Mercurial adapter and extension use URL encoding.
35
  ## Git accepts only binary path in command line parameter.
36
  ## So, there is no way to use binary command line parameter in JRuby.
37
  JRUBY_SKIP     = (RUBY_PLATFORM == 'java')
38
  JRUBY_SKIP_STR = "TODO: This test fails in JRuby"
39

  
40
  def setup
41
    @ruby19_non_utf8_pass =
42
      (RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8')
43

  
44
    User.current = nil
45
    @project    = Project.find(PRJ_ID)
46
    @repository = Repository::Git.create(
47
                      :project       => @project,
48
                      :url           => REPOSITORY_PATH,
49
                      :path_encoding => 'ISO-8859-1'
50
                      )
51
    assert @repository
52
    @char_1        = CHAR_1_HEX.dup
53
    if @char_1.respond_to?(:force_encoding)
54
      @char_1.force_encoding('UTF-8')
55
    end
56
  end
57

  
58
  def test_create_and_update
59
    @request.session[:user_id] = 1
60
    assert_difference 'Repository.count' do
61
      post :create, :project_id => 'subproject1',
62
                    :repository_scm => 'Git',
63
                    :repository => {
64
                       :url => '/test',
65
                       :is_default => '0',
66
                       :identifier => 'test-create',
67
                       :extra_report_last_commit => '1',
68
                     }
69
    end
70
    assert_response 302
71
    repository = Repository.first(:order => 'id DESC')
72
    assert_kind_of Repository::Git, repository
73
    assert_equal '/test', repository.url
74
    assert_equal true, repository.extra_report_last_commit
75

  
76
    put :update, :id => repository.id,
77
                 :repository => {
78
                     :extra_report_last_commit => '0'
79
                 }
80
    assert_response 302
81
    repo2 = Repository.find(repository.id)
82
    assert_equal false, repo2.extra_report_last_commit
83
  end
84

  
85
  if File.directory?(REPOSITORY_PATH)
86
    ## Ruby uses ANSI api to fork a process on Windows.
87
    ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem
88
    ## and these are incompatible with ASCII.
89
    ## Git for Windows (msysGit) changed internal API from ANSI to Unicode in 1.7.10
90
    ## http://code.google.com/p/msysgit/issues/detail?id=80
91
    ## So, Latin-1 path tests fail on Japanese Windows
92
    WINDOWS_PASS = (Redmine::Platform.mswin? &&
93
                         Redmine::Scm::Adapters::GitAdapter.client_version_above?([1, 7, 10]))
94
    WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10"
95

  
96
    def test_get_new
97
      @request.session[:user_id] = 1
98
      @project.repository.destroy
99
      get :new, :project_id => 'subproject1', :repository_scm => 'Git'
100
      assert_response :success
101
      assert_template 'new'
102
      assert_kind_of Repository::Git, assigns(:repository)
103
      assert assigns(:repository).new_record?
104
    end
105

  
106
    def test_browse_root
107
      assert_equal 0, @repository.changesets.count
108
      @repository.fetch_changesets
109
      @project.reload
110
      assert_equal NUM_REV, @repository.changesets.count
111

  
112
      get :show, :id => PRJ_ID
113
      assert_response :success
114
      assert_template 'show'
115
      assert_not_nil assigns(:entries)
116
      assert_equal 9, assigns(:entries).size
117
      assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
118
      assert assigns(:entries).detect {|e| e.name == 'this_is_a_really_long_and_verbose_directory_name' && e.kind == 'dir'}
119
      assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
120
      assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
121
      assert assigns(:entries).detect {|e| e.name == 'copied_README' && e.kind == 'file'}
122
      assert assigns(:entries).detect {|e| e.name == 'new_file.txt' && e.kind == 'file'}
123
      assert assigns(:entries).detect {|e| e.name == 'renamed_test.txt' && e.kind == 'file'}
124
      assert assigns(:entries).detect {|e| e.name == 'filemane with spaces.txt' && e.kind == 'file'}
125
      assert assigns(:entries).detect {|e| e.name == ' filename with a leading space.txt ' && e.kind == 'file'}
126
      assert_not_nil assigns(:changesets)
127
      assert assigns(:changesets).size > 0
128
    end
129

  
130
    def test_browse_branch
131
      assert_equal 0, @repository.changesets.count
132
      @repository.fetch_changesets
133
      @project.reload
134
      assert_equal NUM_REV, @repository.changesets.count
135
      get :show, :id => PRJ_ID, :rev => 'test_branch'
136
      assert_response :success
137
      assert_template 'show'
138
      assert_not_nil assigns(:entries)
139
      assert_equal 4, assigns(:entries).size
140
      assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
141
      assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
142
      assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
143
      assert assigns(:entries).detect {|e| e.name == 'test.txt' && e.kind == 'file'}
144
      assert_not_nil assigns(:changesets)
145
      assert assigns(:changesets).size > 0
146
    end
147

  
148
    def test_browse_tag
149
      assert_equal 0, @repository.changesets.count
150
      @repository.fetch_changesets
151
      @project.reload
152
      assert_equal NUM_REV, @repository.changesets.count
153
       [
154
        "tag00.lightweight",
155
        "tag01.annotated",
156
       ].each do |t1|
157
        get :show, :id => PRJ_ID, :rev => t1
158
        assert_response :success
159
        assert_template 'show'
160
        assert_not_nil assigns(:entries)
161
        assert assigns(:entries).size > 0
162
        assert_not_nil assigns(:changesets)
163
        assert assigns(:changesets).size > 0
164
      end
165
    end
166

  
167
    def test_browse_directory
168
      assert_equal 0, @repository.changesets.count
169
      @repository.fetch_changesets
170
      @project.reload
171
      assert_equal NUM_REV, @repository.changesets.count
172
      get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param]
173
      assert_response :success
174
      assert_template 'show'
175
      assert_not_nil assigns(:entries)
176
      assert_equal ['edit.png'], assigns(:entries).collect(&:name)
177
      entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
178
      assert_not_nil entry
179
      assert_equal 'file', entry.kind
180
      assert_equal 'images/edit.png', entry.path
181
      assert_not_nil assigns(:changesets)
182
      assert assigns(:changesets).size > 0
183
    end
184

  
185
    def test_browse_at_given_revision
186
      assert_equal 0, @repository.changesets.count
187
      @repository.fetch_changesets
188
      @project.reload
189
      assert_equal NUM_REV, @repository.changesets.count
190
      get :show, :id => PRJ_ID, :path => repository_path_hash(['images'])[:param],
191
          :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518'
192
      assert_response :success
193
      assert_template 'show'
194
      assert_not_nil assigns(:entries)
195
      assert_equal ['delete.png'], assigns(:entries).collect(&:name)
196
      assert_not_nil assigns(:changesets)
197
      assert assigns(:changesets).size > 0
198
    end
199

  
200
    def test_changes
201
      get :changes, :id => PRJ_ID,
202
          :path => repository_path_hash(['images', 'edit.png'])[:param]
203
      assert_response :success
204
      assert_template 'changes'
205
      assert_tag :tag => 'h2', :content => 'edit.png'
206
    end
207

  
208
    def test_entry_show
209
      get :entry, :id => PRJ_ID,
210
          :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param]
211
      assert_response :success
212
      assert_template 'entry'
213
      # Line 19
214
      assert_tag :tag => 'th',
215
                 :content => '11',
216
                 :attributes => { :class => 'line-num' },
217
                 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
218
    end
219

  
220
    def test_entry_show_latin_1
221
      if @ruby19_non_utf8_pass
222
        puts_ruby19_non_utf8_pass()
223
      elsif WINDOWS_PASS
224
        puts WINDOWS_SKIP_STR
225
      elsif JRUBY_SKIP
226
        puts JRUBY_SKIP_STR
227
      else
228
        with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
229
          ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1|
230
            get :entry, :id => PRJ_ID,
231
                :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param],
232
                :rev => r1
233
            assert_response :success
234
            assert_template 'entry'
235
            assert_tag :tag => 'th',
236
                   :content => '1',
237
                   :attributes => { :class => 'line-num' },
238
                   :sibling => { :tag => 'td',
239
                                 :content => /test-#{@char_1}.txt/ }
240
          end
241
        end
242
      end
243
    end
244

  
245
    def test_entry_download
246
      get :entry, :id => PRJ_ID,
247
          :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param],
248
          :format => 'raw'
249
      assert_response :success
250
      # File content
251
      assert @response.body.include?('WITHOUT ANY WARRANTY')
252
    end
253

  
254
    def test_directory_entry
255
      get :entry, :id => PRJ_ID,
256
          :path => repository_path_hash(['sources'])[:param]
257
      assert_response :success
258
      assert_template 'show'
259
      assert_not_nil assigns(:entry)
260
      assert_equal 'sources', assigns(:entry).name
261
    end
262

  
263
    def test_diff
264
      assert_equal true, @repository.is_default
265
      assert_nil @repository.identifier
266
      assert_equal 0, @repository.changesets.count
267
      @repository.fetch_changesets
268
      @project.reload
269
      assert_equal NUM_REV, @repository.changesets.count
270
      # Full diff of changeset 2f9c0091
271
      ['inline', 'sbs'].each do |dt|
272
        get :diff,
273
            :id   => PRJ_ID,
274
            :rev  => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7',
275
            :type => dt
276
        assert_response :success
277
        assert_template 'diff'
278
        # Line 22 removed
279
        assert_tag :tag => 'th',
280
                   :content => /22/,
281
                   :sibling => { :tag => 'td',
282
                                 :attributes => { :class => /diff_out/ },
283
                                 :content => /def remove/ }
284
        assert_tag :tag => 'h2', :content => /2f9c0091/
285
      end
286
    end
287

  
288
    def test_diff_with_rev_and_path
289
      assert_equal 0, @repository.changesets.count
290
      @repository.fetch_changesets
291
      @project.reload
292
      assert_equal NUM_REV, @repository.changesets.count
293
      with_settings :diff_max_lines_displayed => 1000 do
294
        # Full diff of changeset 2f9c0091
295
        ['inline', 'sbs'].each do |dt|
296
          get :diff,
297
              :id   => PRJ_ID,
298
              :rev  => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7',
299
              :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param],
300
              :type => dt
301
          assert_response :success
302
          assert_template 'diff'
303
          # Line 22 removed
304
          assert_tag :tag => 'th',
305
                     :content => '22',
306
                     :sibling => { :tag => 'td',
307
                                   :attributes => { :class => /diff_out/ },
308
                                   :content => /def remove/ }
309
          assert_tag :tag => 'h2', :content => /2f9c0091/
310
        end
311
      end
312
    end
313

  
314
    def test_diff_truncated
315
      assert_equal 0, @repository.changesets.count
316
      @repository.fetch_changesets
317
      @project.reload
318
      assert_equal NUM_REV, @repository.changesets.count
319

  
320
      with_settings :diff_max_lines_displayed => 5 do
321
        # Truncated diff of changeset 2f9c0091
322
        with_cache do
323
          with_settings :default_language => 'en' do
324
            get :diff, :id   => PRJ_ID, :type => 'inline',
325
                :rev  => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
326
            assert_response :success
327
            assert @response.body.include?("... This diff was truncated")
328
          end
329
          with_settings :default_language => 'fr' do
330
            get :diff, :id   => PRJ_ID, :type => 'inline',
331
                :rev  => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
332
            assert_response :success
333
            assert ! @response.body.include?("... This diff was truncated")
334
            assert @response.body.include?("... Ce diff")
335
          end
336
        end
337
      end
338
    end
339

  
340
    def test_diff_two_revs
341
      assert_equal 0, @repository.changesets.count
342
      @repository.fetch_changesets
343
      @project.reload
344
      assert_equal NUM_REV, @repository.changesets.count
345
      ['inline', 'sbs'].each do |dt|
346
        get :diff,
347
            :id     => PRJ_ID,
348
            :rev    => '61b685fbe55ab05b5ac68402d5720c1a6ac973d1',
349
            :rev_to => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7',
350
            :type   => dt
351
        assert_response :success
352
        assert_template 'diff'
353
        diff = assigns(:diff)
354
        assert_not_nil diff
355
        assert_tag :tag => 'h2', :content => /2f9c0091:61b685fb/
356
        assert_tag :tag => "form",
357
                   :attributes => {
358
                     :action => "/projects/subproject1/repository/revisions/" +
359
                                   "61b685fbe55ab05b5ac68402d5720c1a6ac973d1/diff"
360
                   }
361
        assert_tag :tag => 'input',
362
                   :attributes => {
363
                     :id => "rev_to",
364
                     :name => "rev_to",
365
                     :type => "hidden",
366
                     :value => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
367
                   }
368
      end
369
    end
370

  
371
    def test_diff_path_in_subrepo
372
      repo = Repository::Git.create(
373
                      :project       => @project,
374
                      :url           => REPOSITORY_PATH,
375
                      :identifier => 'test-diff-path',
376
                      :path_encoding => 'ISO-8859-1'
377
                      );
378
      assert repo
379
      assert_equal false, repo.is_default
380
      assert_equal 'test-diff-path', repo.identifier
381
      get :diff,
382
          :id     => PRJ_ID,
383
          :repository_id => 'test-diff-path',
384
          :rev    => '61b685fbe55ab05b',
385
          :rev_to => '2f9c0091c754a91a',
386
          :type   => 'inline'
387
      assert_response :success
388
      assert_template 'diff'
389
      diff = assigns(:diff)
390
      assert_not_nil diff
391
      assert_tag :tag => "form",
392
                 :attributes => {
393
                   :action => "/projects/subproject1/repository/test-diff-path/" + 
394
                                "revisions/61b685fbe55ab05b/diff"
395
                 }
396
      assert_tag :tag => 'input',
397
                 :attributes => {
398
                   :id => "rev_to",
399
                   :name => "rev_to",
400
                   :type => "hidden",
401
                   :value => '2f9c0091c754a91a'
402
                 }
403
    end
404

  
405
    def test_diff_latin_1
406
      if @ruby19_non_utf8_pass
407
        puts_ruby19_non_utf8_pass()
408
      else
409
        with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
410
          ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1|
411
            ['inline', 'sbs'].each do |dt|
412
              get :diff, :id => PRJ_ID, :rev => r1, :type => dt
413
              assert_response :success
414
              assert_template 'diff'
415
              assert_tag :tag => 'thead',
416
                         :descendant => {
417
                           :tag => 'th',
418
                           :attributes => { :class => 'filename' } ,
419
                           :content => /latin-1-dir\/test-#{@char_1}.txt/ ,
420
                          },
421
                         :sibling => {
422
                           :tag => 'tbody',
423
                           :descendant => {
424
                              :tag => 'td',
425
                              :attributes => { :class => /diff_in/ },
426
                              :content => /test-#{@char_1}.txt/
427
                           }
428
                         }
429
            end
430
          end
431
        end
432
      end
433
    end
434

  
435
    def test_diff_should_show_filenames
436
      get :diff, :id => PRJ_ID, :rev => 'deff712f05a90d96edbd70facc47d944be5897e3', :type => 'inline'
437
      assert_response :success
438
      assert_template 'diff'
439
      # modified file
440
      assert_select 'th.filename', :text => 'sources/watchers_controller.rb'
441
      # deleted file
442
      assert_select 'th.filename', :text => 'test.txt'
443
    end
444

  
445
    def test_save_diff_type
446
      user1 = User.find(1)
447
      user1.pref[:diff_type] = nil
448
      user1.preference.save
449
      user = User.find(1)
450
      assert_nil user.pref[:diff_type]
451

  
452
      @request.session[:user_id] = 1 # admin
453
      get :diff,
454
          :id   => PRJ_ID,
455
          :rev  => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
456
      assert_response :success
457
      assert_template 'diff'
458
      user.reload
459
      assert_equal "inline", user.pref[:diff_type]
460
      get :diff,
461
          :id   => PRJ_ID,
462
          :rev  => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7',
463
          :type => 'sbs'
464
      assert_response :success
465
      assert_template 'diff'
466
      user.reload
467
      assert_equal "sbs", user.pref[:diff_type]
468
    end
469

  
470
    def test_annotate
471
      get :annotate, :id => PRJ_ID,
472
          :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param]
473
      assert_response :success
474
      assert_template 'annotate'
475

  
476
      # Line 23, changeset 2f9c0091
477
      assert_select 'tr' do
478
        assert_select 'th.line-num', :text => '23'
479
        assert_select 'td.revision', :text => /2f9c0091/
480
        assert_select 'td.author', :text => 'jsmith'
481
        assert_select 'td', :text => /remove_watcher/
482
      end
483
    end
484

  
485
    def test_annotate_at_given_revision
486
      assert_equal 0, @repository.changesets.count
487
      @repository.fetch_changesets
488
      @project.reload
489
      assert_equal NUM_REV, @repository.changesets.count
490
      get :annotate, :id => PRJ_ID, :rev => 'deff7',
491
          :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param]
492
      assert_response :success
493
      assert_template 'annotate'
494
      assert_tag :tag => 'h2', :content => /@ deff712f/
495
    end
496

  
497
    def test_annotate_binary_file
498
      get :annotate, :id => PRJ_ID,
499
          :path => repository_path_hash(['images', 'edit.png'])[:param]
500
      assert_response 500
501
      assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ },
502
                              :content => /cannot be annotated/
503
    end
504

  
505
    def test_annotate_error_when_too_big
506
      with_settings :file_max_size_displayed => 1 do
507
        get :annotate, :id => PRJ_ID,
508
            :path => repository_path_hash(['sources', 'watchers_controller.rb'])[:param],
509
            :rev => 'deff712f'
510
        assert_response 500
511
        assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ },
512
                                :content => /exceeds the maximum text file size/
513

  
514
        get :annotate, :id => PRJ_ID,
515
            :path => repository_path_hash(['README'])[:param],
516
            :rev => '7234cb2'
517
        assert_response :success
518
        assert_template 'annotate'
519
      end
520
    end
521

  
522
    def test_annotate_latin_1
523
      if @ruby19_non_utf8_pass
524
        puts_ruby19_non_utf8_pass()
525
      elsif WINDOWS_PASS
526
        puts WINDOWS_SKIP_STR
527
      elsif JRUBY_SKIP
528
        puts JRUBY_SKIP_STR
529
      else
530
        with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do
531
          ['57ca437c', '57ca437c0acbbcb749821fdf3726a1367056d364'].each do |r1|
532
            get :annotate, :id => PRJ_ID,
533
                :path => repository_path_hash(['latin-1-dir', "test-#{@char_1}.txt"])[:param],
534
                :rev => r1
535
            assert_tag :tag => 'th',
536
                       :content => '1',
537
                       :attributes => { :class => 'line-num' },
538
                       :sibling => { :tag => 'td',
539
                                     :content => /test-#{@char_1}.txt/ }
540
          end
541
        end
542
      end
543
    end
544

  
545
    def test_revisions
546
      assert_equal 0, @repository.changesets.count
547
      @repository.fetch_changesets
548
      @project.reload
549
      assert_equal NUM_REV, @repository.changesets.count
550
      get :revisions, :id => PRJ_ID
551
      assert_response :success
552
      assert_template 'revisions'
553
      assert_tag :tag => 'form',
554
                 :attributes => {
555
                   :method => 'get',
556
                   :action => '/projects/subproject1/repository/revision'
557
                 }
558
    end
559

  
560
    def test_revision
561
      assert_equal 0, @repository.changesets.count
562
      @repository.fetch_changesets
563
      @project.reload
564
      assert_equal NUM_REV, @repository.changesets.count
565
      ['61b685fbe55ab05b5ac68402d5720c1a6ac973d1', '61b685f'].each do |r|
566
        get :revision, :id => PRJ_ID, :rev => r
567
        assert_response :success
568
        assert_template 'revision'
569
      end
570
    end
571

  
572
    def test_empty_revision
573
      assert_equal 0, @repository.changesets.count
574
      @repository.fetch_changesets
575
      @project.reload
576
      assert_equal NUM_REV, @repository.changesets.count
577
      ['', ' ', nil].each do |r|
578
        get :revision, :id => PRJ_ID, :rev => r
579
        assert_response 404
580
        assert_error_tag :content => /was not found/
581
      end
582
    end
583

  
584
    def test_destroy_valid_repository
585
      @request.session[:user_id] = 1 # admin
586
      assert_equal 0, @repository.changesets.count
587
      @repository.fetch_changesets
588
      @project.reload
589
      assert_equal NUM_REV, @repository.changesets.count
590

  
591
      assert_difference 'Repository.count', -1 do
592
        delete :destroy, :id => @repository.id
593
      end
594
      assert_response 302
595
      @project.reload
596
      assert_nil @project.repository
597
    end
598

  
599
    def test_destroy_invalid_repository
600
      @request.session[:user_id] = 1 # admin
601
      @project.repository.destroy
602
      @repository = Repository::Git.create!(
603
                      :project       => @project,
604
                      :url           => "/invalid",
605
                      :path_encoding => 'ISO-8859-1'
606
                      )
607
      @repository.fetch_changesets
608
      @repository.reload
609
      assert_equal 0, @repository.changesets.count
610

  
611
      assert_difference 'Repository.count', -1 do
612
        delete :destroy, :id => @repository.id
613
      end
614
      assert_response 302
615
      @project.reload
616
      assert_nil @project.repository
617
    end
618

  
619
    private
620

  
621
    def puts_ruby19_non_utf8_pass
622
      puts "TODO: This test fails in Ruby 1.9 " +
623
           "and Encoding.default_external is not UTF-8. " +
624
           "Current value is '#{Encoding.default_external.to_s}'"
625
    end
626
  else
627
    puts "Git test repository NOT FOUND. Skipping functional tests !!!"
628
    def test_fake; assert true end
629
  end
630

  
631
  private
632
  def with_cache(&block)
633
    before = ActionController::Base.perform_caching
634
    ActionController::Base.perform_caching = true
635
    block.call
636
    ActionController::Base.perform_caching = before
637
  end
638
end
.svn/pristine/74/748d85e16c7b39630792941f331af582630a591a.svn-base
1
/* ***** BEGIN LICENSE BLOCK *****
2
 * This file is part of DotClear.
3
 * Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All
4
 * rights reserved.
5
 *
6
 * DotClear is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
10
 * 
11
 * DotClear is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 * 
16
 * You should have received a copy of the GNU General Public License
17
 * along with DotClear; if not, write to the Free Software
18
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
 *
20
 * ***** END LICENSE BLOCK *****
21
*/
22

  
23
/* Modified by JP LANG for textile formatting */
24

  
25
function jsToolBar(textarea) {
26
	if (!document.createElement) { return; }
27
	
28
	if (!textarea) { return; }
29
	
30
	if ((typeof(document["selection"]) == "undefined")
31
	&& (typeof(textarea["setSelectionRange"]) == "undefined")) {
32
		return;
33
	}
34
	
35
	this.textarea = textarea;
36
	
37
	this.editor = document.createElement('div');
38
	this.editor.className = 'jstEditor';
39
	
40
	this.textarea.parentNode.insertBefore(this.editor,this.textarea);
41
	this.editor.appendChild(this.textarea);
42
	
43
	this.toolbar = document.createElement("div");
44
	this.toolbar.className = 'jstElements';
45
	this.editor.parentNode.insertBefore(this.toolbar,this.editor);
46
	
47
	// Dragable resizing
48
	if (this.editor.addEventListener && navigator.appVersion.match(/\bMSIE\b/))
49
	{
50
		this.handle = document.createElement('div');
51
		this.handle.className = 'jstHandle';
52
		var dragStart = this.resizeDragStart;
53
		var This = this;
54
		this.handle.addEventListener('mousedown',function(event) { dragStart.call(This,event); },false);
55
		// fix memory leak in Firefox (bug #241518)
56
		window.addEventListener('unload',function() { 
57
				var del = This.handle.parentNode.removeChild(This.handle);
58
				delete(This.handle);
59
		},false);
60
		
61
		this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling);
62
	}
63
	
64
	this.context = null;
65
	this.toolNodes = {}; // lorsque la toolbar est dessinée , cet objet est garni 
66
					// de raccourcis vers les éléments DOM correspondants aux outils.
67
}
68

  
69
function jsButton(title, fn, scope, className) {
70
    if(typeof jsToolBar.strings == 'undefined') {
71
      this.title = title || null;
72
    } else {
73
      this.title = jsToolBar.strings[title] || title || null;
74
    }
75
	this.fn = fn || function(){};
76
	this.scope = scope || null;
77
	this.className = className || null;
78
}
79
jsButton.prototype.draw = function() {
80
	if (!this.scope) return null;
81
	
82
	var button = document.createElement('button');
83
	button.setAttribute('type','button');
84
	button.tabIndex = 200;
85
	if (this.className) button.className = this.className;
86
	button.title = this.title;
87
	var span = document.createElement('span');
88
	span.appendChild(document.createTextNode(this.title));
89
	button.appendChild(span);
90
	
91
	if (this.icon != undefined) {
92
		button.style.backgroundImage = 'url('+this.icon+')';
93
	}
94
	if (typeof(this.fn) == 'function') {
95
		var This = this;
96
		button.onclick = function() { try { This.fn.apply(This.scope, arguments) } catch (e) {} return false; };
97
	}
98
	return button;
99
}
100

  
101
function jsSpace(id) {
102
	this.id = id || null;
103
	this.width = null;
104
}
105
jsSpace.prototype.draw = function() {
106
	var span = document.createElement('span');
107
	if (this.id) span.id = this.id;
108
	span.appendChild(document.createTextNode(String.fromCharCode(160)));
109
	span.className = 'jstSpacer';
110
	if (this.width) span.style.marginRight = this.width+'px';
111
	
112
	return span;
113
} 
114

  
115
function jsCombo(title, options, scope, fn, className) {
116
	this.title = title || null;
117
	this.options = options || null;
118
	this.scope = scope || null;
119
	this.fn = fn || function(){};
120
	this.className = className || null;
121
}
122
jsCombo.prototype.draw = function() {
123
	if (!this.scope || !this.options) return null;
124

  
125
	var select = document.createElement('select');
126
	if (this.className) select.className = className;
127
	select.title = this.title;
128
	
129
	for (var o in this.options) {
130
		//var opt = this.options[o];
131
		var option = document.createElement('option');
132
		option.value = o;
133
		option.appendChild(document.createTextNode(this.options[o]));
134
		select.appendChild(option);
135
	}
136

  
137
	var This = this;
138
	select.onchange = function() {
139
		try { 
140
			This.fn.call(This.scope, this.value);
141
		} catch (e) { alert(e); }
142

  
143
		return false;
144
	}
145

  
146
	return select;
147
}
148

  
149

  
150
jsToolBar.prototype = {
151
	base_url: '',
152
	mode: 'wiki',
153
	elements: {},
154
	help_link: '',
155
	
156
	getMode: function() {
157
		return this.mode;
158
	},
159
	
160
	setMode: function(mode) {
161
		this.mode = mode || 'wiki';
162
	},
163
	
164
	switchMode: function(mode) {
165
		mode = mode || 'wiki';
166
		this.draw(mode);
167
	},
168
	
169
	setHelpLink: function(link) {
170
		this.help_link = link;
171
	},
172
	
173
	button: function(toolName) {
174
		var tool = this.elements[toolName];
175
		if (typeof tool.fn[this.mode] != 'function') return null;
176
		var b = new jsButton(tool.title, tool.fn[this.mode], this, 'jstb_'+toolName);
177
		if (tool.icon != undefined) b.icon = tool.icon;
178
		return b;
179
	},
180
	space: function(toolName) {
181
		var tool = new jsSpace(toolName)
182
		if (this.elements[toolName].width !== undefined)
183
			tool.width = this.elements[toolName].width;
184
		return tool;
185
	},
186
	combo: function(toolName) {
187
		var tool = this.elements[toolName];
188
		var length = tool[this.mode].list.length;
189

  
190
		if (typeof tool[this.mode].fn != 'function' || length == 0) {
191
			return null;
192
		} else {
193
			var options = {};
194
			for (var i=0; i < length; i++) {
195
				var opt = tool[this.mode].list[i];
196
				options[opt] = tool.options[opt];
197
			}
198
			return new jsCombo(tool.title, options, this, tool[this.mode].fn);
199
		}
200
	},
201
	draw: function(mode) {
202
		this.setMode(mode);
203
		
204
		// Empty toolbar
205
		while (this.toolbar.hasChildNodes()) {
206
			this.toolbar.removeChild(this.toolbar.firstChild)
207
		}
208
		this.toolNodes = {}; // vide les raccourcis DOM/**/
209

  
210
		var h = document.createElement('div');
211
		h.className = 'help'
212
		h.innerHTML = this.help_link;
213
		'<a href="/help/wiki_syntax.html" onclick="window.open(\'/help/wiki_syntax.html\', \'\', \'resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\'); return false;">Aide</a>';
214
		this.toolbar.appendChild(h);
215

  
216
		// Draw toolbar elements
217
		var b, tool, newTool;
218
		
219
		for (var i in this.elements) {
220
			b = this.elements[i];
221

  
222
			var disabled =
223
			b.type == undefined || b.type == ''
224
			|| (b.disabled != undefined && b.disabled)
225
			|| (b.context != undefined && b.context != null && b.context != this.context);
226
			
227
			if (!disabled && typeof this[b.type] == 'function') {
228
				tool = this[b.type](i);
229
				if (tool) newTool = tool.draw();
230
				if (newTool) {
231
					this.toolNodes[i] = newTool; //mémorise l'accès DOM pour usage éventuel ultérieur
232
					this.toolbar.appendChild(newTool);
233
				}
234
			}
235
		}
236
	},
237
	
238
	singleTag: function(stag,etag) {
239
		stag = stag || null;
240
		etag = etag || stag;
241
		
242
		if (!stag || !etag) { return; }
243
		
244
		this.encloseSelection(stag,etag);
245
	},
246
	
247
	encloseLineSelection: function(prefix, suffix, fn) {
248
		this.textarea.focus();
249
		
250
		prefix = prefix || '';
251
		suffix = suffix || '';
252
		
253
		var start, end, sel, scrollPos, subst, res;
254
		
255
		if (typeof(document["selection"]) != "undefined") {
256
			sel = document.selection.createRange().text;
257
		} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
258
			start = this.textarea.selectionStart;
259
			end = this.textarea.selectionEnd;
260
			scrollPos = this.textarea.scrollTop;
261
			// go to the start of the line
262
			start = this.textarea.value.substring(0, start).replace(/[^\r\n]*$/g,'').length;
263
			// go to the end of the line
264
            end = this.textarea.value.length - this.textarea.value.substring(end, this.textarea.value.length).replace(/^[^\r\n]*/, '').length;
265
			sel = this.textarea.value.substring(start, end);
266
		}
267
		
268
		if (sel.match(/ $/)) { // exclude ending space char, if any
269
			sel = sel.substring(0, sel.length - 1);
270
			suffix = suffix + " ";
271
		}
272
		
273
		if (typeof(fn) == 'function') {
274
			res = (sel) ? fn.call(this,sel) : fn('');
275
		} else {
276
			res = (sel) ? sel : '';
277
		}
278
		
279
		subst = prefix + res + suffix;
280
		
281
		if (typeof(document["selection"]) != "undefined") {
282
			document.selection.createRange().text = subst;
283
			var range = this.textarea.createTextRange();
284
			range.collapse(false);
285
			range.move('character', -suffix.length);
286
			range.select();
287
		} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
288
			this.textarea.value = this.textarea.value.substring(0, start) + subst +
289
			this.textarea.value.substring(end);
290
			if (sel) {
291
				this.textarea.setSelectionRange(start + subst.length, start + subst.length);
292
			} else {
293
				this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
294
			}
295
			this.textarea.scrollTop = scrollPos;
296
		}
297
	},
298
	
299
	encloseSelection: function(prefix, suffix, fn) {
300
		this.textarea.focus();
301
		
302
		prefix = prefix || '';
303
		suffix = suffix || '';
304
		
305
		var start, end, sel, scrollPos, subst, res;
306
		
307
		if (typeof(document["selection"]) != "undefined") {
308
			sel = document.selection.createRange().text;
309
		} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
310
			start = this.textarea.selectionStart;
311
			end = this.textarea.selectionEnd;
312
			scrollPos = this.textarea.scrollTop;
313
			sel = this.textarea.value.substring(start, end);
314
		}
315
		
316
		if (sel.match(/ $/)) { // exclude ending space char, if any
317
			sel = sel.substring(0, sel.length - 1);
318
			suffix = suffix + " ";
319
		}
320
		
321
		if (typeof(fn) == 'function') {
322
			res = (sel) ? fn.call(this,sel) : fn('');
323
		} else {
324
			res = (sel) ? sel : '';
325
		}
326
		
327
		subst = prefix + res + suffix;
328
		
329
		if (typeof(document["selection"]) != "undefined") {
330
			document.selection.createRange().text = subst;
331
			var range = this.textarea.createTextRange();
332
			range.collapse(false);
333
			range.move('character', -suffix.length);
334
			range.select();
335
//			this.textarea.caretPos -= suffix.length;
336
		} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
337
			this.textarea.value = this.textarea.value.substring(0, start) + subst +
338
			this.textarea.value.substring(end);
339
			if (sel) {
340
				this.textarea.setSelectionRange(start + subst.length, start + subst.length);
341
			} else {
342
				this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
343
			}
344
			this.textarea.scrollTop = scrollPos;
345
		}
346
	},
347
	
348
	stripBaseURL: function(url) {
349
		if (this.base_url != '') {
350
			var pos = url.indexOf(this.base_url);
351
			if (pos == 0) {
352
				url = url.substr(this.base_url.length);
353
			}
354
		}
355
		
356
		return url;
357
	}
358
};
359

  
360
/** Resizer
361
-------------------------------------------------------- */
362
jsToolBar.prototype.resizeSetStartH = function() {
363
	this.dragStartH = this.textarea.offsetHeight + 0;
364
};
365
jsToolBar.prototype.resizeDragStart = function(event) {
366
	var This = this;
367
	this.dragStartY = event.clientY;
368
	this.resizeSetStartH();
369
	document.addEventListener('mousemove', this.dragMoveHdlr=function(event){This.resizeDragMove(event);}, false);
370
	document.addEventListener('mouseup', this.dragStopHdlr=function(event){This.resizeDragStop(event);}, false);
371
};
372

  
373
jsToolBar.prototype.resizeDragMove = function(event) {
374
	this.textarea.style.height = (this.dragStartH+event.clientY-this.dragStartY)+'px';
375
};
376

  
377
jsToolBar.prototype.resizeDragStop = function(event) {
378
	document.removeEventListener('mousemove', this.dragMoveHdlr, false);
379
	document.removeEventListener('mouseup', this.dragStopHdlr, false);
380
};
.svn/pristine/74/74ae7eeec3e594a3695dff55d9939fb29749d368.svn-base
1
# encoding: utf-8
2
#
3
# Redmine - project management software
4
# Copyright (C) 2006-2012  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
module AttachmentsHelper
21
  # Displays view/delete links to the attachments of the given object
22
  # Options:
23
  #   :author -- author names are not displayed if set to false
24
  #   :thumbails -- display thumbnails if enabled in settings
25
  def link_to_attachments(container, options = {})
26
    options.assert_valid_keys(:author, :thumbnails)
27

  
28
    if container.attachments.any?
29
      options = {:deletable => container.attachments_deletable?, :author => true}.merge(options)
30
      render :partial => 'attachments/links',
31
        :locals => {:attachments => container.attachments, :options => options, :thumbnails => (options[:thumbnails] && Setting.thumbnails_enabled?)}
32
    end
33
  end
34

  
35
  def render_api_attachment(attachment, api)
36
    api.attachment do
37
      api.id attachment.id
38
      api.filename attachment.filename
39
      api.filesize attachment.filesize
40
      api.content_type attachment.content_type
41
      api.description attachment.description
42
      api.content_url url_for(:controller => 'attachments', :action => 'download', :id => attachment, :filename => attachment.filename, :only_path => false)
43
      api.author(:id => attachment.author.id, :name => attachment.author.name) if attachment.author
44
      api.created_on attachment.created_on
45
    end
46
  end
47
end
.svn/pristine/74/74c1617ddcbb99d4a4a61efc78116f68f0818d6b.svn-base
1
require 'SVG/Graph/Plot'
2

  
3
module SVG
4
  module Graph
5
    # === For creating SVG plots of scalar temporal data
6
    # 
7
    # = Synopsis
8
    # 
9
    #   require 'SVG/Graph/TimeSeriess'
10
    # 
11
    #   # Data sets are x,y pairs
12
    #   data1 = ["6/17/72", 11,    "1/11/72", 7,    "4/13/04 17:31", 11, 
13
    #           "9/11/01", 9,    "9/1/85", 2,    "9/1/88", 1,    "1/15/95", 13]
14
    #   data2 = ["8/1/73", 18,    "3/1/77", 15,    "10/1/98", 4, 
15
    #           "5/1/02", 14,    "3/1/95", 6,    "8/1/91", 12,    "12/1/87", 6, 
16
    #           "5/1/84", 17,    "10/1/80", 12]
17
    #
18
    #   graph = SVG::Graph::TimeSeries.new( {
19
    #     :width => 640,
20
    #     :height => 480,
21
    #     :graph_title => title,
22
    #     :show_graph_title => true,
23
    #     :no_css => true,
24
    #     :key => true,
25
    #     :scale_x_integers => true,
26
    #     :scale_y_integers => true,
27
    #     :min_x_value => 0,
28
    #     :min_y_value => 0,
29
    #     :show_data_labels => true,
30
    #     :show_x_guidelines => true,
31
    #     :show_x_title => true,
32
    #     :x_title => "Time",
33
    #     :show_y_title => true,
34
    #     :y_title => "Ice Cream Cones",
35
    #     :y_title_text_direction => :bt,
36
    #     :stagger_x_labels => true,
37
    #     :x_label_format => "%m/%d/%y",
38
    #   })
39
    #   
40
    #   graph.add_data({
41
    #   	:data => projection
42
    # 	  :title => 'Projected',
43
    #   })
44
    # 
45
    #   graph.add_data({
46
    #   	:data => actual,
47
    # 	  :title => 'Actual',
48
    #   })
49
    #   
50
    #   print graph.burn()
51
    #
52
    # = Description
53
    # 
54
    # Produces a graph of temporal scalar data.
55
    # 
56
    # = Examples
57
    #
58
    # http://www.germane-software/repositories/public/SVG/test/timeseries.rb
59
    # 
60
    # = Notes
61
    # 
62
    # The default stylesheet handles upto 10 data sets, if you
63
    # use more you must create your own stylesheet and add the
64
    # additional settings for the extra data sets. You will know
65
    # if you go over 10 data sets as they will have no style and
66
    # be in black.
67
    #
68
    # Unlike the other types of charts, data sets must contain x,y pairs:
69
    #
70
    #   [ "12:30", 2 ]          # A data set with 1 point: ("12:30",2)
71
    #   [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and 
72
    #                           #                           ("14:20",6)  
73
    #
74
    # Note that multiple data sets within the same chart can differ in length, 
75
    # and that the data in the datasets needn't be in order; they will be ordered
76
    # by the plot along the X-axis.
77
    # 
78
    # The dates must be parseable by ParseDate, but otherwise can be
79
    # any order of magnitude (seconds within the hour, or years)
80
    # 
81
    # = See also
82
    # 
83
    # * SVG::Graph::Graph
84
    # * SVG::Graph::BarHorizontal
85
    # * SVG::Graph::Bar
86
    # * SVG::Graph::Line
87
    # * SVG::Graph::Pie
88
    # * SVG::Graph::Plot
89
    #
90
    # == Author
91
    #
92
    # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
93
    #
94
    # Copyright 2004 Sean E. Russell
95
    # This software is available under the Ruby license[LICENSE.txt]
96
    #
97
    class TimeSeries < Plot
98
      # In addition to the defaults set by Graph::initialize and
99
      # Plot::set_defaults, sets:
100
      # [x_label_format] '%Y-%m-%d %H:%M:%S'
101
      # [popup_format]  '%Y-%m-%d %H:%M:%S'
102
      def set_defaults
103
        super
104
        init_with(
105
          #:max_time_span     => '',
106
          :x_label_format     => '%Y-%m-%d %H:%M:%S',
107
          :popup_format       => '%Y-%m-%d %H:%M:%S'
108
        )
109
      end
110

  
111
      # The format string use do format the X axis labels.
112
      # See Time::strformat
113
      attr_accessor :x_label_format
114
      # Use this to set the spacing between dates on the axis.  The value
115
      # must be of the form 
116
      # "\d+ ?(days|weeks|months|years|hours|minutes|seconds)?"
117
      # 
118
      # EG:
119
      #
120
      #   graph.timescale_divisions = "2 weeks"
121
      #
122
      # will cause the chart to try to divide the X axis up into segments of
123
      # two week periods.
124
      attr_accessor :timescale_divisions
125
      # The formatting used for the popups.  See x_label_format
126
      attr_accessor :popup_format
127

  
128
      # Add data to the plot.
129
      #
130
      #   d1 = [ "12:30", 2 ]          # A data set with 1 point: ("12:30",2)
131
      #   d2 = [ "01:00",2, "14:20",6] # A data set with 2 points: ("01:00",2) and 
132
      #                                #                           ("14:20",6)  
133
      #   graph.add_data( 
134
      #     :data => d1,
135
      #     :title => 'One'
136
      #   )
137
      #   graph.add_data(
138
      #     :data => d2,
139
      #     :title => 'Two'
140
      #   )
141
      #
142
      # Note that the data must be in time,value pairs, and that the date format
143
      # may be any date that is parseable by ParseDate.
144
      def add_data data
145
        @data = [] unless @data
146
       
147
        raise "No data provided by #{@data.inspect}" unless data[:data] and
148
                                                    data[:data].kind_of? Array
149
        raise "Data supplied must be x,y pairs!  "+
150
          "The data provided contained an odd set of "+
151
          "data points" unless data[:data].length % 2 == 0
152
        return if data[:data].length == 0
153

  
154

  
155
        x = []
156
        y = []
157
        data[:data].each_index {|i|
158
          if i%2 == 0
159
            t = DateTime.parse( data[:data][i] ).to_time
160
            x << t.to_i
161
          else
162
            y << data[:data][i]
163
          end
164
        }
165
        sort( x, y )
166
        data[:data] = [x,y]
167
        @data << data
168
      end
169

  
170

  
171
      protected
172

  
173
      def min_x_value=(value)
174
        @min_x_value = DateTime.parse( value ).to_time
175
      end
176

  
177

  
178
      def format x, y
179
        Time.at( x ).strftime( popup_format )
180
      end
181

  
182
      def get_x_labels
183
        get_x_values.collect { |v| Time.at(v).strftime( x_label_format ) }
184
      end
185
      
186
      private
187
      def get_x_values
188
        rv = []
189
        min, max, scale_division = x_range
190
        if timescale_divisions
191
          timescale_divisions =~ /(\d+) ?(day|week|month|year|hour|minute|second)?/
192
          division_units = $2 ? $2 : "day"
193
          amount = $1.to_i
194
          if amount
195
            step =  nil
196
            case division_units
197
            when "month"
198
              cur = min
199
              while cur < max
200
                rv << cur
201
                arr = Time.at( cur ).to_a
202
                arr[4] += amount
203
                if arr[4] > 12
204
                  arr[5] += (arr[4] / 12).to_i
205
                  arr[4] = (arr[4] % 12)
206
                end
207
                cur = Time.local(*arr).to_i
208
              end
209
            when "year"
210
              cur = min
211
              while cur < max
212
                rv << cur
213
                arr = Time.at( cur ).to_a
214
                arr[5] += amount
215
                cur = Time.local(*arr).to_i
216
              end
217
            when "week"
218
              step = 7 * 24 * 60 * 60 * amount
219
            when "day"
220
              step = 24 * 60 * 60 * amount
221
            when "hour"
222
              step = 60 * 60 * amount
223
            when "minute"
224
              step = 60 * amount
225
            when "second"
226
              step = amount
227
            end
228
            min.step( max, step ) {|v| rv << v} if step
229

  
230
            return rv
231
          end
232
        end
233
        min.step( max, scale_division ) {|v| rv << v}
234
        return rv
235
      end
236
    end
237
  end
238
end

Also available in: Unified diff