Show More
@@ -1,67 +1,91 | |||
|
1 | 1 | """Routes configuration |
|
2 | 2 | |
|
3 | 3 | The more specific and detailed routes should be defined first so they |
|
4 | 4 | may take precedent over the more generic routes. For more information |
|
5 | 5 | refer to the routes manual at http://routes.groovie.org/docs/ |
|
6 | 6 | """ |
|
7 | 7 | from routes import Mapper |
|
8 | 8 | |
|
9 | 9 | def make_map(config): |
|
10 | 10 | """Create, configure and return the routes Mapper""" |
|
11 | 11 | map = Mapper(directory=config['pylons.paths']['controllers'], |
|
12 | 12 | always_scan=config['debug']) |
|
13 | 13 | map.minimization = False |
|
14 | 14 | map.explicit = False |
|
15 | 15 | |
|
16 | 16 | # The ErrorController route (handles 404/500 error pages); it should |
|
17 | 17 | # likely stay at the top, ensuring it can always be resolved |
|
18 | 18 | map.connect('/error/{action}', controller='error') |
|
19 | 19 | map.connect('/error/{action}/{id}', controller='error') |
|
20 | 20 | |
|
21 | 21 | # CUSTOM ROUTES HERE |
|
22 | 22 | map.connect('hg_home', '/', controller='hg', action='index') |
|
23 | 23 | |
|
24 | 24 | |
|
25 |
#REST |
|
|
26 | map.resource('repo', 'repos', path_prefix='/_admin') | |
|
25 | #REST routes | |
|
26 | with map.submapper(path_prefix='/_admin', controller='repos') as m: | |
|
27 | m.connect("repos", "/repos", | |
|
28 | action="create", conditions=dict(method=["POST"])) | |
|
29 | m.connect("repos", "/repos", | |
|
30 | action="index", conditions=dict(method=["GET"])) | |
|
31 | m.connect("formatted_repos", "/repos.{format}", | |
|
32 | action="index", | |
|
33 | conditions=dict(method=["GET"])) | |
|
34 | m.connect("new_repo", "/repos/new", | |
|
35 | action="new", conditions=dict(method=["GET"])) | |
|
36 | m.connect("formatted_new_repo", "/repos/new.{format}", | |
|
37 | action="new", conditions=dict(method=["GET"])) | |
|
38 | m.connect("/repos/{id:.*}", | |
|
39 | action="update", conditions=dict(method=["PUT"])) | |
|
40 | m.connect("/repos/{id:.*}", | |
|
41 | action="delete", conditions=dict(method=["DELETE"])) | |
|
42 | m.connect("edit_repo", "/repos/{id:.*}/edit", | |
|
43 | action="edit", conditions=dict(method=["GET"])) | |
|
44 | m.connect("formatted_edit_repo", "/repos/{id:.*}.{format}/edit", | |
|
45 | action="edit", conditions=dict(method=["GET"])) | |
|
46 | m.connect("repo", "/repos/{id:.*}", | |
|
47 | action="show", conditions=dict(method=["GET"])) | |
|
48 | m.connect("formatted_repo", "/repos/{id:.*}.{format}", | |
|
49 | action="show", conditions=dict(method=["GET"])) | |
|
50 | ||
|
27 | 51 | map.resource('user', 'users', path_prefix='/_admin') |
|
28 | 52 | map.resource('permission', 'permissions', path_prefix='/_admin') |
|
29 | 53 | |
|
30 | 54 | #ADMIN |
|
31 | 55 | with map.submapper(path_prefix='/_admin', controller='admin') as m: |
|
32 | 56 | m.connect('admin_home', '/', action='index')#main page |
|
33 | 57 | m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}', |
|
34 | 58 | action='add_repo') |
|
35 | 59 | |
|
36 | 60 | #FEEDS |
|
37 | map.connect('rss_feed_home', '/{repo_name}/feed/rss', | |
|
61 | map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss', | |
|
38 | 62 | controller='feed', action='rss') |
|
39 | map.connect('atom_feed_home', '/{repo_name}/feed/atom', | |
|
63 | map.connect('atom_feed_home', '/{repo_name:.*}/feed/atom', | |
|
40 | 64 | controller='feed', action='atom') |
|
41 | 65 | |
|
42 | 66 | map.connect('login_home', '/login', controller='login') |
|
43 | 67 | map.connect('logout_home', '/logout', controller='login', action='logout') |
|
44 | 68 | |
|
45 | map.connect('changeset_home', '/{repo_name}/changeset/{revision}', | |
|
69 | map.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}', | |
|
46 | 70 | controller='changeset', revision='tip') |
|
47 | map.connect('summary_home', '/{repo_name}/summary', | |
|
71 | map.connect('summary_home', '/{repo_name:.*}/summary', | |
|
48 | 72 | controller='summary') |
|
49 | map.connect('shortlog_home', '/{repo_name}/shortlog', | |
|
73 | map.connect('shortlog_home', '/{repo_name:.*}/shortlog', | |
|
50 | 74 | controller='shortlog') |
|
51 | map.connect('branches_home', '/{repo_name}/branches', | |
|
75 | map.connect('branches_home', '/{repo_name:.*}/branches', | |
|
52 | 76 | controller='branches') |
|
53 | map.connect('tags_home', '/{repo_name}/tags', | |
|
77 | map.connect('tags_home', '/{repo_name:.*}/tags', | |
|
54 | 78 | controller='tags') |
|
55 | map.connect('changelog_home', '/{repo_name}/changelog', | |
|
79 | map.connect('changelog_home', '/{repo_name:.*}/changelog', | |
|
56 | 80 | controller='changelog') |
|
57 | map.connect('files_home', '/{repo_name}/files/{revision}/{f_path:.*}', | |
|
81 | map.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}', | |
|
58 | 82 | controller='files', revision='tip', f_path='') |
|
59 | map.connect('files_diff_home', '/{repo_name}/diff/{f_path:.*}', | |
|
83 | map.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}', | |
|
60 | 84 | controller='files', action='diff', revision='tip', f_path='') |
|
61 | map.connect('files_raw_home', '/{repo_name}/rawfile/{revision}/{f_path:.*}', | |
|
85 | map.connect('files_raw_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}', | |
|
62 | 86 | controller='files', action='rawfile', revision='tip', f_path='') |
|
63 | map.connect('files_annotate_home', '/{repo_name}/annotate/{revision}/{f_path:.*}', | |
|
87 | map.connect('files_annotate_home', '/{repo_name:.*}/annotate/{revision}/{f_path:.*}', | |
|
64 | 88 | controller='files', action='annotate', revision='tip', f_path='') |
|
65 | map.connect('files_archive_home', '/{repo_name}/archive/{revision}/{fileformat}', | |
|
89 | map.connect('files_archive_home', '/{repo_name:.*}/archive/{revision}/{fileformat}', | |
|
66 | 90 | controller='files', action='archivefile', revision='tip') |
|
67 | 91 | return map |
@@ -1,100 +1,103 | |||
|
1 | 1 | from pylons import request, response, session, tmpl_context as c, url, \ |
|
2 | 2 | app_globals as g |
|
3 | 3 | from pylons.controllers.util import abort, redirect |
|
4 | 4 | from pylons_app.lib.auth import LoginRequired |
|
5 | from pylons.i18n.translation import _ | |
|
6 | from pylons_app.lib import helpers as h | |
|
5 | 7 | from pylons_app.lib.base import BaseController, render |
|
6 | 8 | from pylons_app.lib.filters import clean_repo |
|
7 | 9 | from pylons_app.lib.utils import check_repo, invalidate_cache |
|
8 | 10 | from pylons_app.model.hg_model import HgModel |
|
9 | 11 | import logging |
|
10 | 12 | import os |
|
11 | 13 | import shutil |
|
12 | 14 | from operator import itemgetter |
|
13 | 15 | log = logging.getLogger(__name__) |
|
14 | 16 | |
|
15 | 17 | class ReposController(BaseController): |
|
16 | 18 | """REST Controller styled on the Atom Publishing Protocol""" |
|
17 | 19 | # To properly map this controller, ensure your config/routing.py |
|
18 | 20 | # file has a resource setup: |
|
19 | 21 | # map.resource('repo', 'repos') |
|
20 | 22 | @LoginRequired() |
|
21 | 23 | def __before__(self): |
|
22 | 24 | c.admin_user = session.get('admin_user') |
|
23 | 25 | c.admin_username = session.get('admin_username') |
|
24 | 26 | super(ReposController, self).__before__() |
|
25 | 27 | |
|
26 | 28 | def index(self, format='html'): |
|
27 | 29 | """GET /repos: All items in the collection""" |
|
28 | 30 | # url('repos') |
|
29 | 31 | cached_repo_list = HgModel().get_repos() |
|
30 | 32 | c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort')) |
|
31 | 33 | return render('admin/repos/repos.html') |
|
32 | 34 | |
|
33 | 35 | def create(self): |
|
34 | 36 | """POST /repos: Create a new item""" |
|
35 | 37 | # url('repos') |
|
36 | 38 | name = request.POST.get('name') |
|
37 | 39 | |
|
38 | 40 | try: |
|
39 | 41 | self._create_repo(name) |
|
40 | 42 | #clear our cached list for refresh with new repo |
|
41 | 43 | invalidate_cache('cached_repo_list') |
|
44 | h.flash(_('created repository %s') % name, category='success') | |
|
42 | 45 | except Exception as e: |
|
43 | 46 | log.error(e) |
|
44 | 47 | |
|
45 | 48 | return redirect('repos') |
|
46 | 49 | |
|
47 | 50 | def _create_repo(self, repo_name): |
|
48 | 51 | repo_path = os.path.join(g.base_path, repo_name) |
|
49 | 52 | if check_repo(repo_name, g.base_path): |
|
50 | 53 | log.info('creating repo %s in %s', repo_name, repo_path) |
|
51 | 54 | from vcs.backends.hg import MercurialRepository |
|
52 | 55 | MercurialRepository(repo_path, create=True) |
|
53 | 56 | |
|
54 | 57 | |
|
55 | 58 | def new(self, format='html'): |
|
56 | 59 | """GET /repos/new: Form to create a new item""" |
|
57 | 60 | new_repo = request.GET.get('repo', '') |
|
58 | 61 | c.new_repo = clean_repo(new_repo) |
|
59 | 62 | |
|
60 | 63 | return render('admin/repos/repo_add.html') |
|
61 | 64 | |
|
62 | 65 | def update(self, id): |
|
63 | 66 | """PUT /repos/id: Update an existing item""" |
|
64 | 67 | # Forms posted to this method should contain a hidden field: |
|
65 | 68 | # <input type="hidden" name="_method" value="PUT" /> |
|
66 | 69 | # Or using helpers: |
|
67 | 70 | # h.form(url('repo', id=ID), |
|
68 | 71 | # method='put') |
|
69 | 72 | # url('repo', id=ID) |
|
70 | 73 | |
|
71 | 74 | def delete(self, id): |
|
72 | 75 | """DELETE /repos/id: Delete an existing item""" |
|
73 | 76 | # Forms posted to this method should contain a hidden field: |
|
74 | 77 | # <input type="hidden" name="_method" value="DELETE" /> |
|
75 | 78 | # Or using helpers: |
|
76 | 79 | # h.form(url('repo', id=ID), |
|
77 | 80 | # method='delete') |
|
78 | 81 | # url('repo', id=ID) |
|
79 | 82 | from datetime import datetime |
|
80 | 83 | path = g.paths[0][1].replace('*', '') |
|
81 | 84 | rm_path = os.path.join(path, id) |
|
82 | 85 | log.info("Removing %s", rm_path) |
|
83 | 86 | shutil.move(os.path.join(rm_path, '.hg'), os.path.join(rm_path, 'rm__.hg')) |
|
84 | 87 | shutil.move(rm_path, os.path.join(path, 'rm__%s-%s' % (datetime.today(), id))) |
|
85 | 88 | |
|
86 | 89 | #clear our cached list for refresh with new repo |
|
87 | 90 | invalidate_cache('cached_repo_list') |
|
88 | ||
|
91 | h.flash(_('deleted repository %s') % rm_path, category='success') | |
|
89 | 92 | return redirect(url('repos')) |
|
90 | 93 | |
|
91 | 94 | |
|
92 | 95 | def show(self, id, format='html'): |
|
93 | 96 | """GET /repos/id: Show a specific item""" |
|
94 | 97 | # url('repo', id=ID) |
|
95 | 98 | |
|
96 | 99 | def edit(self, id, format='html'): |
|
97 | 100 | """GET /repos/id/edit: Form to edit an existing item""" |
|
98 | 101 | # url('edit_repo', id=ID) |
|
99 | 102 | c.new_repo = id |
|
100 | 103 | return render('admin/repos/repo_edit.html') |
@@ -1,136 +1,138 | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # |
|
4 | 4 | # Copyright (c) 2010 marcink. All rights reserved. |
|
5 | 5 | # |
|
6 | 6 | """ |
|
7 | 7 | Created on 2010-04-28 |
|
8 | 8 | |
|
9 | 9 | @author: marcink |
|
10 | 10 | SimpleHG middleware for handling mercurial protocol request (push/clone etc.) |
|
11 | 11 | It's implemented with basic auth function |
|
12 | 12 | """ |
|
13 | 13 | from datetime import datetime |
|
14 | 14 | from mercurial.hgweb import hgweb |
|
15 | 15 | from mercurial.hgweb.request import wsgiapplication |
|
16 | 16 | from paste.auth.basic import AuthBasicAuthenticator |
|
17 | 17 | from paste.httpheaders import REMOTE_USER, AUTH_TYPE |
|
18 | 18 | from pylons_app.lib.auth import authfunc |
|
19 | 19 | from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache |
|
20 | 20 | from pylons_app.model import meta |
|
21 | 21 | from pylons_app.model.db import UserLog, User |
|
22 | 22 | from webob.exc import HTTPNotFound |
|
23 | 23 | import logging |
|
24 | 24 | import os |
|
25 | 25 | log = logging.getLogger(__name__) |
|
26 | 26 | |
|
27 | 27 | class SimpleHg(object): |
|
28 | 28 | |
|
29 | 29 | def __init__(self, application, config): |
|
30 | 30 | self.application = application |
|
31 | 31 | self.config = config |
|
32 | 32 | #authenticate this mercurial request using |
|
33 | 33 | realm = '%s %s' % (config['hg_app_name'], 'mercurial repository') |
|
34 | 34 | self.authenticate = AuthBasicAuthenticator(realm, authfunc) |
|
35 | 35 | |
|
36 | 36 | def __call__(self, environ, start_response): |
|
37 | 37 | if not is_mercurial(environ): |
|
38 | 38 | return self.application(environ, start_response) |
|
39 | 39 | else: |
|
40 | 40 | #=================================================================== |
|
41 | 41 | # AUTHENTICATE THIS MERCURIAL REQUEST |
|
42 | 42 | #=================================================================== |
|
43 | 43 | username = REMOTE_USER(environ) |
|
44 | 44 | if not username: |
|
45 | 45 | result = self.authenticate(environ) |
|
46 | 46 | if isinstance(result, str): |
|
47 | 47 | AUTH_TYPE.update(environ, 'basic') |
|
48 | 48 | REMOTE_USER.update(environ, result) |
|
49 | 49 | else: |
|
50 | 50 | return result.wsgi_application(environ, start_response) |
|
51 | 51 | |
|
52 | 52 | try: |
|
53 | repo_name = environ['PATH_INFO'].split('/')[1] | |
|
54 | except: | |
|
53 | repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:]) | |
|
54 | except Exception as e: | |
|
55 | log.error(e) | |
|
55 | 56 | return HTTPNotFound()(environ, start_response) |
|
56 | 57 | |
|
57 | 58 | #since we wrap into hgweb, just reset the path |
|
58 | 59 | environ['PATH_INFO'] = '/' |
|
59 | 60 | self.baseui = make_ui(self.config['hg_app_repo_conf']) |
|
60 | 61 | self.basepath = self.baseui.configitems('paths')[0][1]\ |
|
61 | 62 | .replace('*', '') |
|
62 | 63 | self.repo_path = os.path.join(self.basepath, repo_name) |
|
63 | 64 | try: |
|
64 | 65 | app = wsgiapplication(self.__make_app) |
|
65 | 66 | except Exception as e: |
|
67 | log.error(e) | |
|
66 | 68 | return HTTPNotFound()(environ, start_response) |
|
67 | 69 | action = self.__get_action(environ) |
|
68 | 70 | #invalidate cache on push |
|
69 | 71 | if action == 'push': |
|
70 | 72 | self.__invalidate_cache(repo_name) |
|
71 | 73 | |
|
72 | 74 | if action: |
|
73 | 75 | username = self.__get_environ_user(environ) |
|
74 | 76 | self.__log_user_action(username, action, repo_name) |
|
75 | 77 | |
|
76 | 78 | return app(environ, start_response) |
|
77 | 79 | |
|
78 | 80 | def __make_app(self): |
|
79 | 81 | hgserve = hgweb(self.repo_path) |
|
80 | 82 | return self.__load_web_settings(hgserve) |
|
81 | 83 | |
|
82 | 84 | def __get_environ_user(self, environ): |
|
83 | 85 | return environ.get('REMOTE_USER') |
|
84 | 86 | |
|
85 | 87 | def __get_action(self, environ): |
|
86 | 88 | """ |
|
87 | 89 | Maps mercurial request commands into a pull or push command. |
|
88 | 90 | @param environ: |
|
89 | 91 | """ |
|
90 | 92 | mapping = { |
|
91 | 93 | 'changegroup': 'pull', |
|
92 | 94 | 'changegroupsubset': 'pull', |
|
93 | 95 | 'unbundle': 'push', |
|
94 | 96 | 'stream_out': 'pull', |
|
95 | 97 | } |
|
96 | 98 | for qry in environ['QUERY_STRING'].split('&'): |
|
97 | 99 | if qry.startswith('cmd'): |
|
98 | 100 | cmd = qry.split('=')[-1] |
|
99 | 101 | if mapping.has_key(cmd): |
|
100 | 102 | return mapping[cmd] |
|
101 | 103 | |
|
102 | 104 | def __log_user_action(self, username, action, repo): |
|
103 | 105 | sa = meta.Session |
|
104 | 106 | try: |
|
105 | 107 | user = sa.query(User).filter(User.username == username).one() |
|
106 | 108 | user_log = UserLog() |
|
107 | 109 | user_log.user_id = user.user_id |
|
108 | 110 | user_log.action = action |
|
109 | 111 | user_log.repository = repo.replace('/', '') |
|
110 | 112 | user_log.action_date = datetime.now() |
|
111 | 113 | sa.add(user_log) |
|
112 | 114 | sa.commit() |
|
113 | 115 | log.info('Adding user %s, action %s on %s', |
|
114 | 116 | username, action, repo) |
|
115 | 117 | except Exception as e: |
|
116 | 118 | sa.rollback() |
|
117 | 119 | log.error('could not log user action:%s', str(e)) |
|
118 | 120 | |
|
119 | 121 | def __invalidate_cache(self, repo_name): |
|
120 | 122 | """we know that some change was made to repositories and we should |
|
121 | 123 | invalidate the cache to see the changes right away but only for |
|
122 | 124 | push requests""" |
|
123 | 125 | invalidate_cache('cached_repo_list') |
|
124 | 126 | invalidate_cache('full_changelog', repo_name) |
|
125 | 127 | |
|
126 | 128 | |
|
127 | 129 | def __load_web_settings(self, hgserve): |
|
128 | 130 | repoui = make_ui(os.path.join(self.repo_path, '.hg', 'hgrc'), False) |
|
129 | 131 | #set the global ui for hgserve |
|
130 | 132 | hgserve.repo.ui = self.baseui |
|
131 | 133 | |
|
132 | 134 | if repoui: |
|
133 | 135 | #set the repository based config |
|
134 | 136 | hgserve.repo.ui = repoui |
|
135 | 137 | |
|
136 | 138 | return hgserve |
@@ -1,144 +1,134 | |||
|
1 | 1 | import os |
|
2 | 2 | import logging |
|
3 | 3 | from mercurial import ui, config, hg |
|
4 | 4 | from mercurial.error import RepoError |
|
5 | 5 | log = logging.getLogger(__name__) |
|
6 | 6 | |
|
7 | 7 | |
|
8 | 8 | def get_repo_slug(request): |
|
9 | path_info = request.environ.get('PATH_INFO') | |
|
10 | uri_lst = path_info.split('/') | |
|
11 | repo_name = uri_lst[1] | |
|
12 | return repo_name | |
|
9 | return request.environ['pylons.routes_dict'].get('repo_name') | |
|
13 | 10 | |
|
14 | 11 | def is_mercurial(environ): |
|
15 | 12 | """ |
|
16 | 13 | Returns True if request's target is mercurial server - header |
|
17 | 14 | ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``. |
|
18 | 15 | """ |
|
19 | 16 | http_accept = environ.get('HTTP_ACCEPT') |
|
20 | 17 | if http_accept and http_accept.startswith('application/mercurial'): |
|
21 | 18 | return True |
|
22 | 19 | return False |
|
23 | 20 | |
|
24 | 21 | def check_repo_dir(paths): |
|
25 | 22 | repos_path = paths[0][1].split('/') |
|
26 | 23 | if repos_path[-1] in ['*', '**']: |
|
27 | 24 | repos_path = repos_path[:-1] |
|
28 | 25 | if repos_path[0] != '/': |
|
29 | 26 | repos_path[0] = '/' |
|
30 | 27 | if not os.path.isdir(os.path.join(*repos_path)): |
|
31 | 28 | raise Exception('Not a valid repository in %s' % paths[0][1]) |
|
32 | 29 | |
|
33 | 30 | def check_repo(repo_name, base_path): |
|
34 | 31 | |
|
35 | 32 | repo_path = os.path.join(base_path, repo_name) |
|
36 | 33 | |
|
37 | 34 | try: |
|
38 | 35 | r = hg.repository(ui.ui(), repo_path) |
|
39 | 36 | hg.verify(r) |
|
40 | 37 | #here we hnow that repo exists it was verified |
|
41 | 38 | log.info('%s repo is already created', repo_name) |
|
42 | 39 | return False |
|
43 | 40 | #raise Exception('Repo exists') |
|
44 | 41 | except RepoError: |
|
45 | 42 | log.info('%s repo is free for creation', repo_name) |
|
46 | 43 | #it means that there is no valid repo there... |
|
47 | 44 | return True |
|
48 | 45 | |
|
49 | 46 | def make_ui(path=None, checkpaths=True): |
|
50 | 47 | """ |
|
51 | 48 | A funcion that will read python rc files and make an ui from read options |
|
52 | 49 | |
|
53 | 50 | @param path: path to mercurial config file |
|
54 | 51 | """ |
|
55 | 52 | if not path: |
|
56 | 53 | log.error('repos config path is empty !') |
|
57 | 54 | |
|
58 | 55 | if not os.path.isfile(path): |
|
59 | 56 | log.warning('Unable to read config file %s' % path) |
|
60 | 57 | return False |
|
61 | 58 | #propagated from mercurial documentation |
|
62 | 59 | sections = [ |
|
63 | 60 | 'alias', |
|
64 | 61 | 'auth', |
|
65 | 62 | 'decode/encode', |
|
66 | 63 | 'defaults', |
|
67 | 64 | 'diff', |
|
68 | 65 | 'email', |
|
69 | 66 | 'extensions', |
|
70 | 67 | 'format', |
|
71 | 68 | 'merge-patterns', |
|
72 | 69 | 'merge-tools', |
|
73 | 70 | 'hooks', |
|
74 | 71 | 'http_proxy', |
|
75 | 72 | 'smtp', |
|
76 | 73 | 'patch', |
|
77 | 74 | 'paths', |
|
78 | 75 | 'profiling', |
|
79 | 76 | 'server', |
|
80 | 77 | 'trusted', |
|
81 | 78 | 'ui', |
|
82 | 79 | 'web', |
|
83 | 80 | ] |
|
84 | 81 | |
|
85 | 82 | baseui = ui.ui() |
|
86 | 83 | cfg = config.config() |
|
87 | 84 | cfg.read(path) |
|
88 | 85 | if checkpaths:check_repo_dir(cfg.items('paths')) |
|
89 | 86 | |
|
90 | 87 | for section in sections: |
|
91 | 88 | for k, v in cfg.items(section): |
|
92 | 89 | baseui.setconfig(section, k, v) |
|
93 | 90 | |
|
94 | 91 | return baseui |
|
95 | 92 | |
|
96 | 93 | def invalidate_cache(name, *args): |
|
97 | 94 | """Invalidates given name cache""" |
|
98 | 95 | |
|
99 | 96 | from beaker.cache import region_invalidate |
|
100 | 97 | log.info('INVALIDATING CACHE FOR %s', name) |
|
101 | 98 | |
|
102 | 99 | """propagate our arguments to make sure invalidation works. First |
|
103 | 100 | argument has to be the name of cached func name give to cache decorator |
|
104 | 101 | without that the invalidation would not work""" |
|
105 | 102 | tmp = [name] |
|
106 | 103 | tmp.extend(args) |
|
107 | 104 | args = tuple(tmp) |
|
108 | 105 | |
|
109 | 106 | if name == 'cached_repo_list': |
|
110 | 107 | from pylons_app.model.hg_model import _get_repos_cached |
|
111 | 108 | region_invalidate(_get_repos_cached, None, *args) |
|
112 | 109 | |
|
113 | 110 | if name == 'full_changelog': |
|
114 | 111 | from pylons_app.model.hg_model import _full_changelog_cached |
|
115 | 112 | region_invalidate(_full_changelog_cached, None, *args) |
|
116 | 113 | |
|
117 | 114 | from vcs.backends.base import BaseChangeset |
|
118 | 115 | from vcs.utils.lazy import LazyProperty |
|
119 | 116 | class EmptyChangeset(BaseChangeset): |
|
120 | 117 | |
|
121 | 118 | revision = -1 |
|
122 | 119 | |
|
123 | 120 | @LazyProperty |
|
124 | 121 | def raw_id(self): |
|
125 | 122 | """ |
|
126 | 123 | Returns raw string identifing this changeset, useful for web |
|
127 | 124 | representation. |
|
128 | 125 | """ |
|
129 | 126 | return '0' * 12 |
|
130 | 127 | |
|
131 | 128 | |
|
132 | 129 | def repo2db_mapper(): |
|
133 | 130 | """ |
|
134 | put ! | |
|
131 | scann all dirs for .hgdbid | |
|
132 | if some dir doesn't have one generate one. | |
|
135 | 133 | """ |
|
136 |
pass |
|
|
137 | #scann all dirs for .hgdbid | |
|
138 | #if some dir doesn't have one generate one. | |
|
139 | # | |
|
140 | ||
|
141 | ||
|
142 | ||
|
143 | ||
|
144 | ||
|
134 | pass No newline at end of file |
@@ -1,120 +1,126 | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | # |
|
4 | 4 | # Copyright (c) 2010 marcink. All rights reserved. |
|
5 | 5 | # |
|
6 | 6 | ''' |
|
7 | 7 | Created on Apr 9, 2010 |
|
8 | 8 | |
|
9 | 9 | @author: marcink |
|
10 | 10 | ''' |
|
11 | 11 | |
|
12 | 12 | from beaker.cache import cache_region |
|
13 | 13 | from mercurial import ui |
|
14 | 14 | from mercurial.hgweb.hgwebdir_mod import findrepos |
|
15 | 15 | from pylons import app_globals as g |
|
16 | 16 | from vcs.exceptions import RepositoryError, VCSError |
|
17 | 17 | import logging |
|
18 | 18 | import os |
|
19 | 19 | import sys |
|
20 | 20 | log = logging.getLogger(__name__) |
|
21 | 21 | |
|
22 | 22 | try: |
|
23 | 23 | from vcs.backends.hg import MercurialRepository |
|
24 | 24 | except ImportError: |
|
25 | 25 | sys.stderr.write('You have to import vcs module') |
|
26 | 26 | raise Exception('Unable to import vcs') |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @cache_region('long_term', 'cached_repo_list') |
|
30 | 30 | def _get_repos_cached(): |
|
31 | 31 | """ |
|
32 | 32 | return cached dict with repos |
|
33 | 33 | """ |
|
34 | 34 | return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui) |
|
35 | 35 | |
|
36 | 36 | @cache_region('long_term', 'full_changelog') |
|
37 | 37 | def _full_changelog_cached(repo_name): |
|
38 | 38 | log.info('getting full changelog for %s', repo_name) |
|
39 | 39 | return list(reversed(list(HgModel().get_repo(repo_name)))) |
|
40 | 40 | |
|
41 | 41 | class HgModel(object): |
|
42 | 42 | """ |
|
43 | 43 | Mercurial Model |
|
44 | 44 | """ |
|
45 | 45 | |
|
46 | 46 | def __init__(self): |
|
47 | 47 | """ |
|
48 | 48 | Constructor |
|
49 | 49 | """ |
|
50 | 50 | pass |
|
51 | 51 | |
|
52 | 52 | @staticmethod |
|
53 | 53 | def repo_scan(repos_prefix, repos_path, baseui): |
|
54 | 54 | """ |
|
55 | 55 | Listing of repositories in given path. This path should not be a |
|
56 | 56 | repository itself. Return a dictionary of repository objects |
|
57 | 57 | :param repos_path: path to directory it could take syntax with |
|
58 | 58 | * or ** for deep recursive displaying repositories |
|
59 | 59 | """ |
|
60 | 60 | def check_repo_dir(path): |
|
61 | 61 | """ |
|
62 | 62 | Checks the repository |
|
63 | 63 | :param path: |
|
64 | 64 | """ |
|
65 | 65 | repos_path = path.split('/') |
|
66 | 66 | if repos_path[-1] in ['*', '**']: |
|
67 | 67 | repos_path = repos_path[:-1] |
|
68 | 68 | if repos_path[0] != '/': |
|
69 | 69 | repos_path[0] = '/' |
|
70 | 70 | if not os.path.isdir(os.path.join(*repos_path)): |
|
71 | 71 | raise RepositoryError('Not a valid repository in %s' % path[0][1]) |
|
72 | 72 | if not repos_path.endswith('*'): |
|
73 | 73 | raise VCSError('You need to specify * or ** at the end of path ' |
|
74 | 74 | 'for recursive scanning') |
|
75 | 75 | |
|
76 | 76 | check_repo_dir(repos_path) |
|
77 | 77 | log.info('scanning for repositories in %s', repos_path) |
|
78 | 78 | repos = findrepos([(repos_prefix, repos_path)]) |
|
79 | 79 | if not isinstance(baseui, ui.ui): |
|
80 | 80 | baseui = ui.ui() |
|
81 | 81 | |
|
82 | 82 | repos_list = {} |
|
83 | 83 | for name, path in repos: |
|
84 | 84 | try: |
|
85 | repos_list[name] = MercurialRepository(path, baseui=baseui) | |
|
85 | #name = name.split('/')[-1] | |
|
86 | if repos_list.has_key(name): | |
|
87 | raise RepositoryError('Duplicate repository name %s found in' | |
|
88 | ' %s' % (name, path)) | |
|
89 | else: | |
|
90 | repos_list[name] = MercurialRepository(path, baseui=baseui) | |
|
91 | repos_list[name].name = name | |
|
86 | 92 | except OSError: |
|
87 | 93 | continue |
|
88 | 94 | return repos_list |
|
89 | 95 | |
|
90 | 96 | def get_repos(self): |
|
91 | 97 | for name, repo in _get_repos_cached().items(): |
|
92 | 98 | if repo._get_hidden(): |
|
93 | 99 | #skip hidden web repository |
|
94 | 100 | continue |
|
95 | 101 | |
|
96 | 102 | last_change = repo.last_change |
|
97 | 103 | try: |
|
98 | 104 | tip = repo.get_changeset('tip') |
|
99 | 105 | except RepositoryError: |
|
100 | 106 | from pylons_app.lib.utils import EmptyChangeset |
|
101 | 107 | tip = EmptyChangeset() |
|
102 | 108 | |
|
103 | 109 | tmp_d = {} |
|
104 | 110 | tmp_d['name'] = repo.name |
|
105 | 111 | tmp_d['name_sort'] = tmp_d['name'].lower() |
|
106 | 112 | tmp_d['description'] = repo.description |
|
107 | 113 | tmp_d['description_sort'] = tmp_d['description'] |
|
108 | 114 | tmp_d['last_change'] = last_change |
|
109 | 115 | tmp_d['last_change_sort'] = last_change[1] - last_change[0] |
|
110 | 116 | tmp_d['tip'] = tip.raw_id |
|
111 | 117 | tmp_d['tip_sort'] = tip.revision |
|
112 | 118 | tmp_d['rev'] = tip.revision |
|
113 | 119 | tmp_d['contact'] = repo.contact |
|
114 | 120 | tmp_d['contact_sort'] = tmp_d['contact'] |
|
115 | 121 | tmp_d['repo_archives'] = list(repo._get_archives()) |
|
116 | 122 | |
|
117 | 123 | yield tmp_d |
|
118 | 124 | |
|
119 | 125 | def get_repo(self, repo_name): |
|
120 | 126 | return _get_repos_cached()[repo_name] |
General Comments 0
You need to be logged in to leave comments.
Login now