##// END OF EJS Templates
Adds Filesystem adapter (patch #1393 by Paul R)....
Jean-Philippe Lang -
r1494:e69b4647f201
parent child
Show More
@@ -0,0 +1,43
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # FileSystem adapter
5 # File written by Paul Rivier, at Demotera.
6 #
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21 require 'redmine/scm/adapters/filesystem_adapter'
22
23 class Repository::Filesystem < Repository
24 attr_protected :root_url
25 validates_presence_of :url
26
27 def scm_adapter
28 Redmine::Scm::Adapters::FilesystemAdapter
29 end
30
31 def self.scm_name
32 'Filesystem'
33 end
34
35 def entries(path=nil, identifier=nil)
36 scm.entries(path, identifier)
37 end
38
39 def fetch_changesets
40 nil
41 end
42
43 end
@@ -0,0 +1,94
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
4 # FileSystem adapter
5 # File written by Paul Rivier, at Demotera.
6 #
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21 require 'redmine/scm/adapters/abstract_adapter'
22 require 'find'
23
24 module Redmine
25 module Scm
26 module Adapters
27 class FilesystemAdapter < AbstractAdapter
28
29
30 def initialize(url, root_url=nil, login=nil, password=nil)
31 @url = with_trailling_slash(url)
32 end
33
34 def format_path_ends(path, leading=true, trailling=true)
35 path = leading ? with_leading_slash(path) :
36 without_leading_slash(path)
37 trailling ? with_trailling_slash(path) :
38 without_trailling_slash(path)
39 end
40
41 def info
42 info = Info.new({:root_url => target(),
43 :lastrev => nil
44 })
45 info
46 rescue CommandFailed
47 return nil
48 end
49
50 def entries(path="", identifier=nil)
51 entries = Entries.new
52 Dir.new(target(path)).each do |e|
53 relative_path = format_path_ends((format_path_ends(path,
54 false,
55 true) + e),
56 false,false)
57 target = target(relative_path)
58 entries <<
59 Entry.new({ :name => File.basename(e),
60 # below : list unreadable files, but dont link them.
61 :path => File.readable?(target) ? relative_path : "",
62 :kind => (File.directory?(target) ? 'dir' : 'file'),
63 :size => if (File.directory?(target))
64 nil else File.size(target) end,
65 :lastrev =>
66 Revision.new({:time => (File.mtime(target)).localtime,
67 })
68 }) if File.exist?(target) and # paranoid test
69 %w{file directory}.include?(File.ftype(target)) and # avoid special types
70 not File.basename(e).match(/^\.+$/) # avoid . and ..
71 end
72 entries.sort_by_name
73 end
74
75 def cat(path, identifier=nil)
76 File.new(target(path)).read
77 end
78
79 private
80
81 # AbstractAdapter::target is implicitly made to quote paths.
82 # Here we do not shell-out, so we do not want quotes.
83 def target(path=nil)
84 #Prevent the use of ..
85 if path and !path.match(/(^|\/)\.\.(\/|$)/)
86 return "#{self.url}#{without_leading_slash(path)}"
87 end
88 return self.url
89 end
90
91 end
92 end
93 end
94 end
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,42
1
2 require File.dirname(__FILE__) + '/../test_helper'
3
4
5 class FilesystemAdapterTest < Test::Unit::TestCase
6
7 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/filesystem_repository'
8
9 if File.directory?(REPOSITORY_PATH)
10 def setup
11 @adapter = Redmine::Scm::Adapters::FilesystemAdapter.new(REPOSITORY_PATH)
12 end
13
14 def test_entries
15 assert_equal 2, @adapter.entries.size
16 assert_equal ["dir", "test"], @adapter.entries.collect(&:name)
17 assert_equal ["dir", "test"], @adapter.entries(nil).collect(&:name)
18 assert_equal ["dir", "test"], @adapter.entries("/").collect(&:name)
19 ["dir", "/dir", "/dir/", "dir/"].each do |path|
20 assert_equal ["subdir", "dirfile"], @adapter.entries(path).collect(&:name)
21 end
22 # If y try to use "..", the path is ignored
23 ["/../","dir/../", "..", "../", "/..", "dir/.."].each do |path|
24 assert_equal ["dir", "test"], @adapter.entries(path).collect(&:name), ".. must be ignored in path argument"
25 end
26 end
27
28 def test_cat
29 assert_equal "TEST CAT\n", @adapter.cat("test")
30 assert_equal "TEST CAT\n", @adapter.cat("/test")
31 # Revision number is ignored
32 assert_equal "TEST CAT\n", @adapter.cat("/test", 1)
33 end
34
35 else
36 puts "Filesystem test repository NOT FOUND. Skipping unit tests !!! See doc/RUNNING_TESTS."
37 def test_fake; assert true end
38 end
39
40 end
41
42
@@ -0,0 +1,54
1 # redMine - project management software
2 # Copyright (C) 2006-2007 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.dirname(__FILE__) + '/../test_helper'
19
20 class RepositoryFilesystemTest < Test::Unit::TestCase
21 fixtures :projects
22
23 # No '..' in the repository path
24 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/filesystem_repository'
25
26 def setup
27 @project = Project.find(1)
28 Setting.enabled_scm << 'Filesystem' unless Setting.enabled_scm.include?('Filesystem')
29 assert @repository = Repository::Filesystem.create(:project => @project, :url => REPOSITORY_PATH)
30 end
31
32 if File.directory?(REPOSITORY_PATH)
33 def test_fetch_changesets
34 @repository.fetch_changesets
35 @repository.reload
36
37 assert_equal 0, @repository.changesets.count
38 assert_equal 0, @repository.changes.count
39 end
40
41 def test_entries
42 assert_equal 2, @repository.entries("", 2).size
43 assert_equal 2, @repository.entries("dir", 3).size
44 end
45
46 def test_cat
47 assert_equal "TEST CAT\n", @repository.scm.cat("test")
48 end
49
50 else
51 puts "Filesystem test repository NOT FOUND. Skipping unit tests !!! See doc/RUNNING_TESTS."
52 def test_fake; assert true end
53 end
54 end
@@ -1,101 +1,105
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'coderay'
18 require 'coderay'
19 require 'coderay/helpers/file_type'
19 require 'coderay/helpers/file_type'
20 require 'iconv'
20 require 'iconv'
21
21
22 module RepositoriesHelper
22 module RepositoriesHelper
23 def syntax_highlight(name, content)
23 def syntax_highlight(name, content)
24 type = CodeRay::FileType[name]
24 type = CodeRay::FileType[name]
25 type ? CodeRay.scan(content, type).html : h(content)
25 type ? CodeRay.scan(content, type).html : h(content)
26 end
26 end
27
27
28 def format_revision(txt)
28 def format_revision(txt)
29 txt.to_s[0,8]
29 txt.to_s[0,8]
30 end
30 end
31
31
32 def to_utf8(str)
32 def to_utf8(str)
33 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
33 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
34 @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
34 @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
35 @encodings.each do |encoding|
35 @encodings.each do |encoding|
36 begin
36 begin
37 return Iconv.conv('UTF-8', encoding, str)
37 return Iconv.conv('UTF-8', encoding, str)
38 rescue Iconv::Failure
38 rescue Iconv::Failure
39 # do nothing here and try the next encoding
39 # do nothing here and try the next encoding
40 end
40 end
41 end
41 end
42 str
42 str
43 end
43 end
44
44
45 def repository_field_tags(form, repository)
45 def repository_field_tags(form, repository)
46 method = repository.class.name.demodulize.underscore + "_field_tags"
46 method = repository.class.name.demodulize.underscore + "_field_tags"
47 send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
47 send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
48 end
48 end
49
49
50 def scm_select_tag(repository)
50 def scm_select_tag(repository)
51 scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
51 scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
52 REDMINE_SUPPORTED_SCM.each do |scm|
52 REDMINE_SUPPORTED_SCM.each do |scm|
53 scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
53 scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
54 end
54 end
55
55
56 select_tag('repository_scm',
56 select_tag('repository_scm',
57 options_for_select(scm_options, repository.class.name.demodulize),
57 options_for_select(scm_options, repository.class.name.demodulize),
58 :disabled => (repository && !repository.new_record?),
58 :disabled => (repository && !repository.new_record?),
59 :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
59 :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
60 )
60 )
61 end
61 end
62
62
63 def with_leading_slash(path)
63 def with_leading_slash(path)
64 path.to_s.starts_with?('/') ? path : "/#{path}"
64 path.to_s.starts_with?('/') ? path : "/#{path}"
65 end
65 end
66
66
67 def without_leading_slash(path)
67 def without_leading_slash(path)
68 path.gsub(%r{^/+}, '')
68 path.gsub(%r{^/+}, '')
69 end
69 end
70
70
71 def subversion_field_tags(form, repository)
71 def subversion_field_tags(form, repository)
72 content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
72 content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
73 '<br />(http://, https://, svn://, file:///)') +
73 '<br />(http://, https://, svn://, file:///)') +
74 content_tag('p', form.text_field(:login, :size => 30)) +
74 content_tag('p', form.text_field(:login, :size => 30)) +
75 content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
75 content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
76 :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
76 :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
77 :onfocus => "this.value=''; this.name='repository[password]';",
77 :onfocus => "this.value=''; this.name='repository[password]';",
78 :onchange => "this.name='repository[password]';"))
78 :onchange => "this.name='repository[password]';"))
79 end
79 end
80
80
81 def darcs_field_tags(form, repository)
81 def darcs_field_tags(form, repository)
82 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
82 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
83 end
83 end
84
84
85 def mercurial_field_tags(form, repository)
85 def mercurial_field_tags(form, repository)
86 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
86 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
87 end
87 end
88
88
89 def git_field_tags(form, repository)
89 def git_field_tags(form, repository)
90 content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
90 content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
91 end
91 end
92
92
93 def cvs_field_tags(form, repository)
93 def cvs_field_tags(form, repository)
94 content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
94 content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
95 content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
95 content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
96 end
96 end
97
97
98 def bazaar_field_tags(form, repository)
98 def bazaar_field_tags(form, repository)
99 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
99 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
100 end
100 end
101
102 def filesystem_field_tags(form, repository)
103 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
104 end
101 end
105 end
@@ -1,37 +1,41
1 Creating test repositories
1 Creating test repositories
2 ===================
2 ===================
3
3
4 mkdir tmp/test
4 mkdir tmp/test
5
5
6 Subversion
6 Subversion
7 ----------
7 ----------
8 svnadmin create tmp/test/subversion_repository
8 svnadmin create tmp/test/subversion_repository
9 gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load tmp/test/subversion_repository
9 gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load tmp/test/subversion_repository
10
10
11 CVS
11 CVS
12 ---
12 ---
13 gunzip < test/fixtures/repositories/cvs_repository.tar.gz | tar -xv -C tmp/test
13 gunzip < test/fixtures/repositories/cvs_repository.tar.gz | tar -xv -C tmp/test
14
14
15 Bazaar
15 Bazaar
16 ------
16 ------
17 gunzip < test/fixtures/repositories/bazaar_repository.tar.gz | tar -xv -C tmp/test
17 gunzip < test/fixtures/repositories/bazaar_repository.tar.gz | tar -xv -C tmp/test
18
18
19 Mercurial
19 Mercurial
20 ---------
20 ---------
21 gunzip < test/fixtures/repositories/mercurial_repository.tar.gz | tar -xv -C tmp/test
21 gunzip < test/fixtures/repositories/mercurial_repository.tar.gz | tar -xv -C tmp/test
22
22
23 Git
23 Git
24 ---
24 ---
25 gunzip < test/fixtures/repositories/git_repository.tar.gz | tar -xv -C tmp/test
25 gunzip < test/fixtures/repositories/git_repository.tar.gz | tar -xv -C tmp/test
26
26
27 FileSystem
28 ----------
29 gunzip < test/fixtures/repositories/filesystem_repository.tar.gz | tar -xv -C tmp/test
30
27
31
28 Running Tests
32 Running Tests
29 =============
33 =============
30
34
31 Run
35 Run
32
36
33 rake --tasks | grep test
37 rake --tasks | grep test
34
38
35 to see available tests.
39 to see available tests.
36
40
37 RAILS_ENV=test rake test will run tests.
41 RAILS_ENV=test rake test will run tests.
@@ -1,134 +1,134
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/mime_type'
3 require 'redmine/mime_type'
4 require 'redmine/core_ext'
4 require 'redmine/core_ext'
5 require 'redmine/themes'
5 require 'redmine/themes'
6 require 'redmine/plugin'
6 require 'redmine/plugin'
7
7
8 begin
8 begin
9 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
9 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
10 rescue LoadError
10 rescue LoadError
11 # RMagick is not available
11 # RMagick is not available
12 end
12 end
13
13
14 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git )
14 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
15
15
16 # Permissions
16 # Permissions
17 Redmine::AccessControl.map do |map|
17 Redmine::AccessControl.map do |map|
18 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
18 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
19 map.permission :search_project, {:search => :index}, :public => true
19 map.permission :search_project, {:search => :index}, :public => true
20 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
20 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
21 map.permission :select_project_modules, {:projects => :modules}, :require => :member
21 map.permission :select_project_modules, {:projects => :modules}, :require => :member
22 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
22 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
23 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
23 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
24
24
25 map.project_module :issue_tracking do |map|
25 map.project_module :issue_tracking do |map|
26 # Issue categories
26 # Issue categories
27 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
27 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
28 # Issues
28 # Issues
29 map.permission :view_issues, {:projects => [:changelog, :roadmap],
29 map.permission :view_issues, {:projects => [:changelog, :roadmap],
30 :issues => [:index, :changes, :show, :context_menu],
30 :issues => [:index, :changes, :show, :context_menu],
31 :versions => [:show, :status_by],
31 :versions => [:show, :status_by],
32 :queries => :index,
32 :queries => :index,
33 :reports => :issue_report}, :public => true
33 :reports => :issue_report}, :public => true
34 map.permission :add_issues, {:issues => :new}
34 map.permission :add_issues, {:issues => :new}
35 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment]}
35 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment]}
36 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
36 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
37 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
37 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
38 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
38 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
39 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
39 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
40 map.permission :move_issues, {:issues => :move}, :require => :loggedin
40 map.permission :move_issues, {:issues => :move}, :require => :loggedin
41 map.permission :delete_issues, {:issues => :destroy}, :require => :member
41 map.permission :delete_issues, {:issues => :destroy}, :require => :member
42 # Queries
42 # Queries
43 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
43 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
44 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
44 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
45 # Gantt & calendar
45 # Gantt & calendar
46 map.permission :view_gantt, :projects => :gantt
46 map.permission :view_gantt, :projects => :gantt
47 map.permission :view_calendar, :projects => :calendar
47 map.permission :view_calendar, :projects => :calendar
48 end
48 end
49
49
50 map.project_module :time_tracking do |map|
50 map.project_module :time_tracking do |map|
51 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
51 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
52 map.permission :view_time_entries, :timelog => [:details, :report]
52 map.permission :view_time_entries, :timelog => [:details, :report]
53 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
53 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
54 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
54 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
55 end
55 end
56
56
57 map.project_module :news do |map|
57 map.project_module :news do |map|
58 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
58 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
59 map.permission :view_news, {:news => [:index, :show]}, :public => true
59 map.permission :view_news, {:news => [:index, :show]}, :public => true
60 map.permission :comment_news, {:news => :add_comment}
60 map.permission :comment_news, {:news => :add_comment}
61 end
61 end
62
62
63 map.project_module :documents do |map|
63 map.project_module :documents do |map|
64 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
64 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
65 map.permission :view_documents, :documents => [:index, :show, :download]
65 map.permission :view_documents, :documents => [:index, :show, :download]
66 end
66 end
67
67
68 map.project_module :files do |map|
68 map.project_module :files do |map|
69 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
69 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
70 map.permission :view_files, :projects => :list_files, :versions => :download
70 map.permission :view_files, :projects => :list_files, :versions => :download
71 end
71 end
72
72
73 map.project_module :wiki do |map|
73 map.project_module :wiki do |map|
74 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
74 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
75 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
75 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
76 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
76 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
77 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
77 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
78 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
78 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
79 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
79 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
80 end
80 end
81
81
82 map.project_module :repository do |map|
82 map.project_module :repository do |map|
83 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
83 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
84 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
84 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
85 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
85 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
86 end
86 end
87
87
88 map.project_module :boards do |map|
88 map.project_module :boards do |map|
89 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
89 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
90 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
90 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
91 map.permission :add_messages, {:messages => [:new, :reply]}
91 map.permission :add_messages, {:messages => [:new, :reply]}
92 map.permission :edit_messages, {:messages => :edit}, :require => :member
92 map.permission :edit_messages, {:messages => :edit}, :require => :member
93 map.permission :delete_messages, {:messages => :destroy}, :require => :member
93 map.permission :delete_messages, {:messages => :destroy}, :require => :member
94 end
94 end
95 end
95 end
96
96
97 Redmine::MenuManager.map :top_menu do |menu|
97 Redmine::MenuManager.map :top_menu do |menu|
98 menu.push :home, :home_path, :html => { :class => 'home' }
98 menu.push :home, :home_path, :html => { :class => 'home' }
99 menu.push :my_page, { :controller => 'my', :action => 'page' }, :html => { :class => 'mypage' }, :if => Proc.new { User.current.logged? }
99 menu.push :my_page, { :controller => 'my', :action => 'page' }, :html => { :class => 'mypage' }, :if => Proc.new { User.current.logged? }
100 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural, :html => { :class => 'projects' }
100 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural, :html => { :class => 'projects' }
101 menu.push :administration, { :controller => 'admin', :action => 'index' }, :html => { :class => 'admin' }, :if => Proc.new { User.current.admin? }
101 menu.push :administration, { :controller => 'admin', :action => 'index' }, :html => { :class => 'admin' }, :if => Proc.new { User.current.admin? }
102 menu.push :help, Redmine::Info.help_url, :html => { :class => 'help' }
102 menu.push :help, Redmine::Info.help_url, :html => { :class => 'help' }
103 end
103 end
104
104
105 Redmine::MenuManager.map :account_menu do |menu|
105 Redmine::MenuManager.map :account_menu do |menu|
106 menu.push :login, :signin_path, :html => { :class => 'login' }, :if => Proc.new { !User.current.logged? }
106 menu.push :login, :signin_path, :html => { :class => 'login' }, :if => Proc.new { !User.current.logged? }
107 menu.push :register, { :controller => 'account', :action => 'register' }, :html => { :class => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
107 menu.push :register, { :controller => 'account', :action => 'register' }, :html => { :class => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
108 menu.push :my_account, { :controller => 'my', :action => 'account' }, :html => { :class => 'myaccount' }, :if => Proc.new { User.current.logged? }
108 menu.push :my_account, { :controller => 'my', :action => 'account' }, :html => { :class => 'myaccount' }, :if => Proc.new { User.current.logged? }
109 menu.push :logout, :signout_path, :html => { :class => 'logout' }, :if => Proc.new { User.current.logged? }
109 menu.push :logout, :signout_path, :html => { :class => 'logout' }, :if => Proc.new { User.current.logged? }
110 end
110 end
111
111
112 Redmine::MenuManager.map :application_menu do |menu|
112 Redmine::MenuManager.map :application_menu do |menu|
113 # Empty
113 # Empty
114 end
114 end
115
115
116 Redmine::MenuManager.map :project_menu do |menu|
116 Redmine::MenuManager.map :project_menu do |menu|
117 menu.push :overview, { :controller => 'projects', :action => 'show' }
117 menu.push :overview, { :controller => 'projects', :action => 'show' }
118 menu.push :activity, { :controller => 'projects', :action => 'activity' }
118 menu.push :activity, { :controller => 'projects', :action => 'activity' }
119 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
119 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
120 :if => Proc.new { |p| p.versions.any? }
120 :if => Proc.new { |p| p.versions.any? }
121 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
121 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
122 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
122 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
123 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
123 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
124 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
124 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
125 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
125 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
126 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
126 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
127 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
127 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
128 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
128 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
129 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
129 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
130 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
130 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
131 menu.push :repository, { :controller => 'repositories', :action => 'show' },
131 menu.push :repository, { :controller => 'repositories', :action => 'show' },
132 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
132 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
133 menu.push :settings, { :controller => 'projects', :action => 'settings' }
133 menu.push :settings, { :controller => 'projects', :action => 'settings' }
134 end
134 end
@@ -1,410 +1,420
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'cgi'
18 require 'cgi'
19
19
20 module Redmine
20 module Redmine
21 module Scm
21 module Scm
22 module Adapters
22 module Adapters
23 class CommandFailed < StandardError #:nodoc:
23 class CommandFailed < StandardError #:nodoc:
24 end
24 end
25
25
26 class AbstractAdapter #:nodoc:
26 class AbstractAdapter #:nodoc:
27 def initialize(url, root_url=nil, login=nil, password=nil)
27 def initialize(url, root_url=nil, login=nil, password=nil)
28 @url = url
28 @url = url
29 @login = login if login && !login.empty?
29 @login = login if login && !login.empty?
30 @password = (password || "") if @login
30 @password = (password || "") if @login
31 @root_url = root_url.blank? ? retrieve_root_url : root_url
31 @root_url = root_url.blank? ? retrieve_root_url : root_url
32 end
32 end
33
33
34 def adapter_name
34 def adapter_name
35 'Abstract'
35 'Abstract'
36 end
36 end
37
37
38 def supports_cat?
38 def supports_cat?
39 true
39 true
40 end
40 end
41
41
42 def supports_annotate?
42 def supports_annotate?
43 respond_to?('annotate')
43 respond_to?('annotate')
44 end
44 end
45
45
46 def root_url
46 def root_url
47 @root_url
47 @root_url
48 end
48 end
49
49
50 def url
50 def url
51 @url
51 @url
52 end
52 end
53
53
54 # get info about the svn repository
54 # get info about the svn repository
55 def info
55 def info
56 return nil
56 return nil
57 end
57 end
58
58
59 # Returns the entry identified by path and revision identifier
59 # Returns the entry identified by path and revision identifier
60 # or nil if entry doesn't exist in the repository
60 # or nil if entry doesn't exist in the repository
61 def entry(path=nil, identifier=nil)
61 def entry(path=nil, identifier=nil)
62 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
62 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
63 search_path = parts[0..-2].join('/')
63 search_path = parts[0..-2].join('/')
64 search_name = parts[-1]
64 search_name = parts[-1]
65 if search_path.blank? && search_name.blank?
65 if search_path.blank? && search_name.blank?
66 # Root entry
66 # Root entry
67 Entry.new(:path => '', :kind => 'dir')
67 Entry.new(:path => '', :kind => 'dir')
68 else
68 else
69 # Search for the entry in the parent directory
69 # Search for the entry in the parent directory
70 es = entries(search_path, identifier)
70 es = entries(search_path, identifier)
71 es ? es.detect {|e| e.name == search_name} : nil
71 es ? es.detect {|e| e.name == search_name} : nil
72 end
72 end
73 end
73 end
74
74
75 # Returns an Entries collection
75 # Returns an Entries collection
76 # or nil if the given path doesn't exist in the repository
76 # or nil if the given path doesn't exist in the repository
77 def entries(path=nil, identifier=nil)
77 def entries(path=nil, identifier=nil)
78 return nil
78 return nil
79 end
79 end
80
80
81 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
81 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
82 return nil
82 return nil
83 end
83 end
84
84
85 def diff(path, identifier_from, identifier_to=nil, type="inline")
85 def diff(path, identifier_from, identifier_to=nil, type="inline")
86 return nil
86 return nil
87 end
87 end
88
88
89 def cat(path, identifier=nil)
89 def cat(path, identifier=nil)
90 return nil
90 return nil
91 end
91 end
92
92
93 def with_leading_slash(path)
93 def with_leading_slash(path)
94 path ||= ''
94 path ||= ''
95 (path[0,1]!="/") ? "/#{path}" : path
95 (path[0,1]!="/") ? "/#{path}" : path
96 end
96 end
97
97
98 def with_trailling_slash(path)
98 def with_trailling_slash(path)
99 path ||= ''
99 path ||= ''
100 (path[-1,1] == "/") ? path : "#{path}/"
100 (path[-1,1] == "/") ? path : "#{path}/"
101 end
101 end
102
102
103 def without_leading_slash(path)
104 path ||= ''
105 path.gsub(%r{^/+}, '')
106 end
107
108 def without_trailling_slash(path)
109 path ||= ''
110 (path[-1,1] == "/") ? path[0..-2] : path
111 end
112
103 def shell_quote(str)
113 def shell_quote(str)
104 if RUBY_PLATFORM =~ /mswin/
114 if RUBY_PLATFORM =~ /mswin/
105 '"' + str.gsub(/"/, '\\"') + '"'
115 '"' + str.gsub(/"/, '\\"') + '"'
106 else
116 else
107 "'" + str.gsub(/'/, "'\"'\"'") + "'"
117 "'" + str.gsub(/'/, "'\"'\"'") + "'"
108 end
118 end
109 end
119 end
110
120
111 private
121 private
112 def retrieve_root_url
122 def retrieve_root_url
113 info = self.info
123 info = self.info
114 info ? info.root_url : nil
124 info ? info.root_url : nil
115 end
125 end
116
126
117 def target(path)
127 def target(path)
118 path ||= ''
128 path ||= ''
119 base = path.match(/^\//) ? root_url : url
129 base = path.match(/^\//) ? root_url : url
120 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
130 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
121 end
131 end
122
132
123 def logger
133 def logger
124 RAILS_DEFAULT_LOGGER
134 RAILS_DEFAULT_LOGGER
125 end
135 end
126
136
127 def shellout(cmd, &block)
137 def shellout(cmd, &block)
128 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
138 logger.debug "Shelling out: #{cmd}" if logger && logger.debug?
129 begin
139 begin
130 IO.popen(cmd, "r+") do |io|
140 IO.popen(cmd, "r+") do |io|
131 io.close_write
141 io.close_write
132 block.call(io) if block_given?
142 block.call(io) if block_given?
133 end
143 end
134 rescue Errno::ENOENT => e
144 rescue Errno::ENOENT => e
135 msg = strip_credential(e.message)
145 msg = strip_credential(e.message)
136 # The command failed, log it and re-raise
146 # The command failed, log it and re-raise
137 logger.error("SCM command failed: #{strip_credential(cmd)}\n with: #{msg}")
147 logger.error("SCM command failed: #{strip_credential(cmd)}\n with: #{msg}")
138 raise CommandFailed.new(msg)
148 raise CommandFailed.new(msg)
139 end
149 end
140 end
150 end
141
151
142 # Hides username/password in a given command
152 # Hides username/password in a given command
143 def self.hide_credential(cmd)
153 def self.hide_credential(cmd)
144 q = (RUBY_PLATFORM =~ /mswin/ ? '"' : "'")
154 q = (RUBY_PLATFORM =~ /mswin/ ? '"' : "'")
145 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
155 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
146 end
156 end
147
157
148 def strip_credential(cmd)
158 def strip_credential(cmd)
149 self.class.hide_credential(cmd)
159 self.class.hide_credential(cmd)
150 end
160 end
151 end
161 end
152
162
153 class Entries < Array
163 class Entries < Array
154 def sort_by_name
164 def sort_by_name
155 sort {|x,y|
165 sort {|x,y|
156 if x.kind == y.kind
166 if x.kind == y.kind
157 x.name <=> y.name
167 x.name <=> y.name
158 else
168 else
159 x.kind <=> y.kind
169 x.kind <=> y.kind
160 end
170 end
161 }
171 }
162 end
172 end
163
173
164 def revisions
174 def revisions
165 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
175 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
166 end
176 end
167 end
177 end
168
178
169 class Info
179 class Info
170 attr_accessor :root_url, :lastrev
180 attr_accessor :root_url, :lastrev
171 def initialize(attributes={})
181 def initialize(attributes={})
172 self.root_url = attributes[:root_url] if attributes[:root_url]
182 self.root_url = attributes[:root_url] if attributes[:root_url]
173 self.lastrev = attributes[:lastrev]
183 self.lastrev = attributes[:lastrev]
174 end
184 end
175 end
185 end
176
186
177 class Entry
187 class Entry
178 attr_accessor :name, :path, :kind, :size, :lastrev
188 attr_accessor :name, :path, :kind, :size, :lastrev
179 def initialize(attributes={})
189 def initialize(attributes={})
180 self.name = attributes[:name] if attributes[:name]
190 self.name = attributes[:name] if attributes[:name]
181 self.path = attributes[:path] if attributes[:path]
191 self.path = attributes[:path] if attributes[:path]
182 self.kind = attributes[:kind] if attributes[:kind]
192 self.kind = attributes[:kind] if attributes[:kind]
183 self.size = attributes[:size].to_i if attributes[:size]
193 self.size = attributes[:size].to_i if attributes[:size]
184 self.lastrev = attributes[:lastrev]
194 self.lastrev = attributes[:lastrev]
185 end
195 end
186
196
187 def is_file?
197 def is_file?
188 'file' == self.kind
198 'file' == self.kind
189 end
199 end
190
200
191 def is_dir?
201 def is_dir?
192 'dir' == self.kind
202 'dir' == self.kind
193 end
203 end
194
204
195 def is_text?
205 def is_text?
196 Redmine::MimeType.is_type?('text', name)
206 Redmine::MimeType.is_type?('text', name)
197 end
207 end
198 end
208 end
199
209
200 class Revisions < Array
210 class Revisions < Array
201 def latest
211 def latest
202 sort {|x,y|
212 sort {|x,y|
203 unless x.time.nil? or y.time.nil?
213 unless x.time.nil? or y.time.nil?
204 x.time <=> y.time
214 x.time <=> y.time
205 else
215 else
206 0
216 0
207 end
217 end
208 }.last
218 }.last
209 end
219 end
210 end
220 end
211
221
212 class Revision
222 class Revision
213 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
223 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
214 def initialize(attributes={})
224 def initialize(attributes={})
215 self.identifier = attributes[:identifier]
225 self.identifier = attributes[:identifier]
216 self.scmid = attributes[:scmid]
226 self.scmid = attributes[:scmid]
217 self.name = attributes[:name] || self.identifier
227 self.name = attributes[:name] || self.identifier
218 self.author = attributes[:author]
228 self.author = attributes[:author]
219 self.time = attributes[:time]
229 self.time = attributes[:time]
220 self.message = attributes[:message] || ""
230 self.message = attributes[:message] || ""
221 self.paths = attributes[:paths]
231 self.paths = attributes[:paths]
222 self.revision = attributes[:revision]
232 self.revision = attributes[:revision]
223 self.branch = attributes[:branch]
233 self.branch = attributes[:branch]
224 end
234 end
225
235
226 end
236 end
227
237
228 # A line of Diff
238 # A line of Diff
229 class Diff
239 class Diff
230 attr_accessor :nb_line_left
240 attr_accessor :nb_line_left
231 attr_accessor :line_left
241 attr_accessor :line_left
232 attr_accessor :nb_line_right
242 attr_accessor :nb_line_right
233 attr_accessor :line_right
243 attr_accessor :line_right
234 attr_accessor :type_diff_right
244 attr_accessor :type_diff_right
235 attr_accessor :type_diff_left
245 attr_accessor :type_diff_left
236
246
237 def initialize ()
247 def initialize ()
238 self.nb_line_left = ''
248 self.nb_line_left = ''
239 self.nb_line_right = ''
249 self.nb_line_right = ''
240 self.line_left = ''
250 self.line_left = ''
241 self.line_right = ''
251 self.line_right = ''
242 self.type_diff_right = ''
252 self.type_diff_right = ''
243 self.type_diff_left = ''
253 self.type_diff_left = ''
244 end
254 end
245
255
246 def inspect
256 def inspect
247 puts '### Start Line Diff ###'
257 puts '### Start Line Diff ###'
248 puts self.nb_line_left
258 puts self.nb_line_left
249 puts self.line_left
259 puts self.line_left
250 puts self.nb_line_right
260 puts self.nb_line_right
251 puts self.line_right
261 puts self.line_right
252 end
262 end
253 end
263 end
254
264
255 class DiffTableList < Array
265 class DiffTableList < Array
256 def initialize (diff, type="inline")
266 def initialize (diff, type="inline")
257 diff_table = DiffTable.new type
267 diff_table = DiffTable.new type
258 diff.each do |line|
268 diff.each do |line|
259 if line =~ /^(---|\+\+\+) (.*)$/
269 if line =~ /^(---|\+\+\+) (.*)$/
260 self << diff_table if diff_table.length > 1
270 self << diff_table if diff_table.length > 1
261 diff_table = DiffTable.new type
271 diff_table = DiffTable.new type
262 end
272 end
263 a = diff_table.add_line line
273 a = diff_table.add_line line
264 end
274 end
265 self << diff_table unless diff_table.empty?
275 self << diff_table unless diff_table.empty?
266 self
276 self
267 end
277 end
268 end
278 end
269
279
270 # Class for create a Diff
280 # Class for create a Diff
271 class DiffTable < Hash
281 class DiffTable < Hash
272 attr_reader :file_name, :line_num_l, :line_num_r
282 attr_reader :file_name, :line_num_l, :line_num_r
273
283
274 # Initialize with a Diff file and the type of Diff View
284 # Initialize with a Diff file and the type of Diff View
275 # The type view must be inline or sbs (side_by_side)
285 # The type view must be inline or sbs (side_by_side)
276 def initialize(type="inline")
286 def initialize(type="inline")
277 @parsing = false
287 @parsing = false
278 @nb_line = 1
288 @nb_line = 1
279 @start = false
289 @start = false
280 @before = 'same'
290 @before = 'same'
281 @second = true
291 @second = true
282 @type = type
292 @type = type
283 end
293 end
284
294
285 # Function for add a line of this Diff
295 # Function for add a line of this Diff
286 def add_line(line)
296 def add_line(line)
287 unless @parsing
297 unless @parsing
288 if line =~ /^(---|\+\+\+) (.*)$/
298 if line =~ /^(---|\+\+\+) (.*)$/
289 @file_name = $2
299 @file_name = $2
290 return false
300 return false
291 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
301 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
292 @line_num_l = $5.to_i
302 @line_num_l = $5.to_i
293 @line_num_r = $2.to_i
303 @line_num_r = $2.to_i
294 @parsing = true
304 @parsing = true
295 end
305 end
296 else
306 else
297 if line =~ /^[^\+\-\s@\\]/
307 if line =~ /^[^\+\-\s@\\]/
298 @parsing = false
308 @parsing = false
299 return false
309 return false
300 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
310 elsif line =~ /^@@ (\+|\-)(\d+)(,\d+)? (\+|\-)(\d+)(,\d+)? @@/
301 @line_num_l = $5.to_i
311 @line_num_l = $5.to_i
302 @line_num_r = $2.to_i
312 @line_num_r = $2.to_i
303 else
313 else
304 @nb_line += 1 if parse_line(line, @type)
314 @nb_line += 1 if parse_line(line, @type)
305 end
315 end
306 end
316 end
307 return true
317 return true
308 end
318 end
309
319
310 def inspect
320 def inspect
311 puts '### DIFF TABLE ###'
321 puts '### DIFF TABLE ###'
312 puts "file : #{file_name}"
322 puts "file : #{file_name}"
313 self.each do |d|
323 self.each do |d|
314 d.inspect
324 d.inspect
315 end
325 end
316 end
326 end
317
327
318 private
328 private
319 # Test if is a Side By Side type
329 # Test if is a Side By Side type
320 def sbs?(type, func)
330 def sbs?(type, func)
321 if @start and type == "sbs"
331 if @start and type == "sbs"
322 if @before == func and @second
332 if @before == func and @second
323 tmp_nb_line = @nb_line
333 tmp_nb_line = @nb_line
324 self[tmp_nb_line] = Diff.new
334 self[tmp_nb_line] = Diff.new
325 else
335 else
326 @second = false
336 @second = false
327 tmp_nb_line = @start
337 tmp_nb_line = @start
328 @start += 1
338 @start += 1
329 @nb_line -= 1
339 @nb_line -= 1
330 end
340 end
331 else
341 else
332 tmp_nb_line = @nb_line
342 tmp_nb_line = @nb_line
333 @start = @nb_line
343 @start = @nb_line
334 self[tmp_nb_line] = Diff.new
344 self[tmp_nb_line] = Diff.new
335 @second = true
345 @second = true
336 end
346 end
337 unless self[tmp_nb_line]
347 unless self[tmp_nb_line]
338 @nb_line += 1
348 @nb_line += 1
339 self[tmp_nb_line] = Diff.new
349 self[tmp_nb_line] = Diff.new
340 else
350 else
341 self[tmp_nb_line]
351 self[tmp_nb_line]
342 end
352 end
343 end
353 end
344
354
345 # Escape the HTML for the diff
355 # Escape the HTML for the diff
346 def escapeHTML(line)
356 def escapeHTML(line)
347 CGI.escapeHTML(line)
357 CGI.escapeHTML(line)
348 end
358 end
349
359
350 def parse_line(line, type="inline")
360 def parse_line(line, type="inline")
351 if line[0, 1] == "+"
361 if line[0, 1] == "+"
352 diff = sbs? type, 'add'
362 diff = sbs? type, 'add'
353 @before = 'add'
363 @before = 'add'
354 diff.line_left = escapeHTML line[1..-1]
364 diff.line_left = escapeHTML line[1..-1]
355 diff.nb_line_left = @line_num_l
365 diff.nb_line_left = @line_num_l
356 diff.type_diff_left = 'diff_in'
366 diff.type_diff_left = 'diff_in'
357 @line_num_l += 1
367 @line_num_l += 1
358 true
368 true
359 elsif line[0, 1] == "-"
369 elsif line[0, 1] == "-"
360 diff = sbs? type, 'remove'
370 diff = sbs? type, 'remove'
361 @before = 'remove'
371 @before = 'remove'
362 diff.line_right = escapeHTML line[1..-1]
372 diff.line_right = escapeHTML line[1..-1]
363 diff.nb_line_right = @line_num_r
373 diff.nb_line_right = @line_num_r
364 diff.type_diff_right = 'diff_out'
374 diff.type_diff_right = 'diff_out'
365 @line_num_r += 1
375 @line_num_r += 1
366 true
376 true
367 elsif line[0, 1] =~ /\s/
377 elsif line[0, 1] =~ /\s/
368 @before = 'same'
378 @before = 'same'
369 @start = false
379 @start = false
370 diff = Diff.new
380 diff = Diff.new
371 diff.line_right = escapeHTML line[1..-1]
381 diff.line_right = escapeHTML line[1..-1]
372 diff.nb_line_right = @line_num_r
382 diff.nb_line_right = @line_num_r
373 diff.line_left = escapeHTML line[1..-1]
383 diff.line_left = escapeHTML line[1..-1]
374 diff.nb_line_left = @line_num_l
384 diff.nb_line_left = @line_num_l
375 self[@nb_line] = diff
385 self[@nb_line] = diff
376 @line_num_l += 1
386 @line_num_l += 1
377 @line_num_r += 1
387 @line_num_r += 1
378 true
388 true
379 elsif line[0, 1] = "\\"
389 elsif line[0, 1] = "\\"
380 true
390 true
381 else
391 else
382 false
392 false
383 end
393 end
384 end
394 end
385 end
395 end
386
396
387 class Annotate
397 class Annotate
388 attr_reader :lines, :revisions
398 attr_reader :lines, :revisions
389
399
390 def initialize
400 def initialize
391 @lines = []
401 @lines = []
392 @revisions = []
402 @revisions = []
393 end
403 end
394
404
395 def add_line(line, revision)
405 def add_line(line, revision)
396 @lines << line
406 @lines << line
397 @revisions << revision
407 @revisions << revision
398 end
408 end
399
409
400 def content
410 def content
401 content = lines.join("\n")
411 content = lines.join("\n")
402 end
412 end
403
413
404 def empty?
414 def empty?
405 lines.empty?
415 lines.empty?
406 end
416 end
407 end
417 end
408 end
418 end
409 end
419 end
410 end
420 end
@@ -1,118 +1,120
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class RepositoryTest < Test::Unit::TestCase
20 class RepositoryTest < Test::Unit::TestCase
21 fixtures :projects,
21 fixtures :projects,
22 :trackers,
22 :trackers,
23 :projects_trackers,
23 :projects_trackers,
24 :repositories,
24 :repositories,
25 :issues,
25 :issues,
26 :issue_statuses,
26 :issue_statuses,
27 :changesets,
27 :changesets,
28 :changes,
28 :changes,
29 :users,
29 :users,
30 :enumerations
30 :enumerations
31
31
32 def setup
32 def setup
33 @repository = Project.find(1).repository
33 @repository = Project.find(1).repository
34 end
34 end
35
35
36 def test_create
36 def test_create
37 repository = Repository::Subversion.new(:project => Project.find(3))
37 repository = Repository::Subversion.new(:project => Project.find(3))
38 assert !repository.save
38 assert !repository.save
39
39
40 repository.url = "svn://localhost"
40 repository.url = "svn://localhost"
41 assert repository.save
41 assert repository.save
42 repository.reload
42 repository.reload
43
43
44 project = Project.find(3)
44 project = Project.find(3)
45 assert_equal repository, project.repository
45 assert_equal repository, project.repository
46 end
46 end
47
47
48 def test_should_not_create_with_disabled_scm
48 def test_should_not_create_with_disabled_scm
49 # disable Subversion
49 # disable Subversion
50 Setting.enabled_scm = ['Darcs', 'Git']
50 Setting.enabled_scm = ['Darcs', 'Git']
51 repository = Repository::Subversion.new(:project => Project.find(3), :url => "svn://localhost")
51 repository = Repository::Subversion.new(:project => Project.find(3), :url => "svn://localhost")
52 assert !repository.save
52 assert !repository.save
53 assert_equal :activerecord_error_invalid, repository.errors.on(:type)
53 assert_equal :activerecord_error_invalid, repository.errors.on(:type)
54 # re-enable Subversion for following tests
55 Setting.delete_all
54 end
56 end
55
57
56 def test_scan_changesets_for_issue_ids
58 def test_scan_changesets_for_issue_ids
57 # choosing a status to apply to fix issues
59 # choosing a status to apply to fix issues
58 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
60 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
59 Setting.commit_fix_done_ratio = "90"
61 Setting.commit_fix_done_ratio = "90"
60 Setting.commit_ref_keywords = 'refs , references, IssueID'
62 Setting.commit_ref_keywords = 'refs , references, IssueID'
61 Setting.commit_fix_keywords = 'fixes , closes'
63 Setting.commit_fix_keywords = 'fixes , closes'
62 Setting.default_language = 'en'
64 Setting.default_language = 'en'
63 ActionMailer::Base.deliveries.clear
65 ActionMailer::Base.deliveries.clear
64
66
65 # make sure issue 1 is not already closed
67 # make sure issue 1 is not already closed
66 fixed_issue = Issue.find(1)
68 fixed_issue = Issue.find(1)
67 assert !fixed_issue.status.is_closed?
69 assert !fixed_issue.status.is_closed?
68 old_status = fixed_issue.status
70 old_status = fixed_issue.status
69
71
70 Repository.scan_changesets_for_issue_ids
72 Repository.scan_changesets_for_issue_ids
71 assert_equal [101, 102], Issue.find(3).changeset_ids
73 assert_equal [101, 102], Issue.find(3).changeset_ids
72
74
73 # fixed issues
75 # fixed issues
74 fixed_issue.reload
76 fixed_issue.reload
75 assert fixed_issue.status.is_closed?
77 assert fixed_issue.status.is_closed?
76 assert_equal 90, fixed_issue.done_ratio
78 assert_equal 90, fixed_issue.done_ratio
77 assert_equal [101], fixed_issue.changeset_ids
79 assert_equal [101], fixed_issue.changeset_ids
78
80
79 # issue change
81 # issue change
80 journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
82 journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
81 assert_equal User.find_by_login('dlopper'), journal.user
83 assert_equal User.find_by_login('dlopper'), journal.user
82 assert_equal 'Applied in changeset r2.', journal.notes
84 assert_equal 'Applied in changeset r2.', journal.notes
83
85
84 # 2 email notifications
86 # 2 email notifications
85 assert_equal 2, ActionMailer::Base.deliveries.size
87 assert_equal 2, ActionMailer::Base.deliveries.size
86 mail = ActionMailer::Base.deliveries.first
88 mail = ActionMailer::Base.deliveries.first
87 assert_kind_of TMail::Mail, mail
89 assert_kind_of TMail::Mail, mail
88 assert mail.subject.starts_with?("[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
90 assert mail.subject.starts_with?("[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
89 assert mail.body.include?("Status changed from #{old_status} to #{fixed_issue.status}")
91 assert mail.body.include?("Status changed from #{old_status} to #{fixed_issue.status}")
90
92
91 # ignoring commits referencing an issue of another project
93 # ignoring commits referencing an issue of another project
92 assert_equal [], Issue.find(4).changesets
94 assert_equal [], Issue.find(4).changesets
93 end
95 end
94
96
95 def test_for_changeset_comments_strip
97 def test_for_changeset_comments_strip
96 repository = Repository::Mercurial.create( :project => Project.find( 4 ), :url => '/foo/bar/baz' )
98 repository = Repository::Mercurial.create( :project => Project.find( 4 ), :url => '/foo/bar/baz' )
97 comment = <<-COMMENT
99 comment = <<-COMMENT
98 This is a loooooooooooooooooooooooooooong comment
100 This is a loooooooooooooooooooooooooooong comment
99
101
100
102
101 COMMENT
103 COMMENT
102 changeset = Changeset.new(
104 changeset = Changeset.new(
103 :comments => comment, :commit_date => Time.now, :revision => 0, :scmid => 'f39b7922fb3c',
105 :comments => comment, :commit_date => Time.now, :revision => 0, :scmid => 'f39b7922fb3c',
104 :committer => 'foo <foo@example.com>', :committed_on => Time.now, :repository => repository )
106 :committer => 'foo <foo@example.com>', :committed_on => Time.now, :repository => repository )
105 assert( changeset.save )
107 assert( changeset.save )
106 assert_not_equal( comment, changeset.comments )
108 assert_not_equal( comment, changeset.comments )
107 assert_equal( 'This is a loooooooooooooooooooooooooooong comment', changeset.comments )
109 assert_equal( 'This is a loooooooooooooooooooooooooooong comment', changeset.comments )
108 end
110 end
109
111
110 def test_for_urls_strip
112 def test_for_urls_strip
111 repository = Repository::Cvs.create(:project => Project.find(4), :url => ' :pserver:login:password@host:/path/to/the/repository',
113 repository = Repository::Cvs.create(:project => Project.find(4), :url => ' :pserver:login:password@host:/path/to/the/repository',
112 :root_url => 'foo ')
114 :root_url => 'foo ')
113 assert repository.save
115 assert repository.save
114 repository.reload
116 repository.reload
115 assert_equal ':pserver:login:password@host:/path/to/the/repository', repository.url
117 assert_equal ':pserver:login:password@host:/path/to/the/repository', repository.url
116 assert_equal 'foo', repository.root_url
118 assert_equal 'foo', repository.root_url
117 end
119 end
118 end
120 end
General Comments 0
You need to be logged in to leave comments. Login now