##// END OF EJS Templates
Updated readme
marcink -
r325:d8d471cf default
parent child Browse files
Show More
@@ -1,46 +1,57 b''
1 -------------------------------------
1 -------------------------------------
2 Pylons based replacement for hgwebdir
2 Pylons based replacement for hgwebdir
3 -------------------------------------
3 -------------------------------------
4
4
5 Fully customizable, with authentication, permissions. Based on vcs library.
5 Fully customizable, with authentication, permissions. Based on vcs library.
6
6
7 **Overview**
7 **Overview**
8
8
9 - has it's own middleware to handle mercurial protocol request each request can
9 - has it's own middleware to handle mercurial protocol request each request can
10 be logged and authenticated + threaded performance unlikely to hgweb
10 be logged and authenticated + threaded performance unlikely to hgweb
11 - full permissions per project read/write/admin access even on mercurial request
11 - mako templates let's you cusmotize look and feel of appplication.
12 - mako templates let's you cusmotize look and feel of appplication.
12 - diffs annotations and source code all colored by pygments.
13 - diffs annotations and source code all colored by pygments.
13 - mercurial branch graph
14 - mercurial branch graph
14 - admin interface for performing user/permission managments as well as repository
15 - admin interface for performing user/permission managments as well as repository
15 managment
16 managment
17 - backup scripts can do backup of whole app and send it over scp to desired location
16 - setup project descriptions and info inside built in db for easy, non
18 - setup project descriptions and info inside built in db for easy, non
17 file-system operations
19 file-system operations
18 - added cache with invalidation on push/repo managment for high performance and
20 - added cache with invalidation on push/repo managment for high performance and
19 always upto date data.
21 always upto date data.
20 - rss /atom feed customizable
22 - rss /atom feed customizable
21 - future support for git
23 - future support for git
22 - based on pylons 1.0 / sqlalchemy 0.6
24 - based on pylons 1.0 / sqlalchemy 0.6
23
25
24 **Incoming**
26 **Incoming**
25
27
26 - full permissions per project
28 - code review based on hg-review (when it's stable)
27 - git support (when vcs can handle it)
29 - git support (when vcs can handle it)
30 - other cools stuff that i can figure out
28
31
29 .. note::
32 .. note::
30 This software is still in beta mode. I don't guarantee that it'll work.
33 This software is still in beta mode. I don't guarantee that it'll work.
31
34
32
35
33 -------------
36 -------------
34 Installation
37 Installation
35 -------------
38 -------------
39 .. note::
40 I recomend to install tip version of vcs while the app is in beta mode.
41
42
36 - create new virtualenv and activate it
43 - create new virtualenv and activate it
37 - download hg app and run python setup.py install
44 - download hg app from default (not demo) branch from bitbucket and run
38 - goto build/ directory
45 'python setup.py install' this will install all required dependencies needed
39 - goto pylons_app/lib and run python db_manage.py it should create all
46 - goto pylons_app/lib and run python db_manage.py it should create all
40 needed tables and an admin account.
47 needed tables and an admin account. You can play with this file if you wish to
48 use different db than sqlite
41 - edit file repositories.config and change the [paths] where you keep your
49 - edit file repositories.config and change the [paths] where you keep your
42 mercurial repositories, remember about permissions for accessing this dir by
50 mercurial repositories, remember about permissions for accessing this dir by
43 hg app.
51 hg app.
44 - run paster serve development.ini
52 - run paster serve development.ini
45 the app should be available at the 127.0.0.1:5000
53 the app should be available at the 127.0.0.1:5000
46 - use admin account you created to login. No newline at end of file
54 - use admin account you created to login.
55 - default permissions on each repository is read, and owner is admin. So remember
56 to update those.
57 No newline at end of file
@@ -1,158 +1,158 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Model for hg app
3 # Model 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 from sqlalchemy.orm import joinedload
6
5
7 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
10 # 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.
11 #
10 #
12 # 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,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
14 # GNU General Public License for more details.
16 #
15 #
17 # 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
18 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
21
20
22 """
21 """
23 Created on April 9, 2010
22 Created on April 9, 2010
24 Model for hg app
23 Model for hg app
25 @author: marcink
24 @author: marcink
26 """
25 """
27
26
28 from beaker.cache import cache_region
27 from beaker.cache import cache_region
29 from mercurial import ui
28 from mercurial import ui
30 from mercurial.hgweb.hgwebdir_mod import findrepos
29 from mercurial.hgweb.hgwebdir_mod import findrepos
31 from vcs.exceptions import RepositoryError, VCSError
30 from vcs.exceptions import RepositoryError, VCSError
32 from pylons_app.model.meta import Session
31 from pylons_app.model.meta import Session
33 from pylons_app.model.db import Repository
32 from pylons_app.model.db import Repository
33 from sqlalchemy.orm import joinedload
34 import logging
34 import logging
35 import os
35 import os
36 import sys
36 import sys
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 try:
39 try:
40 from vcs.backends.hg import MercurialRepository
40 from vcs.backends.hg import MercurialRepository
41 except ImportError:
41 except ImportError:
42 sys.stderr.write('You have to import vcs module')
42 sys.stderr.write('You have to import vcs module')
43 raise Exception('Unable to import vcs')
43 raise Exception('Unable to import vcs')
44
44
45 def _get_repos_cached_initial(app_globals):
45 def _get_repos_cached_initial(app_globals):
46 """
46 """
47 return cached dict with repos
47 return cached dict with repos
48 """
48 """
49 g = app_globals
49 g = app_globals
50 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
50 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
51
51
52 @cache_region('long_term', 'cached_repo_list')
52 @cache_region('long_term', 'cached_repo_list')
53 def _get_repos_cached():
53 def _get_repos_cached():
54 """
54 """
55 return cached dict with repos
55 return cached dict with repos
56 """
56 """
57 log.info('getting all repositories list')
57 log.info('getting all repositories list')
58 from pylons import app_globals as g
58 from pylons import app_globals as g
59 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
59 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
60
60
61 @cache_region('long_term', 'full_changelog')
61 @cache_region('long_term', 'full_changelog')
62 def _full_changelog_cached(repo_name):
62 def _full_changelog_cached(repo_name):
63 log.info('getting full changelog for %s', repo_name)
63 log.info('getting full changelog for %s', repo_name)
64 return list(reversed(list(HgModel().get_repo(repo_name))))
64 return list(reversed(list(HgModel().get_repo(repo_name))))
65
65
66 class HgModel(object):
66 class HgModel(object):
67 """
67 """
68 Mercurial Model
68 Mercurial Model
69 """
69 """
70
70
71 def __init__(self):
71 def __init__(self):
72 """
72 """
73 Constructor
73 Constructor
74 """
74 """
75
75
76 @staticmethod
76 @staticmethod
77 def repo_scan(repos_prefix, repos_path, baseui):
77 def repo_scan(repos_prefix, repos_path, baseui):
78 """
78 """
79 Listing of repositories in given path. This path should not be a
79 Listing of repositories in given path. This path should not be a
80 repository itself. Return a dictionary of repository objects
80 repository itself. Return a dictionary of repository objects
81 :param repos_path: path to directory it could take syntax with
81 :param repos_path: path to directory it could take syntax with
82 * or ** for deep recursive displaying repositories
82 * or ** for deep recursive displaying repositories
83 """
83 """
84 sa = Session()
84 sa = Session()
85 def check_repo_dir(path):
85 def check_repo_dir(path):
86 """
86 """
87 Checks the repository
87 Checks the repository
88 :param path:
88 :param path:
89 """
89 """
90 repos_path = path.split('/')
90 repos_path = path.split('/')
91 if repos_path[-1] in ['*', '**']:
91 if repos_path[-1] in ['*', '**']:
92 repos_path = repos_path[:-1]
92 repos_path = repos_path[:-1]
93 if repos_path[0] != '/':
93 if repos_path[0] != '/':
94 repos_path[0] = '/'
94 repos_path[0] = '/'
95 if not os.path.isdir(os.path.join(*repos_path)):
95 if not os.path.isdir(os.path.join(*repos_path)):
96 raise RepositoryError('Not a valid repository in %s' % path[0][1])
96 raise RepositoryError('Not a valid repository in %s' % path[0][1])
97 if not repos_path.endswith('*'):
97 if not repos_path.endswith('*'):
98 raise VCSError('You need to specify * or ** at the end of path '
98 raise VCSError('You need to specify * or ** at the end of path '
99 'for recursive scanning')
99 'for recursive scanning')
100
100
101 check_repo_dir(repos_path)
101 check_repo_dir(repos_path)
102 log.info('scanning for repositories in %s', repos_path)
102 log.info('scanning for repositories in %s', repos_path)
103 repos = findrepos([(repos_prefix, repos_path)])
103 repos = findrepos([(repos_prefix, repos_path)])
104 if not isinstance(baseui, ui.ui):
104 if not isinstance(baseui, ui.ui):
105 baseui = ui.ui()
105 baseui = ui.ui()
106
106
107 repos_list = {}
107 repos_list = {}
108 for name, path in repos:
108 for name, path in repos:
109 try:
109 try:
110 #name = name.split('/')[-1]
110 #name = name.split('/')[-1]
111 if repos_list.has_key(name):
111 if repos_list.has_key(name):
112 raise RepositoryError('Duplicate repository name %s found in'
112 raise RepositoryError('Duplicate repository name %s found in'
113 ' %s' % (name, path))
113 ' %s' % (name, path))
114 else:
114 else:
115
115
116 repos_list[name] = MercurialRepository(path, baseui=baseui)
116 repos_list[name] = MercurialRepository(path, baseui=baseui)
117 repos_list[name].name = name
117 repos_list[name].name = name
118 dbrepo = sa.query(Repository).get(name)
118 dbrepo = sa.query(Repository).get(name)
119 if dbrepo:
119 if dbrepo:
120 repos_list[name].dbrepo = dbrepo
120 repos_list[name].dbrepo = dbrepo
121 repos_list[name].description = dbrepo.description
121 repos_list[name].description = dbrepo.description
122 repos_list[name].contact = dbrepo.user.full_contact
122 repos_list[name].contact = dbrepo.user.full_contact
123 except OSError:
123 except OSError:
124 continue
124 continue
125 return repos_list
125 return repos_list
126
126
127 def get_repos(self):
127 def get_repos(self):
128 for name, repo in _get_repos_cached().items():
128 for name, repo in _get_repos_cached().items():
129 if repo._get_hidden():
129 if repo._get_hidden():
130 #skip hidden web repository
130 #skip hidden web repository
131 continue
131 continue
132
132
133 last_change = repo.last_change
133 last_change = repo.last_change
134 try:
134 try:
135 tip = repo.get_changeset('tip')
135 tip = repo.get_changeset('tip')
136 except RepositoryError:
136 except RepositoryError:
137 from pylons_app.lib.utils import EmptyChangeset
137 from pylons_app.lib.utils import EmptyChangeset
138 tip = EmptyChangeset()
138 tip = EmptyChangeset()
139
139
140 tmp_d = {}
140 tmp_d = {}
141 tmp_d['name'] = repo.name
141 tmp_d['name'] = repo.name
142 tmp_d['name_sort'] = tmp_d['name'].lower()
142 tmp_d['name_sort'] = tmp_d['name'].lower()
143 tmp_d['description'] = repo.description
143 tmp_d['description'] = repo.description
144 tmp_d['description_sort'] = tmp_d['description']
144 tmp_d['description_sort'] = tmp_d['description']
145 tmp_d['last_change'] = last_change
145 tmp_d['last_change'] = last_change
146 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
146 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
147 tmp_d['tip'] = tip.raw_id
147 tmp_d['tip'] = tip.raw_id
148 tmp_d['tip_sort'] = tip.revision
148 tmp_d['tip_sort'] = tip.revision
149 tmp_d['rev'] = tip.revision
149 tmp_d['rev'] = tip.revision
150 tmp_d['contact'] = repo.contact
150 tmp_d['contact'] = repo.contact
151 tmp_d['contact_sort'] = tmp_d['contact']
151 tmp_d['contact_sort'] = tmp_d['contact']
152 tmp_d['repo_archives'] = list(repo._get_archives())
152 tmp_d['repo_archives'] = list(repo._get_archives())
153 tmp_d['last_msg'] = tip.message
153 tmp_d['last_msg'] = tip.message
154 tmp_d['repo'] = repo
154 tmp_d['repo'] = repo
155 yield tmp_d
155 yield tmp_d
156
156
157 def get_repo(self, repo_name):
157 def get_repo(self, repo_name):
158 return _get_repos_cached()[repo_name]
158 return _get_repos_cached()[repo_name]
@@ -1,43 +1,43 b''
1 from pylons_app import get_version
1 from pylons_app import get_version
2 try:
2 try:
3 from setuptools import setup, find_packages
3 from setuptools import setup, find_packages
4 except ImportError:
4 except ImportError:
5 from ez_setup import use_setuptools
5 from ez_setup import use_setuptools
6 use_setuptools()
6 use_setuptools()
7 from setuptools import setup, find_packages
7 from setuptools import setup, find_packages
8
8
9 setup(
9 setup(
10 name='pylons_app',
10 name='pylons_app',
11 version=get_version(),
11 version=get_version(),
12 description='',
12 description='',
13 author='marcin kuzminski',
13 author='marcin kuzminski',
14 author_email='marcin@python-works.com',
14 author_email='marcin@python-works.com',
15 url='',
15 url='http://hg.python-works.com',
16 install_requires=[
16 install_requires=[
17 "Pylons>=1.0.0",
17 "Pylons>=1.0.0",
18 "SQLAlchemy>=0.6",
18 "SQLAlchemy>=0.6",
19 "Mako>=0.3.2",
19 "Mako>=0.3.2",
20 "vcs>=0.1.2",
20 "vcs>=0.1.2",
21 "pygments>=1.3.0",
21 "pygments>=1.3.0",
22 "mercurial>=1.5",
22 "mercurial>=1.5",
23 "pysqlite"
23 "pysqlite"
24 ],
24 ],
25 setup_requires=["PasteScript>=1.6.3"],
25 setup_requires=["PasteScript>=1.6.3"],
26 packages=find_packages(exclude=['ez_setup']),
26 packages=find_packages(exclude=['ez_setup']),
27 include_package_data=True,
27 include_package_data=True,
28 test_suite='nose.collector',
28 test_suite='nose.collector',
29 package_data={'pylons_app': ['i18n/*/LC_MESSAGES/*.mo']},
29 package_data={'pylons_app': ['i18n/*/LC_MESSAGES/*.mo']},
30 message_extractors={'pylons_app': [
30 message_extractors={'pylons_app': [
31 ('**.py', 'python', None),
31 ('**.py', 'python', None),
32 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
32 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
33 ('public/**', 'ignore', None)]},
33 ('public/**', 'ignore', None)]},
34 zip_safe=False,
34 zip_safe=False,
35 paster_plugins=['PasteScript', 'Pylons'],
35 paster_plugins=['PasteScript', 'Pylons'],
36 entry_points="""
36 entry_points="""
37 [paste.app_factory]
37 [paste.app_factory]
38 main = pylons_app.config.middleware:make_app
38 main = pylons_app.config.middleware:make_app
39
39
40 [paste.app_install]
40 [paste.app_install]
41 main = pylons.util:PylonsInstaller
41 main = pylons.util:PylonsInstaller
42 """,
42 """,
43 )
43 )
General Comments 0
You need to be logged in to leave comments. Login now