##// END OF EJS Templates
fixes for stable
marcink -
r1216:8363b0d2 default
parent child Browse files
Show More
@@ -1,132 +1,141 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 sources 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 bitbucket
37
38 MIRRORS:
39
40 Issue tracker and sources at bitbucket_
41
38 http://bitbucket.org/marcinkuzminski/rhodecode
42 http://bitbucket.org/marcinkuzminski/rhodecode
39
43
44 Sources at github_
45
46 https://github.com/marcinkuzminski/rhodecode
47
40 Installation
48 Installation
41 ------------
49 ------------
42
50
43 Please visit http://packages.python.org/RhodeCode/installation.html
51 Please visit http://packages.python.org/RhodeCode/installation.html
44
52
45
53
46 RhodeCode Features
54 RhodeCode Features
47 ------------------
55 ------------------
48
56
49 - Has it's own middleware to handle mercurial_ protocol requests.
57 - Has it's own middleware to handle mercurial_ protocol requests.
50 Each request can be logged and authenticated.
58 Each request can be logged and authenticated.
51 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
59 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
52 Supports http/https and LDAP
60 Supports http/https and LDAP
53 - Full permissions (private/read/write/admin) and authentication per project.
61 - Full permissions (private/read/write/admin) and authentication per project.
54 One account for web interface and mercurial_ push/pull/clone operations.
62 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.
63 - 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.
64 - Beautiful diffs, annotations and source code browsing all colored by pygments.
57 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
65 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
58 - Admin interface with user/permission management. Admin activity journal, logs
66 - Admin interface with user/permission management. Admin activity journal, logs
59 pulls, pushes, forks, registrations and other actions made by all users.
67 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
68 - Server side forks. It is possible to fork a project and modify it freely without
61 breaking the main repository.
69 breaking the main repository.
62 - Full text search powered by Whoosh on the source files, and file names.
70 - Full text search powered by Whoosh on the source files, and file names.
63 Build in indexing daemons, with optional incremental index build
71 Build in indexing daemons, with optional incremental index build
64 (no external search servers required all in one application)
72 (no external search servers required all in one application)
65 - Setup project descriptions and info inside built in db for easy, non
73 - Setup project descriptions and info inside built in db for easy, non
66 file-system operations
74 file-system operations
67 - Intelligent cache with invalidation after push or project change, provides high
75 - Intelligent cache with invalidation after push or project change, provides high
68 performance and always up to date data.
76 performance and always up to date data.
69 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
77 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
70 - Async tasks for speed and performance using celery_ (works without them too)
78 - 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
79 - Backup scripts can do backup of whole app and send it over scp to desired
72 location
80 location
73 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
81 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
74
82
75
83
76 .. include:: ./docs/screenshots.rst
84 .. include:: ./docs/screenshots.rst
77
85
78
86
79 Incoming / Plans
87 Incoming / Plans
80 ----------------
88 ----------------
81
89
82 - Project grouping
90 - Project grouping
83 - User groups/teams
91 - User groups/teams
84 - SSH based authentication with server side key management
92 - SSH based authentication with server side key management
85 - Code review (probably based on hg-review)
93 - Code review (probably based on hg-review)
86 - Full git_ support, with push/pull server (currently in beta tests)
94 - Full git_ support, with push/pull server (currently in beta tests)
87 - Redmine integration
95 - Redmine integration
88 - Public accessible activity feeds
96 - Public accessible activity feeds
89 - Commit based built in wiki system
97 - Commit based built in wiki system
90 - Clone points and cloning from remote repositories into RhodeCode
98 - Clone points and cloning from remote repositories into RhodeCode
91 - More statistics and graph (global annotation + some more statistics)
99 - 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)
100 - Other advancements as development continues (or you can of course make additions and or requests)
93
101
94 License
102 License
95 -------
103 -------
96
104
97 ``RhodeCode`` is released under the GPL_ license.
105 ``RhodeCode`` is released under the GPLv3 license.
98
106
99
107
100 Mailing group Q&A
108 Mailing group Q&A
101 -----------------
109 -----------------
102
110
103 Join the `Google group <http://groups.google.com/group/rhodecode>`_
111 Join the `Google group <http://groups.google.com/group/rhodecode>`_
104
112
105 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
113 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
106
114
107 Join #rhodecode on FreeNode (irc.freenode.net)
115 Join #rhodecode on FreeNode (irc.freenode.net)
108 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
116 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
109
117
110 Online documentation
118 Online documentation
111 --------------------
119 --------------------
112
120
113 Online documentation for the current version of RhodeCode is available at
121 Online documentation for the current version of RhodeCode is available at
114 http://packages.python.org/RhodeCode/.
122 http://packages.python.org/RhodeCode/.
115 You may also build the documentation for yourself - go into ``docs/`` and run::
123 You may also build the documentation for yourself - go into ``docs/`` and run::
116
124
117 make html
125 make html
118
126
119 (You need to have sphinx_ installed to build the documentation. If you don't
127 (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``)
128 have sphinx_ installed you can install it via the command:
129 ``easy_install sphinx``)
121
130
122 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
131 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
123 .. _python: http://www.python.org/
132 .. _python: http://www.python.org/
124 .. _sphinx: http://sphinx.pocoo.org/
133 .. _sphinx: http://sphinx.pocoo.org/
125 .. _mercurial: http://mercurial.selenic.com/
134 .. _mercurial: http://mercurial.selenic.com/
126 .. _bitbucket: http://bitbucket.org/
135 .. _bitbucket: http://bitbucket.org/
136 .. _github: http://github.com/
127 .. _subversion: http://subversion.tigris.org/
137 .. _subversion: http://subversion.tigris.org/
128 .. _git: http://git-scm.com/
138 .. _git: http://git-scm.com/
129 .. _celery: http://celeryproject.org/
139 .. _celery: http://celeryproject.org/
130 .. _Sphinx: http://sphinx.pocoo.org/
140 .. _Sphinx: http://sphinx.pocoo.org/
131 .. _GPL: http://www.gnu.org/licenses/gpl.html
132 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
141 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,249 +1,251 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 Changelog
3 Changelog
4 =========
4 =========
5
5
6
6
7 1.1.7 (**2011-03-23**)
7 1.1.8 (**2011-04-XX**)
8 ======================
8 ======================
9
9
10 news
10 news
11 ----
11 ----
12
12
13 fixes
13 fixes
14 -----
14 -----
15
15
16 - fixed #140 freeze of python dateutil library, since new version is python2.x
16 - fixed #140 freeze of python dateutil library, since new version is python2.x
17 incompatible
17 incompatible
18 - setup-app will check for write permission in given path
19
18
20
19 1.1.7 (**2011-03-23**)
21 1.1.7 (**2011-03-23**)
20 ======================
22 ======================
21
23
22 news
24 news
23 ----
25 ----
24
26
25 fixes
27 fixes
26 -----
28 -----
27
29
28 - fixed (again) #136 installation support for FreeBSD
30 - fixed (again) #136 installation support for FreeBSD
29
31
30
32
31 1.1.6 (**2011-03-21**)
33 1.1.6 (**2011-03-21**)
32 ======================
34 ======================
33
35
34 news
36 news
35 ----
37 ----
36
38
37 fixes
39 fixes
38 -----
40 -----
39
41
40 - fixed #136 installation support for FreeBSD
42 - fixed #136 installation support for FreeBSD
41 - RhodeCode will check for python version during installation
43 - RhodeCode will check for python version during installation
42
44
43 1.1.5 (**2011-03-17**)
45 1.1.5 (**2011-03-17**)
44 ======================
46 ======================
45
47
46 news
48 news
47 ----
49 ----
48
50
49 - basic windows support, by exchanging pybcrypt into sha256 for windows only
51 - basic windows support, by exchanging pybcrypt into sha256 for windows only
50 highly inspired by idea of mantis406
52 highly inspired by idea of mantis406
51
53
52 fixes
54 fixes
53 -----
55 -----
54
56
55 - fixed sorting by author in main page
57 - fixed sorting by author in main page
56 - fixed crashes with diffs on binary files
58 - fixed crashes with diffs on binary files
57 - fixed #131 problem with boolean values for LDAP
59 - fixed #131 problem with boolean values for LDAP
58 - fixed #122 mysql problems thanks to striker69
60 - fixed #122 mysql problems thanks to striker69
59 - fixed problem with errors on calling raw/raw_files/annotate functions
61 - fixed problem with errors on calling raw/raw_files/annotate functions
60 with unknown revisions
62 with unknown revisions
61 - fixed returned rawfiles attachment names with international character
63 - fixed returned rawfiles attachment names with international character
62 - cleaned out docs, big thanks to Jason Harris
64 - cleaned out docs, big thanks to Jason Harris
63
65
64 1.1.4 (**2011-02-19**)
66 1.1.4 (**2011-02-19**)
65 ======================
67 ======================
66
68
67 news
69 news
68 ----
70 ----
69
71
70 fixes
72 fixes
71 -----
73 -----
72
74
73 - fixed formencode import problem on settings page, that caused server crash
75 - fixed formencode import problem on settings page, that caused server crash
74 when that page was accessed as first after server start
76 when that page was accessed as first after server start
75 - journal fixes
77 - journal fixes
76 - fixed option to access repository just by entering http://server/<repo_name>
78 - fixed option to access repository just by entering http://server/<repo_name>
77
79
78
80
79 1.1.3 (**2011-02-16**)
81 1.1.3 (**2011-02-16**)
80 ======================
82 ======================
81
83
82 news
84 news
83 ----
85 ----
84
86
85 - implemented #102 allowing the '.' character in username
87 - implemented #102 allowing the '.' character in username
86 - added option to access repository just by entering http://server/<repo_name>
88 - added option to access repository just by entering http://server/<repo_name>
87 - celery task ignores result for better performance
89 - celery task ignores result for better performance
88
90
89 fixes
91 fixes
90 -----
92 -----
91
93
92 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
94 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
93 apollo13 and Johan Walles
95 apollo13 and Johan Walles
94 - small fixes in journal
96 - small fixes in journal
95 - fixed problems with getting setting for celery from .ini files
97 - fixed problems with getting setting for celery from .ini files
96 - registration, password reset and login boxes share the same title as main
98 - registration, password reset and login boxes share the same title as main
97 application now
99 application now
98 - fixed #113: to high permissions to fork repository
100 - fixed #113: to high permissions to fork repository
99 - fixed problem with '[' chars in commit messages in journal
101 - fixed problem with '[' chars in commit messages in journal
100 - removed issue with space inside renamed repository after deletion
102 - removed issue with space inside renamed repository after deletion
101 - db transaction fixes when filesystem repository creation failed
103 - db transaction fixes when filesystem repository creation failed
102 - fixed #106 relation issues on databases different than sqlite
104 - fixed #106 relation issues on databases different than sqlite
103 - fixed static files paths links to use of url() method
105 - fixed static files paths links to use of url() method
104
106
105 1.1.2 (**2011-01-12**)
107 1.1.2 (**2011-01-12**)
106 ======================
108 ======================
107
109
108 news
110 news
109 ----
111 ----
110
112
111
113
112 fixes
114 fixes
113 -----
115 -----
114
116
115 - fixes #98 protection against float division of percentage stats
117 - fixes #98 protection against float division of percentage stats
116 - fixed graph bug
118 - fixed graph bug
117 - forced webhelpers version since it was making troubles during installation
119 - forced webhelpers version since it was making troubles during installation
118
120
119 1.1.1 (**2011-01-06**)
121 1.1.1 (**2011-01-06**)
120 ======================
122 ======================
121
123
122 news
124 news
123 ----
125 ----
124
126
125 - added force https option into ini files for easier https usage (no need to
127 - added force https option into ini files for easier https usage (no need to
126 set server headers with this options)
128 set server headers with this options)
127 - small css updates
129 - small css updates
128
130
129 fixes
131 fixes
130 -----
132 -----
131
133
132 - fixed #96 redirect loop on files view on repositories without changesets
134 - fixed #96 redirect loop on files view on repositories without changesets
133 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
135 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
134 and server crashed with errors
136 and server crashed with errors
135 - fixed large tooltips problems on main page
137 - fixed large tooltips problems on main page
136 - fixed #92 whoosh indexer is more error proof
138 - fixed #92 whoosh indexer is more error proof
137
139
138 1.1.0 (**2010-12-18**)
140 1.1.0 (**2010-12-18**)
139 ======================
141 ======================
140
142
141 news
143 news
142 ----
144 ----
143
145
144 - rewrite of internals for vcs >=0.1.10
146 - rewrite of internals for vcs >=0.1.10
145 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
147 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
146 with older clients
148 with older clients
147 - anonymous access, authentication via ldap
149 - anonymous access, authentication via ldap
148 - performance upgrade for cached repos list - each repository has it's own
150 - performance upgrade for cached repos list - each repository has it's own
149 cache that's invalidated when needed.
151 cache that's invalidated when needed.
150 - performance upgrades on repositories with large amount of commits (20K+)
152 - performance upgrades on repositories with large amount of commits (20K+)
151 - main page quick filter for filtering repositories
153 - main page quick filter for filtering repositories
152 - user dashboards with ability to follow chosen repositories actions
154 - user dashboards with ability to follow chosen repositories actions
153 - sends email to admin on new user registration
155 - sends email to admin on new user registration
154 - added cache/statistics reset options into repository settings
156 - added cache/statistics reset options into repository settings
155 - more detailed action logger (based on hooks) with pushed changesets lists
157 - more detailed action logger (based on hooks) with pushed changesets lists
156 and options to disable those hooks from admin panel
158 and options to disable those hooks from admin panel
157 - introduced new enhanced changelog for merges that shows more accurate results
159 - introduced new enhanced changelog for merges that shows more accurate results
158 - new improved and faster code stats (based on pygments lexers mapping tables,
160 - new improved and faster code stats (based on pygments lexers mapping tables,
159 showing up to 10 trending sources for each repository. Additionally stats
161 showing up to 10 trending sources for each repository. Additionally stats
160 can be disabled in repository settings.
162 can be disabled in repository settings.
161 - gui optimizations, fixed application width to 1024px
163 - gui optimizations, fixed application width to 1024px
162 - added cut off (for large files/changesets) limit into config files
164 - added cut off (for large files/changesets) limit into config files
163 - whoosh, celeryd, upgrade moved to paster command
165 - whoosh, celeryd, upgrade moved to paster command
164 - other than sqlite database backends can be used
166 - other than sqlite database backends can be used
165
167
166 fixes
168 fixes
167 -----
169 -----
168
170
169 - fixes #61 forked repo was showing only after cache expired
171 - fixes #61 forked repo was showing only after cache expired
170 - fixes #76 no confirmation on user deletes
172 - fixes #76 no confirmation on user deletes
171 - fixes #66 Name field misspelled
173 - fixes #66 Name field misspelled
172 - fixes #72 block user removal when he owns repositories
174 - fixes #72 block user removal when he owns repositories
173 - fixes #69 added password confirmation fields
175 - fixes #69 added password confirmation fields
174 - fixes #87 RhodeCode crashes occasionally on updating repository owner
176 - fixes #87 RhodeCode crashes occasionally on updating repository owner
175 - fixes #82 broken annotations on files with more than 1 blank line at the end
177 - fixes #82 broken annotations on files with more than 1 blank line at the end
176 - a lot of fixes and tweaks for file browser
178 - a lot of fixes and tweaks for file browser
177 - fixed detached session issues
179 - fixed detached session issues
178 - fixed when user had no repos he would see all repos listed in my account
180 - fixed when user had no repos he would see all repos listed in my account
179 - fixed ui() instance bug when global hgrc settings was loaded for server
181 - fixed ui() instance bug when global hgrc settings was loaded for server
180 instance and all hgrc options were merged with our db ui() object
182 instance and all hgrc options were merged with our db ui() object
181 - numerous small bugfixes
183 - numerous small bugfixes
182
184
183 (special thanks for TkSoh for detailed feedback)
185 (special thanks for TkSoh for detailed feedback)
184
186
185
187
186 1.0.2 (**2010-11-12**)
188 1.0.2 (**2010-11-12**)
187 ======================
189 ======================
188
190
189 news
191 news
190 ----
192 ----
191
193
192 - tested under python2.7
194 - tested under python2.7
193 - bumped sqlalchemy and celery versions
195 - bumped sqlalchemy and celery versions
194
196
195 fixes
197 fixes
196 -----
198 -----
197
199
198 - fixed #59 missing graph.js
200 - fixed #59 missing graph.js
199 - fixed repo_size crash when repository had broken symlinks
201 - fixed repo_size crash when repository had broken symlinks
200 - fixed python2.5 crashes.
202 - fixed python2.5 crashes.
201
203
202
204
203 1.0.1 (**2010-11-10**)
205 1.0.1 (**2010-11-10**)
204 ======================
206 ======================
205
207
206 news
208 news
207 ----
209 ----
208
210
209 - small css updated
211 - small css updated
210
212
211 fixes
213 fixes
212 -----
214 -----
213
215
214 - fixed #53 python2.5 incompatible enumerate calls
216 - fixed #53 python2.5 incompatible enumerate calls
215 - fixed #52 disable mercurial extension for web
217 - fixed #52 disable mercurial extension for web
216 - fixed #51 deleting repositories don't delete it's dependent objects
218 - fixed #51 deleting repositories don't delete it's dependent objects
217
219
218
220
219 1.0.0 (**2010-11-02**)
221 1.0.0 (**2010-11-02**)
220 ======================
222 ======================
221
223
222 - security bugfix simplehg wasn't checking for permissions on commands
224 - security bugfix simplehg wasn't checking for permissions on commands
223 other than pull or push.
225 other than pull or push.
224 - fixed doubled messages after push or pull in admin journal
226 - fixed doubled messages after push or pull in admin journal
225 - templating and css corrections, fixed repo switcher on chrome, updated titles
227 - templating and css corrections, fixed repo switcher on chrome, updated titles
226 - admin menu accessible from options menu on repository view
228 - admin menu accessible from options menu on repository view
227 - permissions cached queries
229 - permissions cached queries
228
230
229 1.0.0rc4 (**2010-10-12**)
231 1.0.0rc4 (**2010-10-12**)
230 ==========================
232 ==========================
231
233
232 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
234 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
233 - removed cache_manager settings from sqlalchemy meta
235 - removed cache_manager settings from sqlalchemy meta
234 - added sqlalchemy cache settings to ini files
236 - added sqlalchemy cache settings to ini files
235 - validated password length and added second try of failure on paster setup-app
237 - validated password length and added second try of failure on paster setup-app
236 - fixed setup database destroy prompt even when there was no db
238 - fixed setup database destroy prompt even when there was no db
237
239
238
240
239 1.0.0rc3 (**2010-10-11**)
241 1.0.0rc3 (**2010-10-11**)
240 =========================
242 =========================
241
243
242 - fixed i18n during installation.
244 - fixed i18n during installation.
243
245
244 1.0.0rc2 (**2010-10-11**)
246 1.0.0rc2 (**2010-10-11**)
245 =========================
247 =========================
246
248
247 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
249 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
248 occure. After vcs is fixed it'll be put back again.
250 occure. After vcs is fixed it'll be put back again.
249 - templating/css rewrites, optimized css. No newline at end of file
251 - templating/css rewrites, optimized css.
@@ -1,477 +1,491 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.db_manage
3 rhodecode.lib.db_manage
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database creation, and setup module for RhodeCode. Used for creation
6 Database creation, and setup module for RhodeCode. Used for creation
7 of database as well as for migration operations
7 of database as well as for migration operations
8
8
9 :created_on: Apr 10, 2010
9 :created_on: Apr 10, 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 modify
15 # modify it under the terms of the GNU General Public License
15 # it under the terms of the GNU General Public License as published by
16 # as published by the Free Software Foundation; version 2
16 # the Free Software Foundation, either version 3 of the License, or
17 # of the License or (at your opinion) any later version of the license.
17 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
28
26
29 import os
27 import os
30 import sys
28 import sys
31 import uuid
29 import uuid
32 import logging
30 import logging
33 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
34
32
35 from rhodecode import __dbversion__
33 from rhodecode import __dbversion__
36 from rhodecode.model import meta
34 from rhodecode.model import meta
37
35
38 from rhodecode.lib.auth import get_crypt_password
36 from rhodecode.lib.auth import get_crypt_password
39 from rhodecode.lib.utils import ask_ok
37 from rhodecode.lib.utils import ask_ok
40 from rhodecode.model import init_model
38 from rhodecode.model import init_model
41 from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
42 UserToPerm, DbMigrateVersion
40 RhodeCodeSettings, UserToPerm, DbMigrateVersion
43
41
44 from sqlalchemy.engine import create_engine
42 from sqlalchemy.engine import create_engine
45
43
46 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
47
45
46
48 class DbManage(object):
47 class DbManage(object):
49 def __init__(self, log_sql, dbconf, root, tests=False):
48 def __init__(self, log_sql, dbconf, root, tests=False):
50 self.dbname = dbconf.split('/')[-1]
49 self.dbname = dbconf.split('/')[-1]
51 self.tests = tests
50 self.tests = tests
52 self.root = root
51 self.root = root
53 self.dburi = dbconf
52 self.dburi = dbconf
54 self.log_sql = log_sql
53 self.log_sql = log_sql
55 self.db_exists = False
54 self.db_exists = False
56 self.init_db()
55 self.init_db()
57
56
58 def init_db(self):
57 def init_db(self):
59 engine = create_engine(self.dburi, echo=self.log_sql)
58 engine = create_engine(self.dburi, echo=self.log_sql)
60 init_model(engine)
59 init_model(engine)
61 self.sa = meta.Session()
60 self.sa = meta.Session()
62
61
63 def create_tables(self, override=False):
62 def create_tables(self, override=False):
64 """Create a auth database
63 """Create a auth database
65 """
64 """
66
65
67 log.info("Any existing database is going to be destroyed")
66 log.info("Any existing database is going to be destroyed")
68 if self.tests:
67 if self.tests:
69 destroy = True
68 destroy = True
70 else:
69 else:
71 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
70 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
72 if not destroy:
71 if not destroy:
73 sys.exit()
72 sys.exit()
74 if destroy:
73 if destroy:
75 meta.Base.metadata.drop_all()
74 meta.Base.metadata.drop_all()
76
75
77 checkfirst = not override
76 checkfirst = not override
78 meta.Base.metadata.create_all(checkfirst=checkfirst)
77 meta.Base.metadata.create_all(checkfirst=checkfirst)
79 log.info('Created tables for %s', self.dbname)
78 log.info('Created tables for %s', self.dbname)
80
79
81
82
83 def set_db_version(self):
80 def set_db_version(self):
84 try:
81 try:
85 ver = DbMigrateVersion()
82 ver = DbMigrateVersion()
86 ver.version = __dbversion__
83 ver.version = __dbversion__
87 ver.repository_id = 'rhodecode_db_migrations'
84 ver.repository_id = 'rhodecode_db_migrations'
88 ver.repository_path = 'versions'
85 ver.repository_path = 'versions'
89 self.sa.add(ver)
86 self.sa.add(ver)
90 self.sa.commit()
87 self.sa.commit()
91 except:
88 except:
92 self.sa.rollback()
89 self.sa.rollback()
93 raise
90 raise
94 log.info('db version set to: %s', __dbversion__)
91 log.info('db version set to: %s', __dbversion__)
95
92
96
97 def upgrade(self):
93 def upgrade(self):
98 """Upgrades given database schema to given revision following
94 """Upgrades given database schema to given revision following
99 all needed steps, to perform the upgrade
95 all needed steps, to perform the upgrade
100
96
101 :param revision: revision to upgrade to
102 """
97 """
103
98
104 from rhodecode.lib.dbmigrate.migrate.versioning import api
99 from rhodecode.lib.dbmigrate.migrate.versioning import api
105 from rhodecode.lib.dbmigrate.migrate.exceptions import \
100 from rhodecode.lib.dbmigrate.migrate.exceptions import \
106 DatabaseNotControlledError
101 DatabaseNotControlledError
107
102
108 upgrade = ask_ok('You are about to perform database upgrade, make '
103 upgrade = ask_ok('You are about to perform database upgrade, make '
109 'sure You backed up your database before. '
104 'sure You backed up your database before. '
110 'Continue ? [y/n]')
105 'Continue ? [y/n]')
111 if not upgrade:
106 if not upgrade:
112 sys.exit('Nothing done')
107 sys.exit('Nothing done')
113
108
114 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
109 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
115 'rhodecode/lib/dbmigrate')
110 'rhodecode/lib/dbmigrate')
116 db_uri = self.dburi
111 db_uri = self.dburi
117
112
118 try:
113 try:
119 curr_version = api.db_version(db_uri, repository_path)
114 curr_version = api.db_version(db_uri, repository_path)
120 msg = ('Found current database under version'
115 msg = ('Found current database under version'
121 ' control with version %s' % curr_version)
116 ' control with version %s' % curr_version)
122
117
123 except (RuntimeError, DatabaseNotControlledError), e:
118 except (RuntimeError, DatabaseNotControlledError), e:
124 curr_version = 1
119 curr_version = 1
125 msg = ('Current database is not under version control. Setting'
120 msg = ('Current database is not under version control. Setting'
126 ' as version %s' % curr_version)
121 ' as version %s' % curr_version)
127 api.version_control(db_uri, repository_path, curr_version)
122 api.version_control(db_uri, repository_path, curr_version)
128
123
129 print (msg)
124 print (msg)
130
125
131 if curr_version == __dbversion__:
126 if curr_version == __dbversion__:
132 sys.exit('This database is already at the newest version')
127 sys.exit('This database is already at the newest version')
133
128
134 #======================================================================
129 #======================================================================
135 # UPGRADE STEPS
130 # UPGRADE STEPS
136 #======================================================================
131 #======================================================================
137 class UpgradeSteps(object):
132 class UpgradeSteps(object):
138 """Those steps follow schema versions so for example schema
133 """Those steps follow schema versions so for example schema
139 for example schema with seq 002 == step_2 and so on.
134 for example schema with seq 002 == step_2 and so on.
140 """
135 """
141
136
142 def __init__(self, klass):
137 def __init__(self, klass):
143 self.klass = klass
138 self.klass = klass
144
139
145 def step_0(self):
140 def step_0(self):
146 #step 0 is the schema upgrade, and than follow proper upgrades
141 #step 0 is the schema upgrade, and than follow proper upgrades
147 print ('attempting to do database upgrade to version %s' \
142 print ('attempting to do database upgrade to version %s' \
148 % __dbversion__)
143 % __dbversion__)
149 api.upgrade(db_uri, repository_path, __dbversion__)
144 api.upgrade(db_uri, repository_path, __dbversion__)
150 print ('Schema upgrade completed')
145 print ('Schema upgrade completed')
151
146
152 def step_1(self):
147 def step_1(self):
153 pass
148 pass
154
149
155 def step_2(self):
150 def step_2(self):
156 print ('Patching repo paths for newer version of RhodeCode')
151 print ('Patching repo paths for newer version of RhodeCode')
157 self.klass.fix_repo_paths()
152 self.klass.fix_repo_paths()
158
153
159 print ('Patching default user of RhodeCode')
154 print ('Patching default user of RhodeCode')
160 self.klass.fix_default_user()
155 self.klass.fix_default_user()
161
156
162 log.info('Changing ui settings')
157 log.info('Changing ui settings')
163 self.klass.create_ui_settings()
158 self.klass.create_ui_settings()
164
159
165
166 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
160 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
167
161
168 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
162 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
169 for step in upgrade_steps:
163 for step in upgrade_steps:
170 print ('performing upgrade step %s' % step)
164 print ('performing upgrade step %s' % step)
171 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
165 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
172
166
173
174
175 def fix_repo_paths(self):
167 def fix_repo_paths(self):
176 """Fixes a old rhodecode version path into new one without a '*'
168 """Fixes a old rhodecode version path into new one without a '*'
177 """
169 """
178
170
179 paths = self.sa.query(RhodeCodeUi)\
171 paths = self.sa.query(RhodeCodeUi)\
180 .filter(RhodeCodeUi.ui_key == '/')\
172 .filter(RhodeCodeUi.ui_key == '/')\
181 .scalar()
173 .scalar()
182
174
183 paths.ui_value = paths.ui_value.replace('*', '')
175 paths.ui_value = paths.ui_value.replace('*', '')
184
176
185 try:
177 try:
186 self.sa.add(paths)
178 self.sa.add(paths)
187 self.sa.commit()
179 self.sa.commit()
188 except:
180 except:
189 self.sa.rollback()
181 self.sa.rollback()
190 raise
182 raise
191
183
192 def fix_default_user(self):
184 def fix_default_user(self):
193 """Fixes a old default user with some 'nicer' default values,
185 """Fixes a old default user with some 'nicer' default values,
194 used mostly for anonymous access
186 used mostly for anonymous access
195 """
187 """
196 def_user = self.sa.query(User)\
188 def_user = self.sa.query(User)\
197 .filter(User.username == 'default')\
189 .filter(User.username == 'default')\
198 .one()
190 .one()
199
191
200 def_user.name = 'Anonymous'
192 def_user.name = 'Anonymous'
201 def_user.lastname = 'User'
193 def_user.lastname = 'User'
202 def_user.email = 'anonymous@rhodecode.org'
194 def_user.email = 'anonymous@rhodecode.org'
203
195
204 try:
196 try:
205 self.sa.add(def_user)
197 self.sa.add(def_user)
206 self.sa.commit()
198 self.sa.commit()
207 except:
199 except:
208 self.sa.rollback()
200 self.sa.rollback()
209 raise
201 raise
210
202
211
212
213 def admin_prompt(self, second=False):
203 def admin_prompt(self, second=False):
214 if not self.tests:
204 if not self.tests:
215 import getpass
205 import getpass
216
206
217
218 def get_password():
207 def get_password():
219 password = getpass.getpass('Specify admin password (min 6 chars):')
208 password = getpass.getpass('Specify admin password '
209 '(min 6 chars):')
220 confirm = getpass.getpass('Confirm password:')
210 confirm = getpass.getpass('Confirm password:')
221
211
222 if password != confirm:
212 if password != confirm:
223 log.error('passwords mismatch')
213 log.error('passwords mismatch')
224 return False
214 return False
225 if len(password) < 6:
215 if len(password) < 6:
226 log.error('password is to short use at least 6 characters')
216 log.error('password is to short use at least 6 characters')
227 return False
217 return False
228
218
229 return password
219 return password
230
220
231 username = raw_input('Specify admin username:')
221 username = raw_input('Specify admin username:')
232
222
233 password = get_password()
223 password = get_password()
234 if not password:
224 if not password:
235 #second try
225 #second try
236 password = get_password()
226 password = get_password()
237 if not password:
227 if not password:
238 sys.exit()
228 sys.exit()
239
229
240 email = raw_input('Specify admin email:')
230 email = raw_input('Specify admin email:')
241 self.create_user(username, password, email, True)
231 self.create_user(username, password, email, True)
242 else:
232 else:
243 log.info('creating admin and regular test users')
233 log.info('creating admin and regular test users')
244 self.create_user('test_admin', 'test12', 'test_admin@mail.com', True)
234 self.create_user('test_admin', 'test12',
245 self.create_user('test_regular', 'test12', 'test_regular@mail.com', False)
235 'test_admin@mail.com', True)
246 self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False)
236 self.create_user('test_regular', 'test12',
237 'test_regular@mail.com', False)
238 self.create_user('test_regular2', 'test12',
239 'test_regular2@mail.com', False)
247
240
248 def create_ui_settings(self):
241 def create_ui_settings(self):
249 """Creates ui settings, fills out hooks
242 """Creates ui settings, fills out hooks
250 and disables dotencode
243 and disables dotencode
251
244
252 """
245 """
253 #HOOKS
246 #HOOKS
254 hooks1_key = 'changegroup.update'
247 hooks1_key = 'changegroup.update'
255 hooks1_ = self.sa.query(RhodeCodeUi)\
248 hooks1_ = self.sa.query(RhodeCodeUi)\
256 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
249 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
257
250
258 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
251 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
259 hooks1.ui_section = 'hooks'
252 hooks1.ui_section = 'hooks'
260 hooks1.ui_key = hooks1_key
253 hooks1.ui_key = hooks1_key
261 hooks1.ui_value = 'hg update >&2'
254 hooks1.ui_value = 'hg update >&2'
262 hooks1.ui_active = False
255 hooks1.ui_active = False
263
256
264 hooks2_key = 'changegroup.repo_size'
257 hooks2_key = 'changegroup.repo_size'
265 hooks2_ = self.sa.query(RhodeCodeUi)\
258 hooks2_ = self.sa.query(RhodeCodeUi)\
266 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
259 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
267
260
268 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
261 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
269 hooks2.ui_section = 'hooks'
262 hooks2.ui_section = 'hooks'
270 hooks2.ui_key = hooks2_key
263 hooks2.ui_key = hooks2_key
271 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
264 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
272
265
273 hooks3 = RhodeCodeUi()
266 hooks3 = RhodeCodeUi()
274 hooks3.ui_section = 'hooks'
267 hooks3.ui_section = 'hooks'
275 hooks3.ui_key = 'pretxnchangegroup.push_logger'
268 hooks3.ui_key = 'pretxnchangegroup.push_logger'
276 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
269 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
277
270
278 hooks4 = RhodeCodeUi()
271 hooks4 = RhodeCodeUi()
279 hooks4.ui_section = 'hooks'
272 hooks4.ui_section = 'hooks'
280 hooks4.ui_key = 'preoutgoing.pull_logger'
273 hooks4.ui_key = 'preoutgoing.pull_logger'
281 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
274 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
282
275
283 #For mercurial 1.7 set backward comapatibility with format
276 #For mercurial 1.7 set backward comapatibility with format
284 dotencode_disable = RhodeCodeUi()
277 dotencode_disable = RhodeCodeUi()
285 dotencode_disable.ui_section = 'format'
278 dotencode_disable.ui_section = 'format'
286 dotencode_disable.ui_key = 'dotencode'
279 dotencode_disable.ui_key = 'dotencode'
287 dotencode_disable.ui_value = 'false'
280 dotencode_disable.ui_value = 'false'
288
281
289 try:
282 try:
290 self.sa.add(hooks1)
283 self.sa.add(hooks1)
291 self.sa.add(hooks2)
284 self.sa.add(hooks2)
292 self.sa.add(hooks3)
285 self.sa.add(hooks3)
293 self.sa.add(hooks4)
286 self.sa.add(hooks4)
294 self.sa.add(dotencode_disable)
287 self.sa.add(dotencode_disable)
295 self.sa.commit()
288 self.sa.commit()
296 except:
289 except:
297 self.sa.rollback()
290 self.sa.rollback()
298 raise
291 raise
299
292
300
301 def create_ldap_options(self):
293 def create_ldap_options(self):
302 """Creates ldap settings"""
294 """Creates ldap settings"""
303
295
304 try:
296 try:
305 for k, v in [('ldap_active', 'false'),
297 for k, v in [('ldap_active', 'false'),
306 ('ldap_host', ''),
298 ('ldap_host', ''),
307 ('ldap_port', '389'),
299 ('ldap_port', '389'),
308 ('ldap_ldaps', 'false'),
300 ('ldap_ldaps', 'false'),
309 ('ldap_dn_user', ''), ('ldap_dn_pass', ''),
301 ('ldap_dn_user', ''), ('ldap_dn_pass', ''),
310 ('ldap_base_dn', '')]:
302 ('ldap_base_dn', '')]:
311
303
312 setting = RhodeCodeSettings(k, v)
304 setting = RhodeCodeSettings(k, v)
313 self.sa.add(setting)
305 self.sa.add(setting)
314 self.sa.commit()
306 self.sa.commit()
315 except:
307 except:
316 self.sa.rollback()
308 self.sa.rollback()
317 raise
309 raise
318
310
319 def config_prompt(self, test_repo_path=''):
311 def config_prompt(self, test_repo_path='', retries=3):
312 if retries == 3:
320 log.info('Setting up repositories config')
313 log.info('Setting up repositories config')
321
314
322 if not self.tests and not test_repo_path:
315 if not self.tests and not test_repo_path:
323 path = raw_input('Specify valid full path to your repositories'
316 path = raw_input('Specify valid full path to your repositories'
324 ' you can change this later in application settings:')
317 ' you can change this later in application settings:')
325 else:
318 else:
326 path = test_repo_path
319 path = test_repo_path
320 path_ok = True
327
321
322 #check proper dir
328 if not os.path.isdir(path):
323 if not os.path.isdir(path):
329 log.error('You entered wrong path: %s', path)
324 path_ok = False
325 log.error('Entered path is not a valid directory: %s [%s/3]',
326 path, retries)
327
328 #check write access
329 if not os.access(path, os.W_OK):
330 path_ok = False
331
332 log.error('No write permission to given path: %s [%s/3]',
333 path, retries)
334
335 if retries == 0:
330 sys.exit()
336 sys.exit()
337 if path_ok is False:
338 retries -= 1
339 return self.config_prompt(test_repo_path, retries)
340
341 return path
342
343 def create_settings(self, path):
331
344
332 self.create_ui_settings()
345 self.create_ui_settings()
333
346
334 #HG UI OPTIONS
347 #HG UI OPTIONS
335 web1 = RhodeCodeUi()
348 web1 = RhodeCodeUi()
336 web1.ui_section = 'web'
349 web1.ui_section = 'web'
337 web1.ui_key = 'push_ssl'
350 web1.ui_key = 'push_ssl'
338 web1.ui_value = 'false'
351 web1.ui_value = 'false'
339
352
340 web2 = RhodeCodeUi()
353 web2 = RhodeCodeUi()
341 web2.ui_section = 'web'
354 web2.ui_section = 'web'
342 web2.ui_key = 'allow_archive'
355 web2.ui_key = 'allow_archive'
343 web2.ui_value = 'gz zip bz2'
356 web2.ui_value = 'gz zip bz2'
344
357
345 web3 = RhodeCodeUi()
358 web3 = RhodeCodeUi()
346 web3.ui_section = 'web'
359 web3.ui_section = 'web'
347 web3.ui_key = 'allow_push'
360 web3.ui_key = 'allow_push'
348 web3.ui_value = '*'
361 web3.ui_value = '*'
349
362
350 web4 = RhodeCodeUi()
363 web4 = RhodeCodeUi()
351 web4.ui_section = 'web'
364 web4.ui_section = 'web'
352 web4.ui_key = 'baseurl'
365 web4.ui_key = 'baseurl'
353 web4.ui_value = '/'
366 web4.ui_value = '/'
354
367
355 paths = RhodeCodeUi()
368 paths = RhodeCodeUi()
356 paths.ui_section = 'paths'
369 paths.ui_section = 'paths'
357 paths.ui_key = '/'
370 paths.ui_key = '/'
358 paths.ui_value = path
371 paths.ui_value = path
359
372
360
361 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
373 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
362 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
374 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
363
375
364
365 try:
376 try:
366 self.sa.add(web1)
377 self.sa.add(web1)
367 self.sa.add(web2)
378 self.sa.add(web2)
368 self.sa.add(web3)
379 self.sa.add(web3)
369 self.sa.add(web4)
380 self.sa.add(web4)
370 self.sa.add(paths)
381 self.sa.add(paths)
371 self.sa.add(hgsettings1)
382 self.sa.add(hgsettings1)
372 self.sa.add(hgsettings2)
383 self.sa.add(hgsettings2)
373
384
374 self.sa.commit()
385 self.sa.commit()
375 except:
386 except:
376 self.sa.rollback()
387 self.sa.rollback()
377 raise
388 raise
378
389
379 self.create_ldap_options()
390 self.create_ldap_options()
380
391
381 log.info('created ui config')
392 log.info('created ui config')
382
393
383 def create_user(self, username, password, email='', admin=False):
394 def create_user(self, username, password, email='', admin=False):
384 log.info('creating administrator user %s', username)
395 log.info('creating administrator user %s', username)
385 new_user = User()
396 new_user = User()
386 new_user.username = username
397 new_user.username = username
387 new_user.password = get_crypt_password(password)
398 new_user.password = get_crypt_password(password)
388 new_user.name = 'RhodeCode'
399 new_user.name = 'RhodeCode'
389 new_user.lastname = 'Admin'
400 new_user.lastname = 'Admin'
390 new_user.email = email
401 new_user.email = email
391 new_user.admin = admin
402 new_user.admin = admin
392 new_user.active = True
403 new_user.active = True
393
404
394 try:
405 try:
395 self.sa.add(new_user)
406 self.sa.add(new_user)
396 self.sa.commit()
407 self.sa.commit()
397 except:
408 except:
398 self.sa.rollback()
409 self.sa.rollback()
399 raise
410 raise
400
411
401 def create_default_user(self):
412 def create_default_user(self):
402 log.info('creating default user')
413 log.info('creating default user')
403 #create default user for handling default permissions.
414 #create default user for handling default permissions.
404 def_user = User()
415 def_user = User()
405 def_user.username = 'default'
416 def_user.username = 'default'
406 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
417 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
407 def_user.name = 'Anonymous'
418 def_user.name = 'Anonymous'
408 def_user.lastname = 'User'
419 def_user.lastname = 'User'
409 def_user.email = 'anonymous@rhodecode.org'
420 def_user.email = 'anonymous@rhodecode.org'
410 def_user.admin = False
421 def_user.admin = False
411 def_user.active = False
422 def_user.active = False
412 try:
423 try:
413 self.sa.add(def_user)
424 self.sa.add(def_user)
414 self.sa.commit()
425 self.sa.commit()
415 except:
426 except:
416 self.sa.rollback()
427 self.sa.rollback()
417 raise
428 raise
418
429
419 def create_permissions(self):
430 def create_permissions(self):
420 #module.(access|create|change|delete)_[name]
431 #module.(access|create|change|delete)_[name]
421 #module.(read|write|owner)
432 #module.(read|write|owner)
422 perms = [('repository.none', 'Repository no access'),
433 perms = [('repository.none', 'Repository no access'),
423 ('repository.read', 'Repository read access'),
434 ('repository.read', 'Repository read access'),
424 ('repository.write', 'Repository write access'),
435 ('repository.write', 'Repository write access'),
425 ('repository.admin', 'Repository admin access'),
436 ('repository.admin', 'Repository admin access'),
426 ('hg.admin', 'Hg Administrator'),
437 ('hg.admin', 'Hg Administrator'),
427 ('hg.create.repository', 'Repository create'),
438 ('hg.create.repository', 'Repository create'),
428 ('hg.create.none', 'Repository creation disabled'),
439 ('hg.create.none', 'Repository creation disabled'),
429 ('hg.register.none', 'Register disabled'),
440 ('hg.register.none', 'Register disabled'),
430 ('hg.register.manual_activate', 'Register new user with rhodecode without manual activation'),
441 ('hg.register.manual_activate', ('Register new user with '
431 ('hg.register.auto_activate', 'Register new user with rhodecode without auto activation'),
442 'RhodeCode without '
443 'manual activation')),
444 ('hg.register.auto_activate', ('Register new user with '
445 'RhodeCode without auto '
446 'activation')),
432 ]
447 ]
433
448
434 for p in perms:
449 for p in perms:
435 new_perm = Permission()
450 new_perm = Permission()
436 new_perm.permission_name = p[0]
451 new_perm.permission_name = p[0]
437 new_perm.permission_longname = p[1]
452 new_perm.permission_longname = p[1]
438 try:
453 try:
439 self.sa.add(new_perm)
454 self.sa.add(new_perm)
440 self.sa.commit()
455 self.sa.commit()
441 except:
456 except:
442 self.sa.rollback()
457 self.sa.rollback()
443 raise
458 raise
444
459
445 def populate_default_permissions(self):
460 def populate_default_permissions(self):
446 log.info('creating default user permissions')
461 log.info('creating default user permissions')
447
462
448 default_user = self.sa.query(User)\
463 default_user = self.sa.query(User)\
449 .filter(User.username == 'default').scalar()
464 .filter(User.username == 'default').scalar()
450
465
451 reg_perm = UserToPerm()
466 reg_perm = UserToPerm()
452 reg_perm.user = default_user
467 reg_perm.user = default_user
453 reg_perm.permission = self.sa.query(Permission)\
468 reg_perm.permission = self.sa.query(Permission)\
454 .filter(Permission.permission_name == 'hg.register.manual_activate')\
469 .filter(Permission.permission_name == 'hg.register.manual_activate')\
455 .scalar()
470 .scalar()
456
471
457 create_repo_perm = UserToPerm()
472 create_repo_perm = UserToPerm()
458 create_repo_perm.user = default_user
473 create_repo_perm.user = default_user
459 create_repo_perm.permission = self.sa.query(Permission)\
474 create_repo_perm.permission = self.sa.query(Permission)\
460 .filter(Permission.permission_name == 'hg.create.repository')\
475 .filter(Permission.permission_name == 'hg.create.repository')\
461 .scalar()
476 .scalar()
462
477
463 default_repo_perm = UserToPerm()
478 default_repo_perm = UserToPerm()
464 default_repo_perm.user = default_user
479 default_repo_perm.user = default_user
465 default_repo_perm.permission = self.sa.query(Permission)\
480 default_repo_perm.permission = self.sa.query(Permission)\
466 .filter(Permission.permission_name == 'repository.read')\
481 .filter(Permission.permission_name == 'repository.read')\
467 .scalar()
482 .scalar()
468
483
469 try:
484 try:
470 self.sa.add(reg_perm)
485 self.sa.add(reg_perm)
471 self.sa.add(create_repo_perm)
486 self.sa.add(create_repo_perm)
472 self.sa.add(default_repo_perm)
487 self.sa.add(default_repo_perm)
473 self.sa.commit()
488 self.sa.commit()
474 except:
489 except:
475 self.sa.rollback()
490 self.sa.rollback()
476 raise
491 raise
477
@@ -1,378 +1,380 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software: you can redistribute it and/or modify
14 # modify it under the terms of the GNU General Public License
14 # it under the terms of the GNU General Public License as published by
15 # as published by the Free Software Foundation; version 2
15 # the Free Software Foundation, either version 3 of the License, or
16 # of the License or (at your opinion) any later version of the license.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
27 import os
25 import os
28 import time
26 import time
29 import traceback
27 import traceback
30 import logging
28 import logging
31
29
32 from vcs import get_backend
30 from vcs import get_backend
33 from vcs.utils.helpers import get_scm
31 from vcs.utils.helpers import get_scm
34 from vcs.exceptions import RepositoryError, VCSError
32 from vcs.exceptions import RepositoryError, VCSError
35 from vcs.utils.lazy import LazyProperty
33 from vcs.utils.lazy import LazyProperty
36
34
37 from mercurial import ui
35 from mercurial import ui
38
36
39 from beaker.cache import cache_region, region_invalidate
37 from beaker.cache import cache_region, region_invalidate
40
38
41 from rhodecode import BACKENDS
39 from rhodecode import BACKENDS
42 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
43 from rhodecode.lib.auth import HasRepoPermissionAny
41 from rhodecode.lib.auth import HasRepoPermissionAny
44 from rhodecode.lib.utils import get_repos, make_ui, action_logger
42 from rhodecode.lib.utils import get_repos, make_ui, action_logger
45 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
46 from rhodecode.model.user import UserModel
44 from rhodecode.model.user import UserModel
47
45
48 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
46 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
49 UserFollowing, UserLog
47 UserFollowing, UserLog
50 from rhodecode.model.caching_query import FromCache
48 from rhodecode.model.caching_query import FromCache
51
49
52 from sqlalchemy.orm import joinedload
50 from sqlalchemy.orm import joinedload
53 from sqlalchemy.orm.session import make_transient
51 from sqlalchemy.orm.session import make_transient
54 from sqlalchemy.exc import DatabaseError
52 from sqlalchemy.exc import DatabaseError
55
53
56 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
57
55
58
56
59 class UserTemp(object):
57 class UserTemp(object):
60 def __init__(self, user_id):
58 def __init__(self, user_id):
61 self.user_id = user_id
59 self.user_id = user_id
60
61 def __repr__(self):
62 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63
64
62 class RepoTemp(object):
65 class RepoTemp(object):
63 def __init__(self, repo_id):
66 def __init__(self, repo_id):
64 self.repo_id = repo_id
67 self.repo_id = repo_id
65
68
69 def __repr__(self):
70 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71
72
66 class ScmModel(BaseModel):
73 class ScmModel(BaseModel):
67 """Generic Scm Model
74 """Generic Scm Model
68 """
75 """
69
76
70 @LazyProperty
77 @LazyProperty
71 def repos_path(self):
78 def repos_path(self):
72 """Get's the repositories root path from database
79 """Get's the repositories root path from database
73 """
80 """
74
81
75 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
82 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
76
83
77 return q.ui_value
84 return q.ui_value
78
85
79 def repo_scan(self, repos_path, baseui):
86 def repo_scan(self, repos_path, baseui):
80 """Listing of repositories in given path. This path should not be a
87 """Listing of repositories in given path. This path should not be a
81 repository itself. Return a dictionary of repository objects
88 repository itself. Return a dictionary of repository objects
82
89
83 :param repos_path: path to directory containing repositories
90 :param repos_path: path to directory containing repositories
84 :param baseui: baseui instance to instantiate MercurialRepostitory with
91 :param baseui: baseui instance to instantiate MercurialRepostitory with
85 """
92 """
86
93
87 log.info('scanning for repositories in %s', repos_path)
94 log.info('scanning for repositories in %s', repos_path)
88
95
89 if not isinstance(baseui, ui.ui):
96 if not isinstance(baseui, ui.ui):
90 baseui = make_ui('db')
97 baseui = make_ui('db')
91 repos_list = {}
98 repos_list = {}
92
99
93 for name, path in get_repos(repos_path):
100 for name, path in get_repos(repos_path):
94 try:
101 try:
95 if repos_list.has_key(name):
102 if name in repos_list:
96 raise RepositoryError('Duplicate repository name %s '
103 raise RepositoryError('Duplicate repository name %s '
97 'found in %s' % (name, path))
104 'found in %s' % (name, path))
98 else:
105 else:
99
106
100 klass = get_backend(path[0])
107 klass = get_backend(path[0])
101
108
102 if path[0] == 'hg' and path[0] in BACKENDS.keys():
109 if path[0] == 'hg' and path[0] in BACKENDS.keys():
103 repos_list[name] = klass(path[1], baseui=baseui)
110 repos_list[name] = klass(path[1], baseui=baseui)
104
111
105 if path[0] == 'git' and path[0] in BACKENDS.keys():
112 if path[0] == 'git' and path[0] in BACKENDS.keys():
106 repos_list[name] = klass(path[1])
113 repos_list[name] = klass(path[1])
107 except OSError:
114 except OSError:
108 continue
115 continue
109
116
110 return repos_list
117 return repos_list
111
118
112 def get_repos(self, all_repos=None):
119 def get_repos(self, all_repos=None):
113 """Get all repos from db and for each repo create it's backend instance.
120 """Get all repos from db and for each repo create it's
114 and fill that backed with information from database
121 backend instance.and fill that backed with information from database
115
122
116 :param all_repos: give specific repositories list, good for filtering
123 :param all_repos: give specific repositories list, good for filtering
117 """
124 """
118
125
119 if all_repos is None:
126 if all_repos is None:
120 all_repos = self.sa.query(Repository)\
127 all_repos = self.sa.query(Repository)\
121 .order_by(Repository.repo_name).all()
128 .order_by(Repository.repo_name).all()
122
129
123 #get the repositories that should be invalidated
130 #get the repositories that should be invalidated
124 invalidation_list = [str(x.cache_key) for x in \
131 invalidation_list = [str(x.cache_key) for x in \
125 self.sa.query(CacheInvalidation.cache_key)\
132 self.sa.query(CacheInvalidation.cache_key)\
126 .filter(CacheInvalidation.cache_active == False)\
133 .filter(CacheInvalidation.cache_active == False)\
127 .all()]
134 .all()]
128
135
129 for r in all_repos:
136 for r in all_repos:
130
137
131 repo = self.get(r.repo_name, invalidation_list)
138 repo = self.get(r.repo_name, invalidation_list)
132
139
133 if repo is not None:
140 if repo is not None:
134 last_change = repo.last_change
141 last_change = repo.last_change
135 tip = h.get_changeset_safe(repo, 'tip')
142 tip = h.get_changeset_safe(repo, 'tip')
136
143
137 tmp_d = {}
144 tmp_d = {}
138 tmp_d['name'] = repo.name
145 tmp_d['name'] = repo.name
139 tmp_d['name_sort'] = tmp_d['name'].lower()
146 tmp_d['name_sort'] = tmp_d['name'].lower()
140 tmp_d['description'] = repo.dbrepo.description
147 tmp_d['description'] = repo.dbrepo.description
141 tmp_d['description_sort'] = tmp_d['description']
148 tmp_d['description_sort'] = tmp_d['description']
142 tmp_d['last_change'] = last_change
149 tmp_d['last_change'] = last_change
143 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
150 tmp_d['last_change_sort'] = time.mktime(last_change \
151 .timetuple())
144 tmp_d['tip'] = tip.raw_id
152 tmp_d['tip'] = tip.raw_id
145 tmp_d['tip_sort'] = tip.revision
153 tmp_d['tip_sort'] = tip.revision
146 tmp_d['rev'] = tip.revision
154 tmp_d['rev'] = tip.revision
147 tmp_d['contact'] = repo.dbrepo.user.full_contact
155 tmp_d['contact'] = repo.dbrepo.user.full_contact
148 tmp_d['contact_sort'] = tmp_d['contact']
156 tmp_d['contact_sort'] = tmp_d['contact']
149 tmp_d['owner_sort'] = tmp_d['contact']
157 tmp_d['owner_sort'] = tmp_d['contact']
150 tmp_d['repo_archives'] = list(repo._get_archives())
158 tmp_d['repo_archives'] = list(repo._get_archives())
151 tmp_d['last_msg'] = tip.message
159 tmp_d['last_msg'] = tip.message
152 tmp_d['repo'] = repo
160 tmp_d['repo'] = repo
153 yield tmp_d
161 yield tmp_d
154
162
155 def get_repo(self, repo_name):
163 def get_repo(self, repo_name):
156 return self.get(repo_name)
164 return self.get(repo_name)
157
165
158 def get(self, repo_name, invalidation_list=None):
166 def get(self, repo_name, invalidation_list=None):
159 """Get's repository from given name, creates BackendInstance and
167 """Get's repository from given name, creates BackendInstance and
160 propagates it's data from database with all additional information
168 propagates it's data from database with all additional information
161
169
162 :param repo_name:
170 :param repo_name:
163 :param invalidation_list: if a invalidation list is given the get
171 :param invalidation_list: if a invalidation list is given the get
164 method should not manually check if this repository needs
172 method should not manually check if this repository needs
165 invalidation and just invalidate the repositories in list
173 invalidation and just invalidate the repositories in list
166
174
167 """
175 """
168 if not HasRepoPermissionAny('repository.read', 'repository.write',
176 if not HasRepoPermissionAny('repository.read', 'repository.write',
169 'repository.admin')(repo_name, 'get repo check'):
177 'repository.admin')(repo_name, 'get repo check'):
170 return
178 return
171
179
172 #======================================================================
180 #======================================================================
173 # CACHE FUNCTION
181 # CACHE FUNCTION
174 #======================================================================
182 #======================================================================
175 @cache_region('long_term')
183 @cache_region('long_term')
176 def _get_repo(repo_name):
184 def _get_repo(repo_name):
177
185
178 repo_path = os.path.join(self.repos_path, repo_name)
186 repo_path = os.path.join(self.repos_path, repo_name)
179
187
180 try:
188 try:
181 alias = get_scm(repo_path)[0]
189 alias = get_scm(repo_path)[0]
182
190
183 log.debug('Creating instance of %s repository', alias)
191 log.debug('Creating instance of %s repository', alias)
184 backend = get_backend(alias)
192 backend = get_backend(alias)
185 except VCSError:
193 except VCSError:
186 log.error(traceback.format_exc())
194 log.error(traceback.format_exc())
187 return
195 return
188
196
189 if alias == 'hg':
197 if alias == 'hg':
190 from pylons import app_globals as g
198 from pylons import app_globals as g
191 repo = backend(repo_path, create=False, baseui=g.baseui)
199 repo = backend(repo_path, create=False, baseui=g.baseui)
192 #skip hidden web repository
200 #skip hidden web repository
193 if repo._get_hidden():
201 if repo._get_hidden():
194 return
202 return
195 else:
203 else:
196 repo = backend(repo_path, create=False)
204 repo = backend(repo_path, create=False)
197
205
198 dbrepo = self.sa.query(Repository)\
206 dbrepo = self.sa.query(Repository)\
199 .options(joinedload(Repository.fork))\
207 .options(joinedload(Repository.fork))\
200 .options(joinedload(Repository.user))\
208 .options(joinedload(Repository.user))\
201 .filter(Repository.repo_name == repo_name)\
209 .filter(Repository.repo_name == repo_name)\
202 .scalar()
210 .scalar()
203
211
204 make_transient(dbrepo)
212 make_transient(dbrepo)
205 if dbrepo.user:
213 if dbrepo.user:
206 make_transient(dbrepo.user)
214 make_transient(dbrepo.user)
207 if dbrepo.fork:
215 if dbrepo.fork:
208 make_transient(dbrepo.fork)
216 make_transient(dbrepo.fork)
209
217
210 repo.dbrepo = dbrepo
218 repo.dbrepo = dbrepo
211 return repo
219 return repo
212
220
213 pre_invalidate = True
221 pre_invalidate = True
214 if invalidation_list is not None:
222 if invalidation_list is not None:
215 pre_invalidate = repo_name in invalidation_list
223 pre_invalidate = repo_name in invalidation_list
216
224
217 if pre_invalidate:
225 if pre_invalidate:
218 invalidate = self._should_invalidate(repo_name)
226 invalidate = self._should_invalidate(repo_name)
219
227
220 if invalidate:
228 if invalidate:
221 log.info('invalidating cache for repository %s', repo_name)
229 log.info('invalidating cache for repository %s', repo_name)
222 region_invalidate(_get_repo, None, repo_name)
230 region_invalidate(_get_repo, None, repo_name)
223 self._mark_invalidated(invalidate)
231 self._mark_invalidated(invalidate)
224
232
225 return _get_repo(repo_name)
233 return _get_repo(repo_name)
226
234
227
228
229 def mark_for_invalidation(self, repo_name):
235 def mark_for_invalidation(self, repo_name):
230 """Puts cache invalidation task into db for
236 """Puts cache invalidation task into db for
231 further global cache invalidation
237 further global cache invalidation
232
238
233 :param repo_name: this repo that should invalidation take place
239 :param repo_name: this repo that should invalidation take place
234 """
240 """
235
241
236 log.debug('marking %s for invalidation', repo_name)
242 log.debug('marking %s for invalidation', repo_name)
237 cache = self.sa.query(CacheInvalidation)\
243 cache = self.sa.query(CacheInvalidation)\
238 .filter(CacheInvalidation.cache_key == repo_name).scalar()
244 .filter(CacheInvalidation.cache_key == repo_name).scalar()
239
245
240 if cache:
246 if cache:
241 #mark this cache as inactive
247 #mark this cache as inactive
242 cache.cache_active = False
248 cache.cache_active = False
243 else:
249 else:
244 log.debug('cache key not found in invalidation db -> creating one')
250 log.debug('cache key not found in invalidation db -> creating one')
245 cache = CacheInvalidation(repo_name)
251 cache = CacheInvalidation(repo_name)
246
252
247 try:
253 try:
248 self.sa.add(cache)
254 self.sa.add(cache)
249 self.sa.commit()
255 self.sa.commit()
250 except (DatabaseError,):
256 except (DatabaseError,):
251 log.error(traceback.format_exc())
257 log.error(traceback.format_exc())
252 self.sa.rollback()
258 self.sa.rollback()
253
259
254
255 def toggle_following_repo(self, follow_repo_id, user_id):
260 def toggle_following_repo(self, follow_repo_id, user_id):
256
261
257 f = self.sa.query(UserFollowing)\
262 f = self.sa.query(UserFollowing)\
258 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
263 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
259 .filter(UserFollowing.user_id == user_id).scalar()
264 .filter(UserFollowing.user_id == user_id).scalar()
260
265
261 if f is not None:
266 if f is not None:
262
267
263 try:
268 try:
264 self.sa.delete(f)
269 self.sa.delete(f)
265 self.sa.commit()
270 self.sa.commit()
266 action_logger(UserTemp(user_id),
271 action_logger(UserTemp(user_id),
267 'stopped_following_repo',
272 'stopped_following_repo',
268 RepoTemp(follow_repo_id))
273 RepoTemp(follow_repo_id))
269 return
274 return
270 except:
275 except:
271 log.error(traceback.format_exc())
276 log.error(traceback.format_exc())
272 self.sa.rollback()
277 self.sa.rollback()
273 raise
278 raise
274
279
275
276 try:
280 try:
277 f = UserFollowing()
281 f = UserFollowing()
278 f.user_id = user_id
282 f.user_id = user_id
279 f.follows_repo_id = follow_repo_id
283 f.follows_repo_id = follow_repo_id
280 self.sa.add(f)
284 self.sa.add(f)
281 self.sa.commit()
285 self.sa.commit()
282 action_logger(UserTemp(user_id),
286 action_logger(UserTemp(user_id),
283 'started_following_repo',
287 'started_following_repo',
284 RepoTemp(follow_repo_id))
288 RepoTemp(follow_repo_id))
285 except:
289 except:
286 log.error(traceback.format_exc())
290 log.error(traceback.format_exc())
287 self.sa.rollback()
291 self.sa.rollback()
288 raise
292 raise
289
293
290 def toggle_following_user(self, follow_user_id , user_id):
294 def toggle_following_user(self, follow_user_id, user_id):
291 f = self.sa.query(UserFollowing)\
295 f = self.sa.query(UserFollowing)\
292 .filter(UserFollowing.follows_user_id == follow_user_id)\
296 .filter(UserFollowing.follows_user_id == follow_user_id)\
293 .filter(UserFollowing.user_id == user_id).scalar()
297 .filter(UserFollowing.user_id == user_id).scalar()
294
298
295 if f is not None:
299 if f is not None:
296 try:
300 try:
297 self.sa.delete(f)
301 self.sa.delete(f)
298 self.sa.commit()
302 self.sa.commit()
299 return
303 return
300 except:
304 except:
301 log.error(traceback.format_exc())
305 log.error(traceback.format_exc())
302 self.sa.rollback()
306 self.sa.rollback()
303 raise
307 raise
304
308
305 try:
309 try:
306 f = UserFollowing()
310 f = UserFollowing()
307 f.user_id = user_id
311 f.user_id = user_id
308 f.follows_user_id = follow_user_id
312 f.follows_user_id = follow_user_id
309 self.sa.add(f)
313 self.sa.add(f)
310 self.sa.commit()
314 self.sa.commit()
311 except:
315 except:
312 log.error(traceback.format_exc())
316 log.error(traceback.format_exc())
313 self.sa.rollback()
317 self.sa.rollback()
314 raise
318 raise
315
319
316 def is_following_repo(self, repo_name, user_id):
320 def is_following_repo(self, repo_name, user_id):
317 r = self.sa.query(Repository)\
321 r = self.sa.query(Repository)\
318 .filter(Repository.repo_name == repo_name).scalar()
322 .filter(Repository.repo_name == repo_name).scalar()
319
323
320 f = self.sa.query(UserFollowing)\
324 f = self.sa.query(UserFollowing)\
321 .filter(UserFollowing.follows_repository == r)\
325 .filter(UserFollowing.follows_repository == r)\
322 .filter(UserFollowing.user_id == user_id).scalar()
326 .filter(UserFollowing.user_id == user_id).scalar()
323
327
324 return f is not None
328 return f is not None
325
329
326 def is_following_user(self, username, user_id):
330 def is_following_user(self, username, user_id):
327 u = UserModel(self.sa).get_by_username(username)
331 u = UserModel(self.sa).get_by_username(username)
328
332
329 f = self.sa.query(UserFollowing)\
333 f = self.sa.query(UserFollowing)\
330 .filter(UserFollowing.follows_user == u)\
334 .filter(UserFollowing.follows_user == u)\
331 .filter(UserFollowing.user_id == user_id).scalar()
335 .filter(UserFollowing.user_id == user_id).scalar()
332
336
333 return f is not None
337 return f is not None
334
338
335 def get_followers(self, repo_id):
339 def get_followers(self, repo_id):
336 return self.sa.query(UserFollowing)\
340 return self.sa.query(UserFollowing)\
337 .filter(UserFollowing.follows_repo_id == repo_id).count()
341 .filter(UserFollowing.follows_repo_id == repo_id).count()
338
342
339 def get_forks(self, repo_id):
343 def get_forks(self, repo_id):
340 return self.sa.query(Repository)\
344 return self.sa.query(Repository)\
341 .filter(Repository.fork_id == repo_id).count()
345 .filter(Repository.fork_id == repo_id).count()
342
346
343
344 def get_unread_journal(self):
347 def get_unread_journal(self):
345 return self.sa.query(UserLog).count()
348 return self.sa.query(UserLog).count()
346
349
347
348 def _should_invalidate(self, repo_name):
350 def _should_invalidate(self, repo_name):
349 """Looks up database for invalidation signals for this repo_name
351 """Looks up database for invalidation signals for this repo_name
350
352
351 :param repo_name:
353 :param repo_name:
352 """
354 """
353
355
354 ret = self.sa.query(CacheInvalidation)\
356 ret = self.sa.query(CacheInvalidation)\
355 .options(FromCache('sql_cache_short',
357 .options(FromCache('sql_cache_short',
356 'get_invalidation_%s' % repo_name))\
358 'get_invalidation_%s' % repo_name))\
357 .filter(CacheInvalidation.cache_key == repo_name)\
359 .filter(CacheInvalidation.cache_key == repo_name)\
358 .filter(CacheInvalidation.cache_active == False)\
360 .filter(CacheInvalidation.cache_active == False)\
359 .scalar()
361 .scalar()
360
362
361 return ret
363 return ret
362
364
363 def _mark_invalidated(self, cache_key):
365 def _mark_invalidated(self, cache_key):
364 """ Marks all occurences of cache to invaldation as already invalidated
366 """ Marks all occurences of cache to invaldation as
367 already invalidated
365
368
366 :param cache_key:
369 :param cache_key:
367 """
370 """
368
371
369 if cache_key:
372 if cache_key:
370 log.debug('marking %s as already invalidated', cache_key)
373 log.debug('marking %s as already invalidated', cache_key)
371 try:
374 try:
372 cache_key.cache_active = True
375 cache_key.cache_active = True
373 self.sa.add(cache_key)
376 self.sa.add(cache_key)
374 self.sa.commit()
377 self.sa.commit()
375 except (DatabaseError,):
378 except (DatabaseError,):
376 log.error(traceback.format_exc())
379 log.error(traceback.format_exc())
377 self.sa.rollback()
380 self.sa.rollback()
378
@@ -1,375 +1,374 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">
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>
89 <p class="footer-link-right"><a href="${h.url('rhodecode_official')}">RhodeCode</a> ${c.rhodecode_version} &copy; 2010-2011 by Marcin Kuzminski</p>
88 <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>
89 </div>
91 </div>
90 </div>
92 <script type="text/javascript">
91 <script type="text/javascript">
93 function tooltip_activate(){
92 function tooltip_activate(){
94 ${h.tooltip.activate()}
93 ${h.tooltip.activate()}
95 }
94 }
96 tooltip_activate();
95 tooltip_activate();
97 </script>
96 </script>
98 </div>
97 </div>
99 <!-- end footer -->
98 <!-- end footer -->
100 </body>
99 </body>
101
100
102 </html>
101 </html>
103
102
104 ### MAKO DEFS ###
103 ### MAKO DEFS ###
105 <%def name="page_nav()">
104 <%def name="page_nav()">
106 ${self.menu()}
105 ${self.menu()}
107 </%def>
106 </%def>
108
107
109 <%def name="menu(current=None)">
108 <%def name="menu(current=None)">
110 <%
109 <%
111 def is_current(selected):
110 def is_current(selected):
112 if selected == current:
111 if selected == current:
113 return h.literal('class="current"')
112 return h.literal('class="current"')
114 %>
113 %>
115 %if current not in ['home','admin']:
114 %if current not in ['home','admin']:
116 ##REGULAR MENU
115 ##REGULAR MENU
117 <ul id="quick">
116 <ul id="quick">
118 <!-- repo switcher -->
117 <!-- repo switcher -->
119 <li>
118 <li>
120 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
119 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
121 <span class="icon">
120 <span class="icon">
122 <img src="${h.url("/images/icons/database.png")}" alt="${_('Products')}" />
121 <img src="${h.url("/images/icons/database.png")}" alt="${_('Products')}" />
123 </span>
122 </span>
124 <span>&darr;</span>
123 <span>&darr;</span>
125 </a>
124 </a>
126 <ul class="repo_switcher">
125 <ul class="repo_switcher">
127 %for repo in c.cached_repo_list:
126 %for repo in c.cached_repo_list:
128
127
129 %if repo['repo'].dbrepo.private:
128 %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>
129 <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:
130 %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>
131 <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
132 %endif
134 %endfor
133 %endfor
135 </ul>
134 </ul>
136 </li>
135 </li>
137
136
138 <li ${is_current('summary')}>
137 <li ${is_current('summary')}>
139 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
138 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
140 <span class="icon">
139 <span class="icon">
141 <img src="${h.url("/images/icons/clipboard_16.png")}" alt="${_('Summary')}" />
140 <img src="${h.url("/images/icons/clipboard_16.png")}" alt="${_('Summary')}" />
142 </span>
141 </span>
143 <span>${_('Summary')}</span>
142 <span>${_('Summary')}</span>
144 </a>
143 </a>
145 </li>
144 </li>
146 ##<li ${is_current('shortlog')}>
145 ##<li ${is_current('shortlog')}>
147 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
146 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
148 ## <span class="icon">
147 ## <span class="icon">
149 ## <img src="${h.url("/images/icons/application_view_list.png")}" alt="${_('Shortlog')}" />
148 ## <img src="${h.url("/images/icons/application_view_list.png")}" alt="${_('Shortlog')}" />
150 ## </span>
149 ## </span>
151 ## <span>${_('Shortlog')}</span>
150 ## <span>${_('Shortlog')}</span>
152 ## </a>
151 ## </a>
153 ##</li>
152 ##</li>
154 <li ${is_current('changelog')}>
153 <li ${is_current('changelog')}>
155 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
154 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
156 <span class="icon">
155 <span class="icon">
157 <img src="${h.url("/images/icons/time.png")}" alt="${_('Changelog')}" />
156 <img src="${h.url("/images/icons/time.png")}" alt="${_('Changelog')}" />
158 </span>
157 </span>
159 <span>${_('Changelog')}</span>
158 <span>${_('Changelog')}</span>
160 </a>
159 </a>
161 </li>
160 </li>
162
161
163 <li ${is_current('switch_to')}>
162 <li ${is_current('switch_to')}>
164 <a title="${_('Switch to')}" href="#">
163 <a title="${_('Switch to')}" href="#">
165 <span class="icon">
164 <span class="icon">
166 <img src="${h.url("/images/icons/arrow_switch.png")}" alt="${_('Switch to')}" />
165 <img src="${h.url("/images/icons/arrow_switch.png")}" alt="${_('Switch to')}" />
167 </span>
166 </span>
168 <span>${_('Switch to')}</span>
167 <span>${_('Switch to')}</span>
169 </a>
168 </a>
170 <ul>
169 <ul>
171 <li>
170 <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')}
171 ${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>
172 <ul>
174 %if c.repository_branches.values():
173 %if c.repository_branches.values():
175 %for cnt,branch in enumerate(c.repository_branches.items()):
174 %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>
175 <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
176 %endfor
178 %else:
177 %else:
179 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
178 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
180 %endif
179 %endif
181 </ul>
180 </ul>
182 </li>
181 </li>
183 <li>
182 <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')}
183 ${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>
184 <ul>
186 %if c.repository_tags.values():
185 %if c.repository_tags.values():
187 %for cnt,tag in enumerate(c.repository_tags.items()):
186 %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>
187 <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
188 %endfor
190 %else:
189 %else:
191 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
190 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
192 %endif
191 %endif
193 </ul>
192 </ul>
194 </li>
193 </li>
195 </ul>
194 </ul>
196 </li>
195 </li>
197 <li ${is_current('files')}>
196 <li ${is_current('files')}>
198 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
197 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
199 <span class="icon">
198 <span class="icon">
200 <img src="${h.url("/images/icons/file.png")}" alt="${_('Files')}" />
199 <img src="${h.url("/images/icons/file.png")}" alt="${_('Files')}" />
201 </span>
200 </span>
202 <span>${_('Files')}</span>
201 <span>${_('Files')}</span>
203 </a>
202 </a>
204 </li>
203 </li>
205
204
206 <li ${is_current('options')}>
205 <li ${is_current('options')}>
207 <a title="${_('Options')}" href="#">
206 <a title="${_('Options')}" href="#">
208 <span class="icon">
207 <span class="icon">
209 <img src="${h.url("/images/icons/table_gear.png")}" alt="${_('Admin')}" />
208 <img src="${h.url("/images/icons/table_gear.png")}" alt="${_('Admin')}" />
210 </span>
209 </span>
211 <span>${_('Options')}</span>
210 <span>${_('Options')}</span>
212 </a>
211 </a>
213 <ul>
212 <ul>
214 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
213 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
215 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
214 %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>
215 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
217 %else:
216 %else:
218 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
217 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
219 %endif
218 %endif
220 %endif
219 %endif
221 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
220 <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>
221 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
223
222
224 %if h.HasPermissionAll('hg.admin')('access admin main page'):
223 %if h.HasPermissionAll('hg.admin')('access admin main page'):
225 <li>
224 <li>
226 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
225 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
227 <%def name="admin_menu()">
226 <%def name="admin_menu()">
228 <ul>
227 <ul>
229 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
228 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
230 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
229 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
231 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
230 <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>
231 <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>
232 <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>
233 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
235 </ul>
234 </ul>
236 </%def>
235 </%def>
237
236
238 ${admin_menu()}
237 ${admin_menu()}
239 </li>
238 </li>
240 %endif
239 %endif
241
240
242 </ul>
241 </ul>
243 </li>
242 </li>
244
243
245 <li>
244 <li>
246 <a title="${_('Followers')}" href="#">
245 <a title="${_('Followers')}" href="#">
247 <span class="icon_short">
246 <span class="icon_short">
248 <img src="${h.url("/images/icons/heart.png")}" alt="${_('Followers')}" />
247 <img src="${h.url("/images/icons/heart.png")}" alt="${_('Followers')}" />
249 </span>
248 </span>
250 <span class="short">${c.repository_followers}</span>
249 <span class="short">${c.repository_followers}</span>
251 </a>
250 </a>
252 </li>
251 </li>
253 <li>
252 <li>
254 <a title="${_('Forks')}" href="#">
253 <a title="${_('Forks')}" href="#">
255 <span class="icon_short">
254 <span class="icon_short">
256 <img src="${h.url("/images/icons/arrow_divide.png")}" alt="${_('Forks')}" />
255 <img src="${h.url("/images/icons/arrow_divide.png")}" alt="${_('Forks')}" />
257 </span>
256 </span>
258 <span class="short">${c.repository_forks}</span>
257 <span class="short">${c.repository_forks}</span>
259 </a>
258 </a>
260 </li>
259 </li>
261
260
262
261
263
262
264 </ul>
263 </ul>
265 %else:
264 %else:
266 ##ROOT MENU
265 ##ROOT MENU
267 <ul id="quick">
266 <ul id="quick">
268 <li>
267 <li>
269 <a title="${_('Home')}" href="${h.url('home')}">
268 <a title="${_('Home')}" href="${h.url('home')}">
270 <span class="icon">
269 <span class="icon">
271 <img src="${h.url("/images/icons/home_16.png")}" alt="${_('Home')}" />
270 <img src="${h.url("/images/icons/home_16.png")}" alt="${_('Home')}" />
272 </span>
271 </span>
273 <span>${_('Home')}</span>
272 <span>${_('Home')}</span>
274 </a>
273 </a>
275 </li>
274 </li>
276 %if c.rhodecode_user.username != 'default':
275 %if c.rhodecode_user.username != 'default':
277 <li>
276 <li>
278 <a title="${_('Journal')}" href="${h.url('journal')}">
277 <a title="${_('Journal')}" href="${h.url('journal')}">
279 <span class="icon">
278 <span class="icon">
280 <img src="${h.url("/images/icons/book.png")}" alt="${_('Journal')}" />
279 <img src="${h.url("/images/icons/book.png")}" alt="${_('Journal')}" />
281 </span>
280 </span>
282 <span>${_('Journal')}</span>
281 <span>${_('Journal')}</span>
283 </a>
282 </a>
284 </li>
283 </li>
285 %endif
284 %endif
286 <li>
285 <li>
287 <a title="${_('Search')}" href="${h.url('search')}">
286 <a title="${_('Search')}" href="${h.url('search')}">
288 <span class="icon">
287 <span class="icon">
289 <img src="${h.url("/images/icons/search_16.png")}" alt="${_('Search')}" />
288 <img src="${h.url("/images/icons/search_16.png")}" alt="${_('Search')}" />
290 </span>
289 </span>
291 <span>${_('Search')}</span>
290 <span>${_('Search')}</span>
292 </a>
291 </a>
293 </li>
292 </li>
294
293
295 %if h.HasPermissionAll('hg.admin')('access admin main page'):
294 %if h.HasPermissionAll('hg.admin')('access admin main page'):
296 <li ${is_current('admin')}>
295 <li ${is_current('admin')}>
297 <a title="${_('Admin')}" href="${h.url('admin_home')}">
296 <a title="${_('Admin')}" href="${h.url('admin_home')}">
298 <span class="icon">
297 <span class="icon">
299 <img src="${h.url("/images/icons/cog_edit.png")}" alt="${_('Admin')}" />
298 <img src="${h.url("/images/icons/cog_edit.png")}" alt="${_('Admin')}" />
300 </span>
299 </span>
301 <span>${_('Admin')}</span>
300 <span>${_('Admin')}</span>
302 </a>
301 </a>
303 ${admin_menu()}
302 ${admin_menu()}
304 </li>
303 </li>
305 %endif
304 %endif
306 </ul>
305 </ul>
307 %endif
306 %endif
308 </%def>
307 </%def>
309
308
310
309
311 <%def name="css()">
310 <%def name="css()">
312 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen" />
311 <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')}" />
312 <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')}" />
313 <link rel="stylesheet" type="text/css" href="${h.url('/css/diff.css')}" />
315 </%def>
314 </%def>
316
315
317 <%def name="js()">
316 <%def name="js()">
318 ##<script type="text/javascript" src="${h.url('/js/yui/utilities/utilities.js')}"></script>
317 ##<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>
318 ##<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>
319 ##<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>
320 ##<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>
321 ##<script type="text/javascript" src="${h.url('/js/yui/selector/selector-min.js')}"></script>
323
322
324 <script type="text/javascript" src="${h.url('/js/yui2a.js')}"></script>
323 <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]-->
324 <!--[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>
325 <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
327
326
328 <script type="text/javascript">
327 <script type="text/javascript">
329 var base_url = "${h.url('toggle_following')}";
328 var base_url = "${h.url('toggle_following')}";
330 var YUC = YAHOO.util.Connect;
329 var YUC = YAHOO.util.Connect;
331 var YUD = YAHOO.util.Dom;
330 var YUD = YAHOO.util.Dom;
332 var YUE = YAHOO.util.Event;
331 var YUE = YAHOO.util.Event;
333
332
334 function onSuccess(target){
333 function onSuccess(target){
335
334
336 var f = YUD.get(target.id);
335 var f = YUD.get(target.id);
337 if(f.getAttribute('class')=='follow'){
336 if(f.getAttribute('class')=='follow'){
338 f.setAttribute('class','following');
337 f.setAttribute('class','following');
339 f.setAttribute('title',"${_('Stop following this repository')}");
338 f.setAttribute('title',"${_('Stop following this repository')}");
340 }
339 }
341 else{
340 else{
342 f.setAttribute('class','follow');
341 f.setAttribute('class','follow');
343 f.setAttribute('title',"${_('Start following this repository')}");
342 f.setAttribute('title',"${_('Start following this repository')}");
344 }
343 }
345 }
344 }
346
345
347 function toggleFollowingUser(fallows_user_id,token){
346 function toggleFollowingUser(fallows_user_id,token){
348 args = 'follows_user_id='+fallows_user_id;
347 args = 'follows_user_id='+fallows_user_id;
349 args+= '&amp;auth_token='+token;
348 args+= '&amp;auth_token='+token;
350 YUC.asyncRequest('POST',base_url,{
349 YUC.asyncRequest('POST',base_url,{
351 success:function(o){
350 success:function(o){
352 onSuccess();
351 onSuccess();
353 }
352 }
354 },args); return false;
353 },args); return false;
355 }
354 }
356
355
357 function toggleFollowingRepo(target,fallows_repo_id,token){
356 function toggleFollowingRepo(target,fallows_repo_id,token){
358
357
359 args = 'follows_repo_id='+fallows_repo_id;
358 args = 'follows_repo_id='+fallows_repo_id;
360 args+= '&amp;auth_token='+token;
359 args+= '&amp;auth_token='+token;
361 YUC.asyncRequest('POST',base_url,{
360 YUC.asyncRequest('POST',base_url,{
362 success:function(o){
361 success:function(o){
363 onSuccess(target);
362 onSuccess(target);
364 }
363 }
365 },args); return false;
364 },args); return false;
366 }
365 }
367 </script>
366 </script>
368
367
369 </%def>
368 </%def>
370
369
371 <%def name="breadcrumbs()">
370 <%def name="breadcrumbs()">
372 <div class="breadcrumbs">
371 <div class="breadcrumbs">
373 ${self.breadcrumbs_links()}
372 ${self.breadcrumbs_links()}
374 </div>
373 </div>
375 </%def> No newline at end of file
374 </%def>
General Comments 0
You need to be logged in to leave comments. Login now