##// END OF EJS Templates
Replaces the repositories management SOAP API with a simple REST API....
Jean-Philippe Lang -
r2374:8cf3d7a4929e
parent child
Show More
@@ -0,0 +1,55
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 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 require 'sys_controller'
20
21 # Re-raise errors caught by the controller.
22 class SysController; def rescue_action(e) raise e end; end
23
24 class SysControllerTest < Test::Unit::TestCase
25 fixtures :projects, :repositories
26
27 def setup
28 @controller = SysController.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
31 Setting.sys_api_enabled = '1'
32 end
33
34 def test_projects_with_repository_enabled
35 get :projects
36 assert_response :success
37 assert_equal 'application/xml', @response.content_type
38 with_options :tag => 'projects' do |test|
39 test.assert_tag :children => { :count => Project.active.has_module(:repository).count }
40 end
41 end
42
43 def test_create_project_repository
44 assert_nil Project.find(4).repository
45
46 post :create_project_repository, :id => 4,
47 :vendor => 'Subversion',
48 :repository => { :url => 'file:///create/project/repository/subproject2'}
49 assert_response :created
50
51 r = Project.find(4).repository
52 assert r.is_a?(Repository::Subversion)
53 assert_equal 'file:///create/project/repository/subproject2', r.url
54 end
55 end
@@ -1,46 +1,50
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class SysController < ActionController::Base
19 wsdl_service_name 'Sys'
20 web_service_api SysApi
21 web_service_scaffold :invoke
19 before_filter :check_enabled
22 20
23 before_invocation :check_enabled
24
25 # Returns the projects list, with their repositories
26 def projects_with_repository_enabled
27 Project.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
21 def projects
22 p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
23 render :xml => p.to_xml(:include => :repository)
28 24 end
29
30 # Registers a repository for the given project identifier
31 def repository_created(identifier, vendor, url)
32 project = Project.find_by_identifier(identifier)
33 # Do not create the repository if the project has already one
34 return 0 unless project && project.repository.nil?
35 logger.debug "Repository for #{project.name} was created"
36 repository = Repository.factory(vendor, :project => project, :url => url)
37 repository.save
38 repository.id || 0
25
26 def create_project_repository
27 project = Project.find(params[:id])
28 if project.repository
29 render :nothing => true, :status => 409
30 else
31 logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
32 project.repository = Repository.factory(params[:vendor], params[:repository])
33 if project.repository && project.repository.save
34 render :xml => project.repository, :status => 201
35 else
36 render :nothing => true, :status => 422
37 end
38 end
39 39 end
40 40
41 protected
41 protected
42 42
43 def check_enabled(name, args)
44 Setting.sys_api_enabled?
43 def check_enabled
44 User.current = nil
45 unless Setting.sys_api_enabled?
46 render :nothing => 'Access denied. Repository management WS is disabled.', :status => 403
47 return false
48 end
45 49 end
46 50 end
@@ -1,257 +1,258
1 1 ActionController::Routing::Routes.draw do |map|
2 2 # Add your own custom routes here.
3 3 # The priority is based upon order of creation: first created -> highest priority.
4 4
5 5 # Here's a sample route:
6 6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 7 # Keep in mind you can assign values other than :controller and :action
8 8
9 9 # Allow Redmine plugins to map routes and potentially override them
10 10 Rails.plugins.each do |plugin|
11 11 map.from_plugin plugin.name.to_sym
12 12 end
13 13
14 14 map.home '', :controller => 'welcome'
15 15
16 16 map.signin 'login', :controller => 'account', :action => 'login'
17 17 map.signout 'logout', :controller => 'account', :action => 'logout'
18 18
19 19 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
20 20 map.connect 'help/:ctrl/:page', :controller => 'help'
21 21
22 22 map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog'
23 23 map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog'
24 24 map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog'
25 25
26 26 map.with_options :controller => 'timelog' do |timelog|
27 27 timelog.connect 'projects/:project_id/time_entries', :action => 'details'
28 28
29 29 timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details|
30 30 time_details.connect 'time_entries'
31 31 time_details.connect 'time_entries.:format'
32 32 time_details.connect 'issues/:issue_id/time_entries'
33 33 time_details.connect 'issues/:issue_id/time_entries.:format'
34 34 time_details.connect 'projects/:project_id/time_entries.:format'
35 35 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries'
36 36 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format'
37 37 end
38 38 timelog.connect 'projects/:project_id/time_entries/report', :action => 'report'
39 39 timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report|
40 40 time_report.connect 'time_entries/report'
41 41 time_report.connect 'time_entries/report.:format'
42 42 time_report.connect 'projects/:project_id/time_entries/report.:format'
43 43 end
44 44
45 45 timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit|
46 46 time_edit.connect 'issues/:issue_id/time_entries/new'
47 47 end
48 48
49 49 timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post}
50 50 end
51 51
52 52 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
53 53 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
54 54 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
55 55 map.with_options :controller => 'wiki' do |wiki_routes|
56 56 wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
57 57 wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i
58 58 wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil
59 59 wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit'
60 60 wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename'
61 61 wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history'
62 62 wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff'
63 63 wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate'
64 64 end
65 65
66 66 wiki_routes.connect 'projects/:id/wiki/:page/:action',
67 67 :action => /edit|rename|destroy|preview|protect/,
68 68 :conditions => {:method => :post}
69 69 end
70 70
71 71 map.with_options :controller => 'messages' do |messages_routes|
72 72 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
73 73 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
74 74 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
75 75 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
76 76 end
77 77 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
78 78 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
79 79 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
80 80 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
81 81 end
82 82 end
83 83
84 84 map.with_options :controller => 'boards' do |board_routes|
85 85 board_routes.with_options :conditions => {:method => :get} do |board_views|
86 86 board_views.connect 'projects/:project_id/boards', :action => 'index'
87 87 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
88 88 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
89 89 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
90 90 end
91 91 board_routes.with_options :conditions => {:method => :post} do |board_actions|
92 92 board_actions.connect 'projects/:project_id/boards', :action => 'new'
93 93 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
94 94 end
95 95 end
96 96
97 97 map.with_options :controller => 'documents' do |document_routes|
98 98 document_routes.with_options :conditions => {:method => :get} do |document_views|
99 99 document_views.connect 'projects/:project_id/documents', :action => 'index'
100 100 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
101 101 document_views.connect 'documents/:id', :action => 'show'
102 102 document_views.connect 'documents/:id/edit', :action => 'edit'
103 103 end
104 104 document_routes.with_options :conditions => {:method => :post} do |document_actions|
105 105 document_actions.connect 'projects/:project_id/documents', :action => 'new'
106 106 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
107 107 end
108 108 end
109 109
110 110 map.with_options :controller => 'issues' do |issues_routes|
111 111 issues_routes.with_options :conditions => {:method => :get} do |issues_views|
112 112 issues_views.connect 'issues', :action => 'index'
113 113 issues_views.connect 'issues.:format', :action => 'index'
114 114 issues_views.connect 'projects/:project_id/issues', :action => 'index'
115 115 issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
116 116 issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
117 117 issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt'
118 118 issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar'
119 119 issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
120 120 issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
121 121 issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/
122 122 issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/
123 123 issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/
124 124 end
125 125 issues_routes.with_options :conditions => {:method => :post} do |issues_actions|
126 126 issues_actions.connect 'projects/:project_id/issues', :action => 'new'
127 127 issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
128 128 issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
129 129 end
130 130 issues_routes.connect 'issues/:action'
131 131 end
132 132
133 133 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
134 134 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
135 135 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
136 136 end
137 137
138 138 map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports|
139 139 reports.connect 'projects/:id/issues/report'
140 140 reports.connect 'projects/:id/issues/report/:detail'
141 141 end
142 142
143 143 map.with_options :controller => 'news' do |news_routes|
144 144 news_routes.with_options :conditions => {:method => :get} do |news_views|
145 145 news_views.connect 'news', :action => 'index'
146 146 news_views.connect 'projects/:project_id/news', :action => 'index'
147 147 news_views.connect 'projects/:project_id/news.:format', :action => 'index'
148 148 news_views.connect 'news.:format', :action => 'index'
149 149 news_views.connect 'projects/:project_id/news/new', :action => 'new'
150 150 news_views.connect 'news/:id', :action => 'show'
151 151 news_views.connect 'news/:id/edit', :action => 'edit'
152 152 end
153 153 news_routes.with_options do |news_actions|
154 154 news_actions.connect 'projects/:project_id/news', :action => 'new'
155 155 news_actions.connect 'news/:id/edit', :action => 'edit'
156 156 news_actions.connect 'news/:id/destroy', :action => 'destroy'
157 157 end
158 158 end
159 159
160 160 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
161 161
162 162 map.with_options :controller => 'users' do |users|
163 163 users.with_options :conditions => {:method => :get} do |user_views|
164 164 user_views.connect 'users', :action => 'list'
165 165 user_views.connect 'users', :action => 'index'
166 166 user_views.connect 'users/new', :action => 'add'
167 167 user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil
168 168 end
169 169 users.with_options :conditions => {:method => :post} do |user_actions|
170 170 user_actions.connect 'users', :action => 'add'
171 171 user_actions.connect 'users/new', :action => 'add'
172 172 user_actions.connect 'users/:id/edit', :action => 'edit'
173 173 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
174 174 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
175 175 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
176 176 end
177 177 end
178 178
179 179 map.with_options :controller => 'projects' do |projects|
180 180 projects.with_options :conditions => {:method => :get} do |project_views|
181 181 project_views.connect 'projects', :action => 'index'
182 182 project_views.connect 'projects.:format', :action => 'index'
183 183 project_views.connect 'projects/new', :action => 'add'
184 184 project_views.connect 'projects/:id', :action => 'show'
185 185 project_views.connect 'projects/:id/:action', :action => /roadmap|changelog|destroy|settings/
186 186 project_views.connect 'projects/:id/files', :action => 'list_files'
187 187 project_views.connect 'projects/:id/files/new', :action => 'add_file'
188 188 project_views.connect 'projects/:id/versions/new', :action => 'add_version'
189 189 project_views.connect 'projects/:id/categories/new', :action => 'add_issue_category'
190 190 project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
191 191 end
192 192
193 193 projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity|
194 194 activity.connect 'projects/:id/activity'
195 195 activity.connect 'projects/:id/activity.:format'
196 196 activity.connect 'activity'
197 197 activity.connect 'activity.:format'
198 198 end
199 199
200 200 projects.with_options :conditions => {:method => :post} do |project_actions|
201 201 project_actions.connect 'projects/new', :action => 'add'
202 202 project_actions.connect 'projects', :action => 'add'
203 203 project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
204 204 project_actions.connect 'projects/:id/files/new', :action => 'add_file'
205 205 project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
206 206 project_actions.connect 'projects/:id/categories/new', :action => 'add_issue_category'
207 207 end
208 208 end
209 209
210 210 map.with_options :controller => 'repositories' do |repositories|
211 211 repositories.with_options :conditions => {:method => :get} do |repository_views|
212 212 repositories.connect 'projects/:id/repository', :action => 'show'
213 213 repositories.connect 'projects/:id/repository/edit', :action => 'edit'
214 214 repositories.connect 'projects/:id/repository/statistics', :action => 'stats'
215 215 repositories.connect 'projects/:id/repository/revisions', :action => 'revisions'
216 216 repositories.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
217 217 repositories.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
218 218 repositories.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
219 219 repositories.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
220 220 repositories.connect 'projects/:id/repository/revisions/:rev/:action/*path'
221 221 repositories.connect 'projects/:id/repository/:action/*path'
222 222 end
223 223
224 224 repositories.connect 'projects/:id/repository/edit', :action => 'edit', :conditions => {:method => :post}
225 225 end
226 226
227 227 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
228 228 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
229 229 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
230 230
231 231
232 232 #left old routes at the bottom for backwards compat
233 233 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
234 234 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
235 235 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
236 236 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
237 237 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
238 238 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
239 239 map.connect 'projects/:project_id/news/:action', :controller => 'news'
240 240 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
241 241 map.with_options :controller => 'repositories' do |omap|
242 242 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
243 243 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
244 244 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
245 245 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
246 246 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
247 247 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
248 248 end
249
250 # Allow downloading Web Service WSDL as a file with an extension
251 # instead of a file named 'wsdl'
252 map.connect ':controller/service.wsdl', :action => 'wsdl'
249
250 map.with_options :controller => 'sys' do |sys|
251 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
252 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
253 end
253 254
254 255 # Install the default route as the lowest priority.
255 256 map.connect ':controller/:action/:id'
256 257 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
257 258 end
@@ -1,289 +1,295
1 1 #!/usr/bin/env ruby
2 2
3 3 # == Synopsis
4 4 #
5 5 # reposman: manages your repositories with Redmine
6 6 #
7 7 # == Usage
8 8 #
9 9 # reposman [OPTIONS...] -s [DIR] -r [HOST]
10 10 #
11 11 # Examples:
12 12 # reposman --svn-dir=/var/svn --redmine-host=redmine.example.net --scm subversion
13 13 # reposman -s /var/git -r redmine.example.net -u http://svn.example.net --scm git
14 14 #
15 15 # == Arguments (mandatory)
16 16 #
17 17 # -s, --svn-dir=DIR use DIR as base directory for svn repositories
18 18 # -r, --redmine-host=HOST assume Redmine is hosted on HOST. Examples:
19 19 # -r redmine.example.net
20 20 # -r http://redmine.example.net
21 21 # -r https://example.net/redmine
22 22 #
23 23 # == Options
24 24 #
25 25 # -o, --owner=OWNER owner of the repository. using the rails login
26 26 # allow user to browse the repository within
27 27 # Redmine even for private project. If you want to share repositories
28 28 # through Redmine.pm, you need to use the apache owner.
29 29 # --scm=SCM the kind of SCM repository you want to create (and register) in
30 30 # Redmine (default: Subversion). reposman is able to create Git
31 31 # and Subversion repositories. For all other kind (Bazaar,
32 32 # Darcs, Filesystem, Mercurial) you must specify a --command option
33 33 # -u, --url=URL the base url Redmine will use to access your
34 34 # repositories. This option is used to automatically
35 35 # register the repositories in Redmine. The project
36 36 # identifier will be appended to this url. Examples:
37 37 # -u https://example.net/svn
38 38 # -u file:///var/svn/
39 39 # if this option isn't set, reposman won't register
40 40 # the repositories in Redmine
41 41 # -c, --command=COMMAND use this command instead of "svnadmin create" to
42 42 # create a repository. This option can be used to
43 43 # create repositories other than subversion and git kind.
44 44 # This command override the default creation for git and subversion.
45 45 # -f, --force force repository creation even if the project
46 46 # repository is already declared in Redmine
47 47 # -t, --test only show what should be done
48 48 # -h, --help show help and exit
49 49 # -v, --verbose verbose
50 50 # -V, --version print version and exit
51 51 # -q, --quiet no log
52 52 #
53 53 # == References
54 54 #
55 55 # You can find more information on the redmine's wiki : http://www.redmine.org/wiki/redmine/HowTos
56 56
57 57
58 58 require 'getoptlong'
59 59 require 'rdoc/usage'
60 require 'soap/wsdlDriver'
61 60 require 'find'
62 61 require 'etc'
63 62
64 Version = "1.1"
63 Version = "1.2"
65 64 SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
66 65
67 66 opts = GetoptLong.new(
68 67 ['--svn-dir', '-s', GetoptLong::REQUIRED_ARGUMENT],
69 68 ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
70 69 ['--owner', '-o', GetoptLong::REQUIRED_ARGUMENT],
71 70 ['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
72 71 ['--command' , '-c', GetoptLong::REQUIRED_ARGUMENT],
73 72 ['--scm', GetoptLong::REQUIRED_ARGUMENT],
74 73 ['--test', '-t', GetoptLong::NO_ARGUMENT],
75 74 ['--force', '-f', GetoptLong::NO_ARGUMENT],
76 75 ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
77 76 ['--version', '-V', GetoptLong::NO_ARGUMENT],
78 77 ['--help' , '-h', GetoptLong::NO_ARGUMENT],
79 78 ['--quiet' , '-q', GetoptLong::NO_ARGUMENT]
80 79 )
81 80
82 81 $verbose = 0
83 82 $quiet = false
84 83 $redmine_host = ''
85 84 $repos_base = ''
86 85 $svn_owner = 'root'
87 86 $use_groupid = true
88 87 $svn_url = false
89 88 $test = false
90 89 $force = false
91 90 $scm = 'Subversion'
92 91
93 92 def log(text, options={})
94 93 level = options[:level] || 0
95 94 puts text unless $quiet or level > $verbose
96 95 exit 1 if options[:exit]
97 96 end
98 97
99 98 def system_or_raise(command)
100 99 raise "\"#{command}\" failed" unless system command
101 100 end
102 101
103 102 module SCM
104 103
105 104 module Subversion
106 105 def self.create(path)
107 106 system_or_raise "svnadmin create #{path}"
108 107 end
109 108 end
110 109
111 110 module Git
112 111 def self.create(path)
113 112 Dir.mkdir path
114 113 Dir.chdir(path) do
115 114 system_or_raise "git --bare init --shared"
116 115 system_or_raise "git update-server-info"
117 116 end
118 117 end
119 118 end
120 119
121 120 end
122 121
123 122 begin
124 123 opts.each do |opt, arg|
125 124 case opt
126 125 when '--svn-dir'; $repos_base = arg.dup
127 126 when '--redmine-host'; $redmine_host = arg.dup
128 127 when '--owner'; $svn_owner = arg.dup; $use_groupid = false;
129 128 when '--url'; $svn_url = arg.dup
130 129 when '--scm'; $scm = arg.dup.capitalize; log("Invalid SCM: #{$scm}", :exit => true) unless SUPPORTED_SCM.include?($scm)
131 130 when '--command'; $command = arg.dup
132 131 when '--verbose'; $verbose += 1
133 132 when '--test'; $test = true
134 133 when '--force'; $force = true
135 134 when '--version'; puts Version; exit
136 135 when '--help'; RDoc::usage
137 136 when '--quiet'; $quiet = true
138 137 end
139 138 end
140 139 rescue
141 140 exit 1
142 141 end
143 142
144 143 if $test
145 144 log("running in test mode")
146 145 end
147 146
148 147 # Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
149 148 if $command.nil?
150 149 begin
151 150 scm_module = SCM.const_get($scm)
152 151 rescue
153 152 log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
154 153 end
155 154 end
156 155
157 156 $svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
158 157
159 158 if ($redmine_host.empty? or $repos_base.empty?)
160 159 RDoc::usage
161 160 end
162 161
163 162 unless File.directory?($repos_base)
164 163 log("directory '#{$repos_base}' doesn't exists", :exit => true)
165 164 end
166 165
166 begin
167 require 'activeresource'
168 rescue LoadError
169 log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
170 end
171
172 class Project < ActiveResource::Base; end
173
167 174 log("querying Redmine for projects...", :level => 1);
168 175
169 176 $redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
170 177 $redmine_host.gsub!(/\/$/, '')
171 178
172 wsdl_url = "#{$redmine_host}/sys/service.wsdl";
179 Project.site = "#{$redmine_host}/sys";
173 180
174 181 begin
175 soap = SOAP::WSDLDriverFactory.new(wsdl_url).create_rpc_driver
182 # Get all active projects that have the Repository module enabled
183 projects = Project.find(:all)
176 184 rescue => e
177 log("Unable to connect to #{wsdl_url} : #{e}", :exit => true)
185 log("Unable to connect to #{Project.site}: #{e}", :exit => true)
178 186 end
179 187
180 projects = soap.ProjectsWithRepositoryEnabled
181
182 188 if projects.nil?
183 189 log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
184 190 end
185 191
186 192 log("retrieved #{projects.size} projects", :level => 1)
187 193
188 194 def set_owner_and_rights(project, repos_path, &block)
189 195 if RUBY_PLATFORM =~ /mswin/
190 196 yield if block_given?
191 197 else
192 198 uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : 0)
193 199 right = project.is_public ? 0775 : 0770
194 200 yield if block_given?
195 201 Find.find(repos_path) do |f|
196 202 File.chmod right, f
197 203 File.chown uid, gid, f
198 204 end
199 205 end
200 206 end
201 207
202 208 def other_read_right?(file)
203 209 (File.stat(file).mode & 0007).zero? ? false : true
204 210 end
205 211
206 212 def owner_name(file)
207 213 RUBY_PLATFORM =~ /mswin/ ?
208 214 $svn_owner :
209 215 Etc.getpwuid( File.stat(file).uid ).name
210 216 end
211 217
212 218 projects.each do |project|
213 219 log("treating project #{project.name}", :level => 1)
214 220
215 221 if project.identifier.empty?
216 222 log("\tno identifier for project #{project.name}")
217 223 next
218 224 elsif not project.identifier.match(/^[a-z0-9\-]+$/)
219 225 log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
220 226 next;
221 227 end
222 228
223 229 repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
224 230
225 231 if File.directory?(repos_path)
226 232
227 233 # we must verify that repository has the good owner and the good
228 234 # rights before leaving
229 235 other_read = other_read_right?(repos_path)
230 236 owner = owner_name(repos_path)
231 237 next if project.is_public == other_read and owner == $svn_owner
232 238
233 239 if $test
234 240 log("\tchange mode on #{repos_path}")
235 241 next
236 242 end
237 243
238 244 begin
239 245 set_owner_and_rights(project, repos_path)
240 246 rescue Errno::EPERM => e
241 247 log("\tunable to change mode on #{repos_path} : #{e}\n")
242 248 next
243 249 end
244 250
245 251 log("\tmode change on #{repos_path}");
246 252
247 253 else
248 254 # if repository is already declared in redmine, we don't create
249 255 # unless user use -f with reposman
250 if $force == false and not project.repository.nil?
256 if $force == false and project.respond_to?(:repository)
251 257 log("\trepository for project #{project.identifier} already exists in Redmine", :level => 1)
252 258 next
253 259 end
254 260
255 261 project.is_public ? File.umask(0002) : File.umask(0007)
256 262
257 263 if $test
258 264 log("\tcreate repository #{repos_path}")
259 265 log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}") if $svn_url;
260 266 next
261 267 end
262 268
263 269 begin
264 270 set_owner_and_rights(project, repos_path) do
265 271 if scm_module.nil?
266 272 system_or_raise "#{$command} #{repos_path}"
267 273 else
268 274 scm_module.create(repos_path)
269 275 end
270 276 end
271 277 rescue => e
272 278 log("\tunable to create #{repos_path} : #{e}\n")
273 279 next
274 280 end
275 281
276 282 if $svn_url
277 ret = soap.RepositoryCreated project.identifier, $scm, "#{$svn_url}#{project.identifier}"
278 if ret > 0
283 begin
284 project.post(:repository, :vendor => $scm, :repository => {:url => "#{$svn_url}#{project.identifier}"})
279 285 log("\trepository #{repos_path} registered in Redmine with url #{$svn_url}#{project.identifier}");
280 else
281 log("\trepository #{repos_path} not registered in Redmine. Look in your log to find why.");
286 rescue => e
287 log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
282 288 end
283 289 end
284 290
285 291 log("\trepository #{repos_path} created");
286 292 end
287 293
288 294 end
289 295
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (1379 lines changed) Show them Hide them
General Comments 0
You need to be logged in to leave comments. Login now