##// END OF EJS Templates
implemented manual repo rescann and remapping
marcink -
r348:e8fc8754 default
parent child Browse files
Show More
@@ -1,93 +1,109 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # settings controller for pylons
3 # settings controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on July 14, 2010
21 Created on July 14, 2010
22 settings controller for pylons
22 settings controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import request, session, tmpl_context as c, url
26 from pylons import request, session, tmpl_context as c, url, app_globals as g
27 from pylons.controllers.util import abort, redirect
27 from pylons.controllers.util import abort, redirect
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from pylons_app.lib import helpers as h
29 from pylons_app.lib import helpers as h
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from pylons_app.lib.base import BaseController, render
31 from pylons_app.lib.base import BaseController, render
32 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache
32 from pylons_app.model.db import User, UserLog
33 from pylons_app.model.db import User, UserLog
33 from pylons_app.model.forms import UserForm
34 from pylons_app.model.forms import UserForm
35 from pylons_app.model.hg_model import HgModel
34 from pylons_app.model.user_model import UserModel
36 from pylons_app.model.user_model import UserModel
35 import formencode
37 import formencode
36 import logging
38 import logging
37
39
38 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
39
41
40
42
41 class SettingsController(BaseController):
43 class SettingsController(BaseController):
42 """REST Controller styled on the Atom Publishing Protocol"""
44 """REST Controller styled on the Atom Publishing Protocol"""
43 # To properly map this controller, ensure your config/routing.py
45 # To properly map this controller, ensure your config/routing.py
44 # file has a resource setup:
46 # file has a resource setup:
45 # map.resource('setting', 'settings', controller='admin/settings',
47 # map.resource('setting', 'settings', controller='admin/settings',
46 # path_prefix='/admin', name_prefix='admin_')
48 # path_prefix='/admin', name_prefix='admin_')
47
49
48
50
49 @LoginRequired()
51 @LoginRequired()
50 #@HasPermissionAllDecorator('hg.admin')
52 #@HasPermissionAllDecorator('hg.admin')
51 def __before__(self):
53 def __before__(self):
52 c.admin_user = session.get('admin_user')
54 c.admin_user = session.get('admin_user')
53 c.admin_username = session.get('admin_username')
55 c.admin_username = session.get('admin_username')
54 super(SettingsController, self).__before__()
56 super(SettingsController, self).__before__()
55
57
56 def index(self, format='html'):
58 def index(self, format='html'):
57 """GET /admin/settings: All items in the collection"""
59 """GET /admin/settings: All items in the collection"""
58 # url('admin_settings')
60 # url('admin_settings')
59 return render('admin/settings/settings.html')
61 return render('admin/settings/settings.html')
60
62
61 def create(self):
63 def create(self):
62 """POST /admin/settings: Create a new item"""
64 """POST /admin/settings: Create a new item"""
63 # url('admin_settings')
65 # url('admin_settings')
64
66
65 def new(self, format='html'):
67 def new(self, format='html'):
66 """GET /admin/settings/new: Form to create a new item"""
68 """GET /admin/settings/new: Form to create a new item"""
67 # url('admin_new_setting')
69 # url('admin_new_setting')
68
70
69 def update(self, id):
71 def update(self, id):
70 """PUT /admin/settings/id: Update an existing item"""
72 """PUT /admin/settings/id: Update an existing item"""
71 # Forms posted to this method should contain a hidden field:
73 # Forms posted to this method should contain a hidden field:
72 # <input type="hidden" name="_method" value="PUT" />
74 # <input type="hidden" name="_method" value="PUT" />
73 # Or using helpers:
75 # Or using helpers:
74 # h.form(url('admin_setting', id=ID),
76 # h.form(url('admin_setting', id=ID),
75 # method='put')
77 # method='put')
76 # url('admin_setting', id=ID)
78 # url('admin_setting', id=ID)
79 if id == 'mapping':
80 rm_obsolete = request.POST.get('destroy', False)
81 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
82
83 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
84 repo2db_mapper(initial, rm_obsolete)
85 invalidate_cache('cached_repo_list')
86
87
88 return redirect(url('admin_settings'))
89
90
91
92
77
93
78 def delete(self, id):
94 def delete(self, id):
79 """DELETE /admin/settings/id: Delete an existing item"""
95 """DELETE /admin/settings/id: Delete an existing item"""
80 # Forms posted to this method should contain a hidden field:
96 # Forms posted to this method should contain a hidden field:
81 # <input type="hidden" name="_method" value="DELETE" />
97 # <input type="hidden" name="_method" value="DELETE" />
82 # Or using helpers:
98 # Or using helpers:
83 # h.form(url('admin_setting', id=ID),
99 # h.form(url('admin_setting', id=ID),
84 # method='delete')
100 # method='delete')
85 # url('admin_setting', id=ID)
101 # url('admin_setting', id=ID)
86
102
87 def show(self, id, format='html'):
103 def show(self, id, format='html'):
88 """GET /admin/settings/id: Show a specific item"""
104 """GET /admin/settings/id: Show a specific item"""
89 # url('admin_setting', id=ID)
105 # url('admin_setting', id=ID)
90
106
91 def edit(self, id, format='html'):
107 def edit(self, id, format='html'):
92 """GET /admin/settings/id/edit: Form to edit an existing item"""
108 """GET /admin/settings/id/edit: Form to edit an existing item"""
93 # url('admin_edit_setting', id=ID)
109 # url('admin_edit_setting', id=ID)
@@ -1,202 +1,210 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Utilities for hg app
3 # Utilities for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your opinion) any later version of the license.
8 # of the License or (at your opinion) any later version of the license.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
18 # MA 02110-1301, USA.
19 from beaker.cache import cache_region
19 from beaker.cache import cache_region
20
20
21 """
21 """
22 Created on April 18, 2010
22 Created on April 18, 2010
23 Utilities for hg app
23 Utilities for hg app
24 @author: marcink
24 @author: marcink
25 """
25 """
26
26
27 import os
27 import os
28 import logging
28 import logging
29 from mercurial import ui, config, hg
29 from mercurial import ui, config, hg
30 from mercurial.error import RepoError
30 from mercurial.error import RepoError
31 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
31 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
32 from pylons_app.model.meta import Session
32 from pylons_app.model.meta import Session
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 def get_repo_slug(request):
36 def get_repo_slug(request):
37 return request.environ['pylons.routes_dict'].get('repo_name')
37 return request.environ['pylons.routes_dict'].get('repo_name')
38
38
39 def is_mercurial(environ):
39 def is_mercurial(environ):
40 """
40 """
41 Returns True if request's target is mercurial server - header
41 Returns True if request's target is mercurial server - header
42 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
42 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
43 """
43 """
44 http_accept = environ.get('HTTP_ACCEPT')
44 http_accept = environ.get('HTTP_ACCEPT')
45 if http_accept and http_accept.startswith('application/mercurial'):
45 if http_accept and http_accept.startswith('application/mercurial'):
46 return True
46 return True
47 return False
47 return False
48
48
49 def check_repo_dir(paths):
49 def check_repo_dir(paths):
50 repos_path = paths[0][1].split('/')
50 repos_path = paths[0][1].split('/')
51 if repos_path[-1] in ['*', '**']:
51 if repos_path[-1] in ['*', '**']:
52 repos_path = repos_path[:-1]
52 repos_path = repos_path[:-1]
53 if repos_path[0] != '/':
53 if repos_path[0] != '/':
54 repos_path[0] = '/'
54 repos_path[0] = '/'
55 if not os.path.isdir(os.path.join(*repos_path)):
55 if not os.path.isdir(os.path.join(*repos_path)):
56 raise Exception('Not a valid repository in %s' % paths[0][1])
56 raise Exception('Not a valid repository in %s' % paths[0][1])
57
57
58 def check_repo_fast(repo_name, base_path):
58 def check_repo_fast(repo_name, base_path):
59 if os.path.isdir(os.path.join(base_path, repo_name)):return False
59 if os.path.isdir(os.path.join(base_path, repo_name)):return False
60 return True
60 return True
61
61
62 def check_repo(repo_name, base_path, verify=True):
62 def check_repo(repo_name, base_path, verify=True):
63
63
64 repo_path = os.path.join(base_path, repo_name)
64 repo_path = os.path.join(base_path, repo_name)
65
65
66 try:
66 try:
67 if not check_repo_fast(repo_name, base_path):
67 if not check_repo_fast(repo_name, base_path):
68 return False
68 return False
69 r = hg.repository(ui.ui(), repo_path)
69 r = hg.repository(ui.ui(), repo_path)
70 if verify:
70 if verify:
71 hg.verify(r)
71 hg.verify(r)
72 #here we hnow that repo exists it was verified
72 #here we hnow that repo exists it was verified
73 log.info('%s repo is already created', repo_name)
73 log.info('%s repo is already created', repo_name)
74 return False
74 return False
75 except RepoError:
75 except RepoError:
76 #it means that there is no valid repo there...
76 #it means that there is no valid repo there...
77 log.info('%s repo is free for creation', repo_name)
77 log.info('%s repo is free for creation', repo_name)
78 return True
78 return True
79
79
80
80
81 @cache_region('super_short_term', 'cached_hg_ui')
81 @cache_region('super_short_term', 'cached_hg_ui')
82 def get_hg_ui_cached():
82 def get_hg_ui_cached():
83 sa = Session()
83 sa = Session()
84 return sa.query(HgAppUi).all()
84 return sa.query(HgAppUi).all()
85
85
86 def get_hg_settings():
86 def get_hg_settings():
87 sa = Session()
87 sa = Session()
88 ret = sa.query(HgAppSettings).scalar()
88 ret = sa.query(HgAppSettings).scalar()
89 if not ret:
89 if not ret:
90 raise Exception('Could not get application settings !')
90 raise Exception('Could not get application settings !')
91 return ret
91 return ret
92
92
93 def make_ui(read_from='file', path=None, checkpaths=True):
93 def make_ui(read_from='file', path=None, checkpaths=True):
94 """
94 """
95 A function that will read python rc files or database
95 A function that will read python rc files or database
96 and make an mercurial ui object from read options
96 and make an mercurial ui object from read options
97
97
98 @param path: path to mercurial config file
98 @param path: path to mercurial config file
99 @param checkpaths: check the path
99 @param checkpaths: check the path
100 @param read_from: read from 'file' or 'db'
100 @param read_from: read from 'file' or 'db'
101 """
101 """
102 #propagated from mercurial documentation
102 #propagated from mercurial documentation
103 sections = ['alias', 'auth',
103 sections = ['alias', 'auth',
104 'decode/encode', 'defaults',
104 'decode/encode', 'defaults',
105 'diff', 'email',
105 'diff', 'email',
106 'extensions', 'format',
106 'extensions', 'format',
107 'merge-patterns', 'merge-tools',
107 'merge-patterns', 'merge-tools',
108 'hooks', 'http_proxy',
108 'hooks', 'http_proxy',
109 'smtp', 'patch',
109 'smtp', 'patch',
110 'paths', 'profiling',
110 'paths', 'profiling',
111 'server', 'trusted',
111 'server', 'trusted',
112 'ui', 'web', ]
112 'ui', 'web', ]
113 baseui = ui.ui()
113 baseui = ui.ui()
114
114
115
115
116 if read_from == 'file':
116 if read_from == 'file':
117 if not os.path.isfile(path):
117 if not os.path.isfile(path):
118 log.warning('Unable to read config file %s' % path)
118 log.warning('Unable to read config file %s' % path)
119 return False
119 return False
120
120
121 cfg = config.config()
121 cfg = config.config()
122 cfg.read(path)
122 cfg.read(path)
123 for section in sections:
123 for section in sections:
124 for k, v in cfg.items(section):
124 for k, v in cfg.items(section):
125 baseui.setconfig(section, k, v)
125 baseui.setconfig(section, k, v)
126 if checkpaths:check_repo_dir(cfg.items('paths'))
126 if checkpaths:check_repo_dir(cfg.items('paths'))
127
127
128
128
129 elif read_from == 'db':
129 elif read_from == 'db':
130 hg_ui = get_hg_ui_cached()
130 hg_ui = get_hg_ui_cached()
131 for ui_ in hg_ui:
131 for ui_ in hg_ui:
132 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
132 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
133
133
134
134
135 return baseui
135 return baseui
136
136
137
137
138 def set_hg_app_config(config):
138 def set_hg_app_config(config):
139 hgsettings = get_hg_settings()
139 hgsettings = get_hg_settings()
140 config['hg_app_auth_realm'] = hgsettings.app_auth_realm
140 config['hg_app_auth_realm'] = hgsettings.app_auth_realm
141 config['hg_app_name'] = hgsettings.app_title
141 config['hg_app_name'] = hgsettings.app_title
142
142
143 def invalidate_cache(name, *args):
143 def invalidate_cache(name, *args):
144 """Invalidates given name cache"""
144 """Invalidates given name cache"""
145
145
146 from beaker.cache import region_invalidate
146 from beaker.cache import region_invalidate
147 log.info('INVALIDATING CACHE FOR %s', name)
147 log.info('INVALIDATING CACHE FOR %s', name)
148
148
149 """propagate our arguments to make sure invalidation works. First
149 """propagate our arguments to make sure invalidation works. First
150 argument has to be the name of cached func name give to cache decorator
150 argument has to be the name of cached func name give to cache decorator
151 without that the invalidation would not work"""
151 without that the invalidation would not work"""
152 tmp = [name]
152 tmp = [name]
153 tmp.extend(args)
153 tmp.extend(args)
154 args = tuple(tmp)
154 args = tuple(tmp)
155
155
156 if name == 'cached_repo_list':
156 if name == 'cached_repo_list':
157 from pylons_app.model.hg_model import _get_repos_cached
157 from pylons_app.model.hg_model import _get_repos_cached
158 region_invalidate(_get_repos_cached, None, *args)
158 region_invalidate(_get_repos_cached, None, *args)
159
159
160 if name == 'full_changelog':
160 if name == 'full_changelog':
161 from pylons_app.model.hg_model import _full_changelog_cached
161 from pylons_app.model.hg_model import _full_changelog_cached
162 region_invalidate(_full_changelog_cached, None, *args)
162 region_invalidate(_full_changelog_cached, None, *args)
163
163
164 from vcs.backends.base import BaseChangeset
164 from vcs.backends.base import BaseChangeset
165 from vcs.utils.lazy import LazyProperty
165 from vcs.utils.lazy import LazyProperty
166 class EmptyChangeset(BaseChangeset):
166 class EmptyChangeset(BaseChangeset):
167
167
168 revision = -1
168 revision = -1
169 message = ''
169 message = ''
170
170
171 @LazyProperty
171 @LazyProperty
172 def raw_id(self):
172 def raw_id(self):
173 """
173 """
174 Returns raw string identifing this changeset, useful for web
174 Returns raw string identifing this changeset, useful for web
175 representation.
175 representation.
176 """
176 """
177 return '0' * 12
177 return '0' * 12
178
178
179
179
180 def repo2db_mapper(initial_repo_list):
180 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
181 """
181 """
182 maps all found repositories into db
182 maps all found repositories into db
183 """
183 """
184 from pylons_app.model.meta import Session
185 from pylons_app.model.repo_model import RepoModel
184 from pylons_app.model.repo_model import RepoModel
186
185
187 sa = Session()
186 sa = Session()
188 user = sa.query(User).filter(User.admin == True).first()
187 user = sa.query(User).filter(User.admin == True).first()
189
188
190 rm = RepoModel()
189 rm = RepoModel()
191
190
192 for name, repo in initial_repo_list.items():
191 for name, repo in initial_repo_list.items():
193 if not sa.query(Repository).get(name):
192 if not sa.query(Repository).get(name):
194 log.info('repository %s not found creating default', name)
193 log.info('repository %s not found creating default', name)
195
194
196 form_data = {
195 form_data = {
197 'repo_name':name,
196 'repo_name':name,
198 'description':repo.description if repo.description != 'unknown' else \
197 'description':repo.description if repo.description != 'unknown' else \
199 'auto description for %s' % name,
198 'auto description for %s' % name,
200 'private':False
199 'private':False
201 }
200 }
202 rm.create(form_data, user, just_db=True)
201 rm.create(form_data, user, just_db=True)
202
203
204 if remove_obsolete:
205 #remove from database those repositories that are not in the filesystem
206 for repo in sa.query(Repository).all():
207 if repo.repo_name not in initial_repo_list.keys():
208 sa.delete(repo)
209 sa.commit()
210
@@ -1,21 +1,53 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Settings administration')}
5 ${_('Settings administration')}
6 </%def>
6 </%def>
7 <%def name="breadcrumbs()">
7 <%def name="breadcrumbs()">
8 ${h.link_to(u'Admin',h.url('admin_home'))}
8 ${h.link_to(u'Admin',h.url('admin_home'))}
9 /
9 /
10 ${_('Settings')}
10 ${_('Settings')}
11 </%def>
11 </%def>
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 ${self.submenu('settings')}
14 ${self.submenu('settings')}
15 </%def>
15 </%def>
16 <%def name="main()">
16 <%def name="main()">
17 <div>
17 <div>
18 <h2>${_('Settings administration')}</h2>
18 <h2>${_('Settings administration')}</h2>
19
19
20 ${h.form(url('admin_setting', id='mapping'),method='put')}
21 <table class="table_disp">
22 <tr class="header">
23 <td colspan="2">${_('Remap andv rescan repositories')}</td>
24 </tr>
25 <tr align="right">
26 <td><span class="tooltip" tooltip_title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
27 ${_('destroy old data')}</span> ${h.checkbox('destroy',True)}</td>
28 <td>${h.submit('rescan','rescan repositories')}</td>
29 </table>
30 ${h.end_form()}
31 <br/>
32 ${h.form(url('admin_setting', id='global'),method='put')}
33 <table class="table_disp">
34 <tr class="header">
35 <td colspan="3">${_('Global application settings')}</td>
36 </tr>
37 <tr>
38 <td>${_('Application name')}</td>
39 <td>${h.text('app_title')}</td>
40 </tr>
41 <tr>
42 <td>${_('Realm text')}</td>
43 <td>${h.text('app_auth_realm')}</td>
44 </tr>
45 <tr>
46 <td></td>
47 <td>${h.submit('save','save settings')}</td>
48 </tr>
49 </table>
50 ${h.end_form()}
51
20 </div>
52 </div>
21 </%def>
53 </%def>
General Comments 0
You need to be logged in to leave comments. Login now