##// END OF EJS Templates
changes for rhodecode release 1.1.6
marcink -
r1165:c5af1d3c rhodecode-0.0.1.1.6 default
parent child Browse files
Show More
@@ -1,132 +1,132 b''
1
1
2 =================================================
2 =================================================
3 Welcome to RhodeCode (RhodiumCode) documentation!
3 Welcome to RhodeCode (RhodiumCode) documentation!
4 =================================================
4 =================================================
5
5
6 ``RhodeCode`` (formerly hg-app) is a Pylons framework based Mercurial repository
6 ``RhodeCode`` (formerly hg-app) is a Pylons framework based Mercurial repository
7 browser/management tool with a built in push/pull server and full text search.
7 browser/management tool with a built in push/pull server and full text search.
8 It works on http/https and has a built in permission/authentication system with
8 It works on http/https and has a built in permission/authentication system with
9 the ability to authenticate via LDAP.
9 the ability to authenticate via LDAP.
10
10
11 RhodeCode is similar in some respects to github or bitbucket_,
11 RhodeCode is similar in some respects to github or bitbucket_,
12 however RhodeCode can be run as standalone hosted application on your own server.
12 however RhodeCode can be run as standalone hosted application on your own server.
13 It is open source and donation ware and focuses more on providing a customized,
13 It is open source and donation ware and focuses more on providing a customized,
14 self administered interface for Mercurial(and soon GIT) repositories.
14 self administered interface for Mercurial(and soon GIT) repositories.
15 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
15 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
16 handle multiple different version control systems.
16 handle multiple different version control systems.
17
17
18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
19
19
20 RhodeCode demo
20 RhodeCode demo
21 --------------
21 --------------
22
22
23 http://demo.rhodecode.org
23 http://demo.rhodecode.org
24
24
25 The default access is anonymous but you can login to an administrative account
25 The default access is anonymous but you can login to an administrative account
26 using the following credentials:
26 using the following credentials:
27
27
28 - username: demo
28 - username: demo
29 - password: demo
29 - password: demo
30
30
31 Source code
31 Source code
32 -----------
32 -----------
33
33
34 The latest source for RhodeCode can be obtained from official RhodeCode instance
34 The latest source for RhodeCode can be obtained from official RhodeCode instance
35 https://hg.rhodecode.org
35 https://hg.rhodecode.org
36
36
37 Rarely updated source code and issue tracker is available at bitbcuket
37 Rarely updated source code and issue tracker is available at bitbucket
38 http://bitbucket.org/marcinkuzminski/rhodecode
38 http://bitbucket.org/marcinkuzminski/rhodecode
39
39
40 Installation
40 Installation
41 ------------
41 ------------
42
42
43 Please visit http://packages.python.org/RhodeCode/installation.html
43 Please visit http://packages.python.org/RhodeCode/installation.html
44
44
45
45
46 RhodeCode Features
46 RhodeCode Features
47 ------------------
47 ------------------
48
48
49 - Has it's own middleware to handle mercurial_ protocol requests.
49 - Has it's own middleware to handle mercurial_ protocol requests.
50 Each request can be logged and authenticated.
50 Each request can be logged and authenticated.
51 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous. Supports http/https
51 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
52 and LDAP
52 Supports http/https and LDAP
53 - Full permissions (private/read/write/admin) and authentication per project.
53 - Full permissions (private/read/write/admin) and authentication per project.
54 One account for web interface and mercurial_ push/pull/clone operations.
54 One account for web interface and mercurial_ push/pull/clone operations.
55 - Mako templates let's you customize the look and feel of the application.
55 - Mako templates let's you customize the look and feel of the application.
56 - Beautiful diffs, annotations and source code browsing all colored by pygments.
56 - Beautiful diffs, annotations and source code browsing all colored by pygments.
57 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
57 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
58 - Admin interface with user/permission management. Admin activity journal, logs
58 - Admin interface with user/permission management. Admin activity journal, logs
59 pulls, pushes, forks, registrations and other actions made by all users.
59 pulls, pushes, forks, registrations and other actions made by all users.
60 - Server side forks. It is possible to fork a project and modify it freely without
60 - Server side forks. It is possible to fork a project and modify it freely without
61 breaking the main repository.
61 breaking the main repository.
62 - Full text search powered by Whoosh on the source files, and file names.
62 - Full text search powered by Whoosh on the source files, and file names.
63 Build in indexing daemons, with optional incremental index build
63 Build in indexing daemons, with optional incremental index build
64 (no external search servers required all in one application)
64 (no external search servers required all in one application)
65 - Setup project descriptions and info inside built in db for easy, non
65 - Setup project descriptions and info inside built in db for easy, non
66 file-system operations
66 file-system operations
67 - Intelligent cache with invalidation after push or project change, provides high
67 - Intelligent cache with invalidation after push or project change, provides high
68 performance and always up to date data.
68 performance and always up to date data.
69 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
69 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
70 - Async tasks for speed and performance using celery_ (works without them too)
70 - Async tasks for speed and performance using celery_ (works without them too)
71 - Backup scripts can do backup of whole app and send it over scp to desired
71 - Backup scripts can do backup of whole app and send it over scp to desired
72 location
72 location
73 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
73 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
74
74
75
75
76 .. include:: ./docs/screenshots.rst
76 .. include:: ./docs/screenshots.rst
77
77
78
78
79 Incoming / Plans
79 Incoming / Plans
80 ----------------
80 ----------------
81
81
82 - Project grouping
82 - Project grouping
83 - User groups/teams
83 - User groups/teams
84 - SSH based authentication with server side key management
84 - SSH based authentication with server side key management
85 - Code review (probably based on hg-review)
85 - Code review (probably based on hg-review)
86 - Full git_ support, with push/pull server (currently in beta tests)
86 - Full git_ support, with push/pull server (currently in beta tests)
87 - Redmine integration
87 - Redmine integration
88 - Public accessible activity feeds
88 - Public accessible activity feeds
89 - Commit based built in wiki system
89 - Commit based built in wiki system
90 - Clone points and cloning from remote repositories into RhodeCode
90 - Clone points and cloning from remote repositories into RhodeCode
91 - More statistics and graph (global annotation + some more statistics)
91 - More statistics and graph (global annotation + some more statistics)
92 - Other advancements as development continues (or you can of course make additions and or requests)
92 - Other advancements as development continues (or you can of course make additions and or requests)
93
93
94 License
94 License
95 -------
95 -------
96
96
97 ``RhodeCode`` is released under the GPL_ license.
97 ``RhodeCode`` is released under the GPL_ license.
98
98
99
99
100 Mailing group Q&A
100 Mailing group Q&A
101 -----------------
101 -----------------
102
102
103 Join the `Google group <http://groups.google.com/group/rhodecode>`_
103 Join the `Google group <http://groups.google.com/group/rhodecode>`_
104
104
105 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
105 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
106
106
107 Join #rhodecode on FreeNode (irc.freenode.net)
107 Join #rhodecode on FreeNode (irc.freenode.net)
108 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
108 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
109
109
110 Online documentation
110 Online documentation
111 --------------------
111 --------------------
112
112
113 Online documentation for the current version of RhodeCode is available at
113 Online documentation for the current version of RhodeCode is available at
114 http://packages.python.org/RhodeCode/.
114 http://packages.python.org/RhodeCode/.
115 You may also build the documentation for yourself - go into ``docs/`` and run::
115 You may also build the documentation for yourself - go into ``docs/`` and run::
116
116
117 make html
117 make html
118
118
119 (You need to have sphinx installed to build the documentation. If you don't
119 (You need to have sphinx_ installed to build the documentation. If you don't
120 have sphinx installed you can install it via the command: ``easy_install sphinx``)
120 have sphinx_ installed you can install it via the command: ``easy_install sphinx``)
121
121
122 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
122 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
123 .. _python: http://www.python.org/
123 .. _python: http://www.python.org/
124 .. _django: http://www.djangoproject.com/
124 .. _sphinx: http://sphinx.pocoo.org/
125 .. _mercurial: http://mercurial.selenic.com/
125 .. _mercurial: http://mercurial.selenic.com/
126 .. _bitbucket: http://bitbucket.org/
126 .. _bitbucket: http://bitbucket.org/
127 .. _subversion: http://subversion.tigris.org/
127 .. _subversion: http://subversion.tigris.org/
128 .. _git: http://git-scm.com/
128 .. _git: http://git-scm.com/
129 .. _celery: http://celeryproject.org/
129 .. _celery: http://celeryproject.org/
130 .. _Sphinx: http://sphinx.pocoo.org/
130 .. _Sphinx: http://sphinx.pocoo.org/
131 .. _GPL: http://www.gnu.org/licenses/gpl.html
131 .. _GPL: http://www.gnu.org/licenses/gpl.html
132 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
132 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,213 +1,225 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 Changelog
3 Changelog
4 =========
4 =========
5
5
6
6
7 1.1.6 (**2011-03-21**)
8 ======================
9
10 news
11 ----
12
13 fixes
14 -----
15
16 - fixed #136 installation support for FreeBSD
17 - RhodeCode will check for python version during installation
18
7 1.1.5 (**2011-03-17**)
19 1.1.5 (**2011-03-17**)
8 ======================
20 ======================
9
21
10 news
22 news
11 ----
23 ----
12
24
13 - basic windows support, by exchanging pybcrypt into sha256 for windows only
25 - basic windows support, by exchanging pybcrypt into sha256 for windows only
14 highly inspired by idea of mantis406
26 highly inspired by idea of mantis406
15
27
16 fixes
28 fixes
17 -----
29 -----
18
30
19 - fixed sorting by author in main page
31 - fixed sorting by author in main page
20 - fixed crashes with diffs on binary files
32 - fixed crashes with diffs on binary files
21 - fixed #131 problem with boolean values for LDAP
33 - fixed #131 problem with boolean values for LDAP
22 - fixed #122 mysql problems thanks to striker69
34 - fixed #122 mysql problems thanks to striker69
23 - fixed problem with errors on calling raw/raw_files/annotate functions
35 - fixed problem with errors on calling raw/raw_files/annotate functions
24 with unknown revisions
36 with unknown revisions
25 - fixed returned rawfiles attachment names with international character
37 - fixed returned rawfiles attachment names with international character
26 - cleaned out docs, big thanks to Jason Harris
38 - cleaned out docs, big thanks to Jason Harris
27
39
28 1.1.4 (**2011-02-19**)
40 1.1.4 (**2011-02-19**)
29 ======================
41 ======================
30
42
31 news
43 news
32 ----
44 ----
33
45
34 fixes
46 fixes
35 -----
47 -----
36
48
37 - fixed formencode import problem on settings page, that caused server crash
49 - fixed formencode import problem on settings page, that caused server crash
38 when that page was accessed as first after server start
50 when that page was accessed as first after server start
39 - journal fixes
51 - journal fixes
40 - fixed option to access repository just by entering http://server/<repo_name>
52 - fixed option to access repository just by entering http://server/<repo_name>
41
53
42
54
43 1.1.3 (**2011-02-16**)
55 1.1.3 (**2011-02-16**)
44 ======================
56 ======================
45
57
46 news
58 news
47 ----
59 ----
48
60
49 - implemented #102 allowing the '.' character in username
61 - implemented #102 allowing the '.' character in username
50 - added option to access repository just by entering http://server/<repo_name>
62 - added option to access repository just by entering http://server/<repo_name>
51 - celery task ignores result for better performance
63 - celery task ignores result for better performance
52
64
53 fixes
65 fixes
54 -----
66 -----
55
67
56 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
68 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
57 apollo13 and Johan Walles
69 apollo13 and Johan Walles
58 - small fixes in journal
70 - small fixes in journal
59 - fixed problems with getting setting for celery from .ini files
71 - fixed problems with getting setting for celery from .ini files
60 - registration, password reset and login boxes share the same title as main
72 - registration, password reset and login boxes share the same title as main
61 application now
73 application now
62 - fixed #113: to high permissions to fork repository
74 - fixed #113: to high permissions to fork repository
63 - fixed problem with '[' chars in commit messages in journal
75 - fixed problem with '[' chars in commit messages in journal
64 - removed issue with space inside renamed repository after deletion
76 - removed issue with space inside renamed repository after deletion
65 - db transaction fixes when filesystem repository creation failed
77 - db transaction fixes when filesystem repository creation failed
66 - fixed #106 relation issues on databases different than sqlite
78 - fixed #106 relation issues on databases different than sqlite
67 - fixed static files paths links to use of url() method
79 - fixed static files paths links to use of url() method
68
80
69 1.1.2 (**2011-01-12**)
81 1.1.2 (**2011-01-12**)
70 ======================
82 ======================
71
83
72 news
84 news
73 ----
85 ----
74
86
75
87
76 fixes
88 fixes
77 -----
89 -----
78
90
79 - fixes #98 protection against float division of percentage stats
91 - fixes #98 protection against float division of percentage stats
80 - fixed graph bug
92 - fixed graph bug
81 - forced webhelpers version since it was making troubles during installation
93 - forced webhelpers version since it was making troubles during installation
82
94
83 1.1.1 (**2011-01-06**)
95 1.1.1 (**2011-01-06**)
84 ======================
96 ======================
85
97
86 news
98 news
87 ----
99 ----
88
100
89 - added force https option into ini files for easier https usage (no need to
101 - added force https option into ini files for easier https usage (no need to
90 set server headers with this options)
102 set server headers with this options)
91 - small css updates
103 - small css updates
92
104
93 fixes
105 fixes
94 -----
106 -----
95
107
96 - fixed #96 redirect loop on files view on repositories without changesets
108 - fixed #96 redirect loop on files view on repositories without changesets
97 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
109 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
98 and server crashed with errors
110 and server crashed with errors
99 - fixed large tooltips problems on main page
111 - fixed large tooltips problems on main page
100 - fixed #92 whoosh indexer is more error proof
112 - fixed #92 whoosh indexer is more error proof
101
113
102 1.1.0 (**2010-12-18**)
114 1.1.0 (**2010-12-18**)
103 ======================
115 ======================
104
116
105 news
117 news
106 ----
118 ----
107
119
108 - rewrite of internals for vcs >=0.1.10
120 - rewrite of internals for vcs >=0.1.10
109 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
121 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
110 with older clients
122 with older clients
111 - anonymous access, authentication via ldap
123 - anonymous access, authentication via ldap
112 - performance upgrade for cached repos list - each repository has it's own
124 - performance upgrade for cached repos list - each repository has it's own
113 cache that's invalidated when needed.
125 cache that's invalidated when needed.
114 - performance upgrades on repositories with large amount of commits (20K+)
126 - performance upgrades on repositories with large amount of commits (20K+)
115 - main page quick filter for filtering repositories
127 - main page quick filter for filtering repositories
116 - user dashboards with ability to follow chosen repositories actions
128 - user dashboards with ability to follow chosen repositories actions
117 - sends email to admin on new user registration
129 - sends email to admin on new user registration
118 - added cache/statistics reset options into repository settings
130 - added cache/statistics reset options into repository settings
119 - more detailed action logger (based on hooks) with pushed changesets lists
131 - more detailed action logger (based on hooks) with pushed changesets lists
120 and options to disable those hooks from admin panel
132 and options to disable those hooks from admin panel
121 - introduced new enhanced changelog for merges that shows more accurate results
133 - introduced new enhanced changelog for merges that shows more accurate results
122 - new improved and faster code stats (based on pygments lexers mapping tables,
134 - new improved and faster code stats (based on pygments lexers mapping tables,
123 showing up to 10 trending sources for each repository. Additionally stats
135 showing up to 10 trending sources for each repository. Additionally stats
124 can be disabled in repository settings.
136 can be disabled in repository settings.
125 - gui optimizations, fixed application width to 1024px
137 - gui optimizations, fixed application width to 1024px
126 - added cut off (for large files/changesets) limit into config files
138 - added cut off (for large files/changesets) limit into config files
127 - whoosh, celeryd, upgrade moved to paster command
139 - whoosh, celeryd, upgrade moved to paster command
128 - other than sqlite database backends can be used
140 - other than sqlite database backends can be used
129
141
130 fixes
142 fixes
131 -----
143 -----
132
144
133 - fixes #61 forked repo was showing only after cache expired
145 - fixes #61 forked repo was showing only after cache expired
134 - fixes #76 no confirmation on user deletes
146 - fixes #76 no confirmation on user deletes
135 - fixes #66 Name field misspelled
147 - fixes #66 Name field misspelled
136 - fixes #72 block user removal when he owns repositories
148 - fixes #72 block user removal when he owns repositories
137 - fixes #69 added password confirmation fields
149 - fixes #69 added password confirmation fields
138 - fixes #87 RhodeCode crashes occasionally on updating repository owner
150 - fixes #87 RhodeCode crashes occasionally on updating repository owner
139 - fixes #82 broken annotations on files with more than 1 blank line at the end
151 - fixes #82 broken annotations on files with more than 1 blank line at the end
140 - a lot of fixes and tweaks for file browser
152 - a lot of fixes and tweaks for file browser
141 - fixed detached session issues
153 - fixed detached session issues
142 - fixed when user had no repos he would see all repos listed in my account
154 - fixed when user had no repos he would see all repos listed in my account
143 - fixed ui() instance bug when global hgrc settings was loaded for server
155 - fixed ui() instance bug when global hgrc settings was loaded for server
144 instance and all hgrc options were merged with our db ui() object
156 instance and all hgrc options were merged with our db ui() object
145 - numerous small bugfixes
157 - numerous small bugfixes
146
158
147 (special thanks for TkSoh for detailed feedback)
159 (special thanks for TkSoh for detailed feedback)
148
160
149
161
150 1.0.2 (**2010-11-12**)
162 1.0.2 (**2010-11-12**)
151 ======================
163 ======================
152
164
153 news
165 news
154 ----
166 ----
155
167
156 - tested under python2.7
168 - tested under python2.7
157 - bumped sqlalchemy and celery versions
169 - bumped sqlalchemy and celery versions
158
170
159 fixes
171 fixes
160 -----
172 -----
161
173
162 - fixed #59 missing graph.js
174 - fixed #59 missing graph.js
163 - fixed repo_size crash when repository had broken symlinks
175 - fixed repo_size crash when repository had broken symlinks
164 - fixed python2.5 crashes.
176 - fixed python2.5 crashes.
165
177
166
178
167 1.0.1 (**2010-11-10**)
179 1.0.1 (**2010-11-10**)
168 ======================
180 ======================
169
181
170 news
182 news
171 ----
183 ----
172
184
173 - small css updated
185 - small css updated
174
186
175 fixes
187 fixes
176 -----
188 -----
177
189
178 - fixed #53 python2.5 incompatible enumerate calls
190 - fixed #53 python2.5 incompatible enumerate calls
179 - fixed #52 disable mercurial extension for web
191 - fixed #52 disable mercurial extension for web
180 - fixed #51 deleting repositories don't delete it's dependent objects
192 - fixed #51 deleting repositories don't delete it's dependent objects
181
193
182
194
183 1.0.0 (**2010-11-02**)
195 1.0.0 (**2010-11-02**)
184 ======================
196 ======================
185
197
186 - security bugfix simplehg wasn't checking for permissions on commands
198 - security bugfix simplehg wasn't checking for permissions on commands
187 other than pull or push.
199 other than pull or push.
188 - fixed doubled messages after push or pull in admin journal
200 - fixed doubled messages after push or pull in admin journal
189 - templating and css corrections, fixed repo switcher on chrome, updated titles
201 - templating and css corrections, fixed repo switcher on chrome, updated titles
190 - admin menu accessible from options menu on repository view
202 - admin menu accessible from options menu on repository view
191 - permissions cached queries
203 - permissions cached queries
192
204
193 1.0.0rc4 (**2010-10-12**)
205 1.0.0rc4 (**2010-10-12**)
194 ==========================
206 ==========================
195
207
196 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
208 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
197 - removed cache_manager settings from sqlalchemy meta
209 - removed cache_manager settings from sqlalchemy meta
198 - added sqlalchemy cache settings to ini files
210 - added sqlalchemy cache settings to ini files
199 - validated password length and added second try of failure on paster setup-app
211 - validated password length and added second try of failure on paster setup-app
200 - fixed setup database destroy prompt even when there was no db
212 - fixed setup database destroy prompt even when there was no db
201
213
202
214
203 1.0.0rc3 (**2010-10-11**)
215 1.0.0rc3 (**2010-10-11**)
204 =========================
216 =========================
205
217
206 - fixed i18n during installation.
218 - fixed i18n during installation.
207
219
208 1.0.0rc2 (**2010-10-11**)
220 1.0.0rc2 (**2010-10-11**)
209 =========================
221 =========================
210
222
211 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
223 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
212 occure. After vcs is fixed it'll be put back again.
224 occure. After vcs is fixed it'll be put back again.
213 - templating/css rewrites, optimized css. No newline at end of file
225 - templating/css rewrites, optimized css.
@@ -1,53 +1,56 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.__init__
3 rhodecode.__init__
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode, a web based repository management based on pylons
6 RhodeCode, a web based repository management based on pylons
7 versioning implementation: http://semver.org/
7 versioning implementation: http://semver.org/
8
8
9 :created_on: Apr 9, 2010
9 :created_on: Apr 9, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software; you can redistribute it and/or
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
17 # of the License or (at your opinion) any later version of the license.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
27 # MA 02110-1301, USA.
28 import platform
28 import platform
29
29
30 VERSION = (1, 1, 5)
30 VERSION = (1, 1, 6)
31 __version__ = '.'.join((str(each) for each in VERSION[:4]))
31 __version__ = '.'.join((str(each) for each in VERSION[:4]))
32 __dbversion__ = 2 #defines current db version for migrations
32 __dbversion__ = 2 #defines current db version for migrations
33 __platform__ = platform.system()
33 __platform__ = platform.system()
34
34
35 PLATFORM_WIN = ('Windows',)
36 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD',)
37
35 try:
38 try:
36 from rhodecode.lib.utils import get_current_revision
39 from rhodecode.lib.utils import get_current_revision
37 _rev = get_current_revision()
40 _rev = get_current_revision()
38 except ImportError:
41 except ImportError:
39 #this is needed when doing some setup.py operations
42 #this is needed when doing some setup.py operations
40 _rev = False
43 _rev = False
41
44
42 if len(VERSION) > 3 and _rev:
45 if len(VERSION) > 3 and _rev:
43 __version__ += ' [rev:%s]' % _rev[0]
46 __version__ += ' [rev:%s]' % _rev[0]
44
47
45 def get_version():
48 def get_version():
46 """Returns shorter version (digit parts only) as string."""
49 """Returns shorter version (digit parts only) as string."""
47
50
48 return '.'.join((str(each) for each in VERSION[:3]))
51 return '.'.join((str(each) for each in VERSION[:3]))
49
52
50 BACKENDS = {
53 BACKENDS = {
51 'hg': 'Mercurial repository',
54 'hg': 'Mercurial repository',
52 #'git': 'Git repository',
55 #'git': 'Git repository',
53 }
56 }
@@ -1,614 +1,614 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :copyright: (c) 2010 by marcink.
9 :copyright: (c) 2010 by marcink.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 """
11 """
12 # This program is free software; you can redistribute it and/or
12 # This program is free software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; version 2
14 # as published by the Free Software Foundation; version 2
15 # of the License or (at your opinion) any later version of the license.
15 # of the License or (at your opinion) any later version of the license.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # MA 02110-1301, USA.
25 # MA 02110-1301, USA.
26
26
27 import random
27 import random
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from decorator import decorator
31 from decorator import decorator
32
32
33 from pylons import config, session, url, request
33 from pylons import config, session, url, request
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode import __platform__
37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38
38
39 if __platform__ == 'Windows':
39 if __platform__ in PLATFORM_WIN:
40 from hashlib import sha256
40 from hashlib import sha256
41 if __platform__ in ('Linux', 'Darwin'):
41 if __platform__ in PLATFORM_OTHERS:
42 import bcrypt
42 import bcrypt
43
43
44 from rhodecode.lib import str2bool
44 from rhodecode.lib import str2bool
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
46 from rhodecode.lib.utils import get_repo_slug
46 from rhodecode.lib.utils import get_repo_slug
47 from rhodecode.lib.auth_ldap import AuthLdap
47 from rhodecode.lib.auth_ldap import AuthLdap
48
48
49 from rhodecode.model import meta
49 from rhodecode.model import meta
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import Permission, RepoToPerm, Repository, \
51 from rhodecode.model.db import Permission, RepoToPerm, Repository, \
52 User, UserToPerm
52 User, UserToPerm
53
53
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 class PasswordGenerator(object):
57 class PasswordGenerator(object):
58 """This is a simple class for generating password from
58 """This is a simple class for generating password from
59 different sets of characters
59 different sets of characters
60 usage:
60 usage:
61 passwd_gen = PasswordGenerator()
61 passwd_gen = PasswordGenerator()
62 #print 8-letter password containing only big and small letters of alphabet
62 #print 8-letter password containing only big and small letters of alphabet
63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 """
64 """
65 ALPHABETS_NUM = r'''1234567890'''#[0]
65 ALPHABETS_NUM = r'''1234567890'''#[0]
66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
70 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
70 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
71 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
71 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
72 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
72 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
73 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
73 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
74
74
75 def __init__(self, passwd=''):
75 def __init__(self, passwd=''):
76 self.passwd = passwd
76 self.passwd = passwd
77
77
78 def gen_password(self, len, type):
78 def gen_password(self, len, type):
79 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
79 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
80 return self.passwd
80 return self.passwd
81
81
82 class RhodeCodeCrypto(object):
82 class RhodeCodeCrypto(object):
83
83
84 @classmethod
84 @classmethod
85 def hash_string(cls, str_):
85 def hash_string(cls, str_):
86 """
86 """
87 Cryptographic function used for password hashing based on pybcrypt
87 Cryptographic function used for password hashing based on pybcrypt
88 or pycrypto in windows
88 or pycrypto in windows
89
89
90 :param password: password to hash
90 :param password: password to hash
91 """
91 """
92 if __platform__ == 'Windows':
92 if __platform__ in PLATFORM_WIN:
93 return sha256(str_).hexdigest()
93 return sha256(str_).hexdigest()
94 elif __platform__ in ('Linux', 'Darwin'):
94 elif __platform__ in PLATFORM_OTHERS:
95 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
95 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
96 else:
96 else:
97 raise Exception('Unknown or unsupported platform %s' % __platform__)
97 raise Exception('Unknown or unsupported platform %s' % __platform__)
98
98
99 @classmethod
99 @classmethod
100 def hash_check(cls, password, hashed):
100 def hash_check(cls, password, hashed):
101 """
101 """
102 Checks matching password with it's hashed value, runs different
102 Checks matching password with it's hashed value, runs different
103 implementation based on platform it runs on
103 implementation based on platform it runs on
104
104
105 :param password: password
105 :param password: password
106 :param hashed: password in hashed form
106 :param hashed: password in hashed form
107 """
107 """
108
108
109 if __platform__ == 'Windows':
109 if __platform__ == 'Windows':
110 return sha256(password).hexdigest() == hashed
110 return sha256(password).hexdigest() == hashed
111 elif __platform__ in ('Linux', 'Darwin'):
111 elif __platform__ in ('Linux', 'Darwin'):
112 return bcrypt.hashpw(password, hashed) == hashed
112 return bcrypt.hashpw(password, hashed) == hashed
113 else:
113 else:
114 raise Exception('Unknown or unsupported platform %s' % __platform__)
114 raise Exception('Unknown or unsupported platform %s' % __platform__)
115
115
116
116
117 def get_crypt_password(password):
117 def get_crypt_password(password):
118 return RhodeCodeCrypto.hash_string(password)
118 return RhodeCodeCrypto.hash_string(password)
119
119
120 def check_password(password, hashed):
120 def check_password(password, hashed):
121 return RhodeCodeCrypto.hash_check(password, hashed)
121 return RhodeCodeCrypto.hash_check(password, hashed)
122
122
123 def authfunc(environ, username, password):
123 def authfunc(environ, username, password):
124 """
124 """
125 Dummy authentication function used in Mercurial/Git/ and access control,
125 Dummy authentication function used in Mercurial/Git/ and access control,
126
126
127 :param environ: needed only for using in Basic auth
127 :param environ: needed only for using in Basic auth
128 """
128 """
129 return authenticate(username, password)
129 return authenticate(username, password)
130
130
131
131
132 def authenticate(username, password):
132 def authenticate(username, password):
133 """
133 """
134 Authentication function used for access control,
134 Authentication function used for access control,
135 firstly checks for db authentication then if ldap is enabled for ldap
135 firstly checks for db authentication then if ldap is enabled for ldap
136 authentication, also creates ldap user if not in database
136 authentication, also creates ldap user if not in database
137
137
138 :param username: username
138 :param username: username
139 :param password: password
139 :param password: password
140 """
140 """
141 user_model = UserModel()
141 user_model = UserModel()
142 user = user_model.get_by_username(username, cache=False)
142 user = user_model.get_by_username(username, cache=False)
143
143
144 log.debug('Authenticating user using RhodeCode account')
144 log.debug('Authenticating user using RhodeCode account')
145 if user is not None and user.is_ldap is False:
145 if user is not None and user.is_ldap is False:
146 if user.active:
146 if user.active:
147
147
148 if user.username == 'default' and user.active:
148 if user.username == 'default' and user.active:
149 log.info('user %s authenticated correctly as anonymous user',
149 log.info('user %s authenticated correctly as anonymous user',
150 username)
150 username)
151 return True
151 return True
152
152
153 elif user.username == username and check_password(password, user.password):
153 elif user.username == username and check_password(password, user.password):
154 log.info('user %s authenticated correctly', username)
154 log.info('user %s authenticated correctly', username)
155 return True
155 return True
156 else:
156 else:
157 log.warning('user %s is disabled', username)
157 log.warning('user %s is disabled', username)
158
158
159 else:
159 else:
160 log.debug('Regular authentication failed')
160 log.debug('Regular authentication failed')
161 user_obj = user_model.get_by_username(username, cache=False,
161 user_obj = user_model.get_by_username(username, cache=False,
162 case_insensitive=True)
162 case_insensitive=True)
163
163
164 if user_obj is not None and user_obj.is_ldap is False:
164 if user_obj is not None and user_obj.is_ldap is False:
165 log.debug('this user already exists as non ldap')
165 log.debug('this user already exists as non ldap')
166 return False
166 return False
167
167
168 from rhodecode.model.settings import SettingsModel
168 from rhodecode.model.settings import SettingsModel
169 ldap_settings = SettingsModel().get_ldap_settings()
169 ldap_settings = SettingsModel().get_ldap_settings()
170
170
171 #======================================================================
171 #======================================================================
172 # FALLBACK TO LDAP AUTH IN ENABLE
172 # FALLBACK TO LDAP AUTH IN ENABLE
173 #======================================================================
173 #======================================================================
174 if str2bool(ldap_settings.get('ldap_active')):
174 if str2bool(ldap_settings.get('ldap_active')):
175 log.debug("Authenticating user using ldap")
175 log.debug("Authenticating user using ldap")
176 kwargs = {
176 kwargs = {
177 'server':ldap_settings.get('ldap_host', ''),
177 'server':ldap_settings.get('ldap_host', ''),
178 'base_dn':ldap_settings.get('ldap_base_dn', ''),
178 'base_dn':ldap_settings.get('ldap_base_dn', ''),
179 'port':ldap_settings.get('ldap_port'),
179 'port':ldap_settings.get('ldap_port'),
180 'bind_dn':ldap_settings.get('ldap_dn_user'),
180 'bind_dn':ldap_settings.get('ldap_dn_user'),
181 'bind_pass':ldap_settings.get('ldap_dn_pass'),
181 'bind_pass':ldap_settings.get('ldap_dn_pass'),
182 'use_ldaps':str2bool(ldap_settings.get('ldap_ldaps')),
182 'use_ldaps':str2bool(ldap_settings.get('ldap_ldaps')),
183 'ldap_version':3,
183 'ldap_version':3,
184 }
184 }
185 log.debug('Checking for ldap authentication')
185 log.debug('Checking for ldap authentication')
186 try:
186 try:
187 aldap = AuthLdap(**kwargs)
187 aldap = AuthLdap(**kwargs)
188 res = aldap.authenticate_ldap(username, password)
188 res = aldap.authenticate_ldap(username, password)
189 log.debug('Got ldap response %s', res)
189 log.debug('Got ldap response %s', res)
190
190
191 if user_model.create_ldap(username, password):
191 if user_model.create_ldap(username, password):
192 log.info('created new ldap user')
192 log.info('created new ldap user')
193
193
194 return True
194 return True
195 except (LdapUsernameError, LdapPasswordError,):
195 except (LdapUsernameError, LdapPasswordError,):
196 pass
196 pass
197 except (Exception,):
197 except (Exception,):
198 log.error(traceback.format_exc())
198 log.error(traceback.format_exc())
199 pass
199 pass
200 return False
200 return False
201
201
202 class AuthUser(object):
202 class AuthUser(object):
203 """
203 """
204 A simple object that handles a mercurial username for authentication
204 A simple object that handles a mercurial username for authentication
205 """
205 """
206 def __init__(self):
206 def __init__(self):
207 self.username = 'None'
207 self.username = 'None'
208 self.name = ''
208 self.name = ''
209 self.lastname = ''
209 self.lastname = ''
210 self.email = ''
210 self.email = ''
211 self.user_id = None
211 self.user_id = None
212 self.is_authenticated = False
212 self.is_authenticated = False
213 self.is_admin = False
213 self.is_admin = False
214 self.permissions = {}
214 self.permissions = {}
215
215
216 def __repr__(self):
216 def __repr__(self):
217 return "<AuthUser('id:%s:%s')>" % (self.user_id, self.username)
217 return "<AuthUser('id:%s:%s')>" % (self.user_id, self.username)
218
218
219 def set_available_permissions(config):
219 def set_available_permissions(config):
220 """
220 """
221 This function will propagate pylons globals with all available defined
221 This function will propagate pylons globals with all available defined
222 permission given in db. We don't wannt to check each time from db for new
222 permission given in db. We don't wannt to check each time from db for new
223 permissions since adding a new permission also requires application restart
223 permissions since adding a new permission also requires application restart
224 ie. to decorate new views with the newly created permission
224 ie. to decorate new views with the newly created permission
225 :param config:
225 :param config:
226 """
226 """
227 log.info('getting information about all available permissions')
227 log.info('getting information about all available permissions')
228 try:
228 try:
229 sa = meta.Session()
229 sa = meta.Session()
230 all_perms = sa.query(Permission).all()
230 all_perms = sa.query(Permission).all()
231 except:
231 except:
232 pass
232 pass
233 finally:
233 finally:
234 meta.Session.remove()
234 meta.Session.remove()
235
235
236 config['available_permissions'] = [x.permission_name for x in all_perms]
236 config['available_permissions'] = [x.permission_name for x in all_perms]
237
237
238 def set_base_path(config):
238 def set_base_path(config):
239 config['base_path'] = config['pylons.app_globals'].base_path
239 config['base_path'] = config['pylons.app_globals'].base_path
240
240
241
241
242 def fill_perms(user):
242 def fill_perms(user):
243 """
243 """
244 Fills user permission attribute with permissions taken from database
244 Fills user permission attribute with permissions taken from database
245 :param user:
245 :param user:
246 """
246 """
247
247
248 sa = meta.Session()
248 sa = meta.Session()
249 user.permissions['repositories'] = {}
249 user.permissions['repositories'] = {}
250 user.permissions['global'] = set()
250 user.permissions['global'] = set()
251
251
252 #===========================================================================
252 #===========================================================================
253 # fetch default permissions
253 # fetch default permissions
254 #===========================================================================
254 #===========================================================================
255 default_user = UserModel().get_by_username('default', cache=True)
255 default_user = UserModel().get_by_username('default', cache=True)
256
256
257 default_perms = sa.query(RepoToPerm, Repository, Permission)\
257 default_perms = sa.query(RepoToPerm, Repository, Permission)\
258 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
258 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
259 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
259 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
260 .filter(RepoToPerm.user == default_user).all()
260 .filter(RepoToPerm.user == default_user).all()
261
261
262 if user.is_admin:
262 if user.is_admin:
263 #=======================================================================
263 #=======================================================================
264 # #admin have all default rights set to admin
264 # #admin have all default rights set to admin
265 #=======================================================================
265 #=======================================================================
266 user.permissions['global'].add('hg.admin')
266 user.permissions['global'].add('hg.admin')
267
267
268 for perm in default_perms:
268 for perm in default_perms:
269 p = 'repository.admin'
269 p = 'repository.admin'
270 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
270 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
271
271
272 else:
272 else:
273 #=======================================================================
273 #=======================================================================
274 # set default permissions
274 # set default permissions
275 #=======================================================================
275 #=======================================================================
276
276
277 #default global
277 #default global
278 default_global_perms = sa.query(UserToPerm)\
278 default_global_perms = sa.query(UserToPerm)\
279 .filter(UserToPerm.user == sa.query(User)\
279 .filter(UserToPerm.user == sa.query(User)\
280 .filter(User.username == 'default').one())
280 .filter(User.username == 'default').one())
281
281
282 for perm in default_global_perms:
282 for perm in default_global_perms:
283 user.permissions['global'].add(perm.permission.permission_name)
283 user.permissions['global'].add(perm.permission.permission_name)
284
284
285 #default repositories
285 #default repositories
286 for perm in default_perms:
286 for perm in default_perms:
287 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
287 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
288 #disable defaults for private repos,
288 #disable defaults for private repos,
289 p = 'repository.none'
289 p = 'repository.none'
290 elif perm.Repository.user_id == user.user_id:
290 elif perm.Repository.user_id == user.user_id:
291 #set admin if owner
291 #set admin if owner
292 p = 'repository.admin'
292 p = 'repository.admin'
293 else:
293 else:
294 p = perm.Permission.permission_name
294 p = perm.Permission.permission_name
295
295
296 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
296 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
297
297
298 #=======================================================================
298 #=======================================================================
299 # #overwrite default with user permissions if any
299 # #overwrite default with user permissions if any
300 #=======================================================================
300 #=======================================================================
301 user_perms = sa.query(RepoToPerm, Permission, Repository)\
301 user_perms = sa.query(RepoToPerm, Permission, Repository)\
302 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
302 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
303 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
303 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
304 .filter(RepoToPerm.user_id == user.user_id).all()
304 .filter(RepoToPerm.user_id == user.user_id).all()
305
305
306 for perm in user_perms:
306 for perm in user_perms:
307 if perm.Repository.user_id == user.user_id:#set admin if owner
307 if perm.Repository.user_id == user.user_id:#set admin if owner
308 p = 'repository.admin'
308 p = 'repository.admin'
309 else:
309 else:
310 p = perm.Permission.permission_name
310 p = perm.Permission.permission_name
311 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
311 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
312 meta.Session.remove()
312 meta.Session.remove()
313 return user
313 return user
314
314
315 def get_user(session):
315 def get_user(session):
316 """
316 """
317 Gets user from session, and wraps permissions into user
317 Gets user from session, and wraps permissions into user
318 :param session:
318 :param session:
319 """
319 """
320 user = session.get('rhodecode_user', AuthUser())
320 user = session.get('rhodecode_user', AuthUser())
321 #if the user is not logged in we check for anonymous access
321 #if the user is not logged in we check for anonymous access
322 #if user is logged and it's a default user check if we still have anonymous
322 #if user is logged and it's a default user check if we still have anonymous
323 #access enabled
323 #access enabled
324 if user.user_id is None or user.username == 'default':
324 if user.user_id is None or user.username == 'default':
325 anonymous_user = UserModel().get_by_username('default', cache=True)
325 anonymous_user = UserModel().get_by_username('default', cache=True)
326 if anonymous_user.active is True:
326 if anonymous_user.active is True:
327 #then we set this user is logged in
327 #then we set this user is logged in
328 user.is_authenticated = True
328 user.is_authenticated = True
329 user.user_id = anonymous_user.user_id
329 user.user_id = anonymous_user.user_id
330 else:
330 else:
331 user.is_authenticated = False
331 user.is_authenticated = False
332
332
333 if user.is_authenticated:
333 if user.is_authenticated:
334 user = UserModel().fill_data(user)
334 user = UserModel().fill_data(user)
335
335
336 user = fill_perms(user)
336 user = fill_perms(user)
337 session['rhodecode_user'] = user
337 session['rhodecode_user'] = user
338 session.save()
338 session.save()
339 return user
339 return user
340
340
341 #===============================================================================
341 #===============================================================================
342 # CHECK DECORATORS
342 # CHECK DECORATORS
343 #===============================================================================
343 #===============================================================================
344 class LoginRequired(object):
344 class LoginRequired(object):
345 """Must be logged in to execute this function else
345 """Must be logged in to execute this function else
346 redirect to login page"""
346 redirect to login page"""
347
347
348 def __call__(self, func):
348 def __call__(self, func):
349 return decorator(self.__wrapper, func)
349 return decorator(self.__wrapper, func)
350
350
351 def __wrapper(self, func, *fargs, **fkwargs):
351 def __wrapper(self, func, *fargs, **fkwargs):
352 user = session.get('rhodecode_user', AuthUser())
352 user = session.get('rhodecode_user', AuthUser())
353 log.debug('Checking login required for user:%s', user.username)
353 log.debug('Checking login required for user:%s', user.username)
354 if user.is_authenticated:
354 if user.is_authenticated:
355 log.debug('user %s is authenticated', user.username)
355 log.debug('user %s is authenticated', user.username)
356 return func(*fargs, **fkwargs)
356 return func(*fargs, **fkwargs)
357 else:
357 else:
358 log.warn('user %s not authenticated', user.username)
358 log.warn('user %s not authenticated', user.username)
359
359
360 p = ''
360 p = ''
361 if request.environ.get('SCRIPT_NAME') != '/':
361 if request.environ.get('SCRIPT_NAME') != '/':
362 p += request.environ.get('SCRIPT_NAME')
362 p += request.environ.get('SCRIPT_NAME')
363
363
364 p += request.environ.get('PATH_INFO')
364 p += request.environ.get('PATH_INFO')
365 if request.environ.get('QUERY_STRING'):
365 if request.environ.get('QUERY_STRING'):
366 p += '?' + request.environ.get('QUERY_STRING')
366 p += '?' + request.environ.get('QUERY_STRING')
367
367
368 log.debug('redirecting to login page with %s', p)
368 log.debug('redirecting to login page with %s', p)
369 return redirect(url('login_home', came_from=p))
369 return redirect(url('login_home', came_from=p))
370
370
371 class NotAnonymous(object):
371 class NotAnonymous(object):
372 """Must be logged in to execute this function else
372 """Must be logged in to execute this function else
373 redirect to login page"""
373 redirect to login page"""
374
374
375 def __call__(self, func):
375 def __call__(self, func):
376 return decorator(self.__wrapper, func)
376 return decorator(self.__wrapper, func)
377
377
378 def __wrapper(self, func, *fargs, **fkwargs):
378 def __wrapper(self, func, *fargs, **fkwargs):
379 user = session.get('rhodecode_user', AuthUser())
379 user = session.get('rhodecode_user', AuthUser())
380 log.debug('Checking if user is not anonymous')
380 log.debug('Checking if user is not anonymous')
381
381
382 anonymous = user.username == 'default'
382 anonymous = user.username == 'default'
383
383
384 if anonymous:
384 if anonymous:
385 p = ''
385 p = ''
386 if request.environ.get('SCRIPT_NAME') != '/':
386 if request.environ.get('SCRIPT_NAME') != '/':
387 p += request.environ.get('SCRIPT_NAME')
387 p += request.environ.get('SCRIPT_NAME')
388
388
389 p += request.environ.get('PATH_INFO')
389 p += request.environ.get('PATH_INFO')
390 if request.environ.get('QUERY_STRING'):
390 if request.environ.get('QUERY_STRING'):
391 p += '?' + request.environ.get('QUERY_STRING')
391 p += '?' + request.environ.get('QUERY_STRING')
392 return redirect(url('login_home', came_from=p))
392 return redirect(url('login_home', came_from=p))
393 else:
393 else:
394 return func(*fargs, **fkwargs)
394 return func(*fargs, **fkwargs)
395
395
396 class PermsDecorator(object):
396 class PermsDecorator(object):
397 """Base class for decorators"""
397 """Base class for decorators"""
398
398
399 def __init__(self, *required_perms):
399 def __init__(self, *required_perms):
400 available_perms = config['available_permissions']
400 available_perms = config['available_permissions']
401 for perm in required_perms:
401 for perm in required_perms:
402 if perm not in available_perms:
402 if perm not in available_perms:
403 raise Exception("'%s' permission is not defined" % perm)
403 raise Exception("'%s' permission is not defined" % perm)
404 self.required_perms = set(required_perms)
404 self.required_perms = set(required_perms)
405 self.user_perms = None
405 self.user_perms = None
406
406
407 def __call__(self, func):
407 def __call__(self, func):
408 return decorator(self.__wrapper, func)
408 return decorator(self.__wrapper, func)
409
409
410
410
411 def __wrapper(self, func, *fargs, **fkwargs):
411 def __wrapper(self, func, *fargs, **fkwargs):
412 # _wrapper.__name__ = func.__name__
412 # _wrapper.__name__ = func.__name__
413 # _wrapper.__dict__.update(func.__dict__)
413 # _wrapper.__dict__.update(func.__dict__)
414 # _wrapper.__doc__ = func.__doc__
414 # _wrapper.__doc__ = func.__doc__
415 self.user = session.get('rhodecode_user', AuthUser())
415 self.user = session.get('rhodecode_user', AuthUser())
416 self.user_perms = self.user.permissions
416 self.user_perms = self.user.permissions
417 log.debug('checking %s permissions %s for %s %s',
417 log.debug('checking %s permissions %s for %s %s',
418 self.__class__.__name__, self.required_perms, func.__name__,
418 self.__class__.__name__, self.required_perms, func.__name__,
419 self.user)
419 self.user)
420
420
421 if self.check_permissions():
421 if self.check_permissions():
422 log.debug('Permission granted for %s %s', func.__name__, self.user)
422 log.debug('Permission granted for %s %s', func.__name__, self.user)
423
423
424 return func(*fargs, **fkwargs)
424 return func(*fargs, **fkwargs)
425
425
426 else:
426 else:
427 log.warning('Permission denied for %s %s', func.__name__, self.user)
427 log.warning('Permission denied for %s %s', func.__name__, self.user)
428 #redirect with forbidden ret code
428 #redirect with forbidden ret code
429 return abort(403)
429 return abort(403)
430
430
431
431
432
432
433 def check_permissions(self):
433 def check_permissions(self):
434 """Dummy function for overriding"""
434 """Dummy function for overriding"""
435 raise Exception('You have to write this function in child class')
435 raise Exception('You have to write this function in child class')
436
436
437 class HasPermissionAllDecorator(PermsDecorator):
437 class HasPermissionAllDecorator(PermsDecorator):
438 """Checks for access permission for all given predicates. All of them
438 """Checks for access permission for all given predicates. All of them
439 have to be meet in order to fulfill the request
439 have to be meet in order to fulfill the request
440 """
440 """
441
441
442 def check_permissions(self):
442 def check_permissions(self):
443 if self.required_perms.issubset(self.user_perms.get('global')):
443 if self.required_perms.issubset(self.user_perms.get('global')):
444 return True
444 return True
445 return False
445 return False
446
446
447
447
448 class HasPermissionAnyDecorator(PermsDecorator):
448 class HasPermissionAnyDecorator(PermsDecorator):
449 """Checks for access permission for any of given predicates. In order to
449 """Checks for access permission for any of given predicates. In order to
450 fulfill the request any of predicates must be meet
450 fulfill the request any of predicates must be meet
451 """
451 """
452
452
453 def check_permissions(self):
453 def check_permissions(self):
454 if self.required_perms.intersection(self.user_perms.get('global')):
454 if self.required_perms.intersection(self.user_perms.get('global')):
455 return True
455 return True
456 return False
456 return False
457
457
458 class HasRepoPermissionAllDecorator(PermsDecorator):
458 class HasRepoPermissionAllDecorator(PermsDecorator):
459 """Checks for access permission for all given predicates for specific
459 """Checks for access permission for all given predicates for specific
460 repository. All of them have to be meet in order to fulfill the request
460 repository. All of them have to be meet in order to fulfill the request
461 """
461 """
462
462
463 def check_permissions(self):
463 def check_permissions(self):
464 repo_name = get_repo_slug(request)
464 repo_name = get_repo_slug(request)
465 try:
465 try:
466 user_perms = set([self.user_perms['repositories'][repo_name]])
466 user_perms = set([self.user_perms['repositories'][repo_name]])
467 except KeyError:
467 except KeyError:
468 return False
468 return False
469 if self.required_perms.issubset(user_perms):
469 if self.required_perms.issubset(user_perms):
470 return True
470 return True
471 return False
471 return False
472
472
473
473
474 class HasRepoPermissionAnyDecorator(PermsDecorator):
474 class HasRepoPermissionAnyDecorator(PermsDecorator):
475 """Checks for access permission for any of given predicates for specific
475 """Checks for access permission for any of given predicates for specific
476 repository. In order to fulfill the request any of predicates must be meet
476 repository. In order to fulfill the request any of predicates must be meet
477 """
477 """
478
478
479 def check_permissions(self):
479 def check_permissions(self):
480 repo_name = get_repo_slug(request)
480 repo_name = get_repo_slug(request)
481
481
482 try:
482 try:
483 user_perms = set([self.user_perms['repositories'][repo_name]])
483 user_perms = set([self.user_perms['repositories'][repo_name]])
484 except KeyError:
484 except KeyError:
485 return False
485 return False
486 if self.required_perms.intersection(user_perms):
486 if self.required_perms.intersection(user_perms):
487 return True
487 return True
488 return False
488 return False
489 #===============================================================================
489 #===============================================================================
490 # CHECK FUNCTIONS
490 # CHECK FUNCTIONS
491 #===============================================================================
491 #===============================================================================
492
492
493 class PermsFunction(object):
493 class PermsFunction(object):
494 """Base function for other check functions"""
494 """Base function for other check functions"""
495
495
496 def __init__(self, *perms):
496 def __init__(self, *perms):
497 available_perms = config['available_permissions']
497 available_perms = config['available_permissions']
498
498
499 for perm in perms:
499 for perm in perms:
500 if perm not in available_perms:
500 if perm not in available_perms:
501 raise Exception("'%s' permission in not defined" % perm)
501 raise Exception("'%s' permission in not defined" % perm)
502 self.required_perms = set(perms)
502 self.required_perms = set(perms)
503 self.user_perms = None
503 self.user_perms = None
504 self.granted_for = ''
504 self.granted_for = ''
505 self.repo_name = None
505 self.repo_name = None
506
506
507 def __call__(self, check_Location=''):
507 def __call__(self, check_Location=''):
508 user = session.get('rhodecode_user', False)
508 user = session.get('rhodecode_user', False)
509 if not user:
509 if not user:
510 return False
510 return False
511 self.user_perms = user.permissions
511 self.user_perms = user.permissions
512 self.granted_for = user.username
512 self.granted_for = user.username
513 log.debug('checking %s %s %s', self.__class__.__name__,
513 log.debug('checking %s %s %s', self.__class__.__name__,
514 self.required_perms, user)
514 self.required_perms, user)
515
515
516 if self.check_permissions():
516 if self.check_permissions():
517 log.debug('Permission granted for %s @ %s %s', self.granted_for,
517 log.debug('Permission granted for %s @ %s %s', self.granted_for,
518 check_Location, user)
518 check_Location, user)
519 return True
519 return True
520
520
521 else:
521 else:
522 log.warning('Permission denied for %s @ %s %s', self.granted_for,
522 log.warning('Permission denied for %s @ %s %s', self.granted_for,
523 check_Location, user)
523 check_Location, user)
524 return False
524 return False
525
525
526 def check_permissions(self):
526 def check_permissions(self):
527 """Dummy function for overriding"""
527 """Dummy function for overriding"""
528 raise Exception('You have to write this function in child class')
528 raise Exception('You have to write this function in child class')
529
529
530 class HasPermissionAll(PermsFunction):
530 class HasPermissionAll(PermsFunction):
531 def check_permissions(self):
531 def check_permissions(self):
532 if self.required_perms.issubset(self.user_perms.get('global')):
532 if self.required_perms.issubset(self.user_perms.get('global')):
533 return True
533 return True
534 return False
534 return False
535
535
536 class HasPermissionAny(PermsFunction):
536 class HasPermissionAny(PermsFunction):
537 def check_permissions(self):
537 def check_permissions(self):
538 if self.required_perms.intersection(self.user_perms.get('global')):
538 if self.required_perms.intersection(self.user_perms.get('global')):
539 return True
539 return True
540 return False
540 return False
541
541
542 class HasRepoPermissionAll(PermsFunction):
542 class HasRepoPermissionAll(PermsFunction):
543
543
544 def __call__(self, repo_name=None, check_Location=''):
544 def __call__(self, repo_name=None, check_Location=''):
545 self.repo_name = repo_name
545 self.repo_name = repo_name
546 return super(HasRepoPermissionAll, self).__call__(check_Location)
546 return super(HasRepoPermissionAll, self).__call__(check_Location)
547
547
548 def check_permissions(self):
548 def check_permissions(self):
549 if not self.repo_name:
549 if not self.repo_name:
550 self.repo_name = get_repo_slug(request)
550 self.repo_name = get_repo_slug(request)
551
551
552 try:
552 try:
553 self.user_perms = set([self.user_perms['repositories']\
553 self.user_perms = set([self.user_perms['repositories']\
554 [self.repo_name]])
554 [self.repo_name]])
555 except KeyError:
555 except KeyError:
556 return False
556 return False
557 self.granted_for = self.repo_name
557 self.granted_for = self.repo_name
558 if self.required_perms.issubset(self.user_perms):
558 if self.required_perms.issubset(self.user_perms):
559 return True
559 return True
560 return False
560 return False
561
561
562 class HasRepoPermissionAny(PermsFunction):
562 class HasRepoPermissionAny(PermsFunction):
563
563
564 def __call__(self, repo_name=None, check_Location=''):
564 def __call__(self, repo_name=None, check_Location=''):
565 self.repo_name = repo_name
565 self.repo_name = repo_name
566 return super(HasRepoPermissionAny, self).__call__(check_Location)
566 return super(HasRepoPermissionAny, self).__call__(check_Location)
567
567
568 def check_permissions(self):
568 def check_permissions(self):
569 if not self.repo_name:
569 if not self.repo_name:
570 self.repo_name = get_repo_slug(request)
570 self.repo_name = get_repo_slug(request)
571
571
572 try:
572 try:
573 self.user_perms = set([self.user_perms['repositories']\
573 self.user_perms = set([self.user_perms['repositories']\
574 [self.repo_name]])
574 [self.repo_name]])
575 except KeyError:
575 except KeyError:
576 return False
576 return False
577 self.granted_for = self.repo_name
577 self.granted_for = self.repo_name
578 if self.required_perms.intersection(self.user_perms):
578 if self.required_perms.intersection(self.user_perms):
579 return True
579 return True
580 return False
580 return False
581
581
582 #===============================================================================
582 #===============================================================================
583 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
583 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
584 #===============================================================================
584 #===============================================================================
585
585
586 class HasPermissionAnyMiddleware(object):
586 class HasPermissionAnyMiddleware(object):
587 def __init__(self, *perms):
587 def __init__(self, *perms):
588 self.required_perms = set(perms)
588 self.required_perms = set(perms)
589
589
590 def __call__(self, user, repo_name):
590 def __call__(self, user, repo_name):
591 usr = AuthUser()
591 usr = AuthUser()
592 usr.user_id = user.user_id
592 usr.user_id = user.user_id
593 usr.username = user.username
593 usr.username = user.username
594 usr.is_admin = user.admin
594 usr.is_admin = user.admin
595
595
596 try:
596 try:
597 self.user_perms = set([fill_perms(usr)\
597 self.user_perms = set([fill_perms(usr)\
598 .permissions['repositories'][repo_name]])
598 .permissions['repositories'][repo_name]])
599 except:
599 except:
600 self.user_perms = set()
600 self.user_perms = set()
601 self.granted_for = ''
601 self.granted_for = ''
602 self.username = user.username
602 self.username = user.username
603 self.repo_name = repo_name
603 self.repo_name = repo_name
604 return self.check_permissions()
604 return self.check_permissions()
605
605
606 def check_permissions(self):
606 def check_permissions(self):
607 log.debug('checking mercurial protocol '
607 log.debug('checking mercurial protocol '
608 'permissions for user:%s repository:%s',
608 'permissions for user:%s repository:%s',
609 self.username, self.repo_name)
609 self.username, self.repo_name)
610 if self.required_perms.intersection(self.user_perms):
610 if self.required_perms.intersection(self.user_perms):
611 log.debug('permission granted')
611 log.debug('permission granted')
612 return True
612 return True
613 log.debug('permission denied')
613 log.debug('permission denied')
614 return False
614 return False
@@ -1,375 +1,375 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head>
4 <head>
5 <title>${next.title()}</title>
5 <title>${next.title()}</title>
6 <link rel="icon" href="${h.url('/images/icons/database_gear.png')}" type="image/png" />
6 <link rel="icon" href="${h.url('/images/icons/database_gear.png')}" type="image/png" />
7 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
8 <meta name="robots" content="index, nofollow"/>
8 <meta name="robots" content="index, nofollow"/>
9 <!-- stylesheets -->
9 <!-- stylesheets -->
10 ${self.css()}
10 ${self.css()}
11 <!-- scripts -->
11 <!-- scripts -->
12 ${self.js()}
12 ${self.js()}
13 </head>
13 </head>
14 <body>
14 <body>
15 <!-- header -->
15 <!-- header -->
16 <div id="header">
16 <div id="header">
17 <!-- user -->
17 <!-- user -->
18 <ul id="logged-user">
18 <ul id="logged-user">
19 <li class="first">
19 <li class="first">
20 <div class="gravatar">
20 <div class="gravatar">
21 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,20)}" />
21 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,20)}" />
22 </div>
22 </div>
23 <div class="account">
23 <div class="account">
24 %if c.rhodecode_user.username == 'default':
24 %if c.rhodecode_user.username == 'default':
25 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
25 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
26 ${h.link_to('anonymous',h.url('register'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
26 ${h.link_to('anonymous',h.url('register'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
27 %else:
27 %else:
28 ${h.link_to('anonymous',h.url('#'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
28 ${h.link_to('anonymous',h.url('#'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
29 %endif
29 %endif
30
30
31 %else:
31 %else:
32 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
32 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
33 %endif
33 %endif
34 </div>
34 </div>
35 </li>
35 </li>
36 <li>
36 <li>
37 <a href="${h.url('home')}">${_('Home')}</a>
37 <a href="${h.url('home')}">${_('Home')}</a>
38 </li>
38 </li>
39 %if c.rhodecode_user.username != 'default':
39 %if c.rhodecode_user.username != 'default':
40 <li>
40 <li>
41 <a href="${h.url('journal')}">${_('Journal')}</a>
41 <a href="${h.url('journal')}">${_('Journal')}</a>
42 ##(${c.unread_journal})</a>
42 ##(${c.unread_journal})</a>
43 </li>
43 </li>
44 %endif
44 %endif
45 %if c.rhodecode_user.username == 'default':
45 %if c.rhodecode_user.username == 'default':
46 <li class="last highlight">${h.link_to(u'Login',h.url('login_home'))}</li>
46 <li class="last highlight">${h.link_to(u'Login',h.url('login_home'))}</li>
47 %else:
47 %else:
48 <li class="last highlight">${h.link_to(u'Log Out',h.url('logout_home'))}</li>
48 <li class="last highlight">${h.link_to(u'Log Out',h.url('logout_home'))}</li>
49 %endif
49 %endif
50 </ul>
50 </ul>
51 <!-- end user -->
51 <!-- end user -->
52 <div id="header-inner" class="title top-left-rounded-corner top-right-rounded-corner">
52 <div id="header-inner" class="title top-left-rounded-corner top-right-rounded-corner">
53 <!-- logo -->
53 <!-- logo -->
54 <div id="logo">
54 <div id="logo">
55 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
55 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
56 </div>
56 </div>
57 <!-- end logo -->
57 <!-- end logo -->
58 <!-- menu -->
58 <!-- menu -->
59 ${self.page_nav()}
59 ${self.page_nav()}
60 <!-- quick -->
60 <!-- quick -->
61 </div>
61 </div>
62 </div>
62 </div>
63 <!-- end header -->
63 <!-- end header -->
64
64
65 <!-- CONTENT -->
65 <!-- CONTENT -->
66 <div id="content">
66 <div id="content">
67 <div class="flash_msg">
67 <div class="flash_msg">
68 <% messages = h.flash.pop_messages() %>
68 <% messages = h.flash.pop_messages() %>
69 % if messages:
69 % if messages:
70 <ul id="flash-messages">
70 <ul id="flash-messages">
71 % for message in messages:
71 % for message in messages:
72 <li class="${message.category}_msg">${message}</li>
72 <li class="${message.category}_msg">${message}</li>
73 % endfor
73 % endfor
74 </ul>
74 </ul>
75 % endif
75 % endif
76 </div>
76 </div>
77 <div id="main">
77 <div id="main">
78 ${next.main()}
78 ${next.main()}
79 </div>
79 </div>
80 </div>
80 </div>
81 <!-- END CONTENT -->
81 <!-- END CONTENT -->
82
82
83 <!-- footer -->
83 <!-- footer -->
84 <div id="footer">
84 <div id="footer">
85 <div id="footer-inner" class="title bottom-left-rounded-corner bottom-right-rounded-corner">
85 <div id="footer-inner" class="title bottom-left-rounded-corner bottom-right-rounded-corner">
86 <div>
86 <div>
87 <p class="footer-link">${h.link_to(_('Submit a bug'),h.url('bugtracker'))}</p>
87 <p class="footer-link">${h.link_to(_('Submit a bug'),h.url('bugtracker'))}</p>
88 <p class="footer-link">${h.link_to(_('GPL license'),h.url('gpl_license'))}</p>
88 <p class="footer-link">${h.link_to(_('GPL license'),h.url('gpl_license'))}</p>
89 <p class="footer-link-right"><a href="${h.url('rhodecode_official')}">RhodeCode</a> ${c.rhodecode_version} &copy; 2010-2011 by Marcin Kuzminski</p>
89 <p class="footer-link-right"><a href="${h.url('rhodecode_official')}">RhodeCode</a> ${c.rhodecode_version} &copy; 2010-2011 by Marcin Kuzminski</p>
90 </div>
90 </div>
91 </div>
91 </div>
92 <script type="text/javascript">
92 <script type="text/javascript">
93 function tooltip_activate(){
93 function tooltip_activate(){
94 ${h.tooltip.activate()}
94 ${h.tooltip.activate()}
95 }
95 }
96 tooltip_activate();
96 tooltip_activate();
97 </script>
97 </script>
98 </div>
98 </div>
99 <!-- end footer -->
99 <!-- end footer -->
100 </body>
100 </body>
101
101
102 </html>
102 </html>
103
103
104 ### MAKO DEFS ###
104 ### MAKO DEFS ###
105 <%def name="page_nav()">
105 <%def name="page_nav()">
106 ${self.menu()}
106 ${self.menu()}
107 </%def>
107 </%def>
108
108
109 <%def name="menu(current=None)">
109 <%def name="menu(current=None)">
110 <%
110 <%
111 def is_current(selected):
111 def is_current(selected):
112 if selected == current:
112 if selected == current:
113 return h.literal('class="current"')
113 return h.literal('class="current"')
114 %>
114 %>
115 %if current not in ['home','admin']:
115 %if current not in ['home','admin']:
116 ##REGULAR MENU
116 ##REGULAR MENU
117 <ul id="quick">
117 <ul id="quick">
118 <!-- repo switcher -->
118 <!-- repo switcher -->
119 <li>
119 <li>
120 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
120 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
121 <span class="icon">
121 <span class="icon">
122 <img src="${h.url("/images/icons/database.png")}" alt="${_('Products')}" />
122 <img src="${h.url("/images/icons/database.png")}" alt="${_('Products')}" />
123 </span>
123 </span>
124 <span>&darr;</span>
124 <span>&darr;</span>
125 </a>
125 </a>
126 <ul class="repo_switcher">
126 <ul class="repo_switcher">
127 %for repo in c.cached_repo_list:
127 %for repo in c.cached_repo_list:
128
128
129 %if repo['repo'].dbrepo.private:
129 %if repo['repo'].dbrepo.private:
130 <li><img src="${h.url("/images/icons/lock.png")}" alt="${_('Private repository')}" class="repo_switcher_type"/>${h.link_to(repo['repo'].name,h.url('summary_home',repo_name=repo['repo'].name),class_="%s" % repo['repo'].dbrepo.repo_type)}</li>
130 <li><img src="${h.url("/images/icons/lock.png")}" alt="${_('Private repository')}" class="repo_switcher_type"/>${h.link_to(repo['repo'].name,h.url('summary_home',repo_name=repo['repo'].name),class_="%s" % repo['repo'].dbrepo.repo_type)}</li>
131 %else:
131 %else:
132 <li><img src="${h.url("/images/icons/lock_open.png")}" alt="${_('Public repository')}" class="repo_switcher_type" />${h.link_to(repo['repo'].name,h.url('summary_home',repo_name=repo['repo'].name),class_="%s" % repo['repo'].dbrepo.repo_type)}</li>
132 <li><img src="${h.url("/images/icons/lock_open.png")}" alt="${_('Public repository')}" class="repo_switcher_type" />${h.link_to(repo['repo'].name,h.url('summary_home',repo_name=repo['repo'].name),class_="%s" % repo['repo'].dbrepo.repo_type)}</li>
133 %endif
133 %endif
134 %endfor
134 %endfor
135 </ul>
135 </ul>
136 </li>
136 </li>
137
137
138 <li ${is_current('summary')}>
138 <li ${is_current('summary')}>
139 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
139 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
140 <span class="icon">
140 <span class="icon">
141 <img src="${h.url("/images/icons/clipboard_16.png")}" alt="${_('Summary')}" />
141 <img src="${h.url("/images/icons/clipboard_16.png")}" alt="${_('Summary')}" />
142 </span>
142 </span>
143 <span>${_('Summary')}</span>
143 <span>${_('Summary')}</span>
144 </a>
144 </a>
145 </li>
145 </li>
146 ##<li ${is_current('shortlog')}>
146 ##<li ${is_current('shortlog')}>
147 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
147 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
148 ## <span class="icon">
148 ## <span class="icon">
149 ## <img src="${h.url("/images/icons/application_view_list.png")}" alt="${_('Shortlog')}" />
149 ## <img src="${h.url("/images/icons/application_view_list.png")}" alt="${_('Shortlog')}" />
150 ## </span>
150 ## </span>
151 ## <span>${_('Shortlog')}</span>
151 ## <span>${_('Shortlog')}</span>
152 ## </a>
152 ## </a>
153 ##</li>
153 ##</li>
154 <li ${is_current('changelog')}>
154 <li ${is_current('changelog')}>
155 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
155 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
156 <span class="icon">
156 <span class="icon">
157 <img src="${h.url("/images/icons/time.png")}" alt="${_('Changelog')}" />
157 <img src="${h.url("/images/icons/time.png")}" alt="${_('Changelog')}" />
158 </span>
158 </span>
159 <span>${_('Changelog')}</span>
159 <span>${_('Changelog')}</span>
160 </a>
160 </a>
161 </li>
161 </li>
162
162
163 <li ${is_current('switch_to')}>
163 <li ${is_current('switch_to')}>
164 <a title="${_('Switch to')}" href="#">
164 <a title="${_('Switch to')}" href="#">
165 <span class="icon">
165 <span class="icon">
166 <img src="${h.url("/images/icons/arrow_switch.png")}" alt="${_('Switch to')}" />
166 <img src="${h.url("/images/icons/arrow_switch.png")}" alt="${_('Switch to')}" />
167 </span>
167 </span>
168 <span>${_('Switch to')}</span>
168 <span>${_('Switch to')}</span>
169 </a>
169 </a>
170 <ul>
170 <ul>
171 <li>
171 <li>
172 ${h.link_to('%s (%s)' % (_('branches'),len(c.repository_branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
172 ${h.link_to('%s (%s)' % (_('branches'),len(c.repository_branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
173 <ul>
173 <ul>
174 %if c.repository_branches.values():
174 %if c.repository_branches.values():
175 %for cnt,branch in enumerate(c.repository_branches.items()):
175 %for cnt,branch in enumerate(c.repository_branches.items()):
176 <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
176 <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
177 %endfor
177 %endfor
178 %else:
178 %else:
179 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
179 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
180 %endif
180 %endif
181 </ul>
181 </ul>
182 </li>
182 </li>
183 <li>
183 <li>
184 ${h.link_to('%s (%s)' % (_('tags'),len(c.repository_tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
184 ${h.link_to('%s (%s)' % (_('tags'),len(c.repository_tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
185 <ul>
185 <ul>
186 %if c.repository_tags.values():
186 %if c.repository_tags.values():
187 %for cnt,tag in enumerate(c.repository_tags.items()):
187 %for cnt,tag in enumerate(c.repository_tags.items()):
188 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
188 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
189 %endfor
189 %endfor
190 %else:
190 %else:
191 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
191 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
192 %endif
192 %endif
193 </ul>
193 </ul>
194 </li>
194 </li>
195 </ul>
195 </ul>
196 </li>
196 </li>
197 <li ${is_current('files')}>
197 <li ${is_current('files')}>
198 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
198 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
199 <span class="icon">
199 <span class="icon">
200 <img src="${h.url("/images/icons/file.png")}" alt="${_('Files')}" />
200 <img src="${h.url("/images/icons/file.png")}" alt="${_('Files')}" />
201 </span>
201 </span>
202 <span>${_('Files')}</span>
202 <span>${_('Files')}</span>
203 </a>
203 </a>
204 </li>
204 </li>
205
205
206 <li ${is_current('options')}>
206 <li ${is_current('options')}>
207 <a title="${_('Options')}" href="#">
207 <a title="${_('Options')}" href="#">
208 <span class="icon">
208 <span class="icon">
209 <img src="${h.url("/images/icons/table_gear.png")}" alt="${_('Admin')}" />
209 <img src="${h.url("/images/icons/table_gear.png")}" alt="${_('Admin')}" />
210 </span>
210 </span>
211 <span>${_('Options')}</span>
211 <span>${_('Options')}</span>
212 </a>
212 </a>
213 <ul>
213 <ul>
214 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
214 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
215 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
215 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
216 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
216 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
217 %else:
217 %else:
218 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
218 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
219 %endif
219 %endif
220 %endif
220 %endif
221 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
221 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
222 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
222 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
223
223
224 %if h.HasPermissionAll('hg.admin')('access admin main page'):
224 %if h.HasPermissionAll('hg.admin')('access admin main page'):
225 <li>
225 <li>
226 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
226 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
227 <%def name="admin_menu()">
227 <%def name="admin_menu()">
228 <ul>
228 <ul>
229 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
229 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
230 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
230 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
231 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
231 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
232 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
232 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
233 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
233 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
234 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
234 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
235 </ul>
235 </ul>
236 </%def>
236 </%def>
237
237
238 ${admin_menu()}
238 ${admin_menu()}
239 </li>
239 </li>
240 %endif
240 %endif
241
241
242 </ul>
242 </ul>
243 </li>
243 </li>
244
244
245 <li>
245 <li>
246 <a title="${_('Followers')}" href="#">
246 <a title="${_('Followers')}" href="#">
247 <span class="icon_short">
247 <span class="icon_short">
248 <img src="${h.url("/images/icons/heart.png")}" alt="${_('Followers')}" />
248 <img src="${h.url("/images/icons/heart.png")}" alt="${_('Followers')}" />
249 </span>
249 </span>
250 <span class="short">${c.repository_followers}</span>
250 <span class="short">${c.repository_followers}</span>
251 </a>
251 </a>
252 </li>
252 </li>
253 <li>
253 <li>
254 <a title="${_('Forks')}" href="#">
254 <a title="${_('Forks')}" href="#">
255 <span class="icon_short">
255 <span class="icon_short">
256 <img src="${h.url("/images/icons/arrow_divide.png")}" alt="${_('Forks')}" />
256 <img src="${h.url("/images/icons/arrow_divide.png")}" alt="${_('Forks')}" />
257 </span>
257 </span>
258 <span class="short">${c.repository_forks}</span>
258 <span class="short">${c.repository_forks}</span>
259 </a>
259 </a>
260 </li>
260 </li>
261
261
262
262
263
263
264 </ul>
264 </ul>
265 %else:
265 %else:
266 ##ROOT MENU
266 ##ROOT MENU
267 <ul id="quick">
267 <ul id="quick">
268 <li>
268 <li>
269 <a title="${_('Home')}" href="${h.url('home')}">
269 <a title="${_('Home')}" href="${h.url('home')}">
270 <span class="icon">
270 <span class="icon">
271 <img src="${h.url("/images/icons/home_16.png")}" alt="${_('Home')}" />
271 <img src="${h.url("/images/icons/home_16.png")}" alt="${_('Home')}" />
272 </span>
272 </span>
273 <span>${_('Home')}</span>
273 <span>${_('Home')}</span>
274 </a>
274 </a>
275 </li>
275 </li>
276 %if c.rhodecode_user.username != 'default':
276 %if c.rhodecode_user.username != 'default':
277 <li>
277 <li>
278 <a title="${_('Journal')}" href="${h.url('journal')}">
278 <a title="${_('Journal')}" href="${h.url('journal')}">
279 <span class="icon">
279 <span class="icon">
280 <img src="${h.url("/images/icons/book.png")}" alt="${_('Journal')}" />
280 <img src="${h.url("/images/icons/book.png")}" alt="${_('Journal')}" />
281 </span>
281 </span>
282 <span>${_('Journal')}</span>
282 <span>${_('Journal')}</span>
283 </a>
283 </a>
284 </li>
284 </li>
285 %endif
285 %endif
286 <li>
286 <li>
287 <a title="${_('Search')}" href="${h.url('search')}">
287 <a title="${_('Search')}" href="${h.url('search')}">
288 <span class="icon">
288 <span class="icon">
289 <img src="${h.url("/images/icons/search_16.png")}" alt="${_('Search')}" />
289 <img src="${h.url("/images/icons/search_16.png")}" alt="${_('Search')}" />
290 </span>
290 </span>
291 <span>${_('Search')}</span>
291 <span>${_('Search')}</span>
292 </a>
292 </a>
293 </li>
293 </li>
294
294
295 %if h.HasPermissionAll('hg.admin')('access admin main page'):
295 %if h.HasPermissionAll('hg.admin')('access admin main page'):
296 <li ${is_current('admin')}>
296 <li ${is_current('admin')}>
297 <a title="${_('Admin')}" href="${h.url('admin_home')}">
297 <a title="${_('Admin')}" href="${h.url('admin_home')}">
298 <span class="icon">
298 <span class="icon">
299 <img src="${h.url("/images/icons/cog_edit.png")}" alt="${_('Admin')}" />
299 <img src="${h.url("/images/icons/cog_edit.png")}" alt="${_('Admin')}" />
300 </span>
300 </span>
301 <span>${_('Admin')}</span>
301 <span>${_('Admin')}</span>
302 </a>
302 </a>
303 ${admin_menu()}
303 ${admin_menu()}
304 </li>
304 </li>
305 %endif
305 %endif
306 </ul>
306 </ul>
307 %endif
307 %endif
308 </%def>
308 </%def>
309
309
310
310
311 <%def name="css()">
311 <%def name="css()">
312 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
312 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
313 <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css')}" />
313 <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css')}" />
314 <link rel="stylesheet" type="text/css" href="${h.url('/css/diff.css')}" />
314 <link rel="stylesheet" type="text/css" href="${h.url('/css/diff.css')}" />
315 </%def>
315 </%def>
316
316
317 <%def name="js()">
317 <%def name="js()">
318 ##<script type="text/javascript" src="${h.url('/js/yui/utilities/utilities.js')}"></script>
318 ##<script type="text/javascript" src="${h.url('/js/yui/utilities/utilities.js')}"></script>
319 ##<script type="text/javascript" src="${h.url('/js/yui/container/container.js')}"></script>
319 ##<script type="text/javascript" src="${h.url('/js/yui/container/container.js')}"></script>
320 ##<script type="text/javascript" src="${h.url('/js/yui/datasource/datasource.js')}"></script>
320 ##<script type="text/javascript" src="${h.url('/js/yui/datasource/datasource.js')}"></script>
321 ##<script type="text/javascript" src="${h.url('/js/yui/autocomplete/autocomplete.js')}"></script>
321 ##<script type="text/javascript" src="${h.url('/js/yui/autocomplete/autocomplete.js')}"></script>
322 ##<script type="text/javascript" src="${h.url('/js/yui/selector/selector-min.js')}"></script>
322 ##<script type="text/javascript" src="${h.url('/js/yui/selector/selector-min.js')}"></script>
323
323
324 <script type="text/javascript" src="${h.url('/js/yui2a.js')}"></script>
324 <script type="text/javascript" src="${h.url('/js/yui2a.js')}"></script>
325 <!--[if IE]><script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script><![endif]-->
325 <!--[if IE]><script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script><![endif]-->
326 <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
326 <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
327
327
328 <script type="text/javascript">
328 <script type="text/javascript">
329 var base_url = "${h.url('toggle_following')}";
329 var base_url = "${h.url('toggle_following')}";
330 var YUC = YAHOO.util.Connect;
330 var YUC = YAHOO.util.Connect;
331 var YUD = YAHOO.util.Dom;
331 var YUD = YAHOO.util.Dom;
332 var YUE = YAHOO.util.Event;
332 var YUE = YAHOO.util.Event;
333
333
334 function onSuccess(target){
334 function onSuccess(target){
335
335
336 var f = YUD.get(target.id);
336 var f = YUD.get(target.id);
337 if(f.getAttribute('class')=='follow'){
337 if(f.getAttribute('class')=='follow'){
338 f.setAttribute('class','following');
338 f.setAttribute('class','following');
339 f.setAttribute('title',"${_('Stop following this repository')}");
339 f.setAttribute('title',"${_('Stop following this repository')}");
340 }
340 }
341 else{
341 else{
342 f.setAttribute('class','follow');
342 f.setAttribute('class','follow');
343 f.setAttribute('title',"${_('Start following this repository')}");
343 f.setAttribute('title',"${_('Start following this repository')}");
344 }
344 }
345 }
345 }
346
346
347 function toggleFollowingUser(fallows_user_id,token){
347 function toggleFollowingUser(fallows_user_id,token){
348 args = 'follows_user_id='+fallows_user_id;
348 args = 'follows_user_id='+fallows_user_id;
349 args+= '&amp;auth_token='+token;
349 args+= '&amp;auth_token='+token;
350 YUC.asyncRequest('POST',base_url,{
350 YUC.asyncRequest('POST',base_url,{
351 success:function(o){
351 success:function(o){
352 onSuccess();
352 onSuccess();
353 }
353 }
354 },args); return false;
354 },args); return false;
355 }
355 }
356
356
357 function toggleFollowingRepo(target,fallows_repo_id,token){
357 function toggleFollowingRepo(target,fallows_repo_id,token){
358
358
359 args = 'follows_repo_id='+fallows_repo_id;
359 args = 'follows_repo_id='+fallows_repo_id;
360 args+= '&amp;auth_token='+token;
360 args+= '&amp;auth_token='+token;
361 YUC.asyncRequest('POST',base_url,{
361 YUC.asyncRequest('POST',base_url,{
362 success:function(o){
362 success:function(o){
363 onSuccess(target);
363 onSuccess(target);
364 }
364 }
365 },args); return false;
365 },args); return false;
366 }
366 }
367 </script>
367 </script>
368
368
369 </%def>
369 </%def>
370
370
371 <%def name="breadcrumbs()">
371 <%def name="breadcrumbs()">
372 <div class="breadcrumbs">
372 <div class="breadcrumbs">
373 ${self.breadcrumbs_links()}
373 ${self.breadcrumbs_links()}
374 </div>
374 </div>
375 </%def> No newline at end of file
375 </%def>
@@ -1,54 +1,54 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head>
4 <head>
5 <title>Error - ${c.error_message}</title>
5 <title>Error - ${c.error_message}</title>
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 %if c.redirect_time:
7 %if c.redirect_time:
8 <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/>
8 <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/>
9 %endif
9 %endif
10 <link rel="icon" href="${h.url('/images/icons/database_gear.png')}" type="image/png" />
10 <link rel="icon" href="${h.url('/images/icons/database_gear.png')}" type="image/png" />
11 <meta name="robots" content="index, nofollow"/>
11 <meta name="robots" content="index, nofollow"/>
12
12
13 <!-- stylesheets -->
13 <!-- stylesheets -->
14 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
14 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
15 <style type="text/css">
15 <style type="text/css">
16 #main_div{
16 #main_div{
17 border: 0px solid #000;
17 border: 0px solid #000;
18 width: 500px;
18 width: 500px;
19 margin: auto;
19 margin: auto;
20 text-align: center;
20 text-align: center;
21 margin-top: 200px;
21 margin-top: 200px;
22 font-size: 1.6em;
22 font-size: 1.6em;
23 }
23 }
24 .error_message{
24 .error_message{
25 text-align: center;
25 text-align: center;
26 color:#003367;
26 color:#003367;
27 font-size: 1.6em;
27 font-size: 1.6em;
28 margin:10px;
28 margin:10px;
29 }
29 }
30 </style>
30 </style>
31
31
32 </head>
32 </head>
33 <body>
33 <body>
34
34
35 <div id="login">
35 <div id="login">
36 <div class="table">
36 <div class="table">
37 <div id="main_div">
37 <div id="main_div">
38 <div style="font-size:2.0em;margin: 10px">${c.rhodecode_name}</div>
38 <div style="font-size:2.0em;margin: 10px">${c.rhodecode_name}</div>
39 <h1 class="error_message">${c.error_message}</h1>
39 <h1 class="error_message">${c.error_message}</h1>
40
40
41 <p>${c.error_explanation}</p>
41 <p>${c.error_explanation}</p>
42
42
43 %if c.redirect_time:
43 %if c.redirect_time:
44 <p>${_('You will be redirected to %s in %s seconds') % (c.redirect_module,c.redirect_time)}</p>
44 <p>${_('You will be redirected to %s in %s seconds') % (c.redirect_module,c.redirect_time)}</p>
45 %endif
45 %endif
46
46
47 </div>
47 </div>
48 </div>
48 </div>
49 <!-- end login -->
49 <!-- end login -->
50 </div>
50 </div>
51 </body>
51 </body>
52
52
53 </html>
53 </html>
54
54
@@ -1,82 +1,82 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head>
4 <head>
5 <title>${_('Sign In')} - ${c.rhodecode_name}</title>
5 <title>${_('Sign In')} - ${c.rhodecode_name}</title>
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <link rel="icon" href="${h.url("/images/icons/database_gear.png")}" type="image/png" />
7 <link rel="icon" href="${h.url("/images/icons/database_gear.png")}" type="image/png" />
8 <meta name="robots" content="index, nofollow"/>
8 <meta name="robots" content="index, nofollow"/>
9
9
10 <!-- stylesheets -->
10 <!-- stylesheets -->
11 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
11 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
12
12
13 </head>
13 </head>
14 <body>
14 <body>
15 <div id="login">
15 <div id="login">
16 <div class="flash_msg">
16 <div class="flash_msg">
17 <% messages = h.flash.pop_messages() %>
17 <% messages = h.flash.pop_messages() %>
18 % if messages:
18 % if messages:
19 <ul id="flash-messages">
19 <ul id="flash-messages">
20 % for message in messages:
20 % for message in messages:
21 <li class="${message.category}_msg">${message}</li>
21 <li class="${message.category}_msg">${message}</li>
22 % endfor
22 % endfor
23 </ul>
23 </ul>
24 % endif
24 % endif
25 </div>
25 </div>
26 <!-- login -->
26 <!-- login -->
27 <div class="title top-left-rounded-corner top-right-rounded-corner">
27 <div class="title top-left-rounded-corner top-right-rounded-corner">
28 <h5>${_('Sign In to')} ${c.rhodecode_name}</h5>
28 <h5>${_('Sign In to')} ${c.rhodecode_name}</h5>
29 </div>
29 </div>
30 <div class="inner">
30 <div class="inner">
31 ${h.form(h.url.current(came_from=c.came_from))}
31 ${h.form(h.url.current(came_from=c.came_from))}
32 <div class="form">
32 <div class="form">
33 <!-- fields -->
33 <!-- fields -->
34
34
35 <div class="fields">
35 <div class="fields">
36 <div class="field">
36 <div class="field">
37 <div class="label">
37 <div class="label">
38 <label for="username">${_('Username')}:</label>
38 <label for="username">${_('Username')}:</label>
39 </div>
39 </div>
40 <div class="input">
40 <div class="input">
41 ${h.text('username',class_='focus',size=40)}
41 ${h.text('username',class_='focus',size=40)}
42 </div>
42 </div>
43
43
44 </div>
44 </div>
45 <div class="field">
45 <div class="field">
46 <div class="label">
46 <div class="label">
47 <label for="password">${_('Password')}:</label>
47 <label for="password">${_('Password')}:</label>
48 </div>
48 </div>
49 <div class="input">
49 <div class="input">
50 ${h.password('password',class_='focus',size=40)}
50 ${h.password('password',class_='focus',size=40)}
51 </div>
51 </div>
52
52
53 </div>
53 </div>
54 ##<div class="field">
54 ##<div class="field">
55 ## <div class="checkbox">
55 ## <div class="checkbox">
56 ## <input type="checkbox" id="remember" name="remember" />
56 ## <input type="checkbox" id="remember" name="remember" />
57 ## <label for="remember">Remember me</label>
57 ## <label for="remember">Remember me</label>
58 ## </div>
58 ## </div>
59 ##</div>
59 ##</div>
60 <div class="buttons">
60 <div class="buttons">
61 ${h.submit('sign_in','Sign In',class_="ui-button")}
61 ${h.submit('sign_in','Sign In',class_="ui-button")}
62 </div>
62 </div>
63 </div>
63 </div>
64 <!-- end fields -->
64 <!-- end fields -->
65 <!-- links -->
65 <!-- links -->
66 <div class="links">
66 <div class="links">
67 ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))}
67 ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))}
68 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
68 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
69 /
69 /
70 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
70 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
71 %endif
71 %endif
72 </div>
72 </div>
73
73
74 <!-- end links -->
74 <!-- end links -->
75 </div>
75 </div>
76 ${h.end_form()}
76 ${h.end_form()}
77 </div>
77 </div>
78 <!-- end login -->
78 <!-- end login -->
79 </div>
79 </div>
80 </body>
80 </body>
81 </html>
81 </html>
82
82
@@ -1,48 +1,48 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head>
4 <head>
5 <title>${_('Reset You password')} - ${c.rhodecode_name}</title>
5 <title>${_('Reset You password')} - ${c.rhodecode_name}</title>
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <link rel="icon" href="${h.url("/images/hgicon.png")}" type="image/png" />
7 <link rel="icon" href="${h.url("/images/hgicon.png")}" type="image/png" />
8 <meta name="robots" content="index, nofollow"/>
8 <meta name="robots" content="index, nofollow"/>
9
9
10 <!-- stylesheets -->
10 <!-- stylesheets -->
11 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
11 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
12
12
13 </head>
13 </head>
14 <body>
14 <body>
15 <div id="register">
15 <div id="register">
16
16
17 <div class="title top-left-rounded-corner top-right-rounded-corner">
17 <div class="title top-left-rounded-corner top-right-rounded-corner">
18 <h5>${_('Reset You password to')} ${c.rhodecode_name}</h5>
18 <h5>${_('Reset You password to')} ${c.rhodecode_name}</h5>
19 </div>
19 </div>
20 <div class="inner">
20 <div class="inner">
21 ${h.form(url('password_reset'))}
21 ${h.form(url('password_reset'))}
22 <div class="form">
22 <div class="form">
23 <!-- fields -->
23 <!-- fields -->
24 <div class="fields">
24 <div class="fields">
25
25
26 <div class="field">
26 <div class="field">
27 <div class="label">
27 <div class="label">
28 <label for="email">${_('Email address')}:</label>
28 <label for="email">${_('Email address')}:</label>
29 </div>
29 </div>
30 <div class="input">
30 <div class="input">
31 ${h.text('email')}
31 ${h.text('email')}
32 </div>
32 </div>
33 </div>
33 </div>
34
34
35 <div class="buttons">
35 <div class="buttons">
36 <div class="nohighlight">
36 <div class="nohighlight">
37 ${h.submit('send','Reset my password',class_="ui-button")}
37 ${h.submit('send','Reset my password',class_="ui-button")}
38 <div class="activation_msg">${_('Your new password will be send to matching email address')}</div>
38 <div class="activation_msg">${_('Your new password will be send to matching email address')}</div>
39 </div>
39 </div>
40 </div>
40 </div>
41 </div>
41 </div>
42 </div>
42 </div>
43 ${h.end_form()}
43 ${h.end_form()}
44 </div>
44 </div>
45 </div>
45 </div>
46 </body>
46 </body>
47 </html>
47 </html>
48
48
@@ -1,96 +1,96 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head>
4 <head>
5 <title>${_('Sign Up')} - ${c.rhodecode_name}</title>
5 <title>${_('Sign Up')} - ${c.rhodecode_name}</title>
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <link rel="icon" href="${h.url("/images/hgicon.png")}" type="image/png" />
7 <link rel="icon" href="${h.url("/images/hgicon.png")}" type="image/png" />
8 <meta name="robots" content="index, nofollow"/>
8 <meta name="robots" content="index, nofollow"/>
9
9
10 <!-- stylesheets -->
10 <!-- stylesheets -->
11 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
11 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
12
12
13 </head>
13 </head>
14 <body>
14 <body>
15 <div id="register">
15 <div id="register">
16
16
17 <div class="title top-left-rounded-corner top-right-rounded-corner">
17 <div class="title top-left-rounded-corner top-right-rounded-corner">
18 <h5>${_('Sign Up to')} ${c.rhodecode_name}</h5>
18 <h5>${_('Sign Up to')} ${c.rhodecode_name}</h5>
19 </div>
19 </div>
20 <div class="inner">
20 <div class="inner">
21 ${h.form(url('register'))}
21 ${h.form(url('register'))}
22 <div class="form">
22 <div class="form">
23 <!-- fields -->
23 <!-- fields -->
24 <div class="fields">
24 <div class="fields">
25 <div class="field">
25 <div class="field">
26 <div class="label">
26 <div class="label">
27 <label for="username">${_('Username')}:</label>
27 <label for="username">${_('Username')}:</label>
28 </div>
28 </div>
29 <div class="input">
29 <div class="input">
30 ${h.text('username',class_="medium")}
30 ${h.text('username',class_="medium")}
31 </div>
31 </div>
32 </div>
32 </div>
33
33
34 <div class="field">
34 <div class="field">
35 <div class="label">
35 <div class="label">
36 <label for="password">${_('Password')}:</label>
36 <label for="password">${_('Password')}:</label>
37 </div>
37 </div>
38 <div class="input">
38 <div class="input">
39 ${h.password('password',class_="medium")}
39 ${h.password('password',class_="medium")}
40 </div>
40 </div>
41 </div>
41 </div>
42
42
43 <div class="field">
43 <div class="field">
44 <div class="label">
44 <div class="label">
45 <label for="password">${_('Re-enter password')}:</label>
45 <label for="password">${_('Re-enter password')}:</label>
46 </div>
46 </div>
47 <div class="input">
47 <div class="input">
48 ${h.password('password_confirmation',class_="medium")}
48 ${h.password('password_confirmation',class_="medium")}
49 </div>
49 </div>
50 </div>
50 </div>
51
51
52 <div class="field">
52 <div class="field">
53 <div class="label">
53 <div class="label">
54 <label for="name">${_('First Name')}:</label>
54 <label for="name">${_('First Name')}:</label>
55 </div>
55 </div>
56 <div class="input">
56 <div class="input">
57 ${h.text('name',class_="medium")}
57 ${h.text('name',class_="medium")}
58 </div>
58 </div>
59 </div>
59 </div>
60
60
61 <div class="field">
61 <div class="field">
62 <div class="label">
62 <div class="label">
63 <label for="lastname">${_('Last Name')}:</label>
63 <label for="lastname">${_('Last Name')}:</label>
64 </div>
64 </div>
65 <div class="input">
65 <div class="input">
66 ${h.text('lastname',class_="medium")}
66 ${h.text('lastname',class_="medium")}
67 </div>
67 </div>
68 </div>
68 </div>
69
69
70 <div class="field">
70 <div class="field">
71 <div class="label">
71 <div class="label">
72 <label for="email">${_('Email')}:</label>
72 <label for="email">${_('Email')}:</label>
73 </div>
73 </div>
74 <div class="input">
74 <div class="input">
75 ${h.text('email',class_="medium")}
75 ${h.text('email',class_="medium")}
76 </div>
76 </div>
77 </div>
77 </div>
78
78
79 <div class="buttons">
79 <div class="buttons">
80 <div class="nohighlight">
80 <div class="nohighlight">
81 ${h.submit('sign_up','Sign Up',class_="ui-button")}
81 ${h.submit('sign_up','Sign Up',class_="ui-button")}
82 %if c.auto_active:
82 %if c.auto_active:
83 <div class="activation_msg">${_('Your account will be activated right after registration')}</div>
83 <div class="activation_msg">${_('Your account will be activated right after registration')}</div>
84 %else:
84 %else:
85 <div class="activation_msg">${_('Your account must wait for activation by administrator')}</div>
85 <div class="activation_msg">${_('Your account must wait for activation by administrator')}</div>
86 %endif
86 %endif
87 </div>
87 </div>
88 </div>
88 </div>
89 </div>
89 </div>
90 </div>
90 </div>
91 ${h.end_form()}
91 ${h.end_form()}
92 </div>
92 </div>
93 </div>
93 </div>
94 </body>
94 </body>
95 </html>
95 </html>
96
96
@@ -1,113 +1,116 b''
1 import sys
1 import sys
2 from rhodecode import get_version
2 from rhodecode import get_version
3 from rhodecode import __platform__
3 from rhodecode import __platform__
4
4
5 py_version = sys.version_info
5 py_version = sys.version_info
6
6
7 if py_version < (2, 5):
8 raise Exception('RhodeCode requires python 2.5 or later')
9
7 requirements = [
10 requirements = [
8 "Pylons==1.0.0",
11 "Pylons==1.0.0",
9 "WebHelpers==1.2",
12 "WebHelpers==1.2",
10 "SQLAlchemy==0.6.6",
13 "SQLAlchemy==0.6.6",
11 "Mako==0.4.0",
14 "Mako==0.4.0",
12 "vcs==0.1.11",
15 "vcs==0.1.11",
13 "pygments==1.4.0",
16 "pygments==1.4.0",
14 "mercurial==1.7.5",
17 "mercurial==1.7.5",
15 "whoosh==1.3.4",
18 "whoosh==1.3.4",
16 "celery==2.2.4",
19 "celery==2.2.4",
17 "babel",
20 "babel",
18 ]
21 ]
19
22
20 classifiers = ['Development Status :: 5 - Production/Stable',
23 classifiers = ['Development Status :: 5 - Production/Stable',
21 'Environment :: Web Environment',
24 'Environment :: Web Environment',
22 'Framework :: Pylons',
25 'Framework :: Pylons',
23 'Intended Audience :: Developers',
26 'Intended Audience :: Developers',
24 'License :: OSI Approved :: BSD License',
27 'License :: OSI Approved :: BSD License',
25 'Operating System :: OS Independent',
28 'Operating System :: OS Independent',
26 'Programming Language :: Python', ]
29 'Programming Language :: Python', ]
27
30
28 if py_version < (2, 6):
31 if py_version < (2, 6):
29 requirements.append("simplejson")
32 requirements.append("simplejson")
30 requirements.append("pysqlite")
33 requirements.append("pysqlite")
31
34
32 if __platform__ in ('Linux', 'Darwin'):
35 if __platform__ in ('Linux', 'Darwin'):
33 requirements.append("py-bcrypt")
36 requirements.append("py-bcrypt")
34
37
35
38
36 #additional files from project that goes somewhere in the filesystem
39 #additional files from project that goes somewhere in the filesystem
37 #relative to sys.prefix
40 #relative to sys.prefix
38 data_files = []
41 data_files = []
39
42
40 #additional files that goes into package itself
43 #additional files that goes into package itself
41 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
44 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
42
45
43 description = ('Mercurial repository browser/management with '
46 description = ('Mercurial repository browser/management with '
44 'build in push/pull server and full text search')
47 'build in push/pull server and full text search')
45 keywords = ' '.join (['rhodecode', 'rhodiumcode', 'mercurial', 'git',
48 keywords = ' '.join (['rhodecode', 'rhodiumcode', 'mercurial', 'git',
46 'repository management', 'hgweb replacement'
49 'repository management', 'hgweb replacement'
47 'hgwebdir', 'gitweb replacement', 'serving hgweb',
50 'hgwebdir', 'gitweb replacement', 'serving hgweb',
48 ])
51 ])
49 #long description
52 #long description
50 try:
53 try:
51 readme_file = 'README.rst'
54 readme_file = 'README.rst'
52 changelog_file = 'docs/changelog.rst'
55 changelog_file = 'docs/changelog.rst'
53 long_description = open(readme_file).read() + '\n\n' + \
56 long_description = open(readme_file).read() + '\n\n' + \
54 open(changelog_file).read()
57 open(changelog_file).read()
55
58
56 except IOError, err:
59 except IOError, err:
57 sys.stderr.write("[WARNING] Cannot find file specified as "
60 sys.stderr.write("[WARNING] Cannot find file specified as "
58 "long_description (%s)\n or changelog (%s) skipping that file" \
61 "long_description (%s)\n or changelog (%s) skipping that file" \
59 % (readme_file, changelog_file))
62 % (readme_file, changelog_file))
60 long_description = description
63 long_description = description
61
64
62
65
63 try:
66 try:
64 from setuptools import setup, find_packages
67 from setuptools import setup, find_packages
65 except ImportError:
68 except ImportError:
66 from ez_setup import use_setuptools
69 from ez_setup import use_setuptools
67 use_setuptools()
70 use_setuptools()
68 from setuptools import setup, find_packages
71 from setuptools import setup, find_packages
69 #packages
72 #packages
70 packages = find_packages(exclude=['ez_setup'])
73 packages = find_packages(exclude=['ez_setup'])
71
74
72 setup(
75 setup(
73 name='RhodeCode',
76 name='RhodeCode',
74 version=get_version(),
77 version=get_version(),
75 description=description,
78 description=description,
76 long_description=long_description,
79 long_description=long_description,
77 keywords=keywords,
80 keywords=keywords,
78 license='BSD',
81 license='GPLv3',
79 author='Marcin Kuzminski',
82 author='Marcin Kuzminski',
80 author_email='marcin@python-works.com',
83 author_email='marcin@python-works.com',
81 url='http://rhodecode.org',
84 url='http://rhodecode.org',
82 install_requires=requirements,
85 install_requires=requirements,
83 classifiers=classifiers,
86 classifiers=classifiers,
84 setup_requires=["PasteScript>=1.6.3"],
87 setup_requires=["PasteScript>=1.6.3"],
85 data_files=data_files,
88 data_files=data_files,
86 packages=packages,
89 packages=packages,
87 include_package_data=True,
90 include_package_data=True,
88 test_suite='nose.collector',
91 test_suite='nose.collector',
89 package_data=package_data,
92 package_data=package_data,
90 message_extractors={'rhodecode': [
93 message_extractors={'rhodecode': [
91 ('**.py', 'python', None),
94 ('**.py', 'python', None),
92 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
95 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
93 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
96 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
94 ('public/**', 'ignore', None)]},
97 ('public/**', 'ignore', None)]},
95 zip_safe=False,
98 zip_safe=False,
96 paster_plugins=['PasteScript', 'Pylons'],
99 paster_plugins=['PasteScript', 'Pylons'],
97 entry_points="""
100 entry_points="""
98 [paste.app_factory]
101 [paste.app_factory]
99 main = rhodecode.config.middleware:make_app
102 main = rhodecode.config.middleware:make_app
100
103
101 [paste.app_install]
104 [paste.app_install]
102 main = pylons.util:PylonsInstaller
105 main = pylons.util:PylonsInstaller
103
106
104 [paste.global_paster_command]
107 [paste.global_paster_command]
105 make-index = rhodecode.lib.indexers:MakeIndex
108 make-index = rhodecode.lib.indexers:MakeIndex
106 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
109 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
107 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
110 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
108 celerybeat=rhodecode.lib.celerypylons.commands:CeleryBeatCommand
111 celerybeat=rhodecode.lib.celerypylons.commands:CeleryBeatCommand
109 camqadm=rhodecode.lib.celerypylons.commands:CAMQPAdminCommand
112 camqadm=rhodecode.lib.celerypylons.commands:CAMQPAdminCommand
110 celeryev=rhodecode.lib.celerypylons.commands:CeleryEventCommand
113 celeryev=rhodecode.lib.celerypylons.commands:CeleryEventCommand
111
114
112 """,
115 """,
113 )
116 )
General Comments 0
You need to be logged in to leave comments. Login now