##// END OF EJS Templates
Implemented basic locking functionality....
marcink -
r2726:aa17c7a1 beta
parent child Browse files
Show More
@@ -0,0 +1,41 b''
1 .. _locking:
2
3 ===================================
4 RhodeCode repository locking system
5 ===================================
6
7
8 | Repos with **locking function=disabled** is the default, that's how repos work
9 today.
10 | Repos with **locking function=enabled** behaves like follows:
11
12 Repos have a state called `locked` that can be true or false.
13 The hg/git commands `hg/git clone`, `hg/git pull`, and `hg/git push`
14 influence this state:
15
16 - The command `hg/git pull <repo>` will lock that repo (locked=true)
17 if the user has write/admin permissions on this repo
18
19 - The command `hg/git clone <repo>` will lock that repo (locked=true) if the
20 user has write/admin permissions on this repo
21
22
23 RhodeCode will remember the user id who locked the repo
24 only this specific user can unlock the repo (locked=false) by calling
25
26 - `hg/git push <repo>`
27
28 every other command on that repo from this user and
29 every command from any other user will result in http return code 423 (locked)
30
31
32 additionally the http error includes the <user> that locked the repo
33 (e.g. β€œrepository <repo> locked by user <user>”)
34
35
36 So the scenario of use for repos with `locking function` enabled is that
37 every initial clone and every pull gives users (with write permission)
38 the exclusive right to do a push.
39
40
41 Each repo can be manually unlocked by admin from the repo settings menu. No newline at end of file
@@ -0,0 +1,31 b''
1 #!/usr/bin/env python
2 import os
3 import sys
4
5 try:
6 import rhodecode
7 RC_HOOK_VER = '_TMPL_'
8 os.environ['RC_HOOK_VER'] = RC_HOOK_VER
9 from rhodecode.lib.hooks import handle_git_pre_receive
10 except ImportError:
11 rhodecode = None
12
13
14 def main():
15 if rhodecode is None:
16 # exit with success if we cannot import rhodecode !!
17 # this allows simply push to this repo even without
18 # rhodecode
19 sys.exit(0)
20
21 repo_path = os.path.abspath('.')
22 push_data = sys.stdin.readlines()
23 # os.environ is modified here by a subprocess call that
24 # runs git and later git executes this hook.
25 # Environ get's some additional info from rhodecode system
26 # like IP or username from basic-auth
27 handle_git_pre_receive(repo_path, push_data, os.environ)
28 sys.exit(0)
29
30 if __name__ == '__main__':
31 main()
@@ -1,62 +1,63 b''
1 .. _index:
1 .. _index:
2
2
3 .. include:: ./../README.rst
3 .. include:: ./../README.rst
4
4
5 Users Guide
5 Users Guide
6 -----------
6 -----------
7
7
8 **Installation:**
8 **Installation:**
9
9
10 .. toctree::
10 .. toctree::
11 :maxdepth: 1
11 :maxdepth: 1
12
12
13 installation
13 installation
14 setup
14 setup
15 upgrade
15 upgrade
16
16
17 **Usage**
17 **Usage**
18
18
19 .. toctree::
19 .. toctree::
20 :maxdepth: 1
20 :maxdepth: 1
21
21
22 usage/general
22 usage/general
23 usage/git_support
23 usage/git_support
24 usage/performance
24 usage/performance
25 usage/locking
25 usage/statistics
26 usage/statistics
26 usage/backup
27 usage/backup
27 usage/debugging
28 usage/debugging
28 usage/troubleshooting
29 usage/troubleshooting
29
30
30 **Develop**
31 **Develop**
31
32
32 .. toctree::
33 .. toctree::
33 :maxdepth: 1
34 :maxdepth: 1
34
35
35 contributing
36 contributing
36 changelog
37 changelog
37
38
38 **API**
39 **API**
39
40
40 .. toctree::
41 .. toctree::
41 :maxdepth: 1
42 :maxdepth: 1
42
43
43 api/api
44 api/api
44 api/models
45 api/models
45
46
46
47
47 Other topics
48 Other topics
48 ------------
49 ------------
49
50
50 * :ref:`genindex`
51 * :ref:`genindex`
51 * :ref:`search`
52 * :ref:`search`
52
53
53 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
54 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
54 .. _python: http://www.python.org/
55 .. _python: http://www.python.org/
55 .. _django: http://www.djangoproject.com/
56 .. _django: http://www.djangoproject.com/
56 .. _mercurial: http://mercurial.selenic.com/
57 .. _mercurial: http://mercurial.selenic.com/
57 .. _bitbucket: http://bitbucket.org/
58 .. _bitbucket: http://bitbucket.org/
58 .. _subversion: http://subversion.tigris.org/
59 .. _subversion: http://subversion.tigris.org/
59 .. _git: http://git-scm.com/
60 .. _git: http://git-scm.com/
60 .. _celery: http://celeryproject.org/
61 .. _celery: http://celeryproject.org/
61 .. _Sphinx: http://sphinx.pocoo.org/
62 .. _Sphinx: http://sphinx.pocoo.org/
62 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
63 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,573 +1,575 b''
1 """
1 """
2 Routes configuration
2 Routes configuration
3
3
4 The more specific and detailed routes should be defined first so they
4 The more specific and detailed routes should be defined first so they
5 may take precedent over the more generic routes. For more information
5 may take precedent over the more generic routes. For more information
6 refer to the routes manual at http://routes.groovie.org/docs/
6 refer to the routes manual at http://routes.groovie.org/docs/
7 """
7 """
8 from __future__ import with_statement
8 from __future__ import with_statement
9 from routes import Mapper
9 from routes import Mapper
10
10
11 # prefix for non repository related links needs to be prefixed with `/`
11 # prefix for non repository related links needs to be prefixed with `/`
12 ADMIN_PREFIX = '/_admin'
12 ADMIN_PREFIX = '/_admin'
13
13
14
14
15 def make_map(config):
15 def make_map(config):
16 """Create, configure and return the routes Mapper"""
16 """Create, configure and return the routes Mapper"""
17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 always_scan=config['debug'])
18 always_scan=config['debug'])
19 rmap.minimization = False
19 rmap.minimization = False
20 rmap.explicit = False
20 rmap.explicit = False
21
21
22 from rhodecode.lib.utils import is_valid_repo
22 from rhodecode.lib.utils import is_valid_repo
23 from rhodecode.lib.utils import is_valid_repos_group
23 from rhodecode.lib.utils import is_valid_repos_group
24
24
25 def check_repo(environ, match_dict):
25 def check_repo(environ, match_dict):
26 """
26 """
27 check for valid repository for proper 404 handling
27 check for valid repository for proper 404 handling
28
28
29 :param environ:
29 :param environ:
30 :param match_dict:
30 :param match_dict:
31 """
31 """
32 from rhodecode.model.db import Repository
32 from rhodecode.model.db import Repository
33 repo_name = match_dict.get('repo_name')
33 repo_name = match_dict.get('repo_name')
34
34
35 try:
35 try:
36 by_id = repo_name.split('_')
36 by_id = repo_name.split('_')
37 if len(by_id) == 2 and by_id[1].isdigit():
37 if len(by_id) == 2 and by_id[1].isdigit():
38 repo_name = Repository.get(by_id[1]).repo_name
38 repo_name = Repository.get(by_id[1]).repo_name
39 match_dict['repo_name'] = repo_name
39 match_dict['repo_name'] = repo_name
40 except:
40 except:
41 pass
41 pass
42
42
43 return is_valid_repo(repo_name, config['base_path'])
43 return is_valid_repo(repo_name, config['base_path'])
44
44
45 def check_group(environ, match_dict):
45 def check_group(environ, match_dict):
46 """
46 """
47 check for valid repositories group for proper 404 handling
47 check for valid repositories group for proper 404 handling
48
48
49 :param environ:
49 :param environ:
50 :param match_dict:
50 :param match_dict:
51 """
51 """
52 repos_group_name = match_dict.get('group_name')
52 repos_group_name = match_dict.get('group_name')
53
53
54 return is_valid_repos_group(repos_group_name, config['base_path'])
54 return is_valid_repos_group(repos_group_name, config['base_path'])
55
55
56 def check_int(environ, match_dict):
56 def check_int(environ, match_dict):
57 return match_dict.get('id').isdigit()
57 return match_dict.get('id').isdigit()
58
58
59 # The ErrorController route (handles 404/500 error pages); it should
59 # The ErrorController route (handles 404/500 error pages); it should
60 # likely stay at the top, ensuring it can always be resolved
60 # likely stay at the top, ensuring it can always be resolved
61 rmap.connect('/error/{action}', controller='error')
61 rmap.connect('/error/{action}', controller='error')
62 rmap.connect('/error/{action}/{id}', controller='error')
62 rmap.connect('/error/{action}/{id}', controller='error')
63
63
64 #==========================================================================
64 #==========================================================================
65 # CUSTOM ROUTES HERE
65 # CUSTOM ROUTES HERE
66 #==========================================================================
66 #==========================================================================
67
67
68 #MAIN PAGE
68 #MAIN PAGE
69 rmap.connect('home', '/', controller='home', action='index')
69 rmap.connect('home', '/', controller='home', action='index')
70 rmap.connect('repo_switcher', '/repos', controller='home',
70 rmap.connect('repo_switcher', '/repos', controller='home',
71 action='repo_switcher')
71 action='repo_switcher')
72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
73 controller='home', action='branch_tag_switcher')
73 controller='home', action='branch_tag_switcher')
74 rmap.connect('bugtracker',
74 rmap.connect('bugtracker',
75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
76 _static=True)
76 _static=True)
77 rmap.connect('rst_help',
77 rmap.connect('rst_help',
78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 _static=True)
79 _static=True)
80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
81
81
82 #ADMIN REPOSITORY REST ROUTES
82 #ADMIN REPOSITORY REST ROUTES
83 with rmap.submapper(path_prefix=ADMIN_PREFIX,
83 with rmap.submapper(path_prefix=ADMIN_PREFIX,
84 controller='admin/repos') as m:
84 controller='admin/repos') as m:
85 m.connect("repos", "/repos",
85 m.connect("repos", "/repos",
86 action="create", conditions=dict(method=["POST"]))
86 action="create", conditions=dict(method=["POST"]))
87 m.connect("repos", "/repos",
87 m.connect("repos", "/repos",
88 action="index", conditions=dict(method=["GET"]))
88 action="index", conditions=dict(method=["GET"]))
89 m.connect("formatted_repos", "/repos.{format}",
89 m.connect("formatted_repos", "/repos.{format}",
90 action="index",
90 action="index",
91 conditions=dict(method=["GET"]))
91 conditions=dict(method=["GET"]))
92 m.connect("new_repo", "/repos/new",
92 m.connect("new_repo", "/repos/new",
93 action="new", conditions=dict(method=["GET"]))
93 action="new", conditions=dict(method=["GET"]))
94 m.connect("formatted_new_repo", "/repos/new.{format}",
94 m.connect("formatted_new_repo", "/repos/new.{format}",
95 action="new", conditions=dict(method=["GET"]))
95 action="new", conditions=dict(method=["GET"]))
96 m.connect("/repos/{repo_name:.*?}",
96 m.connect("/repos/{repo_name:.*?}",
97 action="update", conditions=dict(method=["PUT"],
97 action="update", conditions=dict(method=["PUT"],
98 function=check_repo))
98 function=check_repo))
99 m.connect("/repos/{repo_name:.*?}",
99 m.connect("/repos/{repo_name:.*?}",
100 action="delete", conditions=dict(method=["DELETE"],
100 action="delete", conditions=dict(method=["DELETE"],
101 function=check_repo))
101 function=check_repo))
102 m.connect("edit_repo", "/repos/{repo_name:.*?}/edit",
102 m.connect("edit_repo", "/repos/{repo_name:.*?}/edit",
103 action="edit", conditions=dict(method=["GET"],
103 action="edit", conditions=dict(method=["GET"],
104 function=check_repo))
104 function=check_repo))
105 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
105 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
106 action="edit", conditions=dict(method=["GET"],
106 action="edit", conditions=dict(method=["GET"],
107 function=check_repo))
107 function=check_repo))
108 m.connect("repo", "/repos/{repo_name:.*?}",
108 m.connect("repo", "/repos/{repo_name:.*?}",
109 action="show", conditions=dict(method=["GET"],
109 action="show", conditions=dict(method=["GET"],
110 function=check_repo))
110 function=check_repo))
111 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
111 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
112 action="show", conditions=dict(method=["GET"],
112 action="show", conditions=dict(method=["GET"],
113 function=check_repo))
113 function=check_repo))
114 #ajax delete repo perm user
114 #ajax delete repo perm user
115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
116 action="delete_perm_user",
116 action="delete_perm_user",
117 conditions=dict(method=["DELETE"], function=check_repo))
117 conditions=dict(method=["DELETE"], function=check_repo))
118
118
119 #ajax delete repo perm users_group
119 #ajax delete repo perm users_group
120 m.connect('delete_repo_users_group',
120 m.connect('delete_repo_users_group',
121 "/repos_delete_users_group/{repo_name:.*?}",
121 "/repos_delete_users_group/{repo_name:.*?}",
122 action="delete_perm_users_group",
122 action="delete_perm_users_group",
123 conditions=dict(method=["DELETE"], function=check_repo))
123 conditions=dict(method=["DELETE"], function=check_repo))
124
124
125 #settings actions
125 #settings actions
126 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
126 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
127 action="repo_stats", conditions=dict(method=["DELETE"],
127 action="repo_stats", conditions=dict(method=["DELETE"],
128 function=check_repo))
128 function=check_repo))
129 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
129 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
130 action="repo_cache", conditions=dict(method=["DELETE"],
130 action="repo_cache", conditions=dict(method=["DELETE"],
131 function=check_repo))
131 function=check_repo))
132 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
132 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
133 action="repo_public_journal", conditions=dict(method=["PUT"],
133 action="repo_public_journal", conditions=dict(method=["PUT"],
134 function=check_repo))
134 function=check_repo))
135 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
135 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
136 action="repo_pull", conditions=dict(method=["PUT"],
136 action="repo_pull", conditions=dict(method=["PUT"],
137 function=check_repo))
137 function=check_repo))
138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
139 action="repo_as_fork", conditions=dict(method=["PUT"],
139 action="repo_as_fork", conditions=dict(method=["PUT"],
140 function=check_repo))
140 function=check_repo))
141
141 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
142 action="repo_locking", conditions=dict(method=["PUT"],
143 function=check_repo))
142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
144 with rmap.submapper(path_prefix=ADMIN_PREFIX,
143 controller='admin/repos_groups') as m:
145 controller='admin/repos_groups') as m:
144 m.connect("repos_groups", "/repos_groups",
146 m.connect("repos_groups", "/repos_groups",
145 action="create", conditions=dict(method=["POST"]))
147 action="create", conditions=dict(method=["POST"]))
146 m.connect("repos_groups", "/repos_groups",
148 m.connect("repos_groups", "/repos_groups",
147 action="index", conditions=dict(method=["GET"]))
149 action="index", conditions=dict(method=["GET"]))
148 m.connect("formatted_repos_groups", "/repos_groups.{format}",
150 m.connect("formatted_repos_groups", "/repos_groups.{format}",
149 action="index", conditions=dict(method=["GET"]))
151 action="index", conditions=dict(method=["GET"]))
150 m.connect("new_repos_group", "/repos_groups/new",
152 m.connect("new_repos_group", "/repos_groups/new",
151 action="new", conditions=dict(method=["GET"]))
153 action="new", conditions=dict(method=["GET"]))
152 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
154 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
153 action="new", conditions=dict(method=["GET"]))
155 action="new", conditions=dict(method=["GET"]))
154 m.connect("update_repos_group", "/repos_groups/{id}",
156 m.connect("update_repos_group", "/repos_groups/{id}",
155 action="update", conditions=dict(method=["PUT"],
157 action="update", conditions=dict(method=["PUT"],
156 function=check_int))
158 function=check_int))
157 m.connect("delete_repos_group", "/repos_groups/{id}",
159 m.connect("delete_repos_group", "/repos_groups/{id}",
158 action="delete", conditions=dict(method=["DELETE"],
160 action="delete", conditions=dict(method=["DELETE"],
159 function=check_int))
161 function=check_int))
160 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
162 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
161 action="edit", conditions=dict(method=["GET"],))
163 action="edit", conditions=dict(method=["GET"],))
162 m.connect("formatted_edit_repos_group",
164 m.connect("formatted_edit_repos_group",
163 "/repos_groups/{id}.{format}/edit",
165 "/repos_groups/{id}.{format}/edit",
164 action="edit", conditions=dict(method=["GET"],
166 action="edit", conditions=dict(method=["GET"],
165 function=check_int))
167 function=check_int))
166 m.connect("repos_group", "/repos_groups/{id}",
168 m.connect("repos_group", "/repos_groups/{id}",
167 action="show", conditions=dict(method=["GET"],
169 action="show", conditions=dict(method=["GET"],
168 function=check_int))
170 function=check_int))
169 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
171 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
170 action="show", conditions=dict(method=["GET"],
172 action="show", conditions=dict(method=["GET"],
171 function=check_int))
173 function=check_int))
172 # ajax delete repos group perm user
174 # ajax delete repos group perm user
173 m.connect('delete_repos_group_user_perm',
175 m.connect('delete_repos_group_user_perm',
174 "/delete_repos_group_user_perm/{group_name:.*}",
176 "/delete_repos_group_user_perm/{group_name:.*}",
175 action="delete_repos_group_user_perm",
177 action="delete_repos_group_user_perm",
176 conditions=dict(method=["DELETE"], function=check_group))
178 conditions=dict(method=["DELETE"], function=check_group))
177
179
178 # ajax delete repos group perm users_group
180 # ajax delete repos group perm users_group
179 m.connect('delete_repos_group_users_group_perm',
181 m.connect('delete_repos_group_users_group_perm',
180 "/delete_repos_group_users_group_perm/{group_name:.*}",
182 "/delete_repos_group_users_group_perm/{group_name:.*}",
181 action="delete_repos_group_users_group_perm",
183 action="delete_repos_group_users_group_perm",
182 conditions=dict(method=["DELETE"], function=check_group))
184 conditions=dict(method=["DELETE"], function=check_group))
183
185
184 #ADMIN USER REST ROUTES
186 #ADMIN USER REST ROUTES
185 with rmap.submapper(path_prefix=ADMIN_PREFIX,
187 with rmap.submapper(path_prefix=ADMIN_PREFIX,
186 controller='admin/users') as m:
188 controller='admin/users') as m:
187 m.connect("users", "/users",
189 m.connect("users", "/users",
188 action="create", conditions=dict(method=["POST"]))
190 action="create", conditions=dict(method=["POST"]))
189 m.connect("users", "/users",
191 m.connect("users", "/users",
190 action="index", conditions=dict(method=["GET"]))
192 action="index", conditions=dict(method=["GET"]))
191 m.connect("formatted_users", "/users.{format}",
193 m.connect("formatted_users", "/users.{format}",
192 action="index", conditions=dict(method=["GET"]))
194 action="index", conditions=dict(method=["GET"]))
193 m.connect("new_user", "/users/new",
195 m.connect("new_user", "/users/new",
194 action="new", conditions=dict(method=["GET"]))
196 action="new", conditions=dict(method=["GET"]))
195 m.connect("formatted_new_user", "/users/new.{format}",
197 m.connect("formatted_new_user", "/users/new.{format}",
196 action="new", conditions=dict(method=["GET"]))
198 action="new", conditions=dict(method=["GET"]))
197 m.connect("update_user", "/users/{id}",
199 m.connect("update_user", "/users/{id}",
198 action="update", conditions=dict(method=["PUT"]))
200 action="update", conditions=dict(method=["PUT"]))
199 m.connect("delete_user", "/users/{id}",
201 m.connect("delete_user", "/users/{id}",
200 action="delete", conditions=dict(method=["DELETE"]))
202 action="delete", conditions=dict(method=["DELETE"]))
201 m.connect("edit_user", "/users/{id}/edit",
203 m.connect("edit_user", "/users/{id}/edit",
202 action="edit", conditions=dict(method=["GET"]))
204 action="edit", conditions=dict(method=["GET"]))
203 m.connect("formatted_edit_user",
205 m.connect("formatted_edit_user",
204 "/users/{id}.{format}/edit",
206 "/users/{id}.{format}/edit",
205 action="edit", conditions=dict(method=["GET"]))
207 action="edit", conditions=dict(method=["GET"]))
206 m.connect("user", "/users/{id}",
208 m.connect("user", "/users/{id}",
207 action="show", conditions=dict(method=["GET"]))
209 action="show", conditions=dict(method=["GET"]))
208 m.connect("formatted_user", "/users/{id}.{format}",
210 m.connect("formatted_user", "/users/{id}.{format}",
209 action="show", conditions=dict(method=["GET"]))
211 action="show", conditions=dict(method=["GET"]))
210
212
211 #EXTRAS USER ROUTES
213 #EXTRAS USER ROUTES
212 m.connect("user_perm", "/users_perm/{id}",
214 m.connect("user_perm", "/users_perm/{id}",
213 action="update_perm", conditions=dict(method=["PUT"]))
215 action="update_perm", conditions=dict(method=["PUT"]))
214 m.connect("user_emails", "/users_emails/{id}",
216 m.connect("user_emails", "/users_emails/{id}",
215 action="add_email", conditions=dict(method=["PUT"]))
217 action="add_email", conditions=dict(method=["PUT"]))
216 m.connect("user_emails_delete", "/users_emails/{id}",
218 m.connect("user_emails_delete", "/users_emails/{id}",
217 action="delete_email", conditions=dict(method=["DELETE"]))
219 action="delete_email", conditions=dict(method=["DELETE"]))
218
220
219 #ADMIN USERS GROUPS REST ROUTES
221 #ADMIN USERS GROUPS REST ROUTES
220 with rmap.submapper(path_prefix=ADMIN_PREFIX,
222 with rmap.submapper(path_prefix=ADMIN_PREFIX,
221 controller='admin/users_groups') as m:
223 controller='admin/users_groups') as m:
222 m.connect("users_groups", "/users_groups",
224 m.connect("users_groups", "/users_groups",
223 action="create", conditions=dict(method=["POST"]))
225 action="create", conditions=dict(method=["POST"]))
224 m.connect("users_groups", "/users_groups",
226 m.connect("users_groups", "/users_groups",
225 action="index", conditions=dict(method=["GET"]))
227 action="index", conditions=dict(method=["GET"]))
226 m.connect("formatted_users_groups", "/users_groups.{format}",
228 m.connect("formatted_users_groups", "/users_groups.{format}",
227 action="index", conditions=dict(method=["GET"]))
229 action="index", conditions=dict(method=["GET"]))
228 m.connect("new_users_group", "/users_groups/new",
230 m.connect("new_users_group", "/users_groups/new",
229 action="new", conditions=dict(method=["GET"]))
231 action="new", conditions=dict(method=["GET"]))
230 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
232 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
231 action="new", conditions=dict(method=["GET"]))
233 action="new", conditions=dict(method=["GET"]))
232 m.connect("update_users_group", "/users_groups/{id}",
234 m.connect("update_users_group", "/users_groups/{id}",
233 action="update", conditions=dict(method=["PUT"]))
235 action="update", conditions=dict(method=["PUT"]))
234 m.connect("delete_users_group", "/users_groups/{id}",
236 m.connect("delete_users_group", "/users_groups/{id}",
235 action="delete", conditions=dict(method=["DELETE"]))
237 action="delete", conditions=dict(method=["DELETE"]))
236 m.connect("edit_users_group", "/users_groups/{id}/edit",
238 m.connect("edit_users_group", "/users_groups/{id}/edit",
237 action="edit", conditions=dict(method=["GET"]))
239 action="edit", conditions=dict(method=["GET"]))
238 m.connect("formatted_edit_users_group",
240 m.connect("formatted_edit_users_group",
239 "/users_groups/{id}.{format}/edit",
241 "/users_groups/{id}.{format}/edit",
240 action="edit", conditions=dict(method=["GET"]))
242 action="edit", conditions=dict(method=["GET"]))
241 m.connect("users_group", "/users_groups/{id}",
243 m.connect("users_group", "/users_groups/{id}",
242 action="show", conditions=dict(method=["GET"]))
244 action="show", conditions=dict(method=["GET"]))
243 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
245 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
244 action="show", conditions=dict(method=["GET"]))
246 action="show", conditions=dict(method=["GET"]))
245
247
246 #EXTRAS USER ROUTES
248 #EXTRAS USER ROUTES
247 m.connect("users_group_perm", "/users_groups_perm/{id}",
249 m.connect("users_group_perm", "/users_groups_perm/{id}",
248 action="update_perm", conditions=dict(method=["PUT"]))
250 action="update_perm", conditions=dict(method=["PUT"]))
249
251
250 #ADMIN GROUP REST ROUTES
252 #ADMIN GROUP REST ROUTES
251 rmap.resource('group', 'groups',
253 rmap.resource('group', 'groups',
252 controller='admin/groups', path_prefix=ADMIN_PREFIX)
254 controller='admin/groups', path_prefix=ADMIN_PREFIX)
253
255
254 #ADMIN PERMISSIONS REST ROUTES
256 #ADMIN PERMISSIONS REST ROUTES
255 rmap.resource('permission', 'permissions',
257 rmap.resource('permission', 'permissions',
256 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
258 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
257
259
258 ##ADMIN LDAP SETTINGS
260 ##ADMIN LDAP SETTINGS
259 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
261 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
260 controller='admin/ldap_settings', action='ldap_settings',
262 controller='admin/ldap_settings', action='ldap_settings',
261 conditions=dict(method=["POST"]))
263 conditions=dict(method=["POST"]))
262
264
263 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
265 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
264 controller='admin/ldap_settings')
266 controller='admin/ldap_settings')
265
267
266 #ADMIN SETTINGS REST ROUTES
268 #ADMIN SETTINGS REST ROUTES
267 with rmap.submapper(path_prefix=ADMIN_PREFIX,
269 with rmap.submapper(path_prefix=ADMIN_PREFIX,
268 controller='admin/settings') as m:
270 controller='admin/settings') as m:
269 m.connect("admin_settings", "/settings",
271 m.connect("admin_settings", "/settings",
270 action="create", conditions=dict(method=["POST"]))
272 action="create", conditions=dict(method=["POST"]))
271 m.connect("admin_settings", "/settings",
273 m.connect("admin_settings", "/settings",
272 action="index", conditions=dict(method=["GET"]))
274 action="index", conditions=dict(method=["GET"]))
273 m.connect("formatted_admin_settings", "/settings.{format}",
275 m.connect("formatted_admin_settings", "/settings.{format}",
274 action="index", conditions=dict(method=["GET"]))
276 action="index", conditions=dict(method=["GET"]))
275 m.connect("admin_new_setting", "/settings/new",
277 m.connect("admin_new_setting", "/settings/new",
276 action="new", conditions=dict(method=["GET"]))
278 action="new", conditions=dict(method=["GET"]))
277 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
279 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
278 action="new", conditions=dict(method=["GET"]))
280 action="new", conditions=dict(method=["GET"]))
279 m.connect("/settings/{setting_id}",
281 m.connect("/settings/{setting_id}",
280 action="update", conditions=dict(method=["PUT"]))
282 action="update", conditions=dict(method=["PUT"]))
281 m.connect("/settings/{setting_id}",
283 m.connect("/settings/{setting_id}",
282 action="delete", conditions=dict(method=["DELETE"]))
284 action="delete", conditions=dict(method=["DELETE"]))
283 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
285 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
284 action="edit", conditions=dict(method=["GET"]))
286 action="edit", conditions=dict(method=["GET"]))
285 m.connect("formatted_admin_edit_setting",
287 m.connect("formatted_admin_edit_setting",
286 "/settings/{setting_id}.{format}/edit",
288 "/settings/{setting_id}.{format}/edit",
287 action="edit", conditions=dict(method=["GET"]))
289 action="edit", conditions=dict(method=["GET"]))
288 m.connect("admin_setting", "/settings/{setting_id}",
290 m.connect("admin_setting", "/settings/{setting_id}",
289 action="show", conditions=dict(method=["GET"]))
291 action="show", conditions=dict(method=["GET"]))
290 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
292 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
291 action="show", conditions=dict(method=["GET"]))
293 action="show", conditions=dict(method=["GET"]))
292 m.connect("admin_settings_my_account", "/my_account",
294 m.connect("admin_settings_my_account", "/my_account",
293 action="my_account", conditions=dict(method=["GET"]))
295 action="my_account", conditions=dict(method=["GET"]))
294 m.connect("admin_settings_my_account_update", "/my_account_update",
296 m.connect("admin_settings_my_account_update", "/my_account_update",
295 action="my_account_update", conditions=dict(method=["PUT"]))
297 action="my_account_update", conditions=dict(method=["PUT"]))
296 m.connect("admin_settings_create_repository", "/create_repository",
298 m.connect("admin_settings_create_repository", "/create_repository",
297 action="create_repository", conditions=dict(method=["GET"]))
299 action="create_repository", conditions=dict(method=["GET"]))
298 m.connect("admin_settings_my_repos", "/my_account/repos",
300 m.connect("admin_settings_my_repos", "/my_account/repos",
299 action="my_account_my_repos", conditions=dict(method=["GET"]))
301 action="my_account_my_repos", conditions=dict(method=["GET"]))
300 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
302 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
301 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
303 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
302
304
303 #NOTIFICATION REST ROUTES
305 #NOTIFICATION REST ROUTES
304 with rmap.submapper(path_prefix=ADMIN_PREFIX,
306 with rmap.submapper(path_prefix=ADMIN_PREFIX,
305 controller='admin/notifications') as m:
307 controller='admin/notifications') as m:
306 m.connect("notifications", "/notifications",
308 m.connect("notifications", "/notifications",
307 action="create", conditions=dict(method=["POST"]))
309 action="create", conditions=dict(method=["POST"]))
308 m.connect("notifications", "/notifications",
310 m.connect("notifications", "/notifications",
309 action="index", conditions=dict(method=["GET"]))
311 action="index", conditions=dict(method=["GET"]))
310 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
312 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
311 action="mark_all_read", conditions=dict(method=["GET"]))
313 action="mark_all_read", conditions=dict(method=["GET"]))
312 m.connect("formatted_notifications", "/notifications.{format}",
314 m.connect("formatted_notifications", "/notifications.{format}",
313 action="index", conditions=dict(method=["GET"]))
315 action="index", conditions=dict(method=["GET"]))
314 m.connect("new_notification", "/notifications/new",
316 m.connect("new_notification", "/notifications/new",
315 action="new", conditions=dict(method=["GET"]))
317 action="new", conditions=dict(method=["GET"]))
316 m.connect("formatted_new_notification", "/notifications/new.{format}",
318 m.connect("formatted_new_notification", "/notifications/new.{format}",
317 action="new", conditions=dict(method=["GET"]))
319 action="new", conditions=dict(method=["GET"]))
318 m.connect("/notification/{notification_id}",
320 m.connect("/notification/{notification_id}",
319 action="update", conditions=dict(method=["PUT"]))
321 action="update", conditions=dict(method=["PUT"]))
320 m.connect("/notification/{notification_id}",
322 m.connect("/notification/{notification_id}",
321 action="delete", conditions=dict(method=["DELETE"]))
323 action="delete", conditions=dict(method=["DELETE"]))
322 m.connect("edit_notification", "/notification/{notification_id}/edit",
324 m.connect("edit_notification", "/notification/{notification_id}/edit",
323 action="edit", conditions=dict(method=["GET"]))
325 action="edit", conditions=dict(method=["GET"]))
324 m.connect("formatted_edit_notification",
326 m.connect("formatted_edit_notification",
325 "/notification/{notification_id}.{format}/edit",
327 "/notification/{notification_id}.{format}/edit",
326 action="edit", conditions=dict(method=["GET"]))
328 action="edit", conditions=dict(method=["GET"]))
327 m.connect("notification", "/notification/{notification_id}",
329 m.connect("notification", "/notification/{notification_id}",
328 action="show", conditions=dict(method=["GET"]))
330 action="show", conditions=dict(method=["GET"]))
329 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
331 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
330 action="show", conditions=dict(method=["GET"]))
332 action="show", conditions=dict(method=["GET"]))
331
333
332 #ADMIN MAIN PAGES
334 #ADMIN MAIN PAGES
333 with rmap.submapper(path_prefix=ADMIN_PREFIX,
335 with rmap.submapper(path_prefix=ADMIN_PREFIX,
334 controller='admin/admin') as m:
336 controller='admin/admin') as m:
335 m.connect('admin_home', '', action='index')
337 m.connect('admin_home', '', action='index')
336 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
338 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
337 action='add_repo')
339 action='add_repo')
338
340
339 #==========================================================================
341 #==========================================================================
340 # API V2
342 # API V2
341 #==========================================================================
343 #==========================================================================
342 with rmap.submapper(path_prefix=ADMIN_PREFIX,
344 with rmap.submapper(path_prefix=ADMIN_PREFIX,
343 controller='api/api') as m:
345 controller='api/api') as m:
344 m.connect('api', '/api')
346 m.connect('api', '/api')
345
347
346 #USER JOURNAL
348 #USER JOURNAL
347 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
349 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
348 controller='journal', action='index')
350 controller='journal', action='index')
349 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
351 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
350 controller='journal', action='journal_rss')
352 controller='journal', action='journal_rss')
351 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
353 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
352 controller='journal', action='journal_atom')
354 controller='journal', action='journal_atom')
353
355
354 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
356 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
355 controller='journal', action="public_journal")
357 controller='journal', action="public_journal")
356
358
357 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
359 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
358 controller='journal', action="public_journal_rss")
360 controller='journal', action="public_journal_rss")
359
361
360 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
362 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
361 controller='journal', action="public_journal_rss")
363 controller='journal', action="public_journal_rss")
362
364
363 rmap.connect('public_journal_atom',
365 rmap.connect('public_journal_atom',
364 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
366 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
365 action="public_journal_atom")
367 action="public_journal_atom")
366
368
367 rmap.connect('public_journal_atom_old',
369 rmap.connect('public_journal_atom_old',
368 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
370 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
369 action="public_journal_atom")
371 action="public_journal_atom")
370
372
371 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
373 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
372 controller='journal', action='toggle_following',
374 controller='journal', action='toggle_following',
373 conditions=dict(method=["POST"]))
375 conditions=dict(method=["POST"]))
374
376
375 #SEARCH
377 #SEARCH
376 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
378 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
377 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
379 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
378 controller='search')
380 controller='search')
379
381
380 #LOGIN/LOGOUT/REGISTER/SIGN IN
382 #LOGIN/LOGOUT/REGISTER/SIGN IN
381 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
383 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
382 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
384 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
383 action='logout')
385 action='logout')
384
386
385 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
387 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
386 action='register')
388 action='register')
387
389
388 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
390 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
389 controller='login', action='password_reset')
391 controller='login', action='password_reset')
390
392
391 rmap.connect('reset_password_confirmation',
393 rmap.connect('reset_password_confirmation',
392 '%s/password_reset_confirmation' % ADMIN_PREFIX,
394 '%s/password_reset_confirmation' % ADMIN_PREFIX,
393 controller='login', action='password_reset_confirmation')
395 controller='login', action='password_reset_confirmation')
394
396
395 #FEEDS
397 #FEEDS
396 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
398 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
397 controller='feed', action='rss',
399 controller='feed', action='rss',
398 conditions=dict(function=check_repo))
400 conditions=dict(function=check_repo))
399
401
400 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
402 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
401 controller='feed', action='atom',
403 controller='feed', action='atom',
402 conditions=dict(function=check_repo))
404 conditions=dict(function=check_repo))
403
405
404 #==========================================================================
406 #==========================================================================
405 # REPOSITORY ROUTES
407 # REPOSITORY ROUTES
406 #==========================================================================
408 #==========================================================================
407 rmap.connect('summary_home', '/{repo_name:.*?}',
409 rmap.connect('summary_home', '/{repo_name:.*?}',
408 controller='summary',
410 controller='summary',
409 conditions=dict(function=check_repo))
411 conditions=dict(function=check_repo))
410
412
411 rmap.connect('repos_group_home', '/{group_name:.*}',
413 rmap.connect('repos_group_home', '/{group_name:.*}',
412 controller='admin/repos_groups', action="show_by_name",
414 controller='admin/repos_groups', action="show_by_name",
413 conditions=dict(function=check_group))
415 conditions=dict(function=check_group))
414
416
415 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
417 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
416 controller='changeset', revision='tip',
418 controller='changeset', revision='tip',
417 conditions=dict(function=check_repo))
419 conditions=dict(function=check_repo))
418
420
419 rmap.connect('changeset_comment',
421 rmap.connect('changeset_comment',
420 '/{repo_name:.*?}/changeset/{revision}/comment',
422 '/{repo_name:.*?}/changeset/{revision}/comment',
421 controller='changeset', revision='tip', action='comment',
423 controller='changeset', revision='tip', action='comment',
422 conditions=dict(function=check_repo))
424 conditions=dict(function=check_repo))
423
425
424 rmap.connect('changeset_comment_delete',
426 rmap.connect('changeset_comment_delete',
425 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
427 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
426 controller='changeset', action='delete_comment',
428 controller='changeset', action='delete_comment',
427 conditions=dict(function=check_repo, method=["DELETE"]))
429 conditions=dict(function=check_repo, method=["DELETE"]))
428
430
429 rmap.connect('raw_changeset_home',
431 rmap.connect('raw_changeset_home',
430 '/{repo_name:.*?}/raw-changeset/{revision}',
432 '/{repo_name:.*?}/raw-changeset/{revision}',
431 controller='changeset', action='raw_changeset',
433 controller='changeset', action='raw_changeset',
432 revision='tip', conditions=dict(function=check_repo))
434 revision='tip', conditions=dict(function=check_repo))
433
435
434 rmap.connect('compare_url',
436 rmap.connect('compare_url',
435 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}',
437 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}',
436 controller='compare', action='index',
438 controller='compare', action='index',
437 conditions=dict(function=check_repo),
439 conditions=dict(function=check_repo),
438 requirements=dict(
440 requirements=dict(
439 org_ref_type='(branch|book|tag|rev|org_ref_type)',
441 org_ref_type='(branch|book|tag|rev|org_ref_type)',
440 other_ref_type='(branch|book|tag|rev|other_ref_type)')
442 other_ref_type='(branch|book|tag|rev|other_ref_type)')
441 )
443 )
442
444
443 rmap.connect('pullrequest_home',
445 rmap.connect('pullrequest_home',
444 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
446 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
445 action='index', conditions=dict(function=check_repo,
447 action='index', conditions=dict(function=check_repo,
446 method=["GET"]))
448 method=["GET"]))
447
449
448 rmap.connect('pullrequest',
450 rmap.connect('pullrequest',
449 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
451 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
450 action='create', conditions=dict(function=check_repo,
452 action='create', conditions=dict(function=check_repo,
451 method=["POST"]))
453 method=["POST"]))
452
454
453 rmap.connect('pullrequest_show',
455 rmap.connect('pullrequest_show',
454 '/{repo_name:.*?}/pull-request/{pull_request_id}',
456 '/{repo_name:.*?}/pull-request/{pull_request_id}',
455 controller='pullrequests',
457 controller='pullrequests',
456 action='show', conditions=dict(function=check_repo,
458 action='show', conditions=dict(function=check_repo,
457 method=["GET"]))
459 method=["GET"]))
458 rmap.connect('pullrequest_update',
460 rmap.connect('pullrequest_update',
459 '/{repo_name:.*?}/pull-request/{pull_request_id}',
461 '/{repo_name:.*?}/pull-request/{pull_request_id}',
460 controller='pullrequests',
462 controller='pullrequests',
461 action='update', conditions=dict(function=check_repo,
463 action='update', conditions=dict(function=check_repo,
462 method=["PUT"]))
464 method=["PUT"]))
463
465
464 rmap.connect('pullrequest_show_all',
466 rmap.connect('pullrequest_show_all',
465 '/{repo_name:.*?}/pull-request',
467 '/{repo_name:.*?}/pull-request',
466 controller='pullrequests',
468 controller='pullrequests',
467 action='show_all', conditions=dict(function=check_repo,
469 action='show_all', conditions=dict(function=check_repo,
468 method=["GET"]))
470 method=["GET"]))
469
471
470 rmap.connect('pullrequest_comment',
472 rmap.connect('pullrequest_comment',
471 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
473 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
472 controller='pullrequests',
474 controller='pullrequests',
473 action='comment', conditions=dict(function=check_repo,
475 action='comment', conditions=dict(function=check_repo,
474 method=["POST"]))
476 method=["POST"]))
475
477
476 rmap.connect('pullrequest_comment_delete',
478 rmap.connect('pullrequest_comment_delete',
477 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
479 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
478 controller='pullrequests', action='delete_comment',
480 controller='pullrequests', action='delete_comment',
479 conditions=dict(function=check_repo, method=["DELETE"]))
481 conditions=dict(function=check_repo, method=["DELETE"]))
480
482
481 rmap.connect('summary_home', '/{repo_name:.*?}/summary',
483 rmap.connect('summary_home', '/{repo_name:.*?}/summary',
482 controller='summary', conditions=dict(function=check_repo))
484 controller='summary', conditions=dict(function=check_repo))
483
485
484 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
486 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
485 controller='shortlog', conditions=dict(function=check_repo))
487 controller='shortlog', conditions=dict(function=check_repo))
486
488
487 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
489 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
488 controller='branches', conditions=dict(function=check_repo))
490 controller='branches', conditions=dict(function=check_repo))
489
491
490 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
492 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
491 controller='tags', conditions=dict(function=check_repo))
493 controller='tags', conditions=dict(function=check_repo))
492
494
493 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
495 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
494 controller='bookmarks', conditions=dict(function=check_repo))
496 controller='bookmarks', conditions=dict(function=check_repo))
495
497
496 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
498 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
497 controller='changelog', conditions=dict(function=check_repo))
499 controller='changelog', conditions=dict(function=check_repo))
498
500
499 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
501 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
500 controller='changelog', action='changelog_details',
502 controller='changelog', action='changelog_details',
501 conditions=dict(function=check_repo))
503 conditions=dict(function=check_repo))
502
504
503 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
505 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
504 controller='files', revision='tip', f_path='',
506 controller='files', revision='tip', f_path='',
505 conditions=dict(function=check_repo))
507 conditions=dict(function=check_repo))
506
508
507 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
509 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
508 controller='files', action='diff', revision='tip', f_path='',
510 controller='files', action='diff', revision='tip', f_path='',
509 conditions=dict(function=check_repo))
511 conditions=dict(function=check_repo))
510
512
511 rmap.connect('files_rawfile_home',
513 rmap.connect('files_rawfile_home',
512 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
514 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
513 controller='files', action='rawfile', revision='tip',
515 controller='files', action='rawfile', revision='tip',
514 f_path='', conditions=dict(function=check_repo))
516 f_path='', conditions=dict(function=check_repo))
515
517
516 rmap.connect('files_raw_home',
518 rmap.connect('files_raw_home',
517 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
519 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
518 controller='files', action='raw', revision='tip', f_path='',
520 controller='files', action='raw', revision='tip', f_path='',
519 conditions=dict(function=check_repo))
521 conditions=dict(function=check_repo))
520
522
521 rmap.connect('files_annotate_home',
523 rmap.connect('files_annotate_home',
522 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
524 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
523 controller='files', action='index', revision='tip',
525 controller='files', action='index', revision='tip',
524 f_path='', annotate=True, conditions=dict(function=check_repo))
526 f_path='', annotate=True, conditions=dict(function=check_repo))
525
527
526 rmap.connect('files_edit_home',
528 rmap.connect('files_edit_home',
527 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
529 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
528 controller='files', action='edit', revision='tip',
530 controller='files', action='edit', revision='tip',
529 f_path='', conditions=dict(function=check_repo))
531 f_path='', conditions=dict(function=check_repo))
530
532
531 rmap.connect('files_add_home',
533 rmap.connect('files_add_home',
532 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
534 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
533 controller='files', action='add', revision='tip',
535 controller='files', action='add', revision='tip',
534 f_path='', conditions=dict(function=check_repo))
536 f_path='', conditions=dict(function=check_repo))
535
537
536 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
538 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
537 controller='files', action='archivefile',
539 controller='files', action='archivefile',
538 conditions=dict(function=check_repo))
540 conditions=dict(function=check_repo))
539
541
540 rmap.connect('files_nodelist_home',
542 rmap.connect('files_nodelist_home',
541 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
543 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
542 controller='files', action='nodelist',
544 controller='files', action='nodelist',
543 conditions=dict(function=check_repo))
545 conditions=dict(function=check_repo))
544
546
545 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
547 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
546 controller='settings', action="delete",
548 controller='settings', action="delete",
547 conditions=dict(method=["DELETE"], function=check_repo))
549 conditions=dict(method=["DELETE"], function=check_repo))
548
550
549 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
551 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
550 controller='settings', action="update",
552 controller='settings', action="update",
551 conditions=dict(method=["PUT"], function=check_repo))
553 conditions=dict(method=["PUT"], function=check_repo))
552
554
553 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
555 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
554 controller='settings', action='index',
556 controller='settings', action='index',
555 conditions=dict(function=check_repo))
557 conditions=dict(function=check_repo))
556
558
557 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
559 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
558 controller='forks', action='fork_create',
560 controller='forks', action='fork_create',
559 conditions=dict(function=check_repo, method=["POST"]))
561 conditions=dict(function=check_repo, method=["POST"]))
560
562
561 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
563 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
562 controller='forks', action='fork',
564 controller='forks', action='fork',
563 conditions=dict(function=check_repo))
565 conditions=dict(function=check_repo))
564
566
565 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
567 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
566 controller='forks', action='forks',
568 controller='forks', action='forks',
567 conditions=dict(function=check_repo))
569 conditions=dict(function=check_repo))
568
570
569 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
571 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
570 controller='followers', action='followers',
572 controller='followers', action='followers',
571 conditions=dict(function=check_repo))
573 conditions=dict(function=check_repo))
572
574
573 return rmap
575 return rmap
@@ -1,487 +1,509 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos
3 rhodecode.controllers.admin.repos
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Repositories controller for RhodeCode
6 Repositories controller for RhodeCode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from webob.exc import HTTPInternalServerError
31 from webob.exc import HTTPInternalServerError
32 from pylons import request, session, tmpl_context as c, url
32 from pylons import request, session, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.exc import IntegrityError
35 from sqlalchemy.exc import IntegrityError
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
43 from rhodecode.lib.helpers import get_token
43 from rhodecode.lib.helpers import get_token
44 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
45 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
45 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
46 from rhodecode.model.forms import RepoForm
46 from rhodecode.model.forms import RepoForm
47 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.scm import ScmModel
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.lib.compat import json
49 from rhodecode.lib.compat import json
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class ReposController(BaseController):
54 class ReposController(BaseController):
55 """
55 """
56 REST Controller styled on the Atom Publishing Protocol"""
56 REST Controller styled on the Atom Publishing Protocol"""
57 # To properly map this controller, ensure your config/routing.py
57 # To properly map this controller, ensure your config/routing.py
58 # file has a resource setup:
58 # file has a resource setup:
59 # map.resource('repo', 'repos')
59 # map.resource('repo', 'repos')
60
60
61 @LoginRequired()
61 @LoginRequired()
62 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
62 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
63 def __before__(self):
63 def __before__(self):
64 c.admin_user = session.get('admin_user')
64 c.admin_user = session.get('admin_user')
65 c.admin_username = session.get('admin_username')
65 c.admin_username = session.get('admin_username')
66 super(ReposController, self).__before__()
66 super(ReposController, self).__before__()
67
67
68 def __load_defaults(self):
68 def __load_defaults(self):
69 c.repo_groups = RepoGroup.groups_choices()
69 c.repo_groups = RepoGroup.groups_choices()
70 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
70 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
71
71
72 repo_model = RepoModel()
72 repo_model = RepoModel()
73 c.users_array = repo_model.get_users_js()
73 c.users_array = repo_model.get_users_js()
74 c.users_groups_array = repo_model.get_users_groups_js()
74 c.users_groups_array = repo_model.get_users_groups_js()
75 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
75 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
76 c.landing_revs_choices = choices
76 c.landing_revs_choices = choices
77
77
78 def __load_data(self, repo_name=None):
78 def __load_data(self, repo_name=None):
79 """
79 """
80 Load defaults settings for edit, and update
80 Load defaults settings for edit, and update
81
81
82 :param repo_name:
82 :param repo_name:
83 """
83 """
84 self.__load_defaults()
84 self.__load_defaults()
85
85
86 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
86 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
87 repo = db_repo.scm_instance
87 repo = db_repo.scm_instance
88
88
89 if c.repo_info is None:
89 if c.repo_info is None:
90 h.flash(_('%s repository is not mapped to db perhaps'
90 h.flash(_('%s repository is not mapped to db perhaps'
91 ' it was created or renamed from the filesystem'
91 ' it was created or renamed from the filesystem'
92 ' please run the application again'
92 ' please run the application again'
93 ' in order to rescan repositories') % repo_name,
93 ' in order to rescan repositories') % repo_name,
94 category='error')
94 category='error')
95
95
96 return redirect(url('repos'))
96 return redirect(url('repos'))
97
97
98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
99 c.landing_revs_choices = choices
99 c.landing_revs_choices = choices
100
100
101 c.default_user_id = User.get_by_username('default').user_id
101 c.default_user_id = User.get_by_username('default').user_id
102 c.in_public_journal = UserFollowing.query()\
102 c.in_public_journal = UserFollowing.query()\
103 .filter(UserFollowing.user_id == c.default_user_id)\
103 .filter(UserFollowing.user_id == c.default_user_id)\
104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
105
105
106 if c.repo_info.stats:
106 if c.repo_info.stats:
107 # this is on what revision we ended up so we add +1 for count
107 # this is on what revision we ended up so we add +1 for count
108 last_rev = c.repo_info.stats.stat_on_revision + 1
108 last_rev = c.repo_info.stats.stat_on_revision + 1
109 else:
109 else:
110 last_rev = 0
110 last_rev = 0
111 c.stats_revision = last_rev
111 c.stats_revision = last_rev
112
112
113 c.repo_last_rev = repo.count() if repo.revisions else 0
113 c.repo_last_rev = repo.count() if repo.revisions else 0
114
114
115 if last_rev == 0 or c.repo_last_rev == 0:
115 if last_rev == 0 or c.repo_last_rev == 0:
116 c.stats_percentage = 0
116 c.stats_percentage = 0
117 else:
117 else:
118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
119 c.repo_last_rev) * 100)
119 c.repo_last_rev) * 100)
120
120
121 defaults = RepoModel()._get_defaults(repo_name)
121 defaults = RepoModel()._get_defaults(repo_name)
122
122
123 c.repos_list = [('', _('--REMOVE FORK--'))]
123 c.repos_list = [('', _('--REMOVE FORK--'))]
124 c.repos_list += [(x.repo_id, x.repo_name) for x in
124 c.repos_list += [(x.repo_id, x.repo_name) for x in
125 Repository.query().order_by(Repository.repo_name).all()
125 Repository.query().order_by(Repository.repo_name).all()
126 if x.repo_id != c.repo_info.repo_id]
126 if x.repo_id != c.repo_info.repo_id]
127
127
128 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
128 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
129 return defaults
129 return defaults
130
130
131 @HasPermissionAllDecorator('hg.admin')
131 @HasPermissionAllDecorator('hg.admin')
132 def index(self, format='html'):
132 def index(self, format='html'):
133 """GET /repos: All items in the collection"""
133 """GET /repos: All items in the collection"""
134 # url('repos')
134 # url('repos')
135
135
136 c.repos_list = Repository.query()\
136 c.repos_list = Repository.query()\
137 .order_by(Repository.repo_name)\
137 .order_by(Repository.repo_name)\
138 .all()
138 .all()
139
139
140 repos_data = []
140 repos_data = []
141 total_records = len(c.repos_list)
141 total_records = len(c.repos_list)
142
142
143 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
143 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
144 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
144 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
145
145
146 quick_menu = lambda repo_name: (template.get_def("quick_menu")
146 quick_menu = lambda repo_name: (template.get_def("quick_menu")
147 .render(repo_name, _=_, h=h, c=c))
147 .render(repo_name, _=_, h=h, c=c))
148 repo_lnk = lambda name, rtype, private, fork_of: (
148 repo_lnk = lambda name, rtype, private, fork_of: (
149 template.get_def("repo_name")
149 template.get_def("repo_name")
150 .render(name, rtype, private, fork_of, short_name=False,
150 .render(name, rtype, private, fork_of, short_name=False,
151 admin=True, _=_, h=h, c=c))
151 admin=True, _=_, h=h, c=c))
152
152
153 repo_actions = lambda repo_name: (template.get_def("repo_actions")
153 repo_actions = lambda repo_name: (template.get_def("repo_actions")
154 .render(repo_name, _=_, h=h, c=c))
154 .render(repo_name, _=_, h=h, c=c))
155
155
156 for repo in c.repos_list:
156 for repo in c.repos_list:
157 repos_data.append({
157 repos_data.append({
158 "menu": quick_menu(repo.repo_name),
158 "menu": quick_menu(repo.repo_name),
159 "raw_name": repo.repo_name,
159 "raw_name": repo.repo_name,
160 "name": repo_lnk(repo.repo_name, repo.repo_type,
160 "name": repo_lnk(repo.repo_name, repo.repo_type,
161 repo.private, repo.fork),
161 repo.private, repo.fork),
162 "desc": repo.description,
162 "desc": repo.description,
163 "owner": repo.user.username,
163 "owner": repo.user.username,
164 "action": repo_actions(repo.repo_name),
164 "action": repo_actions(repo.repo_name),
165 })
165 })
166
166
167 c.data = json.dumps({
167 c.data = json.dumps({
168 "totalRecords": total_records,
168 "totalRecords": total_records,
169 "startIndex": 0,
169 "startIndex": 0,
170 "sort": "name",
170 "sort": "name",
171 "dir": "asc",
171 "dir": "asc",
172 "records": repos_data
172 "records": repos_data
173 })
173 })
174
174
175 return render('admin/repos/repos.html')
175 return render('admin/repos/repos.html')
176
176
177 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
177 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
178 def create(self):
178 def create(self):
179 """
179 """
180 POST /repos: Create a new item"""
180 POST /repos: Create a new item"""
181 # url('repos')
181 # url('repos')
182
182
183 self.__load_defaults()
183 self.__load_defaults()
184 form_result = {}
184 form_result = {}
185 try:
185 try:
186 form_result = RepoForm(repo_groups=c.repo_groups_choices,
186 form_result = RepoForm(repo_groups=c.repo_groups_choices,
187 landing_revs=c.landing_revs_choices)()\
187 landing_revs=c.landing_revs_choices)()\
188 .to_python(dict(request.POST))
188 .to_python(dict(request.POST))
189 new_repo = RepoModel().create(form_result,
189 new_repo = RepoModel().create(form_result,
190 self.rhodecode_user.user_id)
190 self.rhodecode_user.user_id)
191 if form_result['clone_uri']:
191 if form_result['clone_uri']:
192 h.flash(_('created repository %s from %s') \
192 h.flash(_('created repository %s from %s') \
193 % (form_result['repo_name'], form_result['clone_uri']),
193 % (form_result['repo_name'], form_result['clone_uri']),
194 category='success')
194 category='success')
195 else:
195 else:
196 h.flash(_('created repository %s') % form_result['repo_name'],
196 h.flash(_('created repository %s') % form_result['repo_name'],
197 category='success')
197 category='success')
198
198
199 if request.POST.get('user_created'):
199 if request.POST.get('user_created'):
200 # created by regular non admin user
200 # created by regular non admin user
201 action_logger(self.rhodecode_user, 'user_created_repo',
201 action_logger(self.rhodecode_user, 'user_created_repo',
202 form_result['repo_name_full'], self.ip_addr,
202 form_result['repo_name_full'], self.ip_addr,
203 self.sa)
203 self.sa)
204 else:
204 else:
205 action_logger(self.rhodecode_user, 'admin_created_repo',
205 action_logger(self.rhodecode_user, 'admin_created_repo',
206 form_result['repo_name_full'], self.ip_addr,
206 form_result['repo_name_full'], self.ip_addr,
207 self.sa)
207 self.sa)
208 Session().commit()
208 Session().commit()
209 except formencode.Invalid, errors:
209 except formencode.Invalid, errors:
210
210
211 c.new_repo = errors.value['repo_name']
211 c.new_repo = errors.value['repo_name']
212
212
213 if request.POST.get('user_created'):
213 if request.POST.get('user_created'):
214 r = render('admin/repos/repo_add_create_repository.html')
214 r = render('admin/repos/repo_add_create_repository.html')
215 else:
215 else:
216 r = render('admin/repos/repo_add.html')
216 r = render('admin/repos/repo_add.html')
217
217
218 return htmlfill.render(
218 return htmlfill.render(
219 r,
219 r,
220 defaults=errors.value,
220 defaults=errors.value,
221 errors=errors.error_dict or {},
221 errors=errors.error_dict or {},
222 prefix_error=False,
222 prefix_error=False,
223 encoding="UTF-8")
223 encoding="UTF-8")
224
224
225 except Exception:
225 except Exception:
226 log.error(traceback.format_exc())
226 log.error(traceback.format_exc())
227 msg = _('error occurred during creation of repository %s') \
227 msg = _('error occurred during creation of repository %s') \
228 % form_result.get('repo_name')
228 % form_result.get('repo_name')
229 h.flash(msg, category='error')
229 h.flash(msg, category='error')
230 return redirect(url('repos'))
230 return redirect(url('repos'))
231 #redirect to our new repo !
231 #redirect to our new repo !
232 return redirect(url('summary_home', repo_name=new_repo.repo_name))
232 return redirect(url('summary_home', repo_name=new_repo.repo_name))
233
233
234 @HasPermissionAllDecorator('hg.admin')
234 @HasPermissionAllDecorator('hg.admin')
235 def new(self, format='html'):
235 def new(self, format='html'):
236 """GET /repos/new: Form to create a new item"""
236 """GET /repos/new: Form to create a new item"""
237 new_repo = request.GET.get('repo', '')
237 new_repo = request.GET.get('repo', '')
238 c.new_repo = repo_name_slug(new_repo)
238 c.new_repo = repo_name_slug(new_repo)
239 self.__load_defaults()
239 self.__load_defaults()
240 return render('admin/repos/repo_add.html')
240 return render('admin/repos/repo_add.html')
241
241
242 @HasPermissionAllDecorator('hg.admin')
242 @HasPermissionAllDecorator('hg.admin')
243 def update(self, repo_name):
243 def update(self, repo_name):
244 """
244 """
245 PUT /repos/repo_name: Update an existing item"""
245 PUT /repos/repo_name: Update an existing item"""
246 # Forms posted to this method should contain a hidden field:
246 # Forms posted to this method should contain a hidden field:
247 # <input type="hidden" name="_method" value="PUT" />
247 # <input type="hidden" name="_method" value="PUT" />
248 # Or using helpers:
248 # Or using helpers:
249 # h.form(url('repo', repo_name=ID),
249 # h.form(url('repo', repo_name=ID),
250 # method='put')
250 # method='put')
251 # url('repo', repo_name=ID)
251 # url('repo', repo_name=ID)
252 self.__load_defaults()
252 self.__load_defaults()
253 repo_model = RepoModel()
253 repo_model = RepoModel()
254 changed_name = repo_name
254 changed_name = repo_name
255 #override the choices with extracted revisions !
255 #override the choices with extracted revisions !
256 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
256 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
257 c.landing_revs_choices = choices
257 c.landing_revs_choices = choices
258
258
259 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
259 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
260 repo_groups=c.repo_groups_choices,
260 repo_groups=c.repo_groups_choices,
261 landing_revs=c.landing_revs_choices)()
261 landing_revs=c.landing_revs_choices)()
262 try:
262 try:
263 form_result = _form.to_python(dict(request.POST))
263 form_result = _form.to_python(dict(request.POST))
264 repo = repo_model.update(repo_name, form_result)
264 repo = repo_model.update(repo_name, form_result)
265 invalidate_cache('get_repo_cached_%s' % repo_name)
265 invalidate_cache('get_repo_cached_%s' % repo_name)
266 h.flash(_('Repository %s updated successfully') % repo_name,
266 h.flash(_('Repository %s updated successfully') % repo_name,
267 category='success')
267 category='success')
268 changed_name = repo.repo_name
268 changed_name = repo.repo_name
269 action_logger(self.rhodecode_user, 'admin_updated_repo',
269 action_logger(self.rhodecode_user, 'admin_updated_repo',
270 changed_name, self.ip_addr, self.sa)
270 changed_name, self.ip_addr, self.sa)
271 Session().commit()
271 Session().commit()
272 except formencode.Invalid, errors:
272 except formencode.Invalid, errors:
273 defaults = self.__load_data(repo_name)
273 defaults = self.__load_data(repo_name)
274 defaults.update(errors.value)
274 defaults.update(errors.value)
275 return htmlfill.render(
275 return htmlfill.render(
276 render('admin/repos/repo_edit.html'),
276 render('admin/repos/repo_edit.html'),
277 defaults=defaults,
277 defaults=defaults,
278 errors=errors.error_dict or {},
278 errors=errors.error_dict or {},
279 prefix_error=False,
279 prefix_error=False,
280 encoding="UTF-8")
280 encoding="UTF-8")
281
281
282 except Exception:
282 except Exception:
283 log.error(traceback.format_exc())
283 log.error(traceback.format_exc())
284 h.flash(_('error occurred during update of repository %s') \
284 h.flash(_('error occurred during update of repository %s') \
285 % repo_name, category='error')
285 % repo_name, category='error')
286 return redirect(url('edit_repo', repo_name=changed_name))
286 return redirect(url('edit_repo', repo_name=changed_name))
287
287
288 @HasPermissionAllDecorator('hg.admin')
288 @HasPermissionAllDecorator('hg.admin')
289 def delete(self, repo_name):
289 def delete(self, repo_name):
290 """
290 """
291 DELETE /repos/repo_name: Delete an existing item"""
291 DELETE /repos/repo_name: Delete an existing item"""
292 # Forms posted to this method should contain a hidden field:
292 # Forms posted to this method should contain a hidden field:
293 # <input type="hidden" name="_method" value="DELETE" />
293 # <input type="hidden" name="_method" value="DELETE" />
294 # Or using helpers:
294 # Or using helpers:
295 # h.form(url('repo', repo_name=ID),
295 # h.form(url('repo', repo_name=ID),
296 # method='delete')
296 # method='delete')
297 # url('repo', repo_name=ID)
297 # url('repo', repo_name=ID)
298
298
299 repo_model = RepoModel()
299 repo_model = RepoModel()
300 repo = repo_model.get_by_repo_name(repo_name)
300 repo = repo_model.get_by_repo_name(repo_name)
301 if not repo:
301 if not repo:
302 h.flash(_('%s repository is not mapped to db perhaps'
302 h.flash(_('%s repository is not mapped to db perhaps'
303 ' it was moved or renamed from the filesystem'
303 ' it was moved or renamed from the filesystem'
304 ' please run the application again'
304 ' please run the application again'
305 ' in order to rescan repositories') % repo_name,
305 ' in order to rescan repositories') % repo_name,
306 category='error')
306 category='error')
307
307
308 return redirect(url('repos'))
308 return redirect(url('repos'))
309 try:
309 try:
310 action_logger(self.rhodecode_user, 'admin_deleted_repo',
310 action_logger(self.rhodecode_user, 'admin_deleted_repo',
311 repo_name, self.ip_addr, self.sa)
311 repo_name, self.ip_addr, self.sa)
312 repo_model.delete(repo)
312 repo_model.delete(repo)
313 invalidate_cache('get_repo_cached_%s' % repo_name)
313 invalidate_cache('get_repo_cached_%s' % repo_name)
314 h.flash(_('deleted repository %s') % repo_name, category='success')
314 h.flash(_('deleted repository %s') % repo_name, category='success')
315 Session().commit()
315 Session().commit()
316 except IntegrityError, e:
316 except IntegrityError, e:
317 if e.message.find('repositories_fork_id_fkey') != -1:
317 if e.message.find('repositories_fork_id_fkey') != -1:
318 log.error(traceback.format_exc())
318 log.error(traceback.format_exc())
319 h.flash(_('Cannot delete %s it still contains attached '
319 h.flash(_('Cannot delete %s it still contains attached '
320 'forks') % repo_name,
320 'forks') % repo_name,
321 category='warning')
321 category='warning')
322 else:
322 else:
323 log.error(traceback.format_exc())
323 log.error(traceback.format_exc())
324 h.flash(_('An error occurred during '
324 h.flash(_('An error occurred during '
325 'deletion of %s') % repo_name,
325 'deletion of %s') % repo_name,
326 category='error')
326 category='error')
327
327
328 except Exception, e:
328 except Exception, e:
329 log.error(traceback.format_exc())
329 log.error(traceback.format_exc())
330 h.flash(_('An error occurred during deletion of %s') % repo_name,
330 h.flash(_('An error occurred during deletion of %s') % repo_name,
331 category='error')
331 category='error')
332
332
333 return redirect(url('repos'))
333 return redirect(url('repos'))
334
334
335 @HasRepoPermissionAllDecorator('repository.admin')
335 @HasRepoPermissionAllDecorator('repository.admin')
336 def delete_perm_user(self, repo_name):
336 def delete_perm_user(self, repo_name):
337 """
337 """
338 DELETE an existing repository permission user
338 DELETE an existing repository permission user
339
339
340 :param repo_name:
340 :param repo_name:
341 """
341 """
342 try:
342 try:
343 RepoModel().revoke_user_permission(repo=repo_name,
343 RepoModel().revoke_user_permission(repo=repo_name,
344 user=request.POST['user_id'])
344 user=request.POST['user_id'])
345 Session().commit()
345 Session().commit()
346 except Exception:
346 except Exception:
347 log.error(traceback.format_exc())
347 log.error(traceback.format_exc())
348 h.flash(_('An error occurred during deletion of repository user'),
348 h.flash(_('An error occurred during deletion of repository user'),
349 category='error')
349 category='error')
350 raise HTTPInternalServerError()
350 raise HTTPInternalServerError()
351
351
352 @HasRepoPermissionAllDecorator('repository.admin')
352 @HasRepoPermissionAllDecorator('repository.admin')
353 def delete_perm_users_group(self, repo_name):
353 def delete_perm_users_group(self, repo_name):
354 """
354 """
355 DELETE an existing repository permission users group
355 DELETE an existing repository permission users group
356
356
357 :param repo_name:
357 :param repo_name:
358 """
358 """
359
359
360 try:
360 try:
361 RepoModel().revoke_users_group_permission(
361 RepoModel().revoke_users_group_permission(
362 repo=repo_name, group_name=request.POST['users_group_id']
362 repo=repo_name, group_name=request.POST['users_group_id']
363 )
363 )
364 Session().commit()
364 Session().commit()
365 except Exception:
365 except Exception:
366 log.error(traceback.format_exc())
366 log.error(traceback.format_exc())
367 h.flash(_('An error occurred during deletion of repository'
367 h.flash(_('An error occurred during deletion of repository'
368 ' users groups'),
368 ' users groups'),
369 category='error')
369 category='error')
370 raise HTTPInternalServerError()
370 raise HTTPInternalServerError()
371
371
372 @HasPermissionAllDecorator('hg.admin')
372 @HasPermissionAllDecorator('hg.admin')
373 def repo_stats(self, repo_name):
373 def repo_stats(self, repo_name):
374 """
374 """
375 DELETE an existing repository statistics
375 DELETE an existing repository statistics
376
376
377 :param repo_name:
377 :param repo_name:
378 """
378 """
379
379
380 try:
380 try:
381 RepoModel().delete_stats(repo_name)
381 RepoModel().delete_stats(repo_name)
382 Session().commit()
382 Session().commit()
383 except Exception, e:
383 except Exception, e:
384 log.error(traceback.format_exc())
384 h.flash(_('An error occurred during deletion of repository stats'),
385 h.flash(_('An error occurred during deletion of repository stats'),
385 category='error')
386 category='error')
386 return redirect(url('edit_repo', repo_name=repo_name))
387 return redirect(url('edit_repo', repo_name=repo_name))
387
388
388 @HasPermissionAllDecorator('hg.admin')
389 @HasPermissionAllDecorator('hg.admin')
389 def repo_cache(self, repo_name):
390 def repo_cache(self, repo_name):
390 """
391 """
391 INVALIDATE existing repository cache
392 INVALIDATE existing repository cache
392
393
393 :param repo_name:
394 :param repo_name:
394 """
395 """
395
396
396 try:
397 try:
397 ScmModel().mark_for_invalidation(repo_name)
398 ScmModel().mark_for_invalidation(repo_name)
398 Session().commit()
399 Session().commit()
399 except Exception, e:
400 except Exception, e:
401 log.error(traceback.format_exc())
400 h.flash(_('An error occurred during cache invalidation'),
402 h.flash(_('An error occurred during cache invalidation'),
401 category='error')
403 category='error')
402 return redirect(url('edit_repo', repo_name=repo_name))
404 return redirect(url('edit_repo', repo_name=repo_name))
403
405
404 @HasPermissionAllDecorator('hg.admin')
406 @HasPermissionAllDecorator('hg.admin')
407 def repo_locking(self, repo_name):
408 """
409 Unlock repository when it is locked !
410
411 :param repo_name:
412 """
413
414 try:
415 repo = Repository.get_by_repo_name(repo_name)
416 if request.POST.get('set_lock'):
417 Repository.lock(repo, c.rhodecode_user.user_id)
418 elif request.POST.get('set_unlock'):
419 Repository.unlock(repo)
420 except Exception, e:
421 log.error(traceback.format_exc())
422 h.flash(_('An error occurred during unlocking'),
423 category='error')
424 return redirect(url('edit_repo', repo_name=repo_name))
425
426 @HasPermissionAllDecorator('hg.admin')
405 def repo_public_journal(self, repo_name):
427 def repo_public_journal(self, repo_name):
406 """
428 """
407 Set's this repository to be visible in public journal,
429 Set's this repository to be visible in public journal,
408 in other words assing default user to follow this repo
430 in other words assing default user to follow this repo
409
431
410 :param repo_name:
432 :param repo_name:
411 """
433 """
412
434
413 cur_token = request.POST.get('auth_token')
435 cur_token = request.POST.get('auth_token')
414 token = get_token()
436 token = get_token()
415 if cur_token == token:
437 if cur_token == token:
416 try:
438 try:
417 repo_id = Repository.get_by_repo_name(repo_name).repo_id
439 repo_id = Repository.get_by_repo_name(repo_name).repo_id
418 user_id = User.get_by_username('default').user_id
440 user_id = User.get_by_username('default').user_id
419 self.scm_model.toggle_following_repo(repo_id, user_id)
441 self.scm_model.toggle_following_repo(repo_id, user_id)
420 h.flash(_('Updated repository visibility in public journal'),
442 h.flash(_('Updated repository visibility in public journal'),
421 category='success')
443 category='success')
422 Session().commit()
444 Session().commit()
423 except:
445 except:
424 h.flash(_('An error occurred during setting this'
446 h.flash(_('An error occurred during setting this'
425 ' repository in public journal'),
447 ' repository in public journal'),
426 category='error')
448 category='error')
427
449
428 else:
450 else:
429 h.flash(_('Token mismatch'), category='error')
451 h.flash(_('Token mismatch'), category='error')
430 return redirect(url('edit_repo', repo_name=repo_name))
452 return redirect(url('edit_repo', repo_name=repo_name))
431
453
432 @HasPermissionAllDecorator('hg.admin')
454 @HasPermissionAllDecorator('hg.admin')
433 def repo_pull(self, repo_name):
455 def repo_pull(self, repo_name):
434 """
456 """
435 Runs task to update given repository with remote changes,
457 Runs task to update given repository with remote changes,
436 ie. make pull on remote location
458 ie. make pull on remote location
437
459
438 :param repo_name:
460 :param repo_name:
439 """
461 """
440 try:
462 try:
441 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
463 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
442 h.flash(_('Pulled from remote location'), category='success')
464 h.flash(_('Pulled from remote location'), category='success')
443 except Exception, e:
465 except Exception, e:
444 h.flash(_('An error occurred during pull from remote location'),
466 h.flash(_('An error occurred during pull from remote location'),
445 category='error')
467 category='error')
446
468
447 return redirect(url('edit_repo', repo_name=repo_name))
469 return redirect(url('edit_repo', repo_name=repo_name))
448
470
449 @HasPermissionAllDecorator('hg.admin')
471 @HasPermissionAllDecorator('hg.admin')
450 def repo_as_fork(self, repo_name):
472 def repo_as_fork(self, repo_name):
451 """
473 """
452 Mark given repository as a fork of another
474 Mark given repository as a fork of another
453
475
454 :param repo_name:
476 :param repo_name:
455 """
477 """
456 try:
478 try:
457 fork_id = request.POST.get('id_fork_of')
479 fork_id = request.POST.get('id_fork_of')
458 repo = ScmModel().mark_as_fork(repo_name, fork_id,
480 repo = ScmModel().mark_as_fork(repo_name, fork_id,
459 self.rhodecode_user.username)
481 self.rhodecode_user.username)
460 fork = repo.fork.repo_name if repo.fork else _('Nothing')
482 fork = repo.fork.repo_name if repo.fork else _('Nothing')
461 Session().commit()
483 Session().commit()
462 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
484 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
463 category='success')
485 category='success')
464 except Exception, e:
486 except Exception, e:
465 log.error(traceback.format_exc())
487 log.error(traceback.format_exc())
466 h.flash(_('An error occurred during this operation'),
488 h.flash(_('An error occurred during this operation'),
467 category='error')
489 category='error')
468
490
469 return redirect(url('edit_repo', repo_name=repo_name))
491 return redirect(url('edit_repo', repo_name=repo_name))
470
492
471 @HasPermissionAllDecorator('hg.admin')
493 @HasPermissionAllDecorator('hg.admin')
472 def show(self, repo_name, format='html'):
494 def show(self, repo_name, format='html'):
473 """GET /repos/repo_name: Show a specific item"""
495 """GET /repos/repo_name: Show a specific item"""
474 # url('repo', repo_name=ID)
496 # url('repo', repo_name=ID)
475
497
476 @HasPermissionAllDecorator('hg.admin')
498 @HasPermissionAllDecorator('hg.admin')
477 def edit(self, repo_name, format='html'):
499 def edit(self, repo_name, format='html'):
478 """GET /repos/repo_name/edit: Form to edit an existing item"""
500 """GET /repos/repo_name/edit: Form to edit an existing item"""
479 # url('edit_repo', repo_name=ID)
501 # url('edit_repo', repo_name=ID)
480 defaults = self.__load_data(repo_name)
502 defaults = self.__load_data(repo_name)
481
503
482 return htmlfill.render(
504 return htmlfill.render(
483 render('admin/repos/repo_edit.html'),
505 render('admin/repos/repo_edit.html'),
484 defaults=defaults,
506 defaults=defaults,
485 encoding="UTF-8",
507 encoding="UTF-8",
486 force_defaults=False
508 force_defaults=False
487 )
509 )
@@ -1,823 +1,823 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import random
26 import random
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import hashlib
29 import hashlib
30
30
31 from tempfile import _RandomNameSequence
31 from tempfile import _RandomNameSequence
32 from decorator import decorator
32 from decorator import decorator
33
33
34 from pylons import config, url, request
34 from pylons import config, url, request
35 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode import __platform__, is_windows, is_unix
38 from rhodecode import __platform__, is_windows, is_unix
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40
40
41 from rhodecode.lib.utils2 import str2bool, safe_unicode
41 from rhodecode.lib.utils2 import str2bool, safe_unicode
42 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
42 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
43 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
43 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
44 from rhodecode.lib.auth_ldap import AuthLdap
44 from rhodecode.lib.auth_ldap import AuthLdap
45
45
46 from rhodecode.model import meta
46 from rhodecode.model import meta
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.model.db import Permission, RhodeCodeSetting, User
48 from rhodecode.model.db import Permission, RhodeCodeSetting, User
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class PasswordGenerator(object):
53 class PasswordGenerator(object):
54 """
54 """
55 This is a simple class for generating password from different sets of
55 This is a simple class for generating password from different sets of
56 characters
56 characters
57 usage::
57 usage::
58
58
59 passwd_gen = PasswordGenerator()
59 passwd_gen = PasswordGenerator()
60 #print 8-letter password containing only big and small letters
60 #print 8-letter password containing only big and small letters
61 of alphabet
61 of alphabet
62 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
62 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
63 """
63 """
64 ALPHABETS_NUM = r'''1234567890'''
64 ALPHABETS_NUM = r'''1234567890'''
65 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
65 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
66 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
66 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
67 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
67 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
68 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
68 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
69 + ALPHABETS_NUM + ALPHABETS_SPECIAL
69 + ALPHABETS_NUM + ALPHABETS_SPECIAL
70 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
70 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
71 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
71 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
72 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
72 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
73 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
73 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
74
74
75 def __init__(self, passwd=''):
75 def __init__(self, passwd=''):
76 self.passwd = passwd
76 self.passwd = passwd
77
77
78 def gen_password(self, length, type_=None):
78 def gen_password(self, length, type_=None):
79 if type_ is None:
79 if type_ is None:
80 type_ = self.ALPHABETS_FULL
80 type_ = self.ALPHABETS_FULL
81 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
81 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
82 return self.passwd
82 return self.passwd
83
83
84
84
85 class RhodeCodeCrypto(object):
85 class RhodeCodeCrypto(object):
86
86
87 @classmethod
87 @classmethod
88 def hash_string(cls, str_):
88 def hash_string(cls, str_):
89 """
89 """
90 Cryptographic function used for password hashing based on pybcrypt
90 Cryptographic function used for password hashing based on pybcrypt
91 or pycrypto in windows
91 or pycrypto in windows
92
92
93 :param password: password to hash
93 :param password: password to hash
94 """
94 """
95 if is_windows:
95 if is_windows:
96 from hashlib import sha256
96 from hashlib import sha256
97 return sha256(str_).hexdigest()
97 return sha256(str_).hexdigest()
98 elif is_unix:
98 elif is_unix:
99 import bcrypt
99 import bcrypt
100 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
100 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
101 else:
101 else:
102 raise Exception('Unknown or unsupported platform %s' \
102 raise Exception('Unknown or unsupported platform %s' \
103 % __platform__)
103 % __platform__)
104
104
105 @classmethod
105 @classmethod
106 def hash_check(cls, password, hashed):
106 def hash_check(cls, password, hashed):
107 """
107 """
108 Checks matching password with it's hashed value, runs different
108 Checks matching password with it's hashed value, runs different
109 implementation based on platform it runs on
109 implementation based on platform it runs on
110
110
111 :param password: password
111 :param password: password
112 :param hashed: password in hashed form
112 :param hashed: password in hashed form
113 """
113 """
114
114
115 if is_windows:
115 if is_windows:
116 from hashlib import sha256
116 from hashlib import sha256
117 return sha256(password).hexdigest() == hashed
117 return sha256(password).hexdigest() == hashed
118 elif is_unix:
118 elif is_unix:
119 import bcrypt
119 import bcrypt
120 return bcrypt.hashpw(password, hashed) == hashed
120 return bcrypt.hashpw(password, hashed) == hashed
121 else:
121 else:
122 raise Exception('Unknown or unsupported platform %s' \
122 raise Exception('Unknown or unsupported platform %s' \
123 % __platform__)
123 % __platform__)
124
124
125
125
126 def get_crypt_password(password):
126 def get_crypt_password(password):
127 return RhodeCodeCrypto.hash_string(password)
127 return RhodeCodeCrypto.hash_string(password)
128
128
129
129
130 def check_password(password, hashed):
130 def check_password(password, hashed):
131 return RhodeCodeCrypto.hash_check(password, hashed)
131 return RhodeCodeCrypto.hash_check(password, hashed)
132
132
133
133
134 def generate_api_key(str_, salt=None):
134 def generate_api_key(str_, salt=None):
135 """
135 """
136 Generates API KEY from given string
136 Generates API KEY from given string
137
137
138 :param str_:
138 :param str_:
139 :param salt:
139 :param salt:
140 """
140 """
141
141
142 if salt is None:
142 if salt is None:
143 salt = _RandomNameSequence().next()
143 salt = _RandomNameSequence().next()
144
144
145 return hashlib.sha1(str_ + salt).hexdigest()
145 return hashlib.sha1(str_ + salt).hexdigest()
146
146
147
147
148 def authfunc(environ, username, password):
148 def authfunc(environ, username, password):
149 """
149 """
150 Dummy authentication wrapper function used in Mercurial and Git for
150 Dummy authentication wrapper function used in Mercurial and Git for
151 access control.
151 access control.
152
152
153 :param environ: needed only for using in Basic auth
153 :param environ: needed only for using in Basic auth
154 """
154 """
155 return authenticate(username, password)
155 return authenticate(username, password)
156
156
157
157
158 def authenticate(username, password):
158 def authenticate(username, password):
159 """
159 """
160 Authentication function used for access control,
160 Authentication function used for access control,
161 firstly checks for db authentication then if ldap is enabled for ldap
161 firstly checks for db authentication then if ldap is enabled for ldap
162 authentication, also creates ldap user if not in database
162 authentication, also creates ldap user if not in database
163
163
164 :param username: username
164 :param username: username
165 :param password: password
165 :param password: password
166 """
166 """
167
167
168 user_model = UserModel()
168 user_model = UserModel()
169 user = User.get_by_username(username)
169 user = User.get_by_username(username)
170
170
171 log.debug('Authenticating user using RhodeCode account')
171 log.debug('Authenticating user using RhodeCode account')
172 if user is not None and not user.ldap_dn:
172 if user is not None and not user.ldap_dn:
173 if user.active:
173 if user.active:
174 if user.username == 'default' and user.active:
174 if user.username == 'default' and user.active:
175 log.info('user %s authenticated correctly as anonymous user' %
175 log.info('user %s authenticated correctly as anonymous user' %
176 username)
176 username)
177 return True
177 return True
178
178
179 elif user.username == username and check_password(password,
179 elif user.username == username and check_password(password,
180 user.password):
180 user.password):
181 log.info('user %s authenticated correctly' % username)
181 log.info('user %s authenticated correctly' % username)
182 return True
182 return True
183 else:
183 else:
184 log.warning('user %s tried auth but is disabled' % username)
184 log.warning('user %s tried auth but is disabled' % username)
185
185
186 else:
186 else:
187 log.debug('Regular authentication failed')
187 log.debug('Regular authentication failed')
188 user_obj = User.get_by_username(username, case_insensitive=True)
188 user_obj = User.get_by_username(username, case_insensitive=True)
189
189
190 if user_obj is not None and not user_obj.ldap_dn:
190 if user_obj is not None and not user_obj.ldap_dn:
191 log.debug('this user already exists as non ldap')
191 log.debug('this user already exists as non ldap')
192 return False
192 return False
193
193
194 ldap_settings = RhodeCodeSetting.get_ldap_settings()
194 ldap_settings = RhodeCodeSetting.get_ldap_settings()
195 #======================================================================
195 #======================================================================
196 # FALLBACK TO LDAP AUTH IF ENABLE
196 # FALLBACK TO LDAP AUTH IF ENABLE
197 #======================================================================
197 #======================================================================
198 if str2bool(ldap_settings.get('ldap_active')):
198 if str2bool(ldap_settings.get('ldap_active')):
199 log.debug("Authenticating user using ldap")
199 log.debug("Authenticating user using ldap")
200 kwargs = {
200 kwargs = {
201 'server': ldap_settings.get('ldap_host', ''),
201 'server': ldap_settings.get('ldap_host', ''),
202 'base_dn': ldap_settings.get('ldap_base_dn', ''),
202 'base_dn': ldap_settings.get('ldap_base_dn', ''),
203 'port': ldap_settings.get('ldap_port'),
203 'port': ldap_settings.get('ldap_port'),
204 'bind_dn': ldap_settings.get('ldap_dn_user'),
204 'bind_dn': ldap_settings.get('ldap_dn_user'),
205 'bind_pass': ldap_settings.get('ldap_dn_pass'),
205 'bind_pass': ldap_settings.get('ldap_dn_pass'),
206 'tls_kind': ldap_settings.get('ldap_tls_kind'),
206 'tls_kind': ldap_settings.get('ldap_tls_kind'),
207 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
207 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
208 'ldap_filter': ldap_settings.get('ldap_filter'),
208 'ldap_filter': ldap_settings.get('ldap_filter'),
209 'search_scope': ldap_settings.get('ldap_search_scope'),
209 'search_scope': ldap_settings.get('ldap_search_scope'),
210 'attr_login': ldap_settings.get('ldap_attr_login'),
210 'attr_login': ldap_settings.get('ldap_attr_login'),
211 'ldap_version': 3,
211 'ldap_version': 3,
212 }
212 }
213 log.debug('Checking for ldap authentication')
213 log.debug('Checking for ldap authentication')
214 try:
214 try:
215 aldap = AuthLdap(**kwargs)
215 aldap = AuthLdap(**kwargs)
216 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
216 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
217 password)
217 password)
218 log.debug('Got ldap DN response %s' % user_dn)
218 log.debug('Got ldap DN response %s' % user_dn)
219
219
220 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
220 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
221 .get(k), [''])[0]
221 .get(k), [''])[0]
222
222
223 user_attrs = {
223 user_attrs = {
224 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
224 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
225 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
225 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
226 'email': get_ldap_attr('ldap_attr_email'),
226 'email': get_ldap_attr('ldap_attr_email'),
227 }
227 }
228
228
229 # don't store LDAP password since we don't need it. Override
229 # don't store LDAP password since we don't need it. Override
230 # with some random generated password
230 # with some random generated password
231 _password = PasswordGenerator().gen_password(length=8)
231 _password = PasswordGenerator().gen_password(length=8)
232 # create this user on the fly if it doesn't exist in rhodecode
232 # create this user on the fly if it doesn't exist in rhodecode
233 # database
233 # database
234 if user_model.create_ldap(username, _password, user_dn,
234 if user_model.create_ldap(username, _password, user_dn,
235 user_attrs):
235 user_attrs):
236 log.info('created new ldap user %s' % username)
236 log.info('created new ldap user %s' % username)
237
237
238 Session().commit()
238 Session().commit()
239 return True
239 return True
240 except (LdapUsernameError, LdapPasswordError,):
240 except (LdapUsernameError, LdapPasswordError,):
241 pass
241 pass
242 except (Exception,):
242 except (Exception,):
243 log.error(traceback.format_exc())
243 log.error(traceback.format_exc())
244 pass
244 pass
245 return False
245 return False
246
246
247
247
248 def login_container_auth(username):
248 def login_container_auth(username):
249 user = User.get_by_username(username)
249 user = User.get_by_username(username)
250 if user is None:
250 if user is None:
251 user_attrs = {
251 user_attrs = {
252 'name': username,
252 'name': username,
253 'lastname': None,
253 'lastname': None,
254 'email': None,
254 'email': None,
255 }
255 }
256 user = UserModel().create_for_container_auth(username, user_attrs)
256 user = UserModel().create_for_container_auth(username, user_attrs)
257 if not user:
257 if not user:
258 return None
258 return None
259 log.info('User %s was created by container authentication' % username)
259 log.info('User %s was created by container authentication' % username)
260
260
261 if not user.active:
261 if not user.active:
262 return None
262 return None
263
263
264 user.update_lastlogin()
264 user.update_lastlogin()
265 Session().commit()
265 Session().commit()
266
266
267 log.debug('User %s is now logged in by container authentication',
267 log.debug('User %s is now logged in by container authentication',
268 user.username)
268 user.username)
269 return user
269 return user
270
270
271
271
272 def get_container_username(environ, config):
272 def get_container_username(environ, config):
273 username = None
273 username = None
274
274
275 if str2bool(config.get('container_auth_enabled', False)):
275 if str2bool(config.get('container_auth_enabled', False)):
276 from paste.httpheaders import REMOTE_USER
276 from paste.httpheaders import REMOTE_USER
277 username = REMOTE_USER(environ)
277 username = REMOTE_USER(environ)
278
278
279 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
279 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
280 username = environ.get('HTTP_X_FORWARDED_USER')
280 username = environ.get('HTTP_X_FORWARDED_USER')
281
281
282 if username:
282 if username:
283 # Removing realm and domain from username
283 # Removing realm and domain from username
284 username = username.partition('@')[0]
284 username = username.partition('@')[0]
285 username = username.rpartition('\\')[2]
285 username = username.rpartition('\\')[2]
286 log.debug('Received username %s from container' % username)
286 log.debug('Received username %s from container' % username)
287
287
288 return username
288 return username
289
289
290
290
291 class CookieStoreWrapper(object):
291 class CookieStoreWrapper(object):
292
292
293 def __init__(self, cookie_store):
293 def __init__(self, cookie_store):
294 self.cookie_store = cookie_store
294 self.cookie_store = cookie_store
295
295
296 def __repr__(self):
296 def __repr__(self):
297 return 'CookieStore<%s>' % (self.cookie_store)
297 return 'CookieStore<%s>' % (self.cookie_store)
298
298
299 def get(self, key, other=None):
299 def get(self, key, other=None):
300 if isinstance(self.cookie_store, dict):
300 if isinstance(self.cookie_store, dict):
301 return self.cookie_store.get(key, other)
301 return self.cookie_store.get(key, other)
302 elif isinstance(self.cookie_store, AuthUser):
302 elif isinstance(self.cookie_store, AuthUser):
303 return self.cookie_store.__dict__.get(key, other)
303 return self.cookie_store.__dict__.get(key, other)
304
304
305
305
306 class AuthUser(object):
306 class AuthUser(object):
307 """
307 """
308 A simple object that handles all attributes of user in RhodeCode
308 A simple object that handles all attributes of user in RhodeCode
309
309
310 It does lookup based on API key,given user, or user present in session
310 It does lookup based on API key,given user, or user present in session
311 Then it fills all required information for such user. It also checks if
311 Then it fills all required information for such user. It also checks if
312 anonymous access is enabled and if so, it returns default user as logged
312 anonymous access is enabled and if so, it returns default user as logged
313 in
313 in
314 """
314 """
315
315
316 def __init__(self, user_id=None, api_key=None, username=None):
316 def __init__(self, user_id=None, api_key=None, username=None):
317
317
318 self.user_id = user_id
318 self.user_id = user_id
319 self.api_key = None
319 self.api_key = None
320 self.username = username
320 self.username = username
321
321
322 self.name = ''
322 self.name = ''
323 self.lastname = ''
323 self.lastname = ''
324 self.email = ''
324 self.email = ''
325 self.is_authenticated = False
325 self.is_authenticated = False
326 self.admin = False
326 self.admin = False
327 self.inherit_default_permissions = False
327 self.inherit_default_permissions = False
328 self.permissions = {}
328 self.permissions = {}
329 self._api_key = api_key
329 self._api_key = api_key
330 self.propagate_data()
330 self.propagate_data()
331 self._instance = None
331 self._instance = None
332
332
333 def propagate_data(self):
333 def propagate_data(self):
334 user_model = UserModel()
334 user_model = UserModel()
335 self.anonymous_user = User.get_by_username('default', cache=True)
335 self.anonymous_user = User.get_by_username('default', cache=True)
336 is_user_loaded = False
336 is_user_loaded = False
337
337
338 # try go get user by api key
338 # try go get user by api key
339 if self._api_key and self._api_key != self.anonymous_user.api_key:
339 if self._api_key and self._api_key != self.anonymous_user.api_key:
340 log.debug('Auth User lookup by API KEY %s' % self._api_key)
340 log.debug('Auth User lookup by API KEY %s' % self._api_key)
341 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
341 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
342 # lookup by userid
342 # lookup by userid
343 elif (self.user_id is not None and
343 elif (self.user_id is not None and
344 self.user_id != self.anonymous_user.user_id):
344 self.user_id != self.anonymous_user.user_id):
345 log.debug('Auth User lookup by USER ID %s' % self.user_id)
345 log.debug('Auth User lookup by USER ID %s' % self.user_id)
346 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
346 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
347 # lookup by username
347 # lookup by username
348 elif self.username and \
348 elif self.username and \
349 str2bool(config.get('container_auth_enabled', False)):
349 str2bool(config.get('container_auth_enabled', False)):
350
350
351 log.debug('Auth User lookup by USER NAME %s' % self.username)
351 log.debug('Auth User lookup by USER NAME %s' % self.username)
352 dbuser = login_container_auth(self.username)
352 dbuser = login_container_auth(self.username)
353 if dbuser is not None:
353 if dbuser is not None:
354 log.debug('filling all attributes to object')
354 log.debug('filling all attributes to object')
355 for k, v in dbuser.get_dict().items():
355 for k, v in dbuser.get_dict().items():
356 setattr(self, k, v)
356 setattr(self, k, v)
357 self.set_authenticated()
357 self.set_authenticated()
358 is_user_loaded = True
358 is_user_loaded = True
359 else:
359 else:
360 log.debug('No data in %s that could been used to log in' % self)
360 log.debug('No data in %s that could been used to log in' % self)
361
361
362 if not is_user_loaded:
362 if not is_user_loaded:
363 # if we cannot authenticate user try anonymous
363 # if we cannot authenticate user try anonymous
364 if self.anonymous_user.active is True:
364 if self.anonymous_user.active is True:
365 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
365 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
366 # then we set this user is logged in
366 # then we set this user is logged in
367 self.is_authenticated = True
367 self.is_authenticated = True
368 else:
368 else:
369 self.user_id = None
369 self.user_id = None
370 self.username = None
370 self.username = None
371 self.is_authenticated = False
371 self.is_authenticated = False
372
372
373 if not self.username:
373 if not self.username:
374 self.username = 'None'
374 self.username = 'None'
375
375
376 log.debug('Auth User is now %s' % self)
376 log.debug('Auth User is now %s' % self)
377 user_model.fill_perms(self)
377 user_model.fill_perms(self)
378
378
379 @property
379 @property
380 def is_admin(self):
380 def is_admin(self):
381 return self.admin
381 return self.admin
382
382
383 def __repr__(self):
383 def __repr__(self):
384 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
384 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
385 self.is_authenticated)
385 self.is_authenticated)
386
386
387 def set_authenticated(self, authenticated=True):
387 def set_authenticated(self, authenticated=True):
388 if self.user_id != self.anonymous_user.user_id:
388 if self.user_id != self.anonymous_user.user_id:
389 self.is_authenticated = authenticated
389 self.is_authenticated = authenticated
390
390
391 def get_cookie_store(self):
391 def get_cookie_store(self):
392 return {'username': self.username,
392 return {'username': self.username,
393 'user_id': self.user_id,
393 'user_id': self.user_id,
394 'is_authenticated': self.is_authenticated}
394 'is_authenticated': self.is_authenticated}
395
395
396 @classmethod
396 @classmethod
397 def from_cookie_store(cls, cookie_store):
397 def from_cookie_store(cls, cookie_store):
398 """
398 """
399 Creates AuthUser from a cookie store
399 Creates AuthUser from a cookie store
400
400
401 :param cls:
401 :param cls:
402 :param cookie_store:
402 :param cookie_store:
403 """
403 """
404 user_id = cookie_store.get('user_id')
404 user_id = cookie_store.get('user_id')
405 username = cookie_store.get('username')
405 username = cookie_store.get('username')
406 api_key = cookie_store.get('api_key')
406 api_key = cookie_store.get('api_key')
407 return AuthUser(user_id, api_key, username)
407 return AuthUser(user_id, api_key, username)
408
408
409
409
410 def set_available_permissions(config):
410 def set_available_permissions(config):
411 """
411 """
412 This function will propagate pylons globals with all available defined
412 This function will propagate pylons globals with all available defined
413 permission given in db. We don't want to check each time from db for new
413 permission given in db. We don't want to check each time from db for new
414 permissions since adding a new permission also requires application restart
414 permissions since adding a new permission also requires application restart
415 ie. to decorate new views with the newly created permission
415 ie. to decorate new views with the newly created permission
416
416
417 :param config: current pylons config instance
417 :param config: current pylons config instance
418
418
419 """
419 """
420 log.info('getting information about all available permissions')
420 log.info('getting information about all available permissions')
421 try:
421 try:
422 sa = meta.Session
422 sa = meta.Session
423 all_perms = sa.query(Permission).all()
423 all_perms = sa.query(Permission).all()
424 except Exception:
424 except Exception:
425 pass
425 pass
426 finally:
426 finally:
427 meta.Session.remove()
427 meta.Session.remove()
428
428
429 config['available_permissions'] = [x.permission_name for x in all_perms]
429 config['available_permissions'] = [x.permission_name for x in all_perms]
430
430
431
431
432 #==============================================================================
432 #==============================================================================
433 # CHECK DECORATORS
433 # CHECK DECORATORS
434 #==============================================================================
434 #==============================================================================
435 class LoginRequired(object):
435 class LoginRequired(object):
436 """
436 """
437 Must be logged in to execute this function else
437 Must be logged in to execute this function else
438 redirect to login page
438 redirect to login page
439
439
440 :param api_access: if enabled this checks only for valid auth token
440 :param api_access: if enabled this checks only for valid auth token
441 and grants access based on valid token
441 and grants access based on valid token
442 """
442 """
443
443
444 def __init__(self, api_access=False):
444 def __init__(self, api_access=False):
445 self.api_access = api_access
445 self.api_access = api_access
446
446
447 def __call__(self, func):
447 def __call__(self, func):
448 return decorator(self.__wrapper, func)
448 return decorator(self.__wrapper, func)
449
449
450 def __wrapper(self, func, *fargs, **fkwargs):
450 def __wrapper(self, func, *fargs, **fkwargs):
451 cls = fargs[0]
451 cls = fargs[0]
452 user = cls.rhodecode_user
452 user = cls.rhodecode_user
453
453
454 api_access_ok = False
454 api_access_ok = False
455 if self.api_access:
455 if self.api_access:
456 log.debug('Checking API KEY access for %s' % cls)
456 log.debug('Checking API KEY access for %s' % cls)
457 if user.api_key == request.GET.get('api_key'):
457 if user.api_key == request.GET.get('api_key'):
458 api_access_ok = True
458 api_access_ok = True
459 else:
459 else:
460 log.debug("API KEY token not valid")
460 log.debug("API KEY token not valid")
461 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
461 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
462 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
462 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
463 if user.is_authenticated or api_access_ok:
463 if user.is_authenticated or api_access_ok:
464 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
464 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
465 log.info('user %s is authenticated and granted access to %s '
465 log.info('user %s is authenticated and granted access to %s '
466 'using %s' % (user.username, loc, reason)
466 'using %s' % (user.username, loc, reason)
467 )
467 )
468 return func(*fargs, **fkwargs)
468 return func(*fargs, **fkwargs)
469 else:
469 else:
470 log.warn('user %s NOT authenticated on func: %s' % (
470 log.warn('user %s NOT authenticated on func: %s' % (
471 user, loc)
471 user, loc)
472 )
472 )
473 p = url.current()
473 p = url.current()
474
474
475 log.debug('redirecting to login page with %s' % p)
475 log.debug('redirecting to login page with %s' % p)
476 return redirect(url('login_home', came_from=p))
476 return redirect(url('login_home', came_from=p))
477
477
478
478
479 class NotAnonymous(object):
479 class NotAnonymous(object):
480 """
480 """
481 Must be logged in to execute this function else
481 Must be logged in to execute this function else
482 redirect to login page"""
482 redirect to login page"""
483
483
484 def __call__(self, func):
484 def __call__(self, func):
485 return decorator(self.__wrapper, func)
485 return decorator(self.__wrapper, func)
486
486
487 def __wrapper(self, func, *fargs, **fkwargs):
487 def __wrapper(self, func, *fargs, **fkwargs):
488 cls = fargs[0]
488 cls = fargs[0]
489 self.user = cls.rhodecode_user
489 self.user = cls.rhodecode_user
490
490
491 log.debug('Checking if user is not anonymous @%s' % cls)
491 log.debug('Checking if user is not anonymous @%s' % cls)
492
492
493 anonymous = self.user.username == 'default'
493 anonymous = self.user.username == 'default'
494
494
495 if anonymous:
495 if anonymous:
496 p = url.current()
496 p = url.current()
497
497
498 import rhodecode.lib.helpers as h
498 import rhodecode.lib.helpers as h
499 h.flash(_('You need to be a registered user to '
499 h.flash(_('You need to be a registered user to '
500 'perform this action'),
500 'perform this action'),
501 category='warning')
501 category='warning')
502 return redirect(url('login_home', came_from=p))
502 return redirect(url('login_home', came_from=p))
503 else:
503 else:
504 return func(*fargs, **fkwargs)
504 return func(*fargs, **fkwargs)
505
505
506
506
507 class PermsDecorator(object):
507 class PermsDecorator(object):
508 """Base class for controller decorators"""
508 """Base class for controller decorators"""
509
509
510 def __init__(self, *required_perms):
510 def __init__(self, *required_perms):
511 available_perms = config['available_permissions']
511 available_perms = config['available_permissions']
512 for perm in required_perms:
512 for perm in required_perms:
513 if perm not in available_perms:
513 if perm not in available_perms:
514 raise Exception("'%s' permission is not defined" % perm)
514 raise Exception("'%s' permission is not defined" % perm)
515 self.required_perms = set(required_perms)
515 self.required_perms = set(required_perms)
516 self.user_perms = None
516 self.user_perms = None
517
517
518 def __call__(self, func):
518 def __call__(self, func):
519 return decorator(self.__wrapper, func)
519 return decorator(self.__wrapper, func)
520
520
521 def __wrapper(self, func, *fargs, **fkwargs):
521 def __wrapper(self, func, *fargs, **fkwargs):
522 cls = fargs[0]
522 cls = fargs[0]
523 self.user = cls.rhodecode_user
523 self.user = cls.rhodecode_user
524 self.user_perms = self.user.permissions
524 self.user_perms = self.user.permissions
525 log.debug('checking %s permissions %s for %s %s',
525 log.debug('checking %s permissions %s for %s %s',
526 self.__class__.__name__, self.required_perms, cls, self.user)
526 self.__class__.__name__, self.required_perms, cls, self.user)
527
527
528 if self.check_permissions():
528 if self.check_permissions():
529 log.debug('Permission granted for %s %s' % (cls, self.user))
529 log.debug('Permission granted for %s %s' % (cls, self.user))
530 return func(*fargs, **fkwargs)
530 return func(*fargs, **fkwargs)
531
531
532 else:
532 else:
533 log.debug('Permission denied for %s %s' % (cls, self.user))
533 log.debug('Permission denied for %s %s' % (cls, self.user))
534 anonymous = self.user.username == 'default'
534 anonymous = self.user.username == 'default'
535
535
536 if anonymous:
536 if anonymous:
537 p = url.current()
537 p = url.current()
538
538
539 import rhodecode.lib.helpers as h
539 import rhodecode.lib.helpers as h
540 h.flash(_('You need to be a signed in to '
540 h.flash(_('You need to be a signed in to '
541 'view this page'),
541 'view this page'),
542 category='warning')
542 category='warning')
543 return redirect(url('login_home', came_from=p))
543 return redirect(url('login_home', came_from=p))
544
544
545 else:
545 else:
546 # redirect with forbidden ret code
546 # redirect with forbidden ret code
547 return abort(403)
547 return abort(403)
548
548
549 def check_permissions(self):
549 def check_permissions(self):
550 """Dummy function for overriding"""
550 """Dummy function for overriding"""
551 raise Exception('You have to write this function in child class')
551 raise Exception('You have to write this function in child class')
552
552
553
553
554 class HasPermissionAllDecorator(PermsDecorator):
554 class HasPermissionAllDecorator(PermsDecorator):
555 """
555 """
556 Checks for access permission for all given predicates. All of them
556 Checks for access permission for all given predicates. All of them
557 have to be meet in order to fulfill the request
557 have to be meet in order to fulfill the request
558 """
558 """
559
559
560 def check_permissions(self):
560 def check_permissions(self):
561 if self.required_perms.issubset(self.user_perms.get('global')):
561 if self.required_perms.issubset(self.user_perms.get('global')):
562 return True
562 return True
563 return False
563 return False
564
564
565
565
566 class HasPermissionAnyDecorator(PermsDecorator):
566 class HasPermissionAnyDecorator(PermsDecorator):
567 """
567 """
568 Checks for access permission for any of given predicates. In order to
568 Checks for access permission for any of given predicates. In order to
569 fulfill the request any of predicates must be meet
569 fulfill the request any of predicates must be meet
570 """
570 """
571
571
572 def check_permissions(self):
572 def check_permissions(self):
573 if self.required_perms.intersection(self.user_perms.get('global')):
573 if self.required_perms.intersection(self.user_perms.get('global')):
574 return True
574 return True
575 return False
575 return False
576
576
577
577
578 class HasRepoPermissionAllDecorator(PermsDecorator):
578 class HasRepoPermissionAllDecorator(PermsDecorator):
579 """
579 """
580 Checks for access permission for all given predicates for specific
580 Checks for access permission for all given predicates for specific
581 repository. All of them have to be meet in order to fulfill the request
581 repository. All of them have to be meet in order to fulfill the request
582 """
582 """
583
583
584 def check_permissions(self):
584 def check_permissions(self):
585 repo_name = get_repo_slug(request)
585 repo_name = get_repo_slug(request)
586 try:
586 try:
587 user_perms = set([self.user_perms['repositories'][repo_name]])
587 user_perms = set([self.user_perms['repositories'][repo_name]])
588 except KeyError:
588 except KeyError:
589 return False
589 return False
590 if self.required_perms.issubset(user_perms):
590 if self.required_perms.issubset(user_perms):
591 return True
591 return True
592 return False
592 return False
593
593
594
594
595 class HasRepoPermissionAnyDecorator(PermsDecorator):
595 class HasRepoPermissionAnyDecorator(PermsDecorator):
596 """
596 """
597 Checks for access permission for any of given predicates for specific
597 Checks for access permission for any of given predicates for specific
598 repository. In order to fulfill the request any of predicates must be meet
598 repository. In order to fulfill the request any of predicates must be meet
599 """
599 """
600
600
601 def check_permissions(self):
601 def check_permissions(self):
602 repo_name = get_repo_slug(request)
602 repo_name = get_repo_slug(request)
603
603
604 try:
604 try:
605 user_perms = set([self.user_perms['repositories'][repo_name]])
605 user_perms = set([self.user_perms['repositories'][repo_name]])
606 except KeyError:
606 except KeyError:
607 return False
607 return False
608
608
609 if self.required_perms.intersection(user_perms):
609 if self.required_perms.intersection(user_perms):
610 return True
610 return True
611 return False
611 return False
612
612
613
613
614 class HasReposGroupPermissionAllDecorator(PermsDecorator):
614 class HasReposGroupPermissionAllDecorator(PermsDecorator):
615 """
615 """
616 Checks for access permission for all given predicates for specific
616 Checks for access permission for all given predicates for specific
617 repository. All of them have to be meet in order to fulfill the request
617 repository. All of them have to be meet in order to fulfill the request
618 """
618 """
619
619
620 def check_permissions(self):
620 def check_permissions(self):
621 group_name = get_repos_group_slug(request)
621 group_name = get_repos_group_slug(request)
622 try:
622 try:
623 user_perms = set([self.user_perms['repositories_groups'][group_name]])
623 user_perms = set([self.user_perms['repositories_groups'][group_name]])
624 except KeyError:
624 except KeyError:
625 return False
625 return False
626 if self.required_perms.issubset(user_perms):
626 if self.required_perms.issubset(user_perms):
627 return True
627 return True
628 return False
628 return False
629
629
630
630
631 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
631 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
632 """
632 """
633 Checks for access permission for any of given predicates for specific
633 Checks for access permission for any of given predicates for specific
634 repository. In order to fulfill the request any of predicates must be meet
634 repository. In order to fulfill the request any of predicates must be meet
635 """
635 """
636
636
637 def check_permissions(self):
637 def check_permissions(self):
638 group_name = get_repos_group_slug(request)
638 group_name = get_repos_group_slug(request)
639
639
640 try:
640 try:
641 user_perms = set([self.user_perms['repositories_groups'][group_name]])
641 user_perms = set([self.user_perms['repositories_groups'][group_name]])
642 except KeyError:
642 except KeyError:
643 return False
643 return False
644 if self.required_perms.intersection(user_perms):
644 if self.required_perms.intersection(user_perms):
645 return True
645 return True
646 return False
646 return False
647
647
648
648
649 #==============================================================================
649 #==============================================================================
650 # CHECK FUNCTIONS
650 # CHECK FUNCTIONS
651 #==============================================================================
651 #==============================================================================
652 class PermsFunction(object):
652 class PermsFunction(object):
653 """Base function for other check functions"""
653 """Base function for other check functions"""
654
654
655 def __init__(self, *perms):
655 def __init__(self, *perms):
656 available_perms = config['available_permissions']
656 available_perms = config['available_permissions']
657
657
658 for perm in perms:
658 for perm in perms:
659 if perm not in available_perms:
659 if perm not in available_perms:
660 raise Exception("'%s' permission is not defined" % perm)
660 raise Exception("'%s' permission is not defined" % perm)
661 self.required_perms = set(perms)
661 self.required_perms = set(perms)
662 self.user_perms = None
662 self.user_perms = None
663 self.repo_name = None
663 self.repo_name = None
664 self.group_name = None
664 self.group_name = None
665
665
666 def __call__(self, check_Location=''):
666 def __call__(self, check_Location=''):
667 user = request.user
667 user = request.user
668 cls_name = self.__class__.__name__
668 cls_name = self.__class__.__name__
669 check_scope = {
669 check_scope = {
670 'HasPermissionAll': '',
670 'HasPermissionAll': '',
671 'HasPermissionAny': '',
671 'HasPermissionAny': '',
672 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
672 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
673 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
673 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
674 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
674 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
675 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
675 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
676 }.get(cls_name, '?')
676 }.get(cls_name, '?')
677 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
677 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
678 self.required_perms, user, check_scope,
678 self.required_perms, user, check_scope,
679 check_Location or 'unspecified location')
679 check_Location or 'unspecified location')
680 if not user:
680 if not user:
681 log.debug('Empty request user')
681 log.debug('Empty request user')
682 return False
682 return False
683 self.user_perms = user.permissions
683 self.user_perms = user.permissions
684 if self.check_permissions():
684 if self.check_permissions():
685 log.debug('Permission granted for user: %s @ %s', user,
685 log.debug('Permission granted for user: %s @ %s', user,
686 check_Location or 'unspecified location')
686 check_Location or 'unspecified location')
687 return True
687 return True
688
688
689 else:
689 else:
690 log.debug('Permission denied for user: %s @ %s', user,
690 log.debug('Permission denied for user: %s @ %s', user,
691 check_Location or 'unspecified location')
691 check_Location or 'unspecified location')
692 return False
692 return False
693
693
694 def check_permissions(self):
694 def check_permissions(self):
695 """Dummy function for overriding"""
695 """Dummy function for overriding"""
696 raise Exception('You have to write this function in child class')
696 raise Exception('You have to write this function in child class')
697
697
698
698
699 class HasPermissionAll(PermsFunction):
699 class HasPermissionAll(PermsFunction):
700 def check_permissions(self):
700 def check_permissions(self):
701 if self.required_perms.issubset(self.user_perms.get('global')):
701 if self.required_perms.issubset(self.user_perms.get('global')):
702 return True
702 return True
703 return False
703 return False
704
704
705
705
706 class HasPermissionAny(PermsFunction):
706 class HasPermissionAny(PermsFunction):
707 def check_permissions(self):
707 def check_permissions(self):
708 if self.required_perms.intersection(self.user_perms.get('global')):
708 if self.required_perms.intersection(self.user_perms.get('global')):
709 return True
709 return True
710 return False
710 return False
711
711
712
712
713 class HasRepoPermissionAll(PermsFunction):
713 class HasRepoPermissionAll(PermsFunction):
714 def __call__(self, repo_name=None, check_Location=''):
714 def __call__(self, repo_name=None, check_Location=''):
715 self.repo_name = repo_name
715 self.repo_name = repo_name
716 return super(HasRepoPermissionAll, self).__call__(check_Location)
716 return super(HasRepoPermissionAll, self).__call__(check_Location)
717
717
718 def check_permissions(self):
718 def check_permissions(self):
719 if not self.repo_name:
719 if not self.repo_name:
720 self.repo_name = get_repo_slug(request)
720 self.repo_name = get_repo_slug(request)
721
721
722 try:
722 try:
723 self._user_perms = set(
723 self._user_perms = set(
724 [self.user_perms['repositories'][self.repo_name]]
724 [self.user_perms['repositories'][self.repo_name]]
725 )
725 )
726 except KeyError:
726 except KeyError:
727 return False
727 return False
728 if self.required_perms.issubset(self._user_perms):
728 if self.required_perms.issubset(self._user_perms):
729 return True
729 return True
730 return False
730 return False
731
731
732
732
733 class HasRepoPermissionAny(PermsFunction):
733 class HasRepoPermissionAny(PermsFunction):
734 def __call__(self, repo_name=None, check_Location=''):
734 def __call__(self, repo_name=None, check_Location=''):
735 self.repo_name = repo_name
735 self.repo_name = repo_name
736 return super(HasRepoPermissionAny, self).__call__(check_Location)
736 return super(HasRepoPermissionAny, self).__call__(check_Location)
737
737
738 def check_permissions(self):
738 def check_permissions(self):
739 if not self.repo_name:
739 if not self.repo_name:
740 self.repo_name = get_repo_slug(request)
740 self.repo_name = get_repo_slug(request)
741
741
742 try:
742 try:
743 self._user_perms = set(
743 self._user_perms = set(
744 [self.user_perms['repositories'][self.repo_name]]
744 [self.user_perms['repositories'][self.repo_name]]
745 )
745 )
746 except KeyError:
746 except KeyError:
747 return False
747 return False
748 if self.required_perms.intersection(self._user_perms):
748 if self.required_perms.intersection(self._user_perms):
749 return True
749 return True
750 return False
750 return False
751
751
752
752
753 class HasReposGroupPermissionAny(PermsFunction):
753 class HasReposGroupPermissionAny(PermsFunction):
754 def __call__(self, group_name=None, check_Location=''):
754 def __call__(self, group_name=None, check_Location=''):
755 self.group_name = group_name
755 self.group_name = group_name
756 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
756 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
757
757
758 def check_permissions(self):
758 def check_permissions(self):
759 try:
759 try:
760 self._user_perms = set(
760 self._user_perms = set(
761 [self.user_perms['repositories_groups'][self.group_name]]
761 [self.user_perms['repositories_groups'][self.group_name]]
762 )
762 )
763 except KeyError:
763 except KeyError:
764 return False
764 return False
765 if self.required_perms.intersection(self._user_perms):
765 if self.required_perms.intersection(self._user_perms):
766 return True
766 return True
767 return False
767 return False
768
768
769
769
770 class HasReposGroupPermissionAll(PermsFunction):
770 class HasReposGroupPermissionAll(PermsFunction):
771 def __call__(self, group_name=None, check_Location=''):
771 def __call__(self, group_name=None, check_Location=''):
772 self.group_name = group_name
772 self.group_name = group_name
773 return super(HasReposGroupPermissionAll, self).__call__(check_Location)
773 return super(HasReposGroupPermissionAll, self).__call__(check_Location)
774
774
775 def check_permissions(self):
775 def check_permissions(self):
776 try:
776 try:
777 self._user_perms = set(
777 self._user_perms = set(
778 [self.user_perms['repositories_groups'][self.group_name]]
778 [self.user_perms['repositories_groups'][self.group_name]]
779 )
779 )
780 except KeyError:
780 except KeyError:
781 return False
781 return False
782 if self.required_perms.issubset(self._user_perms):
782 if self.required_perms.issubset(self._user_perms):
783 return True
783 return True
784 return False
784 return False
785
785
786
786
787 #==============================================================================
787 #==============================================================================
788 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
788 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
789 #==============================================================================
789 #==============================================================================
790 class HasPermissionAnyMiddleware(object):
790 class HasPermissionAnyMiddleware(object):
791 def __init__(self, *perms):
791 def __init__(self, *perms):
792 self.required_perms = set(perms)
792 self.required_perms = set(perms)
793
793
794 def __call__(self, user, repo_name):
794 def __call__(self, user, repo_name):
795 # repo_name MUST be unicode, since we handle keys in permission
795 # repo_name MUST be unicode, since we handle keys in permission
796 # dict by unicode
796 # dict by unicode
797 repo_name = safe_unicode(repo_name)
797 repo_name = safe_unicode(repo_name)
798 usr = AuthUser(user.user_id)
798 usr = AuthUser(user.user_id)
799 try:
799 try:
800 self.user_perms = set([usr.permissions['repositories'][repo_name]])
800 self.user_perms = set([usr.permissions['repositories'][repo_name]])
801 except Exception:
801 except Exception:
802 log.error('Exception while accessing permissions %s' %
802 log.error('Exception while accessing permissions %s' %
803 traceback.format_exc())
803 traceback.format_exc())
804 self.user_perms = set()
804 self.user_perms = set()
805 self.username = user.username
805 self.username = user.username
806 self.repo_name = repo_name
806 self.repo_name = repo_name
807 return self.check_permissions()
807 return self.check_permissions()
808
808
809 def check_permissions(self):
809 def check_permissions(self):
810 log.debug('checking mercurial protocol '
810 log.debug('checking VCS protocol '
811 'permissions %s for user:%s repository:%s', self.user_perms,
811 'permissions %s for user:%s repository:%s', self.user_perms,
812 self.username, self.repo_name)
812 self.username, self.repo_name)
813 if self.required_perms.intersection(self.user_perms):
813 if self.required_perms.intersection(self.user_perms):
814 log.debug('permission granted for user:%s on repo:%s' % (
814 log.debug('permission granted for user:%s on repo:%s' % (
815 self.username, self.repo_name
815 self.username, self.repo_name
816 )
816 )
817 )
817 )
818 return True
818 return True
819 log.debug('permission denied for user:%s on repo:%s' % (
819 log.debug('permission denied for user:%s on repo:%s' % (
820 self.username, self.repo_name
820 self.username, self.repo_name
821 )
821 )
822 )
822 )
823 return False
823 return False
@@ -1,256 +1,302 b''
1 """The base Controller API
1 """The base Controller API
2
2
3 Provides the BaseController class for subclassing.
3 Provides the BaseController class for subclassing.
4 """
4 """
5 import logging
5 import logging
6 import time
6 import time
7 import traceback
7 import traceback
8
8
9 from paste.auth.basic import AuthBasicAuthenticator
9 from paste.auth.basic import AuthBasicAuthenticator
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 from webob.exc import HTTPClientError
11 from paste.httpheaders import WWW_AUTHENTICATE
12 from paste.httpheaders import WWW_AUTHENTICATE
12
13
13 from pylons import config, tmpl_context as c, request, session, url
14 from pylons import config, tmpl_context as c, request, session, url
14 from pylons.controllers import WSGIController
15 from pylons.controllers import WSGIController
15 from pylons.controllers.util import redirect
16 from pylons.controllers.util import redirect
16 from pylons.templating import render_mako as render
17 from pylons.templating import render_mako as render
17
18
18 from rhodecode import __version__, BACKENDS
19 from rhodecode import __version__, BACKENDS
19
20
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
21 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
22 safe_str
21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
23 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 HasPermissionAnyMiddleware, CookieStoreWrapper
24 HasPermissionAnyMiddleware, CookieStoreWrapper
23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
25 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 from rhodecode.model import meta
26 from rhodecode.model import meta
25
27
26 from rhodecode.model.db import Repository, RhodeCodeUi
28 from rhodecode.model.db import Repository, RhodeCodeUi, User
27 from rhodecode.model.notification import NotificationModel
29 from rhodecode.model.notification import NotificationModel
28 from rhodecode.model.scm import ScmModel
30 from rhodecode.model.scm import ScmModel
31 from rhodecode.model.meta import Session
29
32
30 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
31
34
32
35
33 def _get_ip_addr(environ):
36 def _get_ip_addr(environ):
34 proxy_key = 'HTTP_X_REAL_IP'
37 proxy_key = 'HTTP_X_REAL_IP'
35 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
38 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
36 def_key = 'REMOTE_ADDR'
39 def_key = 'REMOTE_ADDR'
37
40
38 ip = environ.get(proxy_key2)
41 ip = environ.get(proxy_key2)
39 if ip:
42 if ip:
40 return ip
43 return ip
41
44
42 ip = environ.get(proxy_key)
45 ip = environ.get(proxy_key)
43
46
44 if ip:
47 if ip:
45 return ip
48 return ip
46
49
47 ip = environ.get(def_key, '0.0.0.0')
50 ip = environ.get(def_key, '0.0.0.0')
48 return ip
51 return ip
49
52
50
53
51 def _get_access_path(environ):
54 def _get_access_path(environ):
52 path = environ.get('PATH_INFO')
55 path = environ.get('PATH_INFO')
53 org_req = environ.get('pylons.original_request')
56 org_req = environ.get('pylons.original_request')
54 if org_req:
57 if org_req:
55 path = org_req.environ.get('PATH_INFO')
58 path = org_req.environ.get('PATH_INFO')
56 return path
59 return path
57
60
58
61
59 class BasicAuth(AuthBasicAuthenticator):
62 class BasicAuth(AuthBasicAuthenticator):
60
63
61 def __init__(self, realm, authfunc, auth_http_code=None):
64 def __init__(self, realm, authfunc, auth_http_code=None):
62 self.realm = realm
65 self.realm = realm
63 self.authfunc = authfunc
66 self.authfunc = authfunc
64 self._rc_auth_http_code = auth_http_code
67 self._rc_auth_http_code = auth_http_code
65
68
66 def build_authentication(self):
69 def build_authentication(self):
67 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
70 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
68 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
71 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
69 # return 403 if alternative http return code is specified in
72 # return 403 if alternative http return code is specified in
70 # RhodeCode config
73 # RhodeCode config
71 return HTTPForbidden(headers=head)
74 return HTTPForbidden(headers=head)
72 return HTTPUnauthorized(headers=head)
75 return HTTPUnauthorized(headers=head)
73
76
74
77
75 class BaseVCSController(object):
78 class BaseVCSController(object):
76
79
77 def __init__(self, application, config):
80 def __init__(self, application, config):
78 self.application = application
81 self.application = application
79 self.config = config
82 self.config = config
80 # base path of repo locations
83 # base path of repo locations
81 self.basepath = self.config['base_path']
84 self.basepath = self.config['base_path']
82 #authenticate this mercurial request using authfunc
85 #authenticate this mercurial request using authfunc
83 self.authenticate = BasicAuth('', authfunc,
86 self.authenticate = BasicAuth('', authfunc,
84 config.get('auth_ret_code'))
87 config.get('auth_ret_code'))
85 self.ipaddr = '0.0.0.0'
88 self.ipaddr = '0.0.0.0'
86
89
87 def _handle_request(self, environ, start_response):
90 def _handle_request(self, environ, start_response):
88 raise NotImplementedError()
91 raise NotImplementedError()
89
92
90 def _get_by_id(self, repo_name):
93 def _get_by_id(self, repo_name):
91 """
94 """
92 Get's a special pattern _<ID> from clone url and tries to replace it
95 Get's a special pattern _<ID> from clone url and tries to replace it
93 with a repository_name for support of _<ID> non changable urls
96 with a repository_name for support of _<ID> non changable urls
94
97
95 :param repo_name:
98 :param repo_name:
96 """
99 """
97 try:
100 try:
98 data = repo_name.split('/')
101 data = repo_name.split('/')
99 if len(data) >= 2:
102 if len(data) >= 2:
100 by_id = data[1].split('_')
103 by_id = data[1].split('_')
101 if len(by_id) == 2 and by_id[1].isdigit():
104 if len(by_id) == 2 and by_id[1].isdigit():
102 _repo_name = Repository.get(by_id[1]).repo_name
105 _repo_name = Repository.get(by_id[1]).repo_name
103 data[1] = _repo_name
106 data[1] = _repo_name
104 except:
107 except:
105 log.debug('Failed to extract repo_name from id %s' % (
108 log.debug('Failed to extract repo_name from id %s' % (
106 traceback.format_exc()
109 traceback.format_exc()
107 )
110 )
108 )
111 )
109
112
110 return '/'.join(data)
113 return '/'.join(data)
111
114
112 def _invalidate_cache(self, repo_name):
115 def _invalidate_cache(self, repo_name):
113 """
116 """
114 Set's cache for this repository for invalidation on next access
117 Set's cache for this repository for invalidation on next access
115
118
116 :param repo_name: full repo name, also a cache key
119 :param repo_name: full repo name, also a cache key
117 """
120 """
118 invalidate_cache('get_repo_cached_%s' % repo_name)
121 invalidate_cache('get_repo_cached_%s' % repo_name)
119
122
120 def _check_permission(self, action, user, repo_name):
123 def _check_permission(self, action, user, repo_name):
121 """
124 """
122 Checks permissions using action (push/pull) user and repository
125 Checks permissions using action (push/pull) user and repository
123 name
126 name
124
127
125 :param action: push or pull action
128 :param action: push or pull action
126 :param user: user instance
129 :param user: user instance
127 :param repo_name: repository name
130 :param repo_name: repository name
128 """
131 """
129 if action == 'push':
132 if action == 'push':
130 if not HasPermissionAnyMiddleware('repository.write',
133 if not HasPermissionAnyMiddleware('repository.write',
131 'repository.admin')(user,
134 'repository.admin')(user,
132 repo_name):
135 repo_name):
133 return False
136 return False
134
137
135 else:
138 else:
136 #any other action need at least read permission
139 #any other action need at least read permission
137 if not HasPermissionAnyMiddleware('repository.read',
140 if not HasPermissionAnyMiddleware('repository.read',
138 'repository.write',
141 'repository.write',
139 'repository.admin')(user,
142 'repository.admin')(user,
140 repo_name):
143 repo_name):
141 return False
144 return False
142
145
143 return True
146 return True
144
147
145 def _get_ip_addr(self, environ):
148 def _get_ip_addr(self, environ):
146 return _get_ip_addr(environ)
149 return _get_ip_addr(environ)
147
150
148 def _check_ssl(self, environ, start_response):
151 def _check_ssl(self, environ, start_response):
149 """
152 """
150 Checks the SSL check flag and returns False if SSL is not present
153 Checks the SSL check flag and returns False if SSL is not present
151 and required True otherwise
154 and required True otherwise
152 """
155 """
153 org_proto = environ['wsgi._org_proto']
156 org_proto = environ['wsgi._org_proto']
154 #check if we have SSL required ! if not it's a bad request !
157 #check if we have SSL required ! if not it's a bad request !
155 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
158 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
156 if require_ssl and org_proto == 'http':
159 if require_ssl and org_proto == 'http':
157 log.debug('proto is %s and SSL is required BAD REQUEST !'
160 log.debug('proto is %s and SSL is required BAD REQUEST !'
158 % org_proto)
161 % org_proto)
159 return False
162 return False
160 return True
163 return True
161
164
165 def _check_locking_state(self, environ, action, repo, user_id):
166 """
167 Checks locking on this repository, if locking is enabled and lock is
168 present returns a tuple of make_lock, locked, locked_by.
169 make_lock can have 3 states None (do nothing) True, make lock
170 False release lock, This value is later propagated to hooks, which
171 do the locking. Think about this as signals passed to hooks what to do.
172
173 """
174 locked = False
175 make_lock = None
176 repo = Repository.get_by_repo_name(repo)
177 user = User.get(user_id)
178
179 # this is kind of hacky, but due to how mercurial handles client-server
180 # server see all operation on changeset; bookmarks, phases and
181 # obsolescence marker in different transaction, we don't want to check
182 # locking on those
183 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
184 locked_by = repo.locked
185 if repo and repo.enable_locking and not obsolete_call:
186 if action == 'push':
187 #check if it's already locked !, if it is compare users
188 user_id, _date = repo.locked
189 if user.user_id == user_id:
190 log.debug('Got push from user, now unlocking' % (user))
191 # unlock if we have push from user who locked
192 make_lock = False
193 else:
194 # we're not the same user who locked, ban with 423 !
195 locked = True
196 if action == 'pull':
197 if repo.locked[0] and repo.locked[1]:
198 locked = True
199 else:
200 log.debug('Setting lock on repo %s by %s' % (repo, user))
201 make_lock = True
202
203 else:
204 log.debug('Repository %s do not have locking enabled' % (repo))
205
206 return make_lock, locked, locked_by
207
162 def __call__(self, environ, start_response):
208 def __call__(self, environ, start_response):
163 start = time.time()
209 start = time.time()
164 try:
210 try:
165 return self._handle_request(environ, start_response)
211 return self._handle_request(environ, start_response)
166 finally:
212 finally:
167 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
213 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
168 log.debug('Request time: %.3fs' % (time.time() - start))
214 log.debug('Request time: %.3fs' % (time.time() - start))
169 meta.Session.remove()
215 meta.Session.remove()
170
216
171
217
172 class BaseController(WSGIController):
218 class BaseController(WSGIController):
173
219
174 def __before__(self):
220 def __before__(self):
175 c.rhodecode_version = __version__
221 c.rhodecode_version = __version__
176 c.rhodecode_instanceid = config.get('instance_id')
222 c.rhodecode_instanceid = config.get('instance_id')
177 c.rhodecode_name = config.get('rhodecode_title')
223 c.rhodecode_name = config.get('rhodecode_title')
178 c.use_gravatar = str2bool(config.get('use_gravatar'))
224 c.use_gravatar = str2bool(config.get('use_gravatar'))
179 c.ga_code = config.get('rhodecode_ga_code')
225 c.ga_code = config.get('rhodecode_ga_code')
180 # Visual options
226 # Visual options
181 c.visual = AttributeDict({})
227 c.visual = AttributeDict({})
182 c.visual.show_public_icon = str2bool(config.get('rhodecode_show_public_icon'))
228 c.visual.show_public_icon = str2bool(config.get('rhodecode_show_public_icon'))
183 c.visual.show_private_icon = str2bool(config.get('rhodecode_show_private_icon'))
229 c.visual.show_private_icon = str2bool(config.get('rhodecode_show_private_icon'))
184 c.visual.stylify_metatags = str2bool(config.get('rhodecode_stylify_metatags'))
230 c.visual.stylify_metatags = str2bool(config.get('rhodecode_stylify_metatags'))
185
231
186 c.repo_name = get_repo_slug(request)
232 c.repo_name = get_repo_slug(request)
187 c.backends = BACKENDS.keys()
233 c.backends = BACKENDS.keys()
188 c.unread_notifications = NotificationModel()\
234 c.unread_notifications = NotificationModel()\
189 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
235 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
190 self.cut_off_limit = int(config.get('cut_off_limit'))
236 self.cut_off_limit = int(config.get('cut_off_limit'))
191
237
192 self.sa = meta.Session
238 self.sa = meta.Session
193 self.scm_model = ScmModel(self.sa)
239 self.scm_model = ScmModel(self.sa)
194 self.ip_addr = ''
240 self.ip_addr = ''
195
241
196 def __call__(self, environ, start_response):
242 def __call__(self, environ, start_response):
197 """Invoke the Controller"""
243 """Invoke the Controller"""
198 # WSGIController.__call__ dispatches to the Controller method
244 # WSGIController.__call__ dispatches to the Controller method
199 # the request is routed to. This routing information is
245 # the request is routed to. This routing information is
200 # available in environ['pylons.routes_dict']
246 # available in environ['pylons.routes_dict']
201 start = time.time()
247 start = time.time()
202 try:
248 try:
203 self.ip_addr = _get_ip_addr(environ)
249 self.ip_addr = _get_ip_addr(environ)
204 # make sure that we update permissions each time we call controller
250 # make sure that we update permissions each time we call controller
205 api_key = request.GET.get('api_key')
251 api_key = request.GET.get('api_key')
206 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
252 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
207 user_id = cookie_store.get('user_id', None)
253 user_id = cookie_store.get('user_id', None)
208 username = get_container_username(environ, config)
254 username = get_container_username(environ, config)
209 auth_user = AuthUser(user_id, api_key, username)
255 auth_user = AuthUser(user_id, api_key, username)
210 request.user = auth_user
256 request.user = auth_user
211 self.rhodecode_user = c.rhodecode_user = auth_user
257 self.rhodecode_user = c.rhodecode_user = auth_user
212 if not self.rhodecode_user.is_authenticated and \
258 if not self.rhodecode_user.is_authenticated and \
213 self.rhodecode_user.user_id is not None:
259 self.rhodecode_user.user_id is not None:
214 self.rhodecode_user.set_authenticated(
260 self.rhodecode_user.set_authenticated(
215 cookie_store.get('is_authenticated')
261 cookie_store.get('is_authenticated')
216 )
262 )
217 log.info('IP: %s User: %s accessed %s' % (
263 log.info('IP: %s User: %s accessed %s' % (
218 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
264 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
219 )
265 )
220 return WSGIController.__call__(self, environ, start_response)
266 return WSGIController.__call__(self, environ, start_response)
221 finally:
267 finally:
222 log.info('IP: %s Request to %s time: %.3fs' % (
268 log.info('IP: %s Request to %s time: %.3fs' % (
223 _get_ip_addr(environ),
269 _get_ip_addr(environ),
224 safe_unicode(_get_access_path(environ)), time.time() - start)
270 safe_unicode(_get_access_path(environ)), time.time() - start)
225 )
271 )
226 meta.Session.remove()
272 meta.Session.remove()
227
273
228
274
229 class BaseRepoController(BaseController):
275 class BaseRepoController(BaseController):
230 """
276 """
231 Base class for controllers responsible for loading all needed data for
277 Base class for controllers responsible for loading all needed data for
232 repository loaded items are
278 repository loaded items are
233
279
234 c.rhodecode_repo: instance of scm repository
280 c.rhodecode_repo: instance of scm repository
235 c.rhodecode_db_repo: instance of db
281 c.rhodecode_db_repo: instance of db
236 c.repository_followers: number of followers
282 c.repository_followers: number of followers
237 c.repository_forks: number of forks
283 c.repository_forks: number of forks
238 """
284 """
239
285
240 def __before__(self):
286 def __before__(self):
241 super(BaseRepoController, self).__before__()
287 super(BaseRepoController, self).__before__()
242 if c.repo_name:
288 if c.repo_name:
243
289
244 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
290 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
245 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
291 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
246
292
247 if c.rhodecode_repo is None:
293 if c.rhodecode_repo is None:
248 log.error('%s this repository is present in database but it '
294 log.error('%s this repository is present in database but it '
249 'cannot be created as an scm instance', c.repo_name)
295 'cannot be created as an scm instance', c.repo_name)
250
296
251 redirect(url('home'))
297 redirect(url('home'))
252
298
253 # some globals counter for menu
299 # some globals counter for menu
254 c.repository_followers = self.scm_model.get_followers(dbr)
300 c.repository_followers = self.scm_model.get_followers(dbr)
255 c.repository_forks = self.scm_model.get_forks(dbr)
301 c.repository_forks = self.scm_model.get_forks(dbr)
256 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
302 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
@@ -1,544 +1,553 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 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 modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import uuid
29 import uuid
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from rhodecode import __dbversion__
33 from rhodecode import __dbversion__
34 from rhodecode.model import meta
34 from rhodecode.model import meta
35
35
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.lib.utils import ask_ok
37 from rhodecode.lib.utils import ask_ok
38 from rhodecode.model import init_model
38 from rhodecode.model import init_model
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
40 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
41 UserRepoGroupToPerm
41 UserRepoGroupToPerm
42
42
43 from sqlalchemy.engine import create_engine
43 from sqlalchemy.engine import create_engine
44 from rhodecode.model.repos_group import ReposGroupModel
44 from rhodecode.model.repos_group import ReposGroupModel
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class DbManage(object):
49 class DbManage(object):
50 def __init__(self, log_sql, dbconf, root, tests=False):
50 def __init__(self, log_sql, dbconf, root, tests=False):
51 self.dbname = dbconf.split('/')[-1]
51 self.dbname = dbconf.split('/')[-1]
52 self.tests = tests
52 self.tests = tests
53 self.root = root
53 self.root = root
54 self.dburi = dbconf
54 self.dburi = dbconf
55 self.log_sql = log_sql
55 self.log_sql = log_sql
56 self.db_exists = False
56 self.db_exists = False
57 self.init_db()
57 self.init_db()
58
58
59 def init_db(self):
59 def init_db(self):
60 engine = create_engine(self.dburi, echo=self.log_sql)
60 engine = create_engine(self.dburi, echo=self.log_sql)
61 init_model(engine)
61 init_model(engine)
62 self.sa = meta.Session()
62 self.sa = meta.Session()
63
63
64 def create_tables(self, override=False, defaults={}):
64 def create_tables(self, override=False, defaults={}):
65 """
65 """
66 Create a auth database
66 Create a auth database
67 """
67 """
68 quiet = defaults.get('quiet')
68 quiet = defaults.get('quiet')
69 log.info("Any existing database is going to be destroyed")
69 log.info("Any existing database is going to be destroyed")
70 if self.tests or quiet:
70 if self.tests or quiet:
71 destroy = True
71 destroy = True
72 else:
72 else:
73 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
73 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
74 if not destroy:
74 if not destroy:
75 sys.exit()
75 sys.exit()
76 if destroy:
76 if destroy:
77 meta.Base.metadata.drop_all()
77 meta.Base.metadata.drop_all()
78
78
79 checkfirst = not override
79 checkfirst = not override
80 meta.Base.metadata.create_all(checkfirst=checkfirst)
80 meta.Base.metadata.create_all(checkfirst=checkfirst)
81 log.info('Created tables for %s' % self.dbname)
81 log.info('Created tables for %s' % self.dbname)
82
82
83 def set_db_version(self):
83 def set_db_version(self):
84 ver = DbMigrateVersion()
84 ver = DbMigrateVersion()
85 ver.version = __dbversion__
85 ver.version = __dbversion__
86 ver.repository_id = 'rhodecode_db_migrations'
86 ver.repository_id = 'rhodecode_db_migrations'
87 ver.repository_path = 'versions'
87 ver.repository_path = 'versions'
88 self.sa.add(ver)
88 self.sa.add(ver)
89 log.info('db version set to: %s' % __dbversion__)
89 log.info('db version set to: %s' % __dbversion__)
90
90
91 def upgrade(self):
91 def upgrade(self):
92 """
92 """
93 Upgrades given database schema to given revision following
93 Upgrades given database schema to given revision following
94 all needed steps, to perform the upgrade
94 all needed steps, to perform the upgrade
95
95
96 """
96 """
97
97
98 from rhodecode.lib.dbmigrate.migrate.versioning import api
98 from rhodecode.lib.dbmigrate.migrate.versioning import api
99 from rhodecode.lib.dbmigrate.migrate.exceptions import \
99 from rhodecode.lib.dbmigrate.migrate.exceptions import \
100 DatabaseNotControlledError
100 DatabaseNotControlledError
101
101
102 if 'sqlite' in self.dburi:
102 if 'sqlite' in self.dburi:
103 print (
103 print (
104 '********************** WARNING **********************\n'
104 '********************** WARNING **********************\n'
105 'Make sure your version of sqlite is at least 3.7.X. \n'
105 'Make sure your version of sqlite is at least 3.7.X. \n'
106 'Earlier versions are known to fail on some migrations\n'
106 'Earlier versions are known to fail on some migrations\n'
107 '*****************************************************\n'
107 '*****************************************************\n'
108 )
108 )
109 upgrade = ask_ok('You are about to perform database upgrade, make '
109 upgrade = ask_ok('You are about to perform database upgrade, make '
110 'sure You backed up your database before. '
110 'sure You backed up your database before. '
111 'Continue ? [y/n]')
111 'Continue ? [y/n]')
112 if not upgrade:
112 if not upgrade:
113 sys.exit('Nothing done')
113 sys.exit('Nothing done')
114
114
115 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
115 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
116 'rhodecode/lib/dbmigrate')
116 'rhodecode/lib/dbmigrate')
117 db_uri = self.dburi
117 db_uri = self.dburi
118
118
119 try:
119 try:
120 curr_version = api.db_version(db_uri, repository_path)
120 curr_version = api.db_version(db_uri, repository_path)
121 msg = ('Found current database under version'
121 msg = ('Found current database under version'
122 ' control with version %s' % curr_version)
122 ' control with version %s' % curr_version)
123
123
124 except (RuntimeError, DatabaseNotControlledError):
124 except (RuntimeError, DatabaseNotControlledError):
125 curr_version = 1
125 curr_version = 1
126 msg = ('Current database is not under version control. Setting'
126 msg = ('Current database is not under version control. Setting'
127 ' as version %s' % curr_version)
127 ' as version %s' % curr_version)
128 api.version_control(db_uri, repository_path, curr_version)
128 api.version_control(db_uri, repository_path, curr_version)
129
129
130 print (msg)
130 print (msg)
131
131
132 if curr_version == __dbversion__:
132 if curr_version == __dbversion__:
133 sys.exit('This database is already at the newest version')
133 sys.exit('This database is already at the newest version')
134
134
135 #======================================================================
135 #======================================================================
136 # UPGRADE STEPS
136 # UPGRADE STEPS
137 #======================================================================
137 #======================================================================
138 class UpgradeSteps(object):
138 class UpgradeSteps(object):
139 """
139 """
140 Those steps follow schema versions so for example schema
140 Those steps follow schema versions so for example schema
141 for example schema with seq 002 == step_2 and so on.
141 for example schema with seq 002 == step_2 and so on.
142 """
142 """
143
143
144 def __init__(self, klass):
144 def __init__(self, klass):
145 self.klass = klass
145 self.klass = klass
146
146
147 def step_0(self):
147 def step_0(self):
148 # step 0 is the schema upgrade, and than follow proper upgrades
148 # step 0 is the schema upgrade, and than follow proper upgrades
149 print ('attempting to do database upgrade to version %s' \
149 print ('attempting to do database upgrade to version %s' \
150 % __dbversion__)
150 % __dbversion__)
151 api.upgrade(db_uri, repository_path, __dbversion__)
151 api.upgrade(db_uri, repository_path, __dbversion__)
152 print ('Schema upgrade completed')
152 print ('Schema upgrade completed')
153
153
154 def step_1(self):
154 def step_1(self):
155 pass
155 pass
156
156
157 def step_2(self):
157 def step_2(self):
158 print ('Patching repo paths for newer version of RhodeCode')
158 print ('Patching repo paths for newer version of RhodeCode')
159 self.klass.fix_repo_paths()
159 self.klass.fix_repo_paths()
160
160
161 print ('Patching default user of RhodeCode')
161 print ('Patching default user of RhodeCode')
162 self.klass.fix_default_user()
162 self.klass.fix_default_user()
163
163
164 log.info('Changing ui settings')
164 log.info('Changing ui settings')
165 self.klass.create_ui_settings()
165 self.klass.create_ui_settings()
166
166
167 def step_3(self):
167 def step_3(self):
168 print ('Adding additional settings into RhodeCode db')
168 print ('Adding additional settings into RhodeCode db')
169 self.klass.fix_settings()
169 self.klass.fix_settings()
170 print ('Adding ldap defaults')
170 print ('Adding ldap defaults')
171 self.klass.create_ldap_options(skip_existing=True)
171 self.klass.create_ldap_options(skip_existing=True)
172
172
173 def step_4(self):
173 def step_4(self):
174 print ('create permissions and fix groups')
174 print ('create permissions and fix groups')
175 self.klass.create_permissions()
175 self.klass.create_permissions()
176 self.klass.fixup_groups()
176 self.klass.fixup_groups()
177
177
178 def step_5(self):
178 def step_5(self):
179 pass
179 pass
180
180
181 def step_6(self):
181 def step_6(self):
182 pass
182 pass
183 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
183 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
184
184
185 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
185 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
186 for step in upgrade_steps:
186 for step in upgrade_steps:
187 print ('performing upgrade step %s' % step)
187 print ('performing upgrade step %s' % step)
188 getattr(UpgradeSteps(self), 'step_%s' % step)()
188 getattr(UpgradeSteps(self), 'step_%s' % step)()
189 self.sa.commit()
189 self.sa.commit()
190
190
191 def fix_repo_paths(self):
191 def fix_repo_paths(self):
192 """
192 """
193 Fixes a old rhodecode version path into new one without a '*'
193 Fixes a old rhodecode version path into new one without a '*'
194 """
194 """
195
195
196 paths = self.sa.query(RhodeCodeUi)\
196 paths = self.sa.query(RhodeCodeUi)\
197 .filter(RhodeCodeUi.ui_key == '/')\
197 .filter(RhodeCodeUi.ui_key == '/')\
198 .scalar()
198 .scalar()
199
199
200 paths.ui_value = paths.ui_value.replace('*', '')
200 paths.ui_value = paths.ui_value.replace('*', '')
201
201
202 try:
202 try:
203 self.sa.add(paths)
203 self.sa.add(paths)
204 self.sa.commit()
204 self.sa.commit()
205 except:
205 except:
206 self.sa.rollback()
206 self.sa.rollback()
207 raise
207 raise
208
208
209 def fix_default_user(self):
209 def fix_default_user(self):
210 """
210 """
211 Fixes a old default user with some 'nicer' default values,
211 Fixes a old default user with some 'nicer' default values,
212 used mostly for anonymous access
212 used mostly for anonymous access
213 """
213 """
214 def_user = self.sa.query(User)\
214 def_user = self.sa.query(User)\
215 .filter(User.username == 'default')\
215 .filter(User.username == 'default')\
216 .one()
216 .one()
217
217
218 def_user.name = 'Anonymous'
218 def_user.name = 'Anonymous'
219 def_user.lastname = 'User'
219 def_user.lastname = 'User'
220 def_user.email = 'anonymous@rhodecode.org'
220 def_user.email = 'anonymous@rhodecode.org'
221
221
222 try:
222 try:
223 self.sa.add(def_user)
223 self.sa.add(def_user)
224 self.sa.commit()
224 self.sa.commit()
225 except:
225 except:
226 self.sa.rollback()
226 self.sa.rollback()
227 raise
227 raise
228
228
229 def fix_settings(self):
229 def fix_settings(self):
230 """
230 """
231 Fixes rhodecode settings adds ga_code key for google analytics
231 Fixes rhodecode settings adds ga_code key for google analytics
232 """
232 """
233
233
234 hgsettings3 = RhodeCodeSetting('ga_code', '')
234 hgsettings3 = RhodeCodeSetting('ga_code', '')
235
235
236 try:
236 try:
237 self.sa.add(hgsettings3)
237 self.sa.add(hgsettings3)
238 self.sa.commit()
238 self.sa.commit()
239 except:
239 except:
240 self.sa.rollback()
240 self.sa.rollback()
241 raise
241 raise
242
242
243 def admin_prompt(self, second=False, defaults={}):
243 def admin_prompt(self, second=False, defaults={}):
244 if not self.tests:
244 if not self.tests:
245 import getpass
245 import getpass
246
246
247 # defaults
247 # defaults
248 username = defaults.get('username')
248 username = defaults.get('username')
249 password = defaults.get('password')
249 password = defaults.get('password')
250 email = defaults.get('email')
250 email = defaults.get('email')
251
251
252 def get_password():
252 def get_password():
253 password = getpass.getpass('Specify admin password '
253 password = getpass.getpass('Specify admin password '
254 '(min 6 chars):')
254 '(min 6 chars):')
255 confirm = getpass.getpass('Confirm password:')
255 confirm = getpass.getpass('Confirm password:')
256
256
257 if password != confirm:
257 if password != confirm:
258 log.error('passwords mismatch')
258 log.error('passwords mismatch')
259 return False
259 return False
260 if len(password) < 6:
260 if len(password) < 6:
261 log.error('password is to short use at least 6 characters')
261 log.error('password is to short use at least 6 characters')
262 return False
262 return False
263
263
264 return password
264 return password
265 if username is None:
265 if username is None:
266 username = raw_input('Specify admin username:')
266 username = raw_input('Specify admin username:')
267 if password is None:
267 if password is None:
268 password = get_password()
268 password = get_password()
269 if not password:
269 if not password:
270 #second try
270 #second try
271 password = get_password()
271 password = get_password()
272 if not password:
272 if not password:
273 sys.exit()
273 sys.exit()
274 if email is None:
274 if email is None:
275 email = raw_input('Specify admin email:')
275 email = raw_input('Specify admin email:')
276 self.create_user(username, password, email, True)
276 self.create_user(username, password, email, True)
277 else:
277 else:
278 log.info('creating admin and regular test users')
278 log.info('creating admin and regular test users')
279 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
279 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
280 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
280 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
281 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
281 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
282 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
282 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
283 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
283 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
284
284
285 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
285 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
286 TEST_USER_ADMIN_EMAIL, True)
286 TEST_USER_ADMIN_EMAIL, True)
287
287
288 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
288 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
289 TEST_USER_REGULAR_EMAIL, False)
289 TEST_USER_REGULAR_EMAIL, False)
290
290
291 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
291 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
292 TEST_USER_REGULAR2_EMAIL, False)
292 TEST_USER_REGULAR2_EMAIL, False)
293
293
294 def create_ui_settings(self):
294 def create_ui_settings(self):
295 """
295 """
296 Creates ui settings, fills out hooks
296 Creates ui settings, fills out hooks
297 and disables dotencode
297 and disables dotencode
298 """
298 """
299
299
300 #HOOKS
300 #HOOKS
301 hooks1_key = RhodeCodeUi.HOOK_UPDATE
301 hooks1_key = RhodeCodeUi.HOOK_UPDATE
302 hooks1_ = self.sa.query(RhodeCodeUi)\
302 hooks1_ = self.sa.query(RhodeCodeUi)\
303 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
303 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
304
304
305 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
305 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
306 hooks1.ui_section = 'hooks'
306 hooks1.ui_section = 'hooks'
307 hooks1.ui_key = hooks1_key
307 hooks1.ui_key = hooks1_key
308 hooks1.ui_value = 'hg update >&2'
308 hooks1.ui_value = 'hg update >&2'
309 hooks1.ui_active = False
309 hooks1.ui_active = False
310 self.sa.add(hooks1)
310
311
311 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
312 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
312 hooks2_ = self.sa.query(RhodeCodeUi)\
313 hooks2_ = self.sa.query(RhodeCodeUi)\
313 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
314 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
314
315 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
315 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
316 hooks2.ui_section = 'hooks'
316 hooks2.ui_section = 'hooks'
317 hooks2.ui_key = hooks2_key
317 hooks2.ui_key = hooks2_key
318 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
318 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
319 self.sa.add(hooks2)
319
320
320 hooks3 = RhodeCodeUi()
321 hooks3 = RhodeCodeUi()
321 hooks3.ui_section = 'hooks'
322 hooks3.ui_section = 'hooks'
322 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
323 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
323 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
324 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
325 self.sa.add(hooks3)
324
326
325 hooks4 = RhodeCodeUi()
327 hooks4 = RhodeCodeUi()
326 hooks4.ui_section = 'hooks'
328 hooks4.ui_section = 'hooks'
327 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
329 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
328 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
330 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
331 self.sa.add(hooks4)
329
332
330 # For mercurial 1.7 set backward comapatibility with format
333 hooks5 = RhodeCodeUi()
331 dotencode_disable = RhodeCodeUi()
334 hooks5.ui_section = 'hooks'
332 dotencode_disable.ui_section = 'format'
335 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
333 dotencode_disable.ui_key = 'dotencode'
336 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
334 dotencode_disable.ui_value = 'false'
337 self.sa.add(hooks5)
338
339 hooks6 = RhodeCodeUi()
340 hooks6.ui_section = 'hooks'
341 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
342 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
343 self.sa.add(hooks6)
335
344
336 # enable largefiles
345 # enable largefiles
337 largefiles = RhodeCodeUi()
346 largefiles = RhodeCodeUi()
338 largefiles.ui_section = 'extensions'
347 largefiles.ui_section = 'extensions'
339 largefiles.ui_key = 'largefiles'
348 largefiles.ui_key = 'largefiles'
340 largefiles.ui_value = ''
349 largefiles.ui_value = ''
350 self.sa.add(largefiles)
341
351
342 # enable hgsubversion disabled by default
352 # enable hgsubversion disabled by default
343 hgsubversion = RhodeCodeUi()
353 hgsubversion = RhodeCodeUi()
344 hgsubversion.ui_section = 'extensions'
354 hgsubversion.ui_section = 'extensions'
345 hgsubversion.ui_key = 'hgsubversion'
355 hgsubversion.ui_key = 'hgsubversion'
346 hgsubversion.ui_value = ''
356 hgsubversion.ui_value = ''
347 hgsubversion.ui_active = False
357 hgsubversion.ui_active = False
358 self.sa.add(hgsubversion)
348
359
349 # enable hggit disabled by default
360 # enable hggit disabled by default
350 hggit = RhodeCodeUi()
361 hggit = RhodeCodeUi()
351 hggit.ui_section = 'extensions'
362 hggit.ui_section = 'extensions'
352 hggit.ui_key = 'hggit'
363 hggit.ui_key = 'hggit'
353 hggit.ui_value = ''
364 hggit.ui_value = ''
354 hggit.ui_active = False
365 hggit.ui_active = False
355
356 self.sa.add(hooks1)
357 self.sa.add(hooks2)
358 self.sa.add(hooks3)
359 self.sa.add(hooks4)
360 self.sa.add(largefiles)
361 self.sa.add(hgsubversion)
362 self.sa.add(hggit)
366 self.sa.add(hggit)
363
367
364 def create_ldap_options(self, skip_existing=False):
368 def create_ldap_options(self, skip_existing=False):
365 """Creates ldap settings"""
369 """Creates ldap settings"""
366
370
367 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
371 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
368 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
372 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
369 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
373 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
370 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
374 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
371 ('ldap_filter', ''), ('ldap_search_scope', ''),
375 ('ldap_filter', ''), ('ldap_search_scope', ''),
372 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
376 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
373 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
377 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
374
378
375 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
379 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
376 log.debug('Skipping option %s' % k)
380 log.debug('Skipping option %s' % k)
377 continue
381 continue
378 setting = RhodeCodeSetting(k, v)
382 setting = RhodeCodeSetting(k, v)
379 self.sa.add(setting)
383 self.sa.add(setting)
380
384
381 def fixup_groups(self):
385 def fixup_groups(self):
382 def_usr = User.get_by_username('default')
386 def_usr = User.get_by_username('default')
383 for g in RepoGroup.query().all():
387 for g in RepoGroup.query().all():
384 g.group_name = g.get_new_name(g.name)
388 g.group_name = g.get_new_name(g.name)
385 self.sa.add(g)
389 self.sa.add(g)
386 # get default perm
390 # get default perm
387 default = UserRepoGroupToPerm.query()\
391 default = UserRepoGroupToPerm.query()\
388 .filter(UserRepoGroupToPerm.group == g)\
392 .filter(UserRepoGroupToPerm.group == g)\
389 .filter(UserRepoGroupToPerm.user == def_usr)\
393 .filter(UserRepoGroupToPerm.user == def_usr)\
390 .scalar()
394 .scalar()
391
395
392 if default is None:
396 if default is None:
393 log.debug('missing default permission for group %s adding' % g)
397 log.debug('missing default permission for group %s adding' % g)
394 ReposGroupModel()._create_default_perms(g)
398 ReposGroupModel()._create_default_perms(g)
395
399
396 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
400 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
397 _path = defaults.get('repos_location')
401 _path = defaults.get('repos_location')
398 if retries == 3:
402 if retries == 3:
399 log.info('Setting up repositories config')
403 log.info('Setting up repositories config')
400
404
401 if _path is not None:
405 if _path is not None:
402 path = _path
406 path = _path
403 elif not self.tests and not test_repo_path:
407 elif not self.tests and not test_repo_path:
404 path = raw_input(
408 path = raw_input(
405 'Enter a valid absolute path to store repositories. '
409 'Enter a valid absolute path to store repositories. '
406 'All repositories in that path will be added automatically:'
410 'All repositories in that path will be added automatically:'
407 )
411 )
408 else:
412 else:
409 path = test_repo_path
413 path = test_repo_path
410 path_ok = True
414 path_ok = True
411
415
412 # check proper dir
416 # check proper dir
413 if not os.path.isdir(path):
417 if not os.path.isdir(path):
414 path_ok = False
418 path_ok = False
415 log.error('Given path %s is not a valid directory' % path)
419 log.error('Given path %s is not a valid directory' % path)
416
420
417 elif not os.path.isabs(path):
421 elif not os.path.isabs(path):
418 path_ok = False
422 path_ok = False
419 log.error('Given path %s is not an absolute path' % path)
423 log.error('Given path %s is not an absolute path' % path)
420
424
421 # check write access
425 # check write access
422 elif not os.access(path, os.W_OK) and path_ok:
426 elif not os.access(path, os.W_OK) and path_ok:
423 path_ok = False
427 path_ok = False
424 log.error('No write permission to given path %s' % path)
428 log.error('No write permission to given path %s' % path)
425
429
426 if retries == 0:
430 if retries == 0:
427 sys.exit('max retries reached')
431 sys.exit('max retries reached')
428 if path_ok is False:
432 if path_ok is False:
429 retries -= 1
433 retries -= 1
430 return self.config_prompt(test_repo_path, retries)
434 return self.config_prompt(test_repo_path, retries)
431
435
432 return path
436 return path
433
437
434 def create_settings(self, path):
438 def create_settings(self, path):
435
439
436 self.create_ui_settings()
440 self.create_ui_settings()
437
441
438 #HG UI OPTIONS
442 #HG UI OPTIONS
439 web1 = RhodeCodeUi()
443 web1 = RhodeCodeUi()
440 web1.ui_section = 'web'
444 web1.ui_section = 'web'
441 web1.ui_key = 'push_ssl'
445 web1.ui_key = 'push_ssl'
442 web1.ui_value = 'false'
446 web1.ui_value = 'false'
443
447
444 web2 = RhodeCodeUi()
448 web2 = RhodeCodeUi()
445 web2.ui_section = 'web'
449 web2.ui_section = 'web'
446 web2.ui_key = 'allow_archive'
450 web2.ui_key = 'allow_archive'
447 web2.ui_value = 'gz zip bz2'
451 web2.ui_value = 'gz zip bz2'
448
452
449 web3 = RhodeCodeUi()
453 web3 = RhodeCodeUi()
450 web3.ui_section = 'web'
454 web3.ui_section = 'web'
451 web3.ui_key = 'allow_push'
455 web3.ui_key = 'allow_push'
452 web3.ui_value = '*'
456 web3.ui_value = '*'
453
457
454 web4 = RhodeCodeUi()
458 web4 = RhodeCodeUi()
455 web4.ui_section = 'web'
459 web4.ui_section = 'web'
456 web4.ui_key = 'baseurl'
460 web4.ui_key = 'baseurl'
457 web4.ui_value = '/'
461 web4.ui_value = '/'
458
462
459 paths = RhodeCodeUi()
463 paths = RhodeCodeUi()
460 paths.ui_section = 'paths'
464 paths.ui_section = 'paths'
461 paths.ui_key = '/'
465 paths.ui_key = '/'
462 paths.ui_value = path
466 paths.ui_value = path
463
467
468 phases = RhodeCodeUi()
469 phases.ui_section = 'phases'
470 phases.ui_key = 'publish'
471 phases.ui_value = False
472
464 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
473 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
465 sett2 = RhodeCodeSetting('title', 'RhodeCode')
474 sett2 = RhodeCodeSetting('title', 'RhodeCode')
466 sett3 = RhodeCodeSetting('ga_code', '')
475 sett3 = RhodeCodeSetting('ga_code', '')
467
476
468 sett4 = RhodeCodeSetting('show_public_icon', True)
477 sett4 = RhodeCodeSetting('show_public_icon', True)
469 sett5 = RhodeCodeSetting('show_private_icon', True)
478 sett5 = RhodeCodeSetting('show_private_icon', True)
470 sett6 = RhodeCodeSetting('stylify_metatags', False)
479 sett6 = RhodeCodeSetting('stylify_metatags', False)
471
480
472 self.sa.add(web1)
481 self.sa.add(web1)
473 self.sa.add(web2)
482 self.sa.add(web2)
474 self.sa.add(web3)
483 self.sa.add(web3)
475 self.sa.add(web4)
484 self.sa.add(web4)
476 self.sa.add(paths)
485 self.sa.add(paths)
477 self.sa.add(sett1)
486 self.sa.add(sett1)
478 self.sa.add(sett2)
487 self.sa.add(sett2)
479 self.sa.add(sett3)
488 self.sa.add(sett3)
480 self.sa.add(sett4)
489 self.sa.add(sett4)
481 self.sa.add(sett5)
490 self.sa.add(sett5)
482 self.sa.add(sett6)
491 self.sa.add(sett6)
483
492
484 self.create_ldap_options()
493 self.create_ldap_options()
485
494
486 log.info('created ui config')
495 log.info('created ui config')
487
496
488 def create_user(self, username, password, email='', admin=False):
497 def create_user(self, username, password, email='', admin=False):
489 log.info('creating user %s' % username)
498 log.info('creating user %s' % username)
490 UserModel().create_or_update(username, password, email,
499 UserModel().create_or_update(username, password, email,
491 firstname='RhodeCode', lastname='Admin',
500 firstname='RhodeCode', lastname='Admin',
492 active=True, admin=admin)
501 active=True, admin=admin)
493
502
494 def create_default_user(self):
503 def create_default_user(self):
495 log.info('creating default user')
504 log.info('creating default user')
496 # create default user for handling default permissions.
505 # create default user for handling default permissions.
497 UserModel().create_or_update(username='default',
506 UserModel().create_or_update(username='default',
498 password=str(uuid.uuid1())[:8],
507 password=str(uuid.uuid1())[:8],
499 email='anonymous@rhodecode.org',
508 email='anonymous@rhodecode.org',
500 firstname='Anonymous', lastname='User')
509 firstname='Anonymous', lastname='User')
501
510
502 def create_permissions(self):
511 def create_permissions(self):
503 # module.(access|create|change|delete)_[name]
512 # module.(access|create|change|delete)_[name]
504 # module.(none|read|write|admin)
513 # module.(none|read|write|admin)
505
514
506 for p in Permission.PERMS:
515 for p in Permission.PERMS:
507 if not Permission.get_by_key(p[0]):
516 if not Permission.get_by_key(p[0]):
508 new_perm = Permission()
517 new_perm = Permission()
509 new_perm.permission_name = p[0]
518 new_perm.permission_name = p[0]
510 new_perm.permission_longname = p[0]
519 new_perm.permission_longname = p[0]
511 self.sa.add(new_perm)
520 self.sa.add(new_perm)
512
521
513 def populate_default_permissions(self):
522 def populate_default_permissions(self):
514 log.info('creating default user permissions')
523 log.info('creating default user permissions')
515
524
516 default_user = User.get_by_username('default')
525 default_user = User.get_by_username('default')
517
526
518 reg_perm = UserToPerm()
527 reg_perm = UserToPerm()
519 reg_perm.user = default_user
528 reg_perm.user = default_user
520 reg_perm.permission = self.sa.query(Permission)\
529 reg_perm.permission = self.sa.query(Permission)\
521 .filter(Permission.permission_name == 'hg.register.manual_activate')\
530 .filter(Permission.permission_name == 'hg.register.manual_activate')\
522 .scalar()
531 .scalar()
523 self.sa.add(reg_perm)
532 self.sa.add(reg_perm)
524
533
525 create_repo_perm = UserToPerm()
534 create_repo_perm = UserToPerm()
526 create_repo_perm.user = default_user
535 create_repo_perm.user = default_user
527 create_repo_perm.permission = self.sa.query(Permission)\
536 create_repo_perm.permission = self.sa.query(Permission)\
528 .filter(Permission.permission_name == 'hg.create.repository')\
537 .filter(Permission.permission_name == 'hg.create.repository')\
529 .scalar()
538 .scalar()
530 self.sa.add(create_repo_perm)
539 self.sa.add(create_repo_perm)
531
540
532 default_fork_perm = UserToPerm()
541 default_fork_perm = UserToPerm()
533 default_fork_perm.user = default_user
542 default_fork_perm.user = default_user
534 default_fork_perm.permission = self.sa.query(Permission)\
543 default_fork_perm.permission = self.sa.query(Permission)\
535 .filter(Permission.permission_name == 'hg.fork.repository')\
544 .filter(Permission.permission_name == 'hg.fork.repository')\
536 .scalar()
545 .scalar()
537 self.sa.add(default_fork_perm)
546 self.sa.add(default_fork_perm)
538
547
539 default_repo_perm = UserToPerm()
548 default_repo_perm = UserToPerm()
540 default_repo_perm.user = default_user
549 default_repo_perm.user = default_user
541 default_repo_perm.permission = self.sa.query(Permission)\
550 default_repo_perm.permission = self.sa.query(Permission)\
542 .filter(Permission.permission_name == 'repository.read')\
551 .filter(Permission.permission_name == 'repository.read')\
543 .scalar()
552 .scalar()
544 self.sa.add(default_repo_perm)
553 self.sa.add(default_repo_perm)
@@ -1,56 +1,71 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.exceptions
3 rhodecode.lib.exceptions
4 ~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Set of custom exceptions used in RhodeCode
6 Set of custom exceptions used in RhodeCode
7
7
8 :created_on: Nov 17, 2010
8 :created_on: Nov 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 from webob.exc import HTTPClientError
27
26
28
27 class LdapUsernameError(Exception):
29 class LdapUsernameError(Exception):
28 pass
30 pass
29
31
30
32
31 class LdapPasswordError(Exception):
33 class LdapPasswordError(Exception):
32 pass
34 pass
33
35
34
36
35 class LdapConnectionError(Exception):
37 class LdapConnectionError(Exception):
36 pass
38 pass
37
39
38
40
39 class LdapImportError(Exception):
41 class LdapImportError(Exception):
40 pass
42 pass
41
43
42
44
43 class DefaultUserException(Exception):
45 class DefaultUserException(Exception):
44 pass
46 pass
45
47
46
48
47 class UserOwnsReposException(Exception):
49 class UserOwnsReposException(Exception):
48 pass
50 pass
49
51
50
52
51 class UsersGroupsAssignedException(Exception):
53 class UsersGroupsAssignedException(Exception):
52 pass
54 pass
53
55
54
56
55 class StatusChangeOnClosedPullRequestError(Exception):
57 class StatusChangeOnClosedPullRequestError(Exception):
56 pass No newline at end of file
58 pass
59
60
61 class HTTPLockedRC(HTTPClientError):
62 """
63 Special Exception For locked Repos in RhodeCode
64 """
65 code = 423
66 title = explanation = 'Repository Locked'
67
68 def __init__(self, reponame, username, *args, **kwargs):
69 self.title = self.explanation = ('Repository `%s` locked by '
70 'user `%s`' % (reponame, username))
71 super(HTTPLockedRC, self).__init__(*args, **kwargs)
@@ -1,1051 +1,1064 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13
13
14 from datetime import datetime
14 from datetime import datetime
15 from pygments.formatters.html import HtmlFormatter
15 from pygments.formatters.html import HtmlFormatter
16 from pygments import highlight as code_highlight
16 from pygments import highlight as code_highlight
17 from pylons import url, request, config
17 from pylons import url, request, config
18 from pylons.i18n.translation import _, ungettext
18 from pylons.i18n.translation import _, ungettext
19 from hashlib import md5
19 from hashlib import md5
20
20
21 from webhelpers.html import literal, HTML, escape
21 from webhelpers.html import literal, HTML, escape
22 from webhelpers.html.tools import *
22 from webhelpers.html.tools import *
23 from webhelpers.html.builder import make_tag
23 from webhelpers.html.builder import make_tag
24 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
24 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
25 end_form, file, form, hidden, image, javascript_link, link_to, \
25 end_form, file, form, hidden, image, javascript_link, link_to, \
26 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
26 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
27 submit, text, password, textarea, title, ul, xml_declaration, radio
27 submit, text, password, textarea, title, ul, xml_declaration, radio
28 from webhelpers.html.tools import auto_link, button_to, highlight, \
28 from webhelpers.html.tools import auto_link, button_to, highlight, \
29 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
29 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
30 from webhelpers.number import format_byte_size, format_bit_size
30 from webhelpers.number import format_byte_size, format_bit_size
31 from webhelpers.pylonslib import Flash as _Flash
31 from webhelpers.pylonslib import Flash as _Flash
32 from webhelpers.pylonslib.secure_form import secure_form
32 from webhelpers.pylonslib.secure_form import secure_form
33 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
33 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
34 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
34 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
35 replace_whitespace, urlify, truncate, wrap_paragraphs
35 replace_whitespace, urlify, truncate, wrap_paragraphs
36 from webhelpers.date import time_ago_in_words
36 from webhelpers.date import time_ago_in_words
37 from webhelpers.paginate import Page
37 from webhelpers.paginate import Page
38 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
38 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
39 convert_boolean_attrs, NotGiven, _make_safe_id_component
39 convert_boolean_attrs, NotGiven, _make_safe_id_component
40
40
41 from rhodecode.lib.annotate import annotate_highlight
41 from rhodecode.lib.annotate import annotate_highlight
42 from rhodecode.lib.utils import repo_name_slug
42 from rhodecode.lib.utils import repo_name_slug
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
44 get_changeset_safe
44 get_changeset_safe, datetime_to_time, time_to_datetime
45 from rhodecode.lib.markup_renderer import MarkupRenderer
45 from rhodecode.lib.markup_renderer import MarkupRenderer
46 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
46 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
48 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
49 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.db import URL_SEP, Permission
50 from rhodecode.model.db import URL_SEP, Permission
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 html_escape_table = {
55 html_escape_table = {
56 "&": "&amp;",
56 "&": "&amp;",
57 '"': "&quot;",
57 '"': "&quot;",
58 "'": "&apos;",
58 "'": "&apos;",
59 ">": "&gt;",
59 ">": "&gt;",
60 "<": "&lt;",
60 "<": "&lt;",
61 }
61 }
62
62
63
63
64 def html_escape(text):
64 def html_escape(text):
65 """Produce entities within text."""
65 """Produce entities within text."""
66 return "".join(html_escape_table.get(c,c) for c in text)
66 return "".join(html_escape_table.get(c,c) for c in text)
67
67
68
68
69 def shorter(text, size=20):
69 def shorter(text, size=20):
70 postfix = '...'
70 postfix = '...'
71 if len(text) > size:
71 if len(text) > size:
72 return text[:size - len(postfix)] + postfix
72 return text[:size - len(postfix)] + postfix
73 return text
73 return text
74
74
75
75
76 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
76 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
77 """
77 """
78 Reset button
78 Reset button
79 """
79 """
80 _set_input_attrs(attrs, type, name, value)
80 _set_input_attrs(attrs, type, name, value)
81 _set_id_attr(attrs, id, name)
81 _set_id_attr(attrs, id, name)
82 convert_boolean_attrs(attrs, ["disabled"])
82 convert_boolean_attrs(attrs, ["disabled"])
83 return HTML.input(**attrs)
83 return HTML.input(**attrs)
84
84
85 reset = _reset
85 reset = _reset
86 safeid = _make_safe_id_component
86 safeid = _make_safe_id_component
87
87
88
88
89 def FID(raw_id, path):
89 def FID(raw_id, path):
90 """
90 """
91 Creates a uniqe ID for filenode based on it's hash of path and revision
91 Creates a uniqe ID for filenode based on it's hash of path and revision
92 it's safe to use in urls
92 it's safe to use in urls
93
93
94 :param raw_id:
94 :param raw_id:
95 :param path:
95 :param path:
96 """
96 """
97
97
98 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
98 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
99
99
100
100
101 def get_token():
101 def get_token():
102 """Return the current authentication token, creating one if one doesn't
102 """Return the current authentication token, creating one if one doesn't
103 already exist.
103 already exist.
104 """
104 """
105 token_key = "_authentication_token"
105 token_key = "_authentication_token"
106 from pylons import session
106 from pylons import session
107 if not token_key in session:
107 if not token_key in session:
108 try:
108 try:
109 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
109 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
110 except AttributeError: # Python < 2.4
110 except AttributeError: # Python < 2.4
111 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
111 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
112 session[token_key] = token
112 session[token_key] = token
113 if hasattr(session, 'save'):
113 if hasattr(session, 'save'):
114 session.save()
114 session.save()
115 return session[token_key]
115 return session[token_key]
116
116
117
117
118 class _GetError(object):
118 class _GetError(object):
119 """Get error from form_errors, and represent it as span wrapped error
119 """Get error from form_errors, and represent it as span wrapped error
120 message
120 message
121
121
122 :param field_name: field to fetch errors for
122 :param field_name: field to fetch errors for
123 :param form_errors: form errors dict
123 :param form_errors: form errors dict
124 """
124 """
125
125
126 def __call__(self, field_name, form_errors):
126 def __call__(self, field_name, form_errors):
127 tmpl = """<span class="error_msg">%s</span>"""
127 tmpl = """<span class="error_msg">%s</span>"""
128 if form_errors and field_name in form_errors:
128 if form_errors and field_name in form_errors:
129 return literal(tmpl % form_errors.get(field_name))
129 return literal(tmpl % form_errors.get(field_name))
130
130
131 get_error = _GetError()
131 get_error = _GetError()
132
132
133
133
134 class _ToolTip(object):
134 class _ToolTip(object):
135
135
136 def __call__(self, tooltip_title, trim_at=50):
136 def __call__(self, tooltip_title, trim_at=50):
137 """
137 """
138 Special function just to wrap our text into nice formatted
138 Special function just to wrap our text into nice formatted
139 autowrapped text
139 autowrapped text
140
140
141 :param tooltip_title:
141 :param tooltip_title:
142 """
142 """
143 tooltip_title = escape(tooltip_title)
143 tooltip_title = escape(tooltip_title)
144 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
144 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
145 return tooltip_title
145 return tooltip_title
146 tooltip = _ToolTip()
146 tooltip = _ToolTip()
147
147
148
148
149 class _FilesBreadCrumbs(object):
149 class _FilesBreadCrumbs(object):
150
150
151 def __call__(self, repo_name, rev, paths):
151 def __call__(self, repo_name, rev, paths):
152 if isinstance(paths, str):
152 if isinstance(paths, str):
153 paths = safe_unicode(paths)
153 paths = safe_unicode(paths)
154 url_l = [link_to(repo_name, url('files_home',
154 url_l = [link_to(repo_name, url('files_home',
155 repo_name=repo_name,
155 repo_name=repo_name,
156 revision=rev, f_path=''),
156 revision=rev, f_path=''),
157 class_='ypjax-link')]
157 class_='ypjax-link')]
158 paths_l = paths.split('/')
158 paths_l = paths.split('/')
159 for cnt, p in enumerate(paths_l):
159 for cnt, p in enumerate(paths_l):
160 if p != '':
160 if p != '':
161 url_l.append(link_to(p,
161 url_l.append(link_to(p,
162 url('files_home',
162 url('files_home',
163 repo_name=repo_name,
163 repo_name=repo_name,
164 revision=rev,
164 revision=rev,
165 f_path='/'.join(paths_l[:cnt + 1])
165 f_path='/'.join(paths_l[:cnt + 1])
166 ),
166 ),
167 class_='ypjax-link'
167 class_='ypjax-link'
168 )
168 )
169 )
169 )
170
170
171 return literal('/'.join(url_l))
171 return literal('/'.join(url_l))
172
172
173 files_breadcrumbs = _FilesBreadCrumbs()
173 files_breadcrumbs = _FilesBreadCrumbs()
174
174
175
175
176 class CodeHtmlFormatter(HtmlFormatter):
176 class CodeHtmlFormatter(HtmlFormatter):
177 """
177 """
178 My code Html Formatter for source codes
178 My code Html Formatter for source codes
179 """
179 """
180
180
181 def wrap(self, source, outfile):
181 def wrap(self, source, outfile):
182 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
182 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
183
183
184 def _wrap_code(self, source):
184 def _wrap_code(self, source):
185 for cnt, it in enumerate(source):
185 for cnt, it in enumerate(source):
186 i, t = it
186 i, t = it
187 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
187 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
188 yield i, t
188 yield i, t
189
189
190 def _wrap_tablelinenos(self, inner):
190 def _wrap_tablelinenos(self, inner):
191 dummyoutfile = StringIO.StringIO()
191 dummyoutfile = StringIO.StringIO()
192 lncount = 0
192 lncount = 0
193 for t, line in inner:
193 for t, line in inner:
194 if t:
194 if t:
195 lncount += 1
195 lncount += 1
196 dummyoutfile.write(line)
196 dummyoutfile.write(line)
197
197
198 fl = self.linenostart
198 fl = self.linenostart
199 mw = len(str(lncount + fl - 1))
199 mw = len(str(lncount + fl - 1))
200 sp = self.linenospecial
200 sp = self.linenospecial
201 st = self.linenostep
201 st = self.linenostep
202 la = self.lineanchors
202 la = self.lineanchors
203 aln = self.anchorlinenos
203 aln = self.anchorlinenos
204 nocls = self.noclasses
204 nocls = self.noclasses
205 if sp:
205 if sp:
206 lines = []
206 lines = []
207
207
208 for i in range(fl, fl + lncount):
208 for i in range(fl, fl + lncount):
209 if i % st == 0:
209 if i % st == 0:
210 if i % sp == 0:
210 if i % sp == 0:
211 if aln:
211 if aln:
212 lines.append('<a href="#%s%d" class="special">%*d</a>' %
212 lines.append('<a href="#%s%d" class="special">%*d</a>' %
213 (la, i, mw, i))
213 (la, i, mw, i))
214 else:
214 else:
215 lines.append('<span class="special">%*d</span>' % (mw, i))
215 lines.append('<span class="special">%*d</span>' % (mw, i))
216 else:
216 else:
217 if aln:
217 if aln:
218 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
218 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
219 else:
219 else:
220 lines.append('%*d' % (mw, i))
220 lines.append('%*d' % (mw, i))
221 else:
221 else:
222 lines.append('')
222 lines.append('')
223 ls = '\n'.join(lines)
223 ls = '\n'.join(lines)
224 else:
224 else:
225 lines = []
225 lines = []
226 for i in range(fl, fl + lncount):
226 for i in range(fl, fl + lncount):
227 if i % st == 0:
227 if i % st == 0:
228 if aln:
228 if aln:
229 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
229 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
230 else:
230 else:
231 lines.append('%*d' % (mw, i))
231 lines.append('%*d' % (mw, i))
232 else:
232 else:
233 lines.append('')
233 lines.append('')
234 ls = '\n'.join(lines)
234 ls = '\n'.join(lines)
235
235
236 # in case you wonder about the seemingly redundant <div> here: since the
236 # in case you wonder about the seemingly redundant <div> here: since the
237 # content in the other cell also is wrapped in a div, some browsers in
237 # content in the other cell also is wrapped in a div, some browsers in
238 # some configurations seem to mess up the formatting...
238 # some configurations seem to mess up the formatting...
239 if nocls:
239 if nocls:
240 yield 0, ('<table class="%stable">' % self.cssclass +
240 yield 0, ('<table class="%stable">' % self.cssclass +
241 '<tr><td><div class="linenodiv" '
241 '<tr><td><div class="linenodiv" '
242 'style="background-color: #f0f0f0; padding-right: 10px">'
242 'style="background-color: #f0f0f0; padding-right: 10px">'
243 '<pre style="line-height: 125%">' +
243 '<pre style="line-height: 125%">' +
244 ls + '</pre></div></td><td id="hlcode" class="code">')
244 ls + '</pre></div></td><td id="hlcode" class="code">')
245 else:
245 else:
246 yield 0, ('<table class="%stable">' % self.cssclass +
246 yield 0, ('<table class="%stable">' % self.cssclass +
247 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
247 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
248 ls + '</pre></div></td><td id="hlcode" class="code">')
248 ls + '</pre></div></td><td id="hlcode" class="code">')
249 yield 0, dummyoutfile.getvalue()
249 yield 0, dummyoutfile.getvalue()
250 yield 0, '</td></tr></table>'
250 yield 0, '</td></tr></table>'
251
251
252
252
253 def pygmentize(filenode, **kwargs):
253 def pygmentize(filenode, **kwargs):
254 """pygmentize function using pygments
254 """pygmentize function using pygments
255
255
256 :param filenode:
256 :param filenode:
257 """
257 """
258
258
259 return literal(code_highlight(filenode.content,
259 return literal(code_highlight(filenode.content,
260 filenode.lexer, CodeHtmlFormatter(**kwargs)))
260 filenode.lexer, CodeHtmlFormatter(**kwargs)))
261
261
262
262
263 def pygmentize_annotation(repo_name, filenode, **kwargs):
263 def pygmentize_annotation(repo_name, filenode, **kwargs):
264 """
264 """
265 pygmentize function for annotation
265 pygmentize function for annotation
266
266
267 :param filenode:
267 :param filenode:
268 """
268 """
269
269
270 color_dict = {}
270 color_dict = {}
271
271
272 def gen_color(n=10000):
272 def gen_color(n=10000):
273 """generator for getting n of evenly distributed colors using
273 """generator for getting n of evenly distributed colors using
274 hsv color and golden ratio. It always return same order of colors
274 hsv color and golden ratio. It always return same order of colors
275
275
276 :returns: RGB tuple
276 :returns: RGB tuple
277 """
277 """
278
278
279 def hsv_to_rgb(h, s, v):
279 def hsv_to_rgb(h, s, v):
280 if s == 0.0:
280 if s == 0.0:
281 return v, v, v
281 return v, v, v
282 i = int(h * 6.0) # XXX assume int() truncates!
282 i = int(h * 6.0) # XXX assume int() truncates!
283 f = (h * 6.0) - i
283 f = (h * 6.0) - i
284 p = v * (1.0 - s)
284 p = v * (1.0 - s)
285 q = v * (1.0 - s * f)
285 q = v * (1.0 - s * f)
286 t = v * (1.0 - s * (1.0 - f))
286 t = v * (1.0 - s * (1.0 - f))
287 i = i % 6
287 i = i % 6
288 if i == 0:
288 if i == 0:
289 return v, t, p
289 return v, t, p
290 if i == 1:
290 if i == 1:
291 return q, v, p
291 return q, v, p
292 if i == 2:
292 if i == 2:
293 return p, v, t
293 return p, v, t
294 if i == 3:
294 if i == 3:
295 return p, q, v
295 return p, q, v
296 if i == 4:
296 if i == 4:
297 return t, p, v
297 return t, p, v
298 if i == 5:
298 if i == 5:
299 return v, p, q
299 return v, p, q
300
300
301 golden_ratio = 0.618033988749895
301 golden_ratio = 0.618033988749895
302 h = 0.22717784590367374
302 h = 0.22717784590367374
303
303
304 for _ in xrange(n):
304 for _ in xrange(n):
305 h += golden_ratio
305 h += golden_ratio
306 h %= 1
306 h %= 1
307 HSV_tuple = [h, 0.95, 0.95]
307 HSV_tuple = [h, 0.95, 0.95]
308 RGB_tuple = hsv_to_rgb(*HSV_tuple)
308 RGB_tuple = hsv_to_rgb(*HSV_tuple)
309 yield map(lambda x: str(int(x * 256)), RGB_tuple)
309 yield map(lambda x: str(int(x * 256)), RGB_tuple)
310
310
311 cgenerator = gen_color()
311 cgenerator = gen_color()
312
312
313 def get_color_string(cs):
313 def get_color_string(cs):
314 if cs in color_dict:
314 if cs in color_dict:
315 col = color_dict[cs]
315 col = color_dict[cs]
316 else:
316 else:
317 col = color_dict[cs] = cgenerator.next()
317 col = color_dict[cs] = cgenerator.next()
318 return "color: rgb(%s)! important;" % (', '.join(col))
318 return "color: rgb(%s)! important;" % (', '.join(col))
319
319
320 def url_func(repo_name):
320 def url_func(repo_name):
321
321
322 def _url_func(changeset):
322 def _url_func(changeset):
323 author = changeset.author
323 author = changeset.author
324 date = changeset.date
324 date = changeset.date
325 message = tooltip(changeset.message)
325 message = tooltip(changeset.message)
326
326
327 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
327 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
328 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
328 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
329 "</b> %s<br/></div>")
329 "</b> %s<br/></div>")
330
330
331 tooltip_html = tooltip_html % (author, date, message)
331 tooltip_html = tooltip_html % (author, date, message)
332 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
332 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
333 short_id(changeset.raw_id))
333 short_id(changeset.raw_id))
334 uri = link_to(
334 uri = link_to(
335 lnk_format,
335 lnk_format,
336 url('changeset_home', repo_name=repo_name,
336 url('changeset_home', repo_name=repo_name,
337 revision=changeset.raw_id),
337 revision=changeset.raw_id),
338 style=get_color_string(changeset.raw_id),
338 style=get_color_string(changeset.raw_id),
339 class_='tooltip',
339 class_='tooltip',
340 title=tooltip_html
340 title=tooltip_html
341 )
341 )
342
342
343 uri += '\n'
343 uri += '\n'
344 return uri
344 return uri
345 return _url_func
345 return _url_func
346
346
347 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
347 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
348
348
349
349
350 def is_following_repo(repo_name, user_id):
350 def is_following_repo(repo_name, user_id):
351 from rhodecode.model.scm import ScmModel
351 from rhodecode.model.scm import ScmModel
352 return ScmModel().is_following_repo(repo_name, user_id)
352 return ScmModel().is_following_repo(repo_name, user_id)
353
353
354 flash = _Flash()
354 flash = _Flash()
355
355
356 #==============================================================================
356 #==============================================================================
357 # SCM FILTERS available via h.
357 # SCM FILTERS available via h.
358 #==============================================================================
358 #==============================================================================
359 from rhodecode.lib.vcs.utils import author_name, author_email
359 from rhodecode.lib.vcs.utils import author_name, author_email
360 from rhodecode.lib.utils2 import credentials_filter, age as _age
360 from rhodecode.lib.utils2 import credentials_filter, age as _age
361 from rhodecode.model.db import User, ChangesetStatus
361 from rhodecode.model.db import User, ChangesetStatus
362
362
363 age = lambda x: _age(x)
363 age = lambda x: _age(x)
364 capitalize = lambda x: x.capitalize()
364 capitalize = lambda x: x.capitalize()
365 email = author_email
365 email = author_email
366 short_id = lambda x: x[:12]
366 short_id = lambda x: x[:12]
367 hide_credentials = lambda x: ''.join(credentials_filter(x))
367 hide_credentials = lambda x: ''.join(credentials_filter(x))
368
368
369
369
370 def fmt_date(date):
370 def fmt_date(date):
371 if date:
371 if date:
372 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
372 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
373 return date.strftime(_fmt).decode('utf8')
373 return date.strftime(_fmt).decode('utf8')
374
374
375 return ""
375 return ""
376
376
377
377
378 def is_git(repository):
378 def is_git(repository):
379 if hasattr(repository, 'alias'):
379 if hasattr(repository, 'alias'):
380 _type = repository.alias
380 _type = repository.alias
381 elif hasattr(repository, 'repo_type'):
381 elif hasattr(repository, 'repo_type'):
382 _type = repository.repo_type
382 _type = repository.repo_type
383 else:
383 else:
384 _type = repository
384 _type = repository
385 return _type == 'git'
385 return _type == 'git'
386
386
387
387
388 def is_hg(repository):
388 def is_hg(repository):
389 if hasattr(repository, 'alias'):
389 if hasattr(repository, 'alias'):
390 _type = repository.alias
390 _type = repository.alias
391 elif hasattr(repository, 'repo_type'):
391 elif hasattr(repository, 'repo_type'):
392 _type = repository.repo_type
392 _type = repository.repo_type
393 else:
393 else:
394 _type = repository
394 _type = repository
395 return _type == 'hg'
395 return _type == 'hg'
396
396
397
397
398 def email_or_none(author):
398 def email_or_none(author):
399 # extract email from the commit string
399 # extract email from the commit string
400 _email = email(author)
400 _email = email(author)
401 if _email != '':
401 if _email != '':
402 # check it against RhodeCode database, and use the MAIN email for this
402 # check it against RhodeCode database, and use the MAIN email for this
403 # user
403 # user
404 user = User.get_by_email(_email, case_insensitive=True, cache=True)
404 user = User.get_by_email(_email, case_insensitive=True, cache=True)
405 if user is not None:
405 if user is not None:
406 return user.email
406 return user.email
407 return _email
407 return _email
408
408
409 # See if it contains a username we can get an email from
409 # See if it contains a username we can get an email from
410 user = User.get_by_username(author_name(author), case_insensitive=True,
410 user = User.get_by_username(author_name(author), case_insensitive=True,
411 cache=True)
411 cache=True)
412 if user is not None:
412 if user is not None:
413 return user.email
413 return user.email
414
414
415 # No valid email, not a valid user in the system, none!
415 # No valid email, not a valid user in the system, none!
416 return None
416 return None
417
417
418
418
419 def person(author):
419 def person(author):
420 # attr to return from fetched user
420 # attr to return from fetched user
421 person_getter = lambda usr: usr.username
421 person_getter = lambda usr: usr.username
422
422
423 # Valid email in the attribute passed, see if they're in the system
423 # Valid email in the attribute passed, see if they're in the system
424 _email = email(author)
424 _email = email(author)
425 if _email != '':
425 if _email != '':
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 if user is not None:
427 if user is not None:
428 return person_getter(user)
428 return person_getter(user)
429 return _email
429 return _email
430
430
431 # Maybe it's a username?
431 # Maybe it's a username?
432 _author = author_name(author)
432 _author = author_name(author)
433 user = User.get_by_username(_author, case_insensitive=True,
433 user = User.get_by_username(_author, case_insensitive=True,
434 cache=True)
434 cache=True)
435 if user is not None:
435 if user is not None:
436 return person_getter(user)
436 return person_getter(user)
437
437
438 # Still nothing? Just pass back the author name then
438 # Still nothing? Just pass back the author name then
439 return _author
439 return _author
440
440
441
441
442 def person_by_id(id_):
443 # attr to return from fetched user
444 person_getter = lambda usr: usr.username
445
446 #maybe it's an ID ?
447 if str(id_).isdigit() or isinstance(id_, int):
448 id_ = int(id_)
449 user = User.get(id_)
450 if user is not None:
451 return person_getter(user)
452 return id_
453
454
442 def desc_stylize(value):
455 def desc_stylize(value):
443 """
456 """
444 converts tags from value into html equivalent
457 converts tags from value into html equivalent
445
458
446 :param value:
459 :param value:
447 """
460 """
448 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
461 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
449 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
462 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
450 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
463 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
451 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
464 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
452 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
465 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
453 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
466 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
454 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
467 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
455 '<div class="metatag" tag="lang">\\2</div>', value)
468 '<div class="metatag" tag="lang">\\2</div>', value)
456 value = re.sub(r'\[([a-z]+)\]',
469 value = re.sub(r'\[([a-z]+)\]',
457 '<div class="metatag" tag="\\1">\\1</div>', value)
470 '<div class="metatag" tag="\\1">\\1</div>', value)
458
471
459 return value
472 return value
460
473
461
474
462 def bool2icon(value):
475 def bool2icon(value):
463 """Returns True/False values represented as small html image of true/false
476 """Returns True/False values represented as small html image of true/false
464 icons
477 icons
465
478
466 :param value: bool value
479 :param value: bool value
467 """
480 """
468
481
469 if value is True:
482 if value is True:
470 return HTML.tag('img', src=url("/images/icons/accept.png"),
483 return HTML.tag('img', src=url("/images/icons/accept.png"),
471 alt=_('True'))
484 alt=_('True'))
472
485
473 if value is False:
486 if value is False:
474 return HTML.tag('img', src=url("/images/icons/cancel.png"),
487 return HTML.tag('img', src=url("/images/icons/cancel.png"),
475 alt=_('False'))
488 alt=_('False'))
476
489
477 return value
490 return value
478
491
479
492
480 def action_parser(user_log, feed=False):
493 def action_parser(user_log, feed=False):
481 """
494 """
482 This helper will action_map the specified string action into translated
495 This helper will action_map the specified string action into translated
483 fancy names with icons and links
496 fancy names with icons and links
484
497
485 :param user_log: user log instance
498 :param user_log: user log instance
486 :param feed: use output for feeds (no html and fancy icons)
499 :param feed: use output for feeds (no html and fancy icons)
487 """
500 """
488
501
489 action = user_log.action
502 action = user_log.action
490 action_params = ' '
503 action_params = ' '
491
504
492 x = action.split(':')
505 x = action.split(':')
493
506
494 if len(x) > 1:
507 if len(x) > 1:
495 action, action_params = x
508 action, action_params = x
496
509
497 def get_cs_links():
510 def get_cs_links():
498 revs_limit = 3 # display this amount always
511 revs_limit = 3 # display this amount always
499 revs_top_limit = 50 # show upto this amount of changesets hidden
512 revs_top_limit = 50 # show upto this amount of changesets hidden
500 revs_ids = action_params.split(',')
513 revs_ids = action_params.split(',')
501 deleted = user_log.repository is None
514 deleted = user_log.repository is None
502 if deleted:
515 if deleted:
503 return ','.join(revs_ids)
516 return ','.join(revs_ids)
504
517
505 repo_name = user_log.repository.repo_name
518 repo_name = user_log.repository.repo_name
506
519
507 repo = user_log.repository.scm_instance
520 repo = user_log.repository.scm_instance
508
521
509 def lnk(rev, repo_name):
522 def lnk(rev, repo_name):
510
523
511 if isinstance(rev, BaseChangeset):
524 if isinstance(rev, BaseChangeset):
512 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
525 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
513 _url = url('changeset_home', repo_name=repo_name,
526 _url = url('changeset_home', repo_name=repo_name,
514 revision=rev.raw_id)
527 revision=rev.raw_id)
515 title = tooltip(rev.message)
528 title = tooltip(rev.message)
516 else:
529 else:
517 lbl = '%s' % rev
530 lbl = '%s' % rev
518 _url = '#'
531 _url = '#'
519 title = _('Changeset not found')
532 title = _('Changeset not found')
520
533
521 return link_to(lbl, _url, title=title, class_='tooltip',)
534 return link_to(lbl, _url, title=title, class_='tooltip',)
522
535
523 revs = []
536 revs = []
524 if len(filter(lambda v: v != '', revs_ids)) > 0:
537 if len(filter(lambda v: v != '', revs_ids)) > 0:
525 for rev in revs_ids[:revs_top_limit]:
538 for rev in revs_ids[:revs_top_limit]:
526 try:
539 try:
527 rev = repo.get_changeset(rev)
540 rev = repo.get_changeset(rev)
528 revs.append(rev)
541 revs.append(rev)
529 except ChangesetDoesNotExistError:
542 except ChangesetDoesNotExistError:
530 log.error('cannot find revision %s in this repo' % rev)
543 log.error('cannot find revision %s in this repo' % rev)
531 revs.append(rev)
544 revs.append(rev)
532 continue
545 continue
533 cs_links = []
546 cs_links = []
534 cs_links.append(" " + ', '.join(
547 cs_links.append(" " + ', '.join(
535 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
548 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
536 )
549 )
537 )
550 )
538
551
539 compare_view = (
552 compare_view = (
540 ' <div class="compare_view tooltip" title="%s">'
553 ' <div class="compare_view tooltip" title="%s">'
541 '<a href="%s">%s</a> </div>' % (
554 '<a href="%s">%s</a> </div>' % (
542 _('Show all combined changesets %s->%s') % (
555 _('Show all combined changesets %s->%s') % (
543 revs_ids[0], revs_ids[-1]
556 revs_ids[0], revs_ids[-1]
544 ),
557 ),
545 url('changeset_home', repo_name=repo_name,
558 url('changeset_home', repo_name=repo_name,
546 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
559 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
547 ),
560 ),
548 _('compare view')
561 _('compare view')
549 )
562 )
550 )
563 )
551
564
552 # if we have exactly one more than normally displayed
565 # if we have exactly one more than normally displayed
553 # just display it, takes less space than displaying
566 # just display it, takes less space than displaying
554 # "and 1 more revisions"
567 # "and 1 more revisions"
555 if len(revs_ids) == revs_limit + 1:
568 if len(revs_ids) == revs_limit + 1:
556 rev = revs[revs_limit]
569 rev = revs[revs_limit]
557 cs_links.append(", " + lnk(rev, repo_name))
570 cs_links.append(", " + lnk(rev, repo_name))
558
571
559 # hidden-by-default ones
572 # hidden-by-default ones
560 if len(revs_ids) > revs_limit + 1:
573 if len(revs_ids) > revs_limit + 1:
561 uniq_id = revs_ids[0]
574 uniq_id = revs_ids[0]
562 html_tmpl = (
575 html_tmpl = (
563 '<span> %s <a class="show_more" id="_%s" '
576 '<span> %s <a class="show_more" id="_%s" '
564 'href="#more">%s</a> %s</span>'
577 'href="#more">%s</a> %s</span>'
565 )
578 )
566 if not feed:
579 if not feed:
567 cs_links.append(html_tmpl % (
580 cs_links.append(html_tmpl % (
568 _('and'),
581 _('and'),
569 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
582 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
570 _('revisions')
583 _('revisions')
571 )
584 )
572 )
585 )
573
586
574 if not feed:
587 if not feed:
575 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
588 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
576 else:
589 else:
577 html_tmpl = '<span id="%s"> %s </span>'
590 html_tmpl = '<span id="%s"> %s </span>'
578
591
579 morelinks = ', '.join(
592 morelinks = ', '.join(
580 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
593 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
581 )
594 )
582
595
583 if len(revs_ids) > revs_top_limit:
596 if len(revs_ids) > revs_top_limit:
584 morelinks += ', ...'
597 morelinks += ', ...'
585
598
586 cs_links.append(html_tmpl % (uniq_id, morelinks))
599 cs_links.append(html_tmpl % (uniq_id, morelinks))
587 if len(revs) > 1:
600 if len(revs) > 1:
588 cs_links.append(compare_view)
601 cs_links.append(compare_view)
589 return ''.join(cs_links)
602 return ''.join(cs_links)
590
603
591 def get_fork_name():
604 def get_fork_name():
592 repo_name = action_params
605 repo_name = action_params
593 return _('fork name ') + str(link_to(action_params, url('summary_home',
606 return _('fork name ') + str(link_to(action_params, url('summary_home',
594 repo_name=repo_name,)))
607 repo_name=repo_name,)))
595
608
596 def get_user_name():
609 def get_user_name():
597 user_name = action_params
610 user_name = action_params
598 return user_name
611 return user_name
599
612
600 def get_users_group():
613 def get_users_group():
601 group_name = action_params
614 group_name = action_params
602 return group_name
615 return group_name
603
616
604 def get_pull_request():
617 def get_pull_request():
605 pull_request_id = action_params
618 pull_request_id = action_params
606 repo_name = user_log.repository.repo_name
619 repo_name = user_log.repository.repo_name
607 return link_to(_('Pull request #%s') % pull_request_id,
620 return link_to(_('Pull request #%s') % pull_request_id,
608 url('pullrequest_show', repo_name=repo_name,
621 url('pullrequest_show', repo_name=repo_name,
609 pull_request_id=pull_request_id))
622 pull_request_id=pull_request_id))
610
623
611 # action : translated str, callback(extractor), icon
624 # action : translated str, callback(extractor), icon
612 action_map = {
625 action_map = {
613 'user_deleted_repo': (_('[deleted] repository'),
626 'user_deleted_repo': (_('[deleted] repository'),
614 None, 'database_delete.png'),
627 None, 'database_delete.png'),
615 'user_created_repo': (_('[created] repository'),
628 'user_created_repo': (_('[created] repository'),
616 None, 'database_add.png'),
629 None, 'database_add.png'),
617 'user_created_fork': (_('[created] repository as fork'),
630 'user_created_fork': (_('[created] repository as fork'),
618 None, 'arrow_divide.png'),
631 None, 'arrow_divide.png'),
619 'user_forked_repo': (_('[forked] repository'),
632 'user_forked_repo': (_('[forked] repository'),
620 get_fork_name, 'arrow_divide.png'),
633 get_fork_name, 'arrow_divide.png'),
621 'user_updated_repo': (_('[updated] repository'),
634 'user_updated_repo': (_('[updated] repository'),
622 None, 'database_edit.png'),
635 None, 'database_edit.png'),
623 'admin_deleted_repo': (_('[delete] repository'),
636 'admin_deleted_repo': (_('[delete] repository'),
624 None, 'database_delete.png'),
637 None, 'database_delete.png'),
625 'admin_created_repo': (_('[created] repository'),
638 'admin_created_repo': (_('[created] repository'),
626 None, 'database_add.png'),
639 None, 'database_add.png'),
627 'admin_forked_repo': (_('[forked] repository'),
640 'admin_forked_repo': (_('[forked] repository'),
628 None, 'arrow_divide.png'),
641 None, 'arrow_divide.png'),
629 'admin_updated_repo': (_('[updated] repository'),
642 'admin_updated_repo': (_('[updated] repository'),
630 None, 'database_edit.png'),
643 None, 'database_edit.png'),
631 'admin_created_user': (_('[created] user'),
644 'admin_created_user': (_('[created] user'),
632 get_user_name, 'user_add.png'),
645 get_user_name, 'user_add.png'),
633 'admin_updated_user': (_('[updated] user'),
646 'admin_updated_user': (_('[updated] user'),
634 get_user_name, 'user_edit.png'),
647 get_user_name, 'user_edit.png'),
635 'admin_created_users_group': (_('[created] users group'),
648 'admin_created_users_group': (_('[created] users group'),
636 get_users_group, 'group_add.png'),
649 get_users_group, 'group_add.png'),
637 'admin_updated_users_group': (_('[updated] users group'),
650 'admin_updated_users_group': (_('[updated] users group'),
638 get_users_group, 'group_edit.png'),
651 get_users_group, 'group_edit.png'),
639 'user_commented_revision': (_('[commented] on revision in repository'),
652 'user_commented_revision': (_('[commented] on revision in repository'),
640 get_cs_links, 'comment_add.png'),
653 get_cs_links, 'comment_add.png'),
641 'user_commented_pull_request': (_('[commented] on pull request for'),
654 'user_commented_pull_request': (_('[commented] on pull request for'),
642 get_pull_request, 'comment_add.png'),
655 get_pull_request, 'comment_add.png'),
643 'user_closed_pull_request': (_('[closed] pull request for'),
656 'user_closed_pull_request': (_('[closed] pull request for'),
644 get_pull_request, 'tick.png'),
657 get_pull_request, 'tick.png'),
645 'push': (_('[pushed] into'),
658 'push': (_('[pushed] into'),
646 get_cs_links, 'script_add.png'),
659 get_cs_links, 'script_add.png'),
647 'push_local': (_('[committed via RhodeCode] into repository'),
660 'push_local': (_('[committed via RhodeCode] into repository'),
648 get_cs_links, 'script_edit.png'),
661 get_cs_links, 'script_edit.png'),
649 'push_remote': (_('[pulled from remote] into repository'),
662 'push_remote': (_('[pulled from remote] into repository'),
650 get_cs_links, 'connect.png'),
663 get_cs_links, 'connect.png'),
651 'pull': (_('[pulled] from'),
664 'pull': (_('[pulled] from'),
652 None, 'down_16.png'),
665 None, 'down_16.png'),
653 'started_following_repo': (_('[started following] repository'),
666 'started_following_repo': (_('[started following] repository'),
654 None, 'heart_add.png'),
667 None, 'heart_add.png'),
655 'stopped_following_repo': (_('[stopped following] repository'),
668 'stopped_following_repo': (_('[stopped following] repository'),
656 None, 'heart_delete.png'),
669 None, 'heart_delete.png'),
657 }
670 }
658
671
659 action_str = action_map.get(action, action)
672 action_str = action_map.get(action, action)
660 if feed:
673 if feed:
661 action = action_str[0].replace('[', '').replace(']', '')
674 action = action_str[0].replace('[', '').replace(']', '')
662 else:
675 else:
663 action = action_str[0]\
676 action = action_str[0]\
664 .replace('[', '<span class="journal_highlight">')\
677 .replace('[', '<span class="journal_highlight">')\
665 .replace(']', '</span>')
678 .replace(']', '</span>')
666
679
667 action_params_func = lambda: ""
680 action_params_func = lambda: ""
668
681
669 if callable(action_str[1]):
682 if callable(action_str[1]):
670 action_params_func = action_str[1]
683 action_params_func = action_str[1]
671
684
672 def action_parser_icon():
685 def action_parser_icon():
673 action = user_log.action
686 action = user_log.action
674 action_params = None
687 action_params = None
675 x = action.split(':')
688 x = action.split(':')
676
689
677 if len(x) > 1:
690 if len(x) > 1:
678 action, action_params = x
691 action, action_params = x
679
692
680 tmpl = """<img src="%s%s" alt="%s"/>"""
693 tmpl = """<img src="%s%s" alt="%s"/>"""
681 ico = action_map.get(action, ['', '', ''])[2]
694 ico = action_map.get(action, ['', '', ''])[2]
682 return literal(tmpl % ((url('/images/icons/')), ico, action))
695 return literal(tmpl % ((url('/images/icons/')), ico, action))
683
696
684 # returned callbacks we need to call to get
697 # returned callbacks we need to call to get
685 return [lambda: literal(action), action_params_func, action_parser_icon]
698 return [lambda: literal(action), action_params_func, action_parser_icon]
686
699
687
700
688
701
689 #==============================================================================
702 #==============================================================================
690 # PERMS
703 # PERMS
691 #==============================================================================
704 #==============================================================================
692 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
705 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
693 HasRepoPermissionAny, HasRepoPermissionAll
706 HasRepoPermissionAny, HasRepoPermissionAll
694
707
695
708
696 #==============================================================================
709 #==============================================================================
697 # GRAVATAR URL
710 # GRAVATAR URL
698 #==============================================================================
711 #==============================================================================
699
712
700 def gravatar_url(email_address, size=30):
713 def gravatar_url(email_address, size=30):
701 if (not str2bool(config['app_conf'].get('use_gravatar')) or
714 if (not str2bool(config['app_conf'].get('use_gravatar')) or
702 not email_address or email_address == 'anonymous@rhodecode.org'):
715 not email_address or email_address == 'anonymous@rhodecode.org'):
703 f = lambda a, l: min(l, key=lambda x: abs(x - a))
716 f = lambda a, l: min(l, key=lambda x: abs(x - a))
704 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
717 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
705
718
706 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
719 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
707 default = 'identicon'
720 default = 'identicon'
708 baseurl_nossl = "http://www.gravatar.com/avatar/"
721 baseurl_nossl = "http://www.gravatar.com/avatar/"
709 baseurl_ssl = "https://secure.gravatar.com/avatar/"
722 baseurl_ssl = "https://secure.gravatar.com/avatar/"
710 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
723 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
711
724
712 if isinstance(email_address, unicode):
725 if isinstance(email_address, unicode):
713 #hashlib crashes on unicode items
726 #hashlib crashes on unicode items
714 email_address = safe_str(email_address)
727 email_address = safe_str(email_address)
715 # construct the url
728 # construct the url
716 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
729 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
717 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
730 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
718
731
719 return gravatar_url
732 return gravatar_url
720
733
721
734
722 #==============================================================================
735 #==============================================================================
723 # REPO PAGER, PAGER FOR REPOSITORY
736 # REPO PAGER, PAGER FOR REPOSITORY
724 #==============================================================================
737 #==============================================================================
725 class RepoPage(Page):
738 class RepoPage(Page):
726
739
727 def __init__(self, collection, page=1, items_per_page=20,
740 def __init__(self, collection, page=1, items_per_page=20,
728 item_count=None, url=None, **kwargs):
741 item_count=None, url=None, **kwargs):
729
742
730 """Create a "RepoPage" instance. special pager for paging
743 """Create a "RepoPage" instance. special pager for paging
731 repository
744 repository
732 """
745 """
733 self._url_generator = url
746 self._url_generator = url
734
747
735 # Safe the kwargs class-wide so they can be used in the pager() method
748 # Safe the kwargs class-wide so they can be used in the pager() method
736 self.kwargs = kwargs
749 self.kwargs = kwargs
737
750
738 # Save a reference to the collection
751 # Save a reference to the collection
739 self.original_collection = collection
752 self.original_collection = collection
740
753
741 self.collection = collection
754 self.collection = collection
742
755
743 # The self.page is the number of the current page.
756 # The self.page is the number of the current page.
744 # The first page has the number 1!
757 # The first page has the number 1!
745 try:
758 try:
746 self.page = int(page) # make it int() if we get it as a string
759 self.page = int(page) # make it int() if we get it as a string
747 except (ValueError, TypeError):
760 except (ValueError, TypeError):
748 self.page = 1
761 self.page = 1
749
762
750 self.items_per_page = items_per_page
763 self.items_per_page = items_per_page
751
764
752 # Unless the user tells us how many items the collections has
765 # Unless the user tells us how many items the collections has
753 # we calculate that ourselves.
766 # we calculate that ourselves.
754 if item_count is not None:
767 if item_count is not None:
755 self.item_count = item_count
768 self.item_count = item_count
756 else:
769 else:
757 self.item_count = len(self.collection)
770 self.item_count = len(self.collection)
758
771
759 # Compute the number of the first and last available page
772 # Compute the number of the first and last available page
760 if self.item_count > 0:
773 if self.item_count > 0:
761 self.first_page = 1
774 self.first_page = 1
762 self.page_count = int(math.ceil(float(self.item_count) /
775 self.page_count = int(math.ceil(float(self.item_count) /
763 self.items_per_page))
776 self.items_per_page))
764 self.last_page = self.first_page + self.page_count - 1
777 self.last_page = self.first_page + self.page_count - 1
765
778
766 # Make sure that the requested page number is the range of
779 # Make sure that the requested page number is the range of
767 # valid pages
780 # valid pages
768 if self.page > self.last_page:
781 if self.page > self.last_page:
769 self.page = self.last_page
782 self.page = self.last_page
770 elif self.page < self.first_page:
783 elif self.page < self.first_page:
771 self.page = self.first_page
784 self.page = self.first_page
772
785
773 # Note: the number of items on this page can be less than
786 # Note: the number of items on this page can be less than
774 # items_per_page if the last page is not full
787 # items_per_page if the last page is not full
775 self.first_item = max(0, (self.item_count) - (self.page *
788 self.first_item = max(0, (self.item_count) - (self.page *
776 items_per_page))
789 items_per_page))
777 self.last_item = ((self.item_count - 1) - items_per_page *
790 self.last_item = ((self.item_count - 1) - items_per_page *
778 (self.page - 1))
791 (self.page - 1))
779
792
780 self.items = list(self.collection[self.first_item:self.last_item + 1])
793 self.items = list(self.collection[self.first_item:self.last_item + 1])
781
794
782 # Links to previous and next page
795 # Links to previous and next page
783 if self.page > self.first_page:
796 if self.page > self.first_page:
784 self.previous_page = self.page - 1
797 self.previous_page = self.page - 1
785 else:
798 else:
786 self.previous_page = None
799 self.previous_page = None
787
800
788 if self.page < self.last_page:
801 if self.page < self.last_page:
789 self.next_page = self.page + 1
802 self.next_page = self.page + 1
790 else:
803 else:
791 self.next_page = None
804 self.next_page = None
792
805
793 # No items available
806 # No items available
794 else:
807 else:
795 self.first_page = None
808 self.first_page = None
796 self.page_count = 0
809 self.page_count = 0
797 self.last_page = None
810 self.last_page = None
798 self.first_item = None
811 self.first_item = None
799 self.last_item = None
812 self.last_item = None
800 self.previous_page = None
813 self.previous_page = None
801 self.next_page = None
814 self.next_page = None
802 self.items = []
815 self.items = []
803
816
804 # This is a subclass of the 'list' type. Initialise the list now.
817 # This is a subclass of the 'list' type. Initialise the list now.
805 list.__init__(self, reversed(self.items))
818 list.__init__(self, reversed(self.items))
806
819
807
820
808 def changed_tooltip(nodes):
821 def changed_tooltip(nodes):
809 """
822 """
810 Generates a html string for changed nodes in changeset page.
823 Generates a html string for changed nodes in changeset page.
811 It limits the output to 30 entries
824 It limits the output to 30 entries
812
825
813 :param nodes: LazyNodesGenerator
826 :param nodes: LazyNodesGenerator
814 """
827 """
815 if nodes:
828 if nodes:
816 pref = ': <br/> '
829 pref = ': <br/> '
817 suf = ''
830 suf = ''
818 if len(nodes) > 30:
831 if len(nodes) > 30:
819 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
832 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
820 return literal(pref + '<br/> '.join([safe_unicode(x.path)
833 return literal(pref + '<br/> '.join([safe_unicode(x.path)
821 for x in nodes[:30]]) + suf)
834 for x in nodes[:30]]) + suf)
822 else:
835 else:
823 return ': ' + _('No Files')
836 return ': ' + _('No Files')
824
837
825
838
826 def repo_link(groups_and_repos):
839 def repo_link(groups_and_repos):
827 """
840 """
828 Makes a breadcrumbs link to repo within a group
841 Makes a breadcrumbs link to repo within a group
829 joins &raquo; on each group to create a fancy link
842 joins &raquo; on each group to create a fancy link
830
843
831 ex::
844 ex::
832 group >> subgroup >> repo
845 group >> subgroup >> repo
833
846
834 :param groups_and_repos:
847 :param groups_and_repos:
835 """
848 """
836 groups, repo_name = groups_and_repos
849 groups, repo_name = groups_and_repos
837
850
838 if not groups:
851 if not groups:
839 return repo_name
852 return repo_name
840 else:
853 else:
841 def make_link(group):
854 def make_link(group):
842 return link_to(group.name, url('repos_group_home',
855 return link_to(group.name, url('repos_group_home',
843 group_name=group.group_name))
856 group_name=group.group_name))
844 return literal(' &raquo; '.join(map(make_link, groups)) + \
857 return literal(' &raquo; '.join(map(make_link, groups)) + \
845 " &raquo; " + repo_name)
858 " &raquo; " + repo_name)
846
859
847
860
848 def fancy_file_stats(stats):
861 def fancy_file_stats(stats):
849 """
862 """
850 Displays a fancy two colored bar for number of added/deleted
863 Displays a fancy two colored bar for number of added/deleted
851 lines of code on file
864 lines of code on file
852
865
853 :param stats: two element list of added/deleted lines of code
866 :param stats: two element list of added/deleted lines of code
854 """
867 """
855
868
856 a, d, t = stats[0], stats[1], stats[0] + stats[1]
869 a, d, t = stats[0], stats[1], stats[0] + stats[1]
857 width = 100
870 width = 100
858 unit = float(width) / (t or 1)
871 unit = float(width) / (t or 1)
859
872
860 # needs > 9% of width to be visible or 0 to be hidden
873 # needs > 9% of width to be visible or 0 to be hidden
861 a_p = max(9, unit * a) if a > 0 else 0
874 a_p = max(9, unit * a) if a > 0 else 0
862 d_p = max(9, unit * d) if d > 0 else 0
875 d_p = max(9, unit * d) if d > 0 else 0
863 p_sum = a_p + d_p
876 p_sum = a_p + d_p
864
877
865 if p_sum > width:
878 if p_sum > width:
866 #adjust the percentage to be == 100% since we adjusted to 9
879 #adjust the percentage to be == 100% since we adjusted to 9
867 if a_p > d_p:
880 if a_p > d_p:
868 a_p = a_p - (p_sum - width)
881 a_p = a_p - (p_sum - width)
869 else:
882 else:
870 d_p = d_p - (p_sum - width)
883 d_p = d_p - (p_sum - width)
871
884
872 a_v = a if a > 0 else ''
885 a_v = a if a > 0 else ''
873 d_v = d if d > 0 else ''
886 d_v = d if d > 0 else ''
874
887
875 def cgen(l_type):
888 def cgen(l_type):
876 mapping = {'tr': 'top-right-rounded-corner-mid',
889 mapping = {'tr': 'top-right-rounded-corner-mid',
877 'tl': 'top-left-rounded-corner-mid',
890 'tl': 'top-left-rounded-corner-mid',
878 'br': 'bottom-right-rounded-corner-mid',
891 'br': 'bottom-right-rounded-corner-mid',
879 'bl': 'bottom-left-rounded-corner-mid'}
892 'bl': 'bottom-left-rounded-corner-mid'}
880 map_getter = lambda x: mapping[x]
893 map_getter = lambda x: mapping[x]
881
894
882 if l_type == 'a' and d_v:
895 if l_type == 'a' and d_v:
883 #case when added and deleted are present
896 #case when added and deleted are present
884 return ' '.join(map(map_getter, ['tl', 'bl']))
897 return ' '.join(map(map_getter, ['tl', 'bl']))
885
898
886 if l_type == 'a' and not d_v:
899 if l_type == 'a' and not d_v:
887 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
900 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
888
901
889 if l_type == 'd' and a_v:
902 if l_type == 'd' and a_v:
890 return ' '.join(map(map_getter, ['tr', 'br']))
903 return ' '.join(map(map_getter, ['tr', 'br']))
891
904
892 if l_type == 'd' and not a_v:
905 if l_type == 'd' and not a_v:
893 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
906 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
894
907
895 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
908 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
896 cgen('a'), a_p, a_v
909 cgen('a'), a_p, a_v
897 )
910 )
898 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
911 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
899 cgen('d'), d_p, d_v
912 cgen('d'), d_p, d_v
900 )
913 )
901 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
914 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
902
915
903
916
904 def urlify_text(text_):
917 def urlify_text(text_):
905 import re
918 import re
906
919
907 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
920 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
908 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
921 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
909
922
910 def url_func(match_obj):
923 def url_func(match_obj):
911 url_full = match_obj.groups()[0]
924 url_full = match_obj.groups()[0]
912 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
925 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
913
926
914 return literal(url_pat.sub(url_func, text_))
927 return literal(url_pat.sub(url_func, text_))
915
928
916
929
917 def urlify_changesets(text_, repository):
930 def urlify_changesets(text_, repository):
918 """
931 """
919 Extract revision ids from changeset and make link from them
932 Extract revision ids from changeset and make link from them
920
933
921 :param text_:
934 :param text_:
922 :param repository:
935 :param repository:
923 """
936 """
924 import re
937 import re
925 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
938 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
926
939
927 def url_func(match_obj):
940 def url_func(match_obj):
928 rev = match_obj.groups()[0]
941 rev = match_obj.groups()[0]
929 pref = ''
942 pref = ''
930 if match_obj.group().startswith(' '):
943 if match_obj.group().startswith(' '):
931 pref = ' '
944 pref = ' '
932 tmpl = (
945 tmpl = (
933 '%(pref)s<a class="%(cls)s" href="%(url)s">'
946 '%(pref)s<a class="%(cls)s" href="%(url)s">'
934 '%(rev)s'
947 '%(rev)s'
935 '</a>'
948 '</a>'
936 )
949 )
937 return tmpl % {
950 return tmpl % {
938 'pref': pref,
951 'pref': pref,
939 'cls': 'revision-link',
952 'cls': 'revision-link',
940 'url': url('changeset_home', repo_name=repository, revision=rev),
953 'url': url('changeset_home', repo_name=repository, revision=rev),
941 'rev': rev,
954 'rev': rev,
942 }
955 }
943
956
944 newtext = URL_PAT.sub(url_func, text_)
957 newtext = URL_PAT.sub(url_func, text_)
945
958
946 return newtext
959 return newtext
947
960
948
961
949 def urlify_commit(text_, repository=None, link_=None):
962 def urlify_commit(text_, repository=None, link_=None):
950 """
963 """
951 Parses given text message and makes proper links.
964 Parses given text message and makes proper links.
952 issues are linked to given issue-server, and rest is a changeset link
965 issues are linked to given issue-server, and rest is a changeset link
953 if link_ is given, in other case it's a plain text
966 if link_ is given, in other case it's a plain text
954
967
955 :param text_:
968 :param text_:
956 :param repository:
969 :param repository:
957 :param link_: changeset link
970 :param link_: changeset link
958 """
971 """
959 import re
972 import re
960 import traceback
973 import traceback
961
974
962 def escaper(string):
975 def escaper(string):
963 return string.replace('<', '&lt;').replace('>', '&gt;')
976 return string.replace('<', '&lt;').replace('>', '&gt;')
964
977
965 def linkify_others(t, l):
978 def linkify_others(t, l):
966 urls = re.compile(r'(\<a.*?\<\/a\>)',)
979 urls = re.compile(r'(\<a.*?\<\/a\>)',)
967 links = []
980 links = []
968 for e in urls.split(t):
981 for e in urls.split(t):
969 if not urls.match(e):
982 if not urls.match(e):
970 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
983 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
971 else:
984 else:
972 links.append(e)
985 links.append(e)
973
986
974 return ''.join(links)
987 return ''.join(links)
975
988
976 # urlify changesets - extrac revisions and make link out of them
989 # urlify changesets - extrac revisions and make link out of them
977 text_ = urlify_changesets(escaper(text_), repository)
990 text_ = urlify_changesets(escaper(text_), repository)
978
991
979 try:
992 try:
980 conf = config['app_conf']
993 conf = config['app_conf']
981
994
982 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
995 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
983
996
984 if URL_PAT:
997 if URL_PAT:
985 ISSUE_SERVER_LNK = conf.get('issue_server_link')
998 ISSUE_SERVER_LNK = conf.get('issue_server_link')
986 ISSUE_PREFIX = conf.get('issue_prefix')
999 ISSUE_PREFIX = conf.get('issue_prefix')
987
1000
988 def url_func(match_obj):
1001 def url_func(match_obj):
989 pref = ''
1002 pref = ''
990 if match_obj.group().startswith(' '):
1003 if match_obj.group().startswith(' '):
991 pref = ' '
1004 pref = ' '
992
1005
993 issue_id = ''.join(match_obj.groups())
1006 issue_id = ''.join(match_obj.groups())
994 tmpl = (
1007 tmpl = (
995 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1008 '%(pref)s<a class="%(cls)s" href="%(url)s">'
996 '%(issue-prefix)s%(id-repr)s'
1009 '%(issue-prefix)s%(id-repr)s'
997 '</a>'
1010 '</a>'
998 )
1011 )
999 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1012 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1000 if repository:
1013 if repository:
1001 url = url.replace('{repo}', repository)
1014 url = url.replace('{repo}', repository)
1002 repo_name = repository.split(URL_SEP)[-1]
1015 repo_name = repository.split(URL_SEP)[-1]
1003 url = url.replace('{repo_name}', repo_name)
1016 url = url.replace('{repo_name}', repo_name)
1004 return tmpl % {
1017 return tmpl % {
1005 'pref': pref,
1018 'pref': pref,
1006 'cls': 'issue-tracker-link',
1019 'cls': 'issue-tracker-link',
1007 'url': url,
1020 'url': url,
1008 'id-repr': issue_id,
1021 'id-repr': issue_id,
1009 'issue-prefix': ISSUE_PREFIX,
1022 'issue-prefix': ISSUE_PREFIX,
1010 'serv': ISSUE_SERVER_LNK,
1023 'serv': ISSUE_SERVER_LNK,
1011 }
1024 }
1012
1025
1013 newtext = URL_PAT.sub(url_func, text_)
1026 newtext = URL_PAT.sub(url_func, text_)
1014
1027
1015 if link_:
1028 if link_:
1016 # wrap not links into final link => link_
1029 # wrap not links into final link => link_
1017 newtext = linkify_others(newtext, link_)
1030 newtext = linkify_others(newtext, link_)
1018
1031
1019 return literal(newtext)
1032 return literal(newtext)
1020 except:
1033 except:
1021 log.error(traceback.format_exc())
1034 log.error(traceback.format_exc())
1022 pass
1035 pass
1023
1036
1024 return text_
1037 return text_
1025
1038
1026
1039
1027 def rst(source):
1040 def rst(source):
1028 return literal('<div class="rst-block">%s</div>' %
1041 return literal('<div class="rst-block">%s</div>' %
1029 MarkupRenderer.rst(source))
1042 MarkupRenderer.rst(source))
1030
1043
1031
1044
1032 def rst_w_mentions(source):
1045 def rst_w_mentions(source):
1033 """
1046 """
1034 Wrapped rst renderer with @mention highlighting
1047 Wrapped rst renderer with @mention highlighting
1035
1048
1036 :param source:
1049 :param source:
1037 """
1050 """
1038 return literal('<div class="rst-block">%s</div>' %
1051 return literal('<div class="rst-block">%s</div>' %
1039 MarkupRenderer.rst_with_mentions(source))
1052 MarkupRenderer.rst_with_mentions(source))
1040
1053
1041
1054
1042 def changeset_status(repo, revision):
1055 def changeset_status(repo, revision):
1043 return ChangesetStatusModel().get_status(repo, revision)
1056 return ChangesetStatusModel().get_status(repo, revision)
1044
1057
1045
1058
1046 def changeset_status_lbl(changeset_status):
1059 def changeset_status_lbl(changeset_status):
1047 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1060 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1048
1061
1049
1062
1050 def get_permission_name(key):
1063 def get_permission_name(key):
1051 return dict(Permission.PERMS).get(key)
1064 return dict(Permission.PERMS).get(key)
@@ -1,303 +1,377 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.hooks
3 rhodecode.lib.hooks
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Hooks runned by rhodecode
6 Hooks runned by rhodecode
7
7
8 :created_on: Aug 6, 2010
8 :created_on: Aug 6, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import sys
26 import sys
27 import binascii
27 import binascii
28 from inspect import isfunction
28 from inspect import isfunction
29
29
30 from mercurial.scmutil import revrange
30 from mercurial.scmutil import revrange
31 from mercurial.node import nullrev
31 from mercurial.node import nullrev
32
32
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.utils import action_logger
34 from rhodecode.lib.utils import action_logger
35 from rhodecode.lib.vcs.backends.base import EmptyChangeset
35 from rhodecode.lib.vcs.backends.base import EmptyChangeset
36 from rhodecode.lib.compat import json
36 from rhodecode.lib.compat import json
37 from rhodecode.model.db import Repository, User
38 from rhodecode.lib.utils2 import safe_str
39 from rhodecode.lib.exceptions import HTTPLockedRC
37
40
38
41
39 def _get_scm_size(alias, root_path):
42 def _get_scm_size(alias, root_path):
40
43
41 if not alias.startswith('.'):
44 if not alias.startswith('.'):
42 alias += '.'
45 alias += '.'
43
46
44 size_scm, size_root = 0, 0
47 size_scm, size_root = 0, 0
45 for path, dirs, files in os.walk(root_path):
48 for path, dirs, files in os.walk(root_path):
46 if path.find(alias) != -1:
49 if path.find(alias) != -1:
47 for f in files:
50 for f in files:
48 try:
51 try:
49 size_scm += os.path.getsize(os.path.join(path, f))
52 size_scm += os.path.getsize(os.path.join(path, f))
50 except OSError:
53 except OSError:
51 pass
54 pass
52 else:
55 else:
53 for f in files:
56 for f in files:
54 try:
57 try:
55 size_root += os.path.getsize(os.path.join(path, f))
58 size_root += os.path.getsize(os.path.join(path, f))
56 except OSError:
59 except OSError:
57 pass
60 pass
58
61
59 size_scm_f = h.format_byte_size(size_scm)
62 size_scm_f = h.format_byte_size(size_scm)
60 size_root_f = h.format_byte_size(size_root)
63 size_root_f = h.format_byte_size(size_root)
61 size_total_f = h.format_byte_size(size_root + size_scm)
64 size_total_f = h.format_byte_size(size_root + size_scm)
62
65
63 return size_scm_f, size_root_f, size_total_f
66 return size_scm_f, size_root_f, size_total_f
64
67
65
68
66 def repo_size(ui, repo, hooktype=None, **kwargs):
69 def repo_size(ui, repo, hooktype=None, **kwargs):
67 """
70 """
68 Presents size of repository after push
71 Presents size of repository after push
69
72
70 :param ui:
73 :param ui:
71 :param repo:
74 :param repo:
72 :param hooktype:
75 :param hooktype:
73 """
76 """
74
77
75 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
78 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
76
79
77 last_cs = repo[len(repo) - 1]
80 last_cs = repo[len(repo) - 1]
78
81
79 msg = ('Repository size .hg:%s repo:%s total:%s\n'
82 msg = ('Repository size .hg:%s repo:%s total:%s\n'
80 'Last revision is now r%s:%s\n') % (
83 'Last revision is now r%s:%s\n') % (
81 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
84 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
82 )
85 )
83
86
84 sys.stdout.write(msg)
87 sys.stdout.write(msg)
85
88
86
89
90 def pre_push(ui, repo, **kwargs):
91 # pre push function, currently used to ban pushing when
92 # repository is locked
93 try:
94 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
95 except:
96 rc_extras = {}
97 extras = dict(repo.ui.configitems('rhodecode_extras'))
98
99 if 'username' in extras:
100 username = extras['username']
101 repository = extras['repository']
102 scm = extras['scm']
103 locked_by = extras['locked_by']
104 elif 'username' in rc_extras:
105 username = rc_extras['username']
106 repository = rc_extras['repository']
107 scm = rc_extras['scm']
108 locked_by = rc_extras['locked_by']
109 else:
110 raise Exception('Missing data in repo.ui and os.environ')
111
112 usr = User.get_by_username(username)
113
114 if locked_by[0] and usr.user_id != int(locked_by[0]):
115 raise HTTPLockedRC(username, repository)
116
117
118 def pre_pull(ui, repo, **kwargs):
119 # pre push function, currently used to ban pushing when
120 # repository is locked
121 try:
122 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
123 except:
124 rc_extras = {}
125 extras = dict(repo.ui.configitems('rhodecode_extras'))
126 if 'username' in extras:
127 username = extras['username']
128 repository = extras['repository']
129 scm = extras['scm']
130 locked_by = extras['locked_by']
131 elif 'username' in rc_extras:
132 username = rc_extras['username']
133 repository = rc_extras['repository']
134 scm = rc_extras['scm']
135 locked_by = rc_extras['locked_by']
136 else:
137 raise Exception('Missing data in repo.ui and os.environ')
138
139 if locked_by[0]:
140 raise HTTPLockedRC(username, repository)
141
142
87 def log_pull_action(ui, repo, **kwargs):
143 def log_pull_action(ui, repo, **kwargs):
88 """
144 """
89 Logs user last pull action
145 Logs user last pull action
90
146
91 :param ui:
147 :param ui:
92 :param repo:
148 :param repo:
93 """
149 """
94 try:
150 try:
95 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
151 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
96 except:
152 except:
97 rc_extras = {}
153 rc_extras = {}
98 extras = dict(repo.ui.configitems('rhodecode_extras'))
154 extras = dict(repo.ui.configitems('rhodecode_extras'))
99 if 'username' in extras:
155 if 'username' in extras:
100 username = extras['username']
156 username = extras['username']
101 repository = extras['repository']
157 repository = extras['repository']
102 scm = extras['scm']
158 scm = extras['scm']
159 make_lock = extras['make_lock']
103 elif 'username' in rc_extras:
160 elif 'username' in rc_extras:
104 username = rc_extras['username']
161 username = rc_extras['username']
105 repository = rc_extras['repository']
162 repository = rc_extras['repository']
106 scm = rc_extras['scm']
163 scm = rc_extras['scm']
164 make_lock = rc_extras['make_lock']
107 else:
165 else:
108 raise Exception('Missing data in repo.ui and os.environ')
166 raise Exception('Missing data in repo.ui and os.environ')
109
167 user = User.get_by_username(username)
110 action = 'pull'
168 action = 'pull'
111 action_logger(username, action, repository, extras['ip'], commit=True)
169 action_logger(user, action, repository, extras['ip'], commit=True)
112 # extension hook call
170 # extension hook call
113 from rhodecode import EXTENSIONS
171 from rhodecode import EXTENSIONS
114 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
172 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
115
173
116 if isfunction(callback):
174 if isfunction(callback):
117 kw = {}
175 kw = {}
118 kw.update(extras)
176 kw.update(extras)
119 callback(**kw)
177 callback(**kw)
178
179 if make_lock is True:
180 Repository.lock(Repository.get_by_repo_name(repository), user.user_id)
181 #msg = 'Made lock on repo `%s`' % repository
182 #sys.stdout.write(msg)
183
120 return 0
184 return 0
121
185
122
186
123 def log_push_action(ui, repo, **kwargs):
187 def log_push_action(ui, repo, **kwargs):
124 """
188 """
125 Maps user last push action to new changeset id, from mercurial
189 Maps user last push action to new changeset id, from mercurial
126
190
127 :param ui:
191 :param ui:
128 :param repo: repo object containing the `ui` object
192 :param repo: repo object containing the `ui` object
129 """
193 """
130
194
131 try:
195 try:
132 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
196 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
133 except:
197 except:
134 rc_extras = {}
198 rc_extras = {}
135
199
136 extras = dict(repo.ui.configitems('rhodecode_extras'))
200 extras = dict(repo.ui.configitems('rhodecode_extras'))
137 if 'username' in extras:
201 if 'username' in extras:
138 username = extras['username']
202 username = extras['username']
139 repository = extras['repository']
203 repository = extras['repository']
140 scm = extras['scm']
204 scm = extras['scm']
205 make_lock = extras['make_lock']
141 elif 'username' in rc_extras:
206 elif 'username' in rc_extras:
142 username = rc_extras['username']
207 username = rc_extras['username']
143 repository = rc_extras['repository']
208 repository = rc_extras['repository']
144 scm = rc_extras['scm']
209 scm = rc_extras['scm']
210 make_lock = rc_extras['make_lock']
145 else:
211 else:
146 raise Exception('Missing data in repo.ui and os.environ')
212 raise Exception('Missing data in repo.ui and os.environ')
147
213
148 action = 'push' + ':%s'
214 action = 'push' + ':%s'
149
215
150 if scm == 'hg':
216 if scm == 'hg':
151 node = kwargs['node']
217 node = kwargs['node']
152
218
153 def get_revs(repo, rev_opt):
219 def get_revs(repo, rev_opt):
154 if rev_opt:
220 if rev_opt:
155 revs = revrange(repo, rev_opt)
221 revs = revrange(repo, rev_opt)
156
222
157 if len(revs) == 0:
223 if len(revs) == 0:
158 return (nullrev, nullrev)
224 return (nullrev, nullrev)
159 return (max(revs), min(revs))
225 return (max(revs), min(revs))
160 else:
226 else:
161 return (len(repo) - 1, 0)
227 return (len(repo) - 1, 0)
162
228
163 stop, start = get_revs(repo, [node + ':'])
229 stop, start = get_revs(repo, [node + ':'])
164 h = binascii.hexlify
230 h = binascii.hexlify
165 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
231 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
166 elif scm == 'git':
232 elif scm == 'git':
167 revs = kwargs.get('_git_revs', [])
233 revs = kwargs.get('_git_revs', [])
168 if '_git_revs' in kwargs:
234 if '_git_revs' in kwargs:
169 kwargs.pop('_git_revs')
235 kwargs.pop('_git_revs')
170
236
171 action = action % ','.join(revs)
237 action = action % ','.join(revs)
172
238
173 action_logger(username, action, repository, extras['ip'], commit=True)
239 action_logger(username, action, repository, extras['ip'], commit=True)
174
240
175 # extension hook call
241 # extension hook call
176 from rhodecode import EXTENSIONS
242 from rhodecode import EXTENSIONS
177 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
243 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
178 if isfunction(callback):
244 if isfunction(callback):
179 kw = {'pushed_revs': revs}
245 kw = {'pushed_revs': revs}
180 kw.update(extras)
246 kw.update(extras)
181 callback(**kw)
247 callback(**kw)
248
249 if make_lock is False:
250 Repository.unlock(Repository.get_by_repo_name(repository))
251 msg = 'Released lock on repo `%s`\n' % repository
252 sys.stdout.write(msg)
253
182 return 0
254 return 0
183
255
184
256
185 def log_create_repository(repository_dict, created_by, **kwargs):
257 def log_create_repository(repository_dict, created_by, **kwargs):
186 """
258 """
187 Post create repository Hook. This is a dummy function for admins to re-use
259 Post create repository Hook. This is a dummy function for admins to re-use
188 if needed. It's taken from rhodecode-extensions module and executed
260 if needed. It's taken from rhodecode-extensions module and executed
189 if present
261 if present
190
262
191 :param repository: dict dump of repository object
263 :param repository: dict dump of repository object
192 :param created_by: username who created repository
264 :param created_by: username who created repository
193 :param created_date: date of creation
265 :param created_date: date of creation
194
266
195 available keys of repository_dict:
267 available keys of repository_dict:
196
268
197 'repo_type',
269 'repo_type',
198 'description',
270 'description',
199 'private',
271 'private',
200 'created_on',
272 'created_on',
201 'enable_downloads',
273 'enable_downloads',
202 'repo_id',
274 'repo_id',
203 'user_id',
275 'user_id',
204 'enable_statistics',
276 'enable_statistics',
205 'clone_uri',
277 'clone_uri',
206 'fork_id',
278 'fork_id',
207 'group_id',
279 'group_id',
208 'repo_name'
280 'repo_name'
209
281
210 """
282 """
211 from rhodecode import EXTENSIONS
283 from rhodecode import EXTENSIONS
212 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
284 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
213 if isfunction(callback):
285 if isfunction(callback):
214 kw = {}
286 kw = {}
215 kw.update(repository_dict)
287 kw.update(repository_dict)
216 kw.update({'created_by': created_by})
288 kw.update({'created_by': created_by})
217 kw.update(kwargs)
289 kw.update(kwargs)
218 return callback(**kw)
290 return callback(**kw)
219
291
220 return 0
292 return 0
221
293
294 handle_git_pre_receive = (lambda repo_path, revs, env:
295 handle_git_receive(repo_path, revs, env, hook_type='pre'))
296 handle_git_post_receive = (lambda repo_path, revs, env:
297 handle_git_receive(repo_path, revs, env, hook_type='post'))
222
298
223 def handle_git_post_receive(repo_path, revs, env):
299
300 def handle_git_receive(repo_path, revs, env, hook_type='post'):
224 """
301 """
225 A really hacky method that is runned by git post-receive hook and logs
302 A really hacky method that is runned by git post-receive hook and logs
226 an push action together with pushed revisions. It's executed by subprocess
303 an push action together with pushed revisions. It's executed by subprocess
227 thus needs all info to be able to create a on the fly pylons enviroment,
304 thus needs all info to be able to create a on the fly pylons enviroment,
228 connect to database and run the logging code. Hacky as sh*t but works.
305 connect to database and run the logging code. Hacky as sh*t but works.
229
306
230 :param repo_path:
307 :param repo_path:
231 :type repo_path:
308 :type repo_path:
232 :param revs:
309 :param revs:
233 :type revs:
310 :type revs:
234 :param env:
311 :param env:
235 :type env:
312 :type env:
236 """
313 """
237 from paste.deploy import appconfig
314 from paste.deploy import appconfig
238 from sqlalchemy import engine_from_config
315 from sqlalchemy import engine_from_config
239 from rhodecode.config.environment import load_environment
316 from rhodecode.config.environment import load_environment
240 from rhodecode.model import init_model
317 from rhodecode.model import init_model
241 from rhodecode.model.db import RhodeCodeUi
318 from rhodecode.model.db import RhodeCodeUi
242 from rhodecode.lib.utils import make_ui
319 from rhodecode.lib.utils import make_ui
243 from rhodecode.model.db import Repository
244
320
245 path, ini_name = os.path.split(env['RHODECODE_CONFIG_FILE'])
321 path, ini_name = os.path.split(env['RHODECODE_CONFIG_FILE'])
246 conf = appconfig('config:%s' % ini_name, relative_to=path)
322 conf = appconfig('config:%s' % ini_name, relative_to=path)
247 load_environment(conf.global_conf, conf.local_conf)
323 load_environment(conf.global_conf, conf.local_conf)
248
324
249 engine = engine_from_config(conf, 'sqlalchemy.db1.')
325 engine = engine_from_config(conf, 'sqlalchemy.db1.')
250 init_model(engine)
326 init_model(engine)
251
327
252 baseui = make_ui('db')
328 baseui = make_ui('db')
253 # fix if it's not a bare repo
329 # fix if it's not a bare repo
254 if repo_path.endswith('.git'):
330 if repo_path.endswith('.git'):
255 repo_path = repo_path[:-4]
331 repo_path = repo_path[:-4]
256 repo = Repository.get_by_full_path(repo_path)
332 repo = Repository.get_by_full_path(repo_path)
257 _hooks = dict(baseui.configitems('hooks')) or {}
333 _hooks = dict(baseui.configitems('hooks')) or {}
258 # if push hook is enabled via web interface
259 if repo and _hooks.get(RhodeCodeUi.HOOK_PUSH):
260
334
261 extras = {
335 extras = json.loads(env['RHODECODE_EXTRAS'])
262 'username': env['RHODECODE_USER'],
336 for k, v in extras.items():
263 'repository': repo.repo_name,
337 baseui.setconfig('rhodecode_extras', k, v)
264 'scm': 'git',
338 repo = repo.scm_instance
265 'action': 'push',
339 repo.ui = baseui
266 'ip': env['RHODECODE_CONFIG_IP'],
340
267 }
341 if hook_type == 'pre':
268 for k, v in extras.items():
342 pre_push(baseui, repo)
269 baseui.setconfig('rhodecode_extras', k, v)
343
270 repo = repo.scm_instance
344 # if push hook is enabled via web interface
271 repo.ui = baseui
345 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
272
346
273 rev_data = []
347 rev_data = []
274 for l in revs:
348 for l in revs:
275 old_rev, new_rev, ref = l.split(' ')
349 old_rev, new_rev, ref = l.split(' ')
276 _ref_data = ref.split('/')
350 _ref_data = ref.split('/')
277 if _ref_data[1] in ['tags', 'heads']:
351 if _ref_data[1] in ['tags', 'heads']:
278 rev_data.append({'old_rev': old_rev,
352 rev_data.append({'old_rev': old_rev,
279 'new_rev': new_rev,
353 'new_rev': new_rev,
280 'ref': ref,
354 'ref': ref,
281 'type': _ref_data[1],
355 'type': _ref_data[1],
282 'name': _ref_data[2].strip()})
356 'name': _ref_data[2].strip()})
283
357
284 git_revs = []
358 git_revs = []
285 for push_ref in rev_data:
359 for push_ref in rev_data:
286 _type = push_ref['type']
360 _type = push_ref['type']
287 if _type == 'heads':
361 if _type == 'heads':
288 if push_ref['old_rev'] == EmptyChangeset().raw_id:
362 if push_ref['old_rev'] == EmptyChangeset().raw_id:
289 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
363 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
290 heads = repo.run_git_command(cmd)[0]
364 heads = repo.run_git_command(cmd)[0]
291 heads = heads.replace(push_ref['ref'], '')
365 heads = heads.replace(push_ref['ref'], '')
292 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
366 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
293 heads.splitlines()))
367 heads.splitlines()))
294 cmd = (('log %(new_rev)s' % push_ref) +
368 cmd = (('log %(new_rev)s' % push_ref) +
295 ' --reverse --pretty=format:"%H" --not ' + heads)
369 ' --reverse --pretty=format:"%H" --not ' + heads)
296 else:
370 else:
297 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
371 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
298 ' --reverse --pretty=format:"%H"')
372 ' --reverse --pretty=format:"%H"')
299 git_revs += repo.run_git_command(cmd)[0].splitlines()
373 git_revs += repo.run_git_command(cmd)[0].splitlines()
300 elif _type == 'tags':
374 elif _type == 'tags':
301 git_revs += [push_ref['name']]
375 git_revs += [push_ref['name']]
302
376
303 log_push_action(baseui, repo, _git_revs=git_revs)
377 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,202 +1,201 b''
1 import os
1 import os
2 import socket
2 import socket
3 import logging
3 import logging
4 import subprocess
4 import subprocess
5
5
6 from webob import Request, Response, exc
6 from webob import Request, Response, exc
7
7
8 from rhodecode.lib import subprocessio
8 from rhodecode.lib import subprocessio
9
9
10 log = logging.getLogger(__name__)
10 log = logging.getLogger(__name__)
11
11
12
12
13 class FileWrapper(object):
13 class FileWrapper(object):
14
14
15 def __init__(self, fd, content_length):
15 def __init__(self, fd, content_length):
16 self.fd = fd
16 self.fd = fd
17 self.content_length = content_length
17 self.content_length = content_length
18 self.remain = content_length
18 self.remain = content_length
19
19
20 def read(self, size):
20 def read(self, size):
21 if size <= self.remain:
21 if size <= self.remain:
22 try:
22 try:
23 data = self.fd.read(size)
23 data = self.fd.read(size)
24 except socket.error:
24 except socket.error:
25 raise IOError(self)
25 raise IOError(self)
26 self.remain -= size
26 self.remain -= size
27 elif self.remain:
27 elif self.remain:
28 data = self.fd.read(self.remain)
28 data = self.fd.read(self.remain)
29 self.remain = 0
29 self.remain = 0
30 else:
30 else:
31 data = None
31 data = None
32 return data
32 return data
33
33
34 def __repr__(self):
34 def __repr__(self):
35 return '<FileWrapper %s len: %s, read: %s>' % (
35 return '<FileWrapper %s len: %s, read: %s>' % (
36 self.fd, self.content_length, self.content_length - self.remain
36 self.fd, self.content_length, self.content_length - self.remain
37 )
37 )
38
38
39
39
40 class GitRepository(object):
40 class GitRepository(object):
41 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
41 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
42 commands = ['git-upload-pack', 'git-receive-pack']
42 commands = ['git-upload-pack', 'git-receive-pack']
43
43
44 def __init__(self, repo_name, content_path, username):
44 def __init__(self, repo_name, content_path, extras):
45 files = set([f.lower() for f in os.listdir(content_path)])
45 files = set([f.lower() for f in os.listdir(content_path)])
46 if not (self.git_folder_signature.intersection(files)
46 if not (self.git_folder_signature.intersection(files)
47 == self.git_folder_signature):
47 == self.git_folder_signature):
48 raise OSError('%s missing git signature' % content_path)
48 raise OSError('%s missing git signature' % content_path)
49 self.content_path = content_path
49 self.content_path = content_path
50 self.valid_accepts = ['application/x-%s-result' %
50 self.valid_accepts = ['application/x-%s-result' %
51 c for c in self.commands]
51 c for c in self.commands]
52 self.repo_name = repo_name
52 self.repo_name = repo_name
53 self.username = username
53 self.extras = extras
54
54
55 def _get_fixedpath(self, path):
55 def _get_fixedpath(self, path):
56 """
56 """
57 Small fix for repo_path
57 Small fix for repo_path
58
58
59 :param path:
59 :param path:
60 :type path:
60 :type path:
61 """
61 """
62 return path.split(self.repo_name, 1)[-1].strip('/')
62 return path.split(self.repo_name, 1)[-1].strip('/')
63
63
64 def inforefs(self, request, environ):
64 def inforefs(self, request, environ):
65 """
65 """
66 WSGI Response producer for HTTP GET Git Smart
66 WSGI Response producer for HTTP GET Git Smart
67 HTTP /info/refs request.
67 HTTP /info/refs request.
68 """
68 """
69
69
70 git_command = request.GET['service']
70 git_command = request.GET.get('service')
71 if git_command not in self.commands:
71 if git_command not in self.commands:
72 log.debug('command %s not allowed' % git_command)
72 log.debug('command %s not allowed' % git_command)
73 return exc.HTTPMethodNotAllowed()
73 return exc.HTTPMethodNotAllowed()
74
74
75 # note to self:
75 # note to self:
76 # please, resist the urge to add '\n' to git capture and increment
76 # please, resist the urge to add '\n' to git capture and increment
77 # line count by 1.
77 # line count by 1.
78 # The code in Git client not only does NOT need '\n', but actually
78 # The code in Git client not only does NOT need '\n', but actually
79 # blows up if you sprinkle "flush" (0000) as "0001\n".
79 # blows up if you sprinkle "flush" (0000) as "0001\n".
80 # It reads binary, per number of bytes specified.
80 # It reads binary, per number of bytes specified.
81 # if you do add '\n' as part of data, count it.
81 # if you do add '\n' as part of data, count it.
82 server_advert = '# service=%s' % git_command
82 server_advert = '# service=%s' % git_command
83 packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
83 packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
84 try:
84 try:
85 out = subprocessio.SubprocessIOChunker(
85 out = subprocessio.SubprocessIOChunker(
86 r'git %s --stateless-rpc --advertise-refs "%s"' % (
86 r'git %s --stateless-rpc --advertise-refs "%s"' % (
87 git_command[4:], self.content_path),
87 git_command[4:], self.content_path),
88 starting_values=[
88 starting_values=[
89 packet_len + server_advert + '0000'
89 packet_len + server_advert + '0000'
90 ]
90 ]
91 )
91 )
92 except EnvironmentError, e:
92 except EnvironmentError, e:
93 log.exception(e)
93 log.exception(e)
94 raise exc.HTTPExpectationFailed()
94 raise exc.HTTPExpectationFailed()
95 resp = Response()
95 resp = Response()
96 resp.content_type = 'application/x-%s-advertisement' % str(git_command)
96 resp.content_type = 'application/x-%s-advertisement' % str(git_command)
97 resp.charset = None
97 resp.charset = None
98 resp.app_iter = out
98 resp.app_iter = out
99 return resp
99 return resp
100
100
101 def backend(self, request, environ):
101 def backend(self, request, environ):
102 """
102 """
103 WSGI Response producer for HTTP POST Git Smart HTTP requests.
103 WSGI Response producer for HTTP POST Git Smart HTTP requests.
104 Reads commands and data from HTTP POST's body.
104 Reads commands and data from HTTP POST's body.
105 returns an iterator obj with contents of git command's
105 returns an iterator obj with contents of git command's
106 response to stdout
106 response to stdout
107 """
107 """
108 git_command = self._get_fixedpath(request.path_info)
108 git_command = self._get_fixedpath(request.path_info)
109 if git_command not in self.commands:
109 if git_command not in self.commands:
110 log.debug('command %s not allowed' % git_command)
110 log.debug('command %s not allowed' % git_command)
111 return exc.HTTPMethodNotAllowed()
111 return exc.HTTPMethodNotAllowed()
112
112
113 if 'CONTENT_LENGTH' in environ:
113 if 'CONTENT_LENGTH' in environ:
114 inputstream = FileWrapper(environ['wsgi.input'],
114 inputstream = FileWrapper(environ['wsgi.input'],
115 request.content_length)
115 request.content_length)
116 else:
116 else:
117 inputstream = environ['wsgi.input']
117 inputstream = environ['wsgi.input']
118
118
119 try:
119 try:
120 gitenv = os.environ
120 gitenv = os.environ
121 from rhodecode import CONFIG
121 from rhodecode import CONFIG
122 from rhodecode.lib.base import _get_ip_addr
122 from rhodecode.lib.compat import json
123 gitenv['RHODECODE_USER'] = self.username
123 gitenv['RHODECODE_EXTRAS'] = json.dumps(self.extras)
124 gitenv['RHODECODE_CONFIG_IP'] = _get_ip_addr(environ)
125 # forget all configs
124 # forget all configs
126 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
125 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
127 # we need current .ini file used to later initialize rhodecode
126 # we need current .ini file used to later initialize rhodecode
128 # env and connect to db
127 # env and connect to db
129 gitenv['RHODECODE_CONFIG_FILE'] = CONFIG['__file__']
128 gitenv['RHODECODE_CONFIG_FILE'] = CONFIG['__file__']
130 opts = dict(
129 opts = dict(
131 env=gitenv,
130 env=gitenv,
132 cwd=os.getcwd()
131 cwd=os.getcwd()
133 )
132 )
134 out = subprocessio.SubprocessIOChunker(
133 out = subprocessio.SubprocessIOChunker(
135 r'git %s --stateless-rpc "%s"' % (git_command[4:],
134 r'git %s --stateless-rpc "%s"' % (git_command[4:],
136 self.content_path),
135 self.content_path),
137 inputstream=inputstream,
136 inputstream=inputstream,
138 **opts
137 **opts
139 )
138 )
140 except EnvironmentError, e:
139 except EnvironmentError, e:
141 log.exception(e)
140 log.exception(e)
142 raise exc.HTTPExpectationFailed()
141 raise exc.HTTPExpectationFailed()
143
142
144 if git_command in [u'git-receive-pack']:
143 if git_command in [u'git-receive-pack']:
145 # updating refs manually after each push.
144 # updating refs manually after each push.
146 # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
145 # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
147 subprocess.call(u'git --git-dir "%s" '
146 subprocess.call(u'git --git-dir "%s" '
148 'update-server-info' % self.content_path,
147 'update-server-info' % self.content_path,
149 shell=True)
148 shell=True)
150
149
151 resp = Response()
150 resp = Response()
152 resp.content_type = 'application/x-%s-result' % git_command.encode('utf8')
151 resp.content_type = 'application/x-%s-result' % git_command.encode('utf8')
153 resp.charset = None
152 resp.charset = None
154 resp.app_iter = out
153 resp.app_iter = out
155 return resp
154 return resp
156
155
157 def __call__(self, environ, start_response):
156 def __call__(self, environ, start_response):
158 request = Request(environ)
157 request = Request(environ)
159 _path = self._get_fixedpath(request.path_info)
158 _path = self._get_fixedpath(request.path_info)
160 if _path.startswith('info/refs'):
159 if _path.startswith('info/refs'):
161 app = self.inforefs
160 app = self.inforefs
162 elif [a for a in self.valid_accepts if a in request.accept]:
161 elif [a for a in self.valid_accepts if a in request.accept]:
163 app = self.backend
162 app = self.backend
164 try:
163 try:
165 resp = app(request, environ)
164 resp = app(request, environ)
166 except exc.HTTPException, e:
165 except exc.HTTPException, e:
167 resp = e
166 resp = e
168 log.exception(e)
167 log.exception(e)
169 except Exception, e:
168 except Exception, e:
170 log.exception(e)
169 log.exception(e)
171 resp = exc.HTTPInternalServerError()
170 resp = exc.HTTPInternalServerError()
172 return resp(environ, start_response)
171 return resp(environ, start_response)
173
172
174
173
175 class GitDirectory(object):
174 class GitDirectory(object):
176
175
177 def __init__(self, repo_root, repo_name, username):
176 def __init__(self, repo_root, repo_name, extras):
178 repo_location = os.path.join(repo_root, repo_name)
177 repo_location = os.path.join(repo_root, repo_name)
179 if not os.path.isdir(repo_location):
178 if not os.path.isdir(repo_location):
180 raise OSError(repo_location)
179 raise OSError(repo_location)
181
180
182 self.content_path = repo_location
181 self.content_path = repo_location
183 self.repo_name = repo_name
182 self.repo_name = repo_name
184 self.repo_location = repo_location
183 self.repo_location = repo_location
185 self.username = username
184 self.extras = extras
186
185
187 def __call__(self, environ, start_response):
186 def __call__(self, environ, start_response):
188 content_path = self.content_path
187 content_path = self.content_path
189 try:
188 try:
190 app = GitRepository(self.repo_name, content_path, self.username)
189 app = GitRepository(self.repo_name, content_path, self.extras)
191 except (AssertionError, OSError):
190 except (AssertionError, OSError):
192 if os.path.isdir(os.path.join(content_path, '.git')):
191 if os.path.isdir(os.path.join(content_path, '.git')):
193 app = GitRepository(self.repo_name,
192 app = GitRepository(self.repo_name,
194 os.path.join(content_path, '.git'),
193 os.path.join(content_path, '.git'),
195 self.username)
194 self.username)
196 else:
195 else:
197 return exc.HTTPNotFound()(environ, start_response)
196 return exc.HTTPNotFound()(environ, start_response)
198 return app(environ, start_response)
197 return app(environ, start_response)
199
198
200
199
201 def make_wsgi_app(repo_name, repo_root, username):
200 def make_wsgi_app(repo_name, repo_root, extras):
202 return GitDirectory(repo_root, repo_name, username)
201 return GitDirectory(repo_root, repo_name, extras)
@@ -1,308 +1,335 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplegit
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
7 It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 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 modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import re
28 import re
29 import logging
29 import logging
30 import traceback
30 import traceback
31
31
32 from dulwich import server as dulserver
32 from dulwich import server as dulserver
33 from dulwich.web import LimitedInputFilter, GunzipFilter
33 from dulwich.web import LimitedInputFilter, GunzipFilter
34 from rhodecode.lib.exceptions import HTTPLockedRC
35 from rhodecode.lib.hooks import pre_pull
34
36
35
37
36 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
38 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
37
39
38 def handle(self):
40 def handle(self):
39 write = lambda x: self.proto.write_sideband(1, x)
41 write = lambda x: self.proto.write_sideband(1, x)
40
42
41 graph_walker = dulserver.ProtocolGraphWalker(self,
43 graph_walker = dulserver.ProtocolGraphWalker(self,
42 self.repo.object_store,
44 self.repo.object_store,
43 self.repo.get_peeled)
45 self.repo.get_peeled)
44 objects_iter = self.repo.fetch_objects(
46 objects_iter = self.repo.fetch_objects(
45 graph_walker.determine_wants, graph_walker, self.progress,
47 graph_walker.determine_wants, graph_walker, self.progress,
46 get_tagged=self.get_tagged)
48 get_tagged=self.get_tagged)
47
49
48 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
50 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
49 # that the client still expects a 0-object pack in most cases.
51 # that the client still expects a 0-object pack in most cases.
50 if objects_iter is None:
52 if objects_iter is None:
51 return
53 return
52
54
53 self.progress("counting objects: %d, done.\n" % len(objects_iter))
55 self.progress("counting objects: %d, done.\n" % len(objects_iter))
54 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
56 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
55 objects_iter)
57 objects_iter)
56 messages = []
58 messages = []
57 messages.append('thank you for using rhodecode')
59 messages.append('thank you for using rhodecode')
58
60
59 for msg in messages:
61 for msg in messages:
60 self.progress(msg + "\n")
62 self.progress(msg + "\n")
61 # we are done
63 # we are done
62 self.proto.write("0000")
64 self.proto.write("0000")
63
65
64
66
65 dulserver.DEFAULT_HANDLERS = {
67 dulserver.DEFAULT_HANDLERS = {
66 #git-ls-remote, git-clone, git-fetch and git-pull
68 #git-ls-remote, git-clone, git-fetch and git-pull
67 'git-upload-pack': SimpleGitUploadPackHandler,
69 'git-upload-pack': SimpleGitUploadPackHandler,
68 #git-push
70 #git-push
69 'git-receive-pack': dulserver.ReceivePackHandler,
71 'git-receive-pack': dulserver.ReceivePackHandler,
70 }
72 }
71
73
72 # not used for now until dulwich get's fixed
74 # not used for now until dulwich get's fixed
73 #from dulwich.repo import Repo
75 #from dulwich.repo import Repo
74 #from dulwich.web import make_wsgi_chain
76 #from dulwich.web import make_wsgi_chain
75
77
76 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
78 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
79 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
78 HTTPBadRequest, HTTPNotAcceptable
80 HTTPBadRequest, HTTPNotAcceptable
79
81
80 from rhodecode.lib.utils2 import safe_str
82 from rhodecode.lib.utils2 import safe_str
81 from rhodecode.lib.base import BaseVCSController
83 from rhodecode.lib.base import BaseVCSController
82 from rhodecode.lib.auth import get_container_username
84 from rhodecode.lib.auth import get_container_username
83 from rhodecode.lib.utils import is_valid_repo, make_ui
85 from rhodecode.lib.utils import is_valid_repo, make_ui
84 from rhodecode.lib.compat import json
86 from rhodecode.lib.compat import json
85 from rhodecode.model.db import User, RhodeCodeUi
87 from rhodecode.model.db import User, RhodeCodeUi
86
88
87 log = logging.getLogger(__name__)
89 log = logging.getLogger(__name__)
88
90
89
91
90 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
92 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
91
93
92
94
93 def is_git(environ):
95 def is_git(environ):
94 path_info = environ['PATH_INFO']
96 path_info = environ['PATH_INFO']
95 isgit_path = GIT_PROTO_PAT.match(path_info)
97 isgit_path = GIT_PROTO_PAT.match(path_info)
96 log.debug('pathinfo: %s detected as GIT %s' % (
98 log.debug('pathinfo: %s detected as GIT %s' % (
97 path_info, isgit_path != None)
99 path_info, isgit_path != None)
98 )
100 )
99 return isgit_path
101 return isgit_path
100
102
101
103
102 class SimpleGit(BaseVCSController):
104 class SimpleGit(BaseVCSController):
103
105
104 def _handle_request(self, environ, start_response):
106 def _handle_request(self, environ, start_response):
105
106 if not is_git(environ):
107 if not is_git(environ):
107 return self.application(environ, start_response)
108 return self.application(environ, start_response)
108 if not self._check_ssl(environ, start_response):
109 if not self._check_ssl(environ, start_response):
109 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
110 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
111
110 ipaddr = self._get_ip_addr(environ)
112 ipaddr = self._get_ip_addr(environ)
111 username = None
113 username = None
112 self._git_first_op = False
114 self._git_first_op = False
113 # skip passing error to error controller
115 # skip passing error to error controller
114 environ['pylons.status_code_redirect'] = True
116 environ['pylons.status_code_redirect'] = True
115
117
116 #======================================================================
118 #======================================================================
117 # EXTRACT REPOSITORY NAME FROM ENV
119 # EXTRACT REPOSITORY NAME FROM ENV
118 #======================================================================
120 #======================================================================
119 try:
121 try:
120 repo_name = self.__get_repository(environ)
122 repo_name = self.__get_repository(environ)
121 log.debug('Extracted repo name is %s' % repo_name)
123 log.debug('Extracted repo name is %s' % repo_name)
122 except:
124 except:
123 return HTTPInternalServerError()(environ, start_response)
125 return HTTPInternalServerError()(environ, start_response)
124
126
125 # quick check if that dir exists...
127 # quick check if that dir exists...
126 if is_valid_repo(repo_name, self.basepath, 'git') is False:
128 if is_valid_repo(repo_name, self.basepath, 'git') is False:
127 return HTTPNotFound()(environ, start_response)
129 return HTTPNotFound()(environ, start_response)
128
130
129 #======================================================================
131 #======================================================================
130 # GET ACTION PULL or PUSH
132 # GET ACTION PULL or PUSH
131 #======================================================================
133 #======================================================================
132 action = self.__get_action(environ)
134 action = self.__get_action(environ)
133
135
134 #======================================================================
136 #======================================================================
135 # CHECK ANONYMOUS PERMISSION
137 # CHECK ANONYMOUS PERMISSION
136 #======================================================================
138 #======================================================================
137 if action in ['pull', 'push']:
139 if action in ['pull', 'push']:
138 anonymous_user = self.__get_user('default')
140 anonymous_user = self.__get_user('default')
139 username = anonymous_user.username
141 username = anonymous_user.username
140 anonymous_perm = self._check_permission(action, anonymous_user,
142 anonymous_perm = self._check_permission(action, anonymous_user,
141 repo_name)
143 repo_name)
142
144
143 if anonymous_perm is not True or anonymous_user.active is False:
145 if anonymous_perm is not True or anonymous_user.active is False:
144 if anonymous_perm is not True:
146 if anonymous_perm is not True:
145 log.debug('Not enough credentials to access this '
147 log.debug('Not enough credentials to access this '
146 'repository as anonymous user')
148 'repository as anonymous user')
147 if anonymous_user.active is False:
149 if anonymous_user.active is False:
148 log.debug('Anonymous access is disabled, running '
150 log.debug('Anonymous access is disabled, running '
149 'authentication')
151 'authentication')
150 #==============================================================
152 #==============================================================
151 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
153 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
152 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
154 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
153 #==============================================================
155 #==============================================================
154
156
155 # Attempting to retrieve username from the container
157 # Attempting to retrieve username from the container
156 username = get_container_username(environ, self.config)
158 username = get_container_username(environ, self.config)
157
159
158 # If not authenticated by the container, running basic auth
160 # If not authenticated by the container, running basic auth
159 if not username:
161 if not username:
160 self.authenticate.realm = \
162 self.authenticate.realm = \
161 safe_str(self.config['rhodecode_realm'])
163 safe_str(self.config['rhodecode_realm'])
162 result = self.authenticate(environ)
164 result = self.authenticate(environ)
163 if isinstance(result, str):
165 if isinstance(result, str):
164 AUTH_TYPE.update(environ, 'basic')
166 AUTH_TYPE.update(environ, 'basic')
165 REMOTE_USER.update(environ, result)
167 REMOTE_USER.update(environ, result)
166 username = result
168 username = result
167 else:
169 else:
168 return result.wsgi_application(environ, start_response)
170 return result.wsgi_application(environ, start_response)
169
171
170 #==============================================================
172 #==============================================================
171 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
173 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
172 #==============================================================
174 #==============================================================
173 try:
175 try:
174 user = self.__get_user(username)
176 user = self.__get_user(username)
175 if user is None or not user.active:
177 if user is None or not user.active:
176 return HTTPForbidden()(environ, start_response)
178 return HTTPForbidden()(environ, start_response)
177 username = user.username
179 username = user.username
178 except:
180 except:
179 log.error(traceback.format_exc())
181 log.error(traceback.format_exc())
180 return HTTPInternalServerError()(environ, start_response)
182 return HTTPInternalServerError()(environ, start_response)
181
183
182 #check permissions for this repository
184 #check permissions for this repository
183 perm = self._check_permission(action, user, repo_name)
185 perm = self._check_permission(action, user, repo_name)
184 if perm is not True:
186 if perm is not True:
185 return HTTPForbidden()(environ, start_response)
187 return HTTPForbidden()(environ, start_response)
186
188
189 # extras are injected into UI object and later available
190 # in hooks executed by rhodecode
187 extras = {
191 extras = {
188 'ip': ipaddr,
192 'ip': ipaddr,
189 'username': username,
193 'username': username,
190 'action': action,
194 'action': action,
191 'repository': repo_name,
195 'repository': repo_name,
192 'scm': 'git',
196 'scm': 'git',
197 'make_lock': None,
198 'locked_by': [None, None]
193 }
199 }
194 # set the environ variables for this request
200
195 os.environ['RC_SCM_DATA'] = json.dumps(extras)
196 #===================================================================
201 #===================================================================
197 # GIT REQUEST HANDLING
202 # GIT REQUEST HANDLING
198 #===================================================================
203 #===================================================================
199 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
204 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
200 log.debug('Repository path is %s' % repo_path)
205 log.debug('Repository path is %s' % repo_path)
201
206
207 # CHECK LOCKING only if it's not ANONYMOUS USER
208 if username != User.DEFAULT_USER:
209 log.debug('Checking locking on repository')
210 (make_lock,
211 locked,
212 locked_by) = self._check_locking_state(
213 environ=environ, action=action,
214 repo=repo_name, user_id=user.user_id
215 )
216 # store the make_lock for later evaluation in hooks
217 extras.update({'make_lock': make_lock,
218 'locked_by': locked_by})
219 # set the environ variables for this request
220 os.environ['RC_SCM_DATA'] = json.dumps(extras)
221 log.debug('HOOKS extras is %s' % extras)
202 baseui = make_ui('db')
222 baseui = make_ui('db')
203 self.__inject_extras(repo_path, baseui, extras)
223 self.__inject_extras(repo_path, baseui, extras)
204
224
205 try:
225 try:
206 # invalidate cache on push
226 # invalidate cache on push
207 if action == 'push':
227 if action == 'push':
208 self._invalidate_cache(repo_name)
228 self._invalidate_cache(repo_name)
209 self._handle_githooks(repo_name, action, baseui, environ)
229 self._handle_githooks(repo_name, action, baseui, environ)
210
230
211 log.info('%s action on GIT repo "%s"' % (action, repo_name))
231 log.info('%s action on GIT repo "%s"' % (action, repo_name))
212 app = self.__make_app(repo_name, repo_path, username)
232 app = self.__make_app(repo_name, repo_path, extras)
213 return app(environ, start_response)
233 return app(environ, start_response)
234 except HTTPLockedRC, e:
235 log.debug('Repositry LOCKED ret code 423!')
236 return e(environ, start_response)
214 except Exception:
237 except Exception:
215 log.error(traceback.format_exc())
238 log.error(traceback.format_exc())
216 return HTTPInternalServerError()(environ, start_response)
239 return HTTPInternalServerError()(environ, start_response)
217
240
218 def __make_app(self, repo_name, repo_path, username):
241 def __make_app(self, repo_name, repo_path, extras):
219 """
242 """
220 Make an wsgi application using dulserver
243 Make an wsgi application using dulserver
221
244
222 :param repo_name: name of the repository
245 :param repo_name: name of the repository
223 :param repo_path: full path to the repository
246 :param repo_path: full path to the repository
224 """
247 """
225
248
226 from rhodecode.lib.middleware.pygrack import make_wsgi_app
249 from rhodecode.lib.middleware.pygrack import make_wsgi_app
227 app = make_wsgi_app(
250 app = make_wsgi_app(
228 repo_root=safe_str(self.basepath),
251 repo_root=safe_str(self.basepath),
229 repo_name=repo_name,
252 repo_name=repo_name,
230 username=username,
253 extras=extras,
231 )
254 )
232 app = GunzipFilter(LimitedInputFilter(app))
255 app = GunzipFilter(LimitedInputFilter(app))
233 return app
256 return app
234
257
235 def __get_repository(self, environ):
258 def __get_repository(self, environ):
236 """
259 """
237 Get's repository name out of PATH_INFO header
260 Get's repository name out of PATH_INFO header
238
261
239 :param environ: environ where PATH_INFO is stored
262 :param environ: environ where PATH_INFO is stored
240 """
263 """
241 try:
264 try:
242 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
265 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
243 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
266 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
244 except:
267 except:
245 log.error(traceback.format_exc())
268 log.error(traceback.format_exc())
246 raise
269 raise
247
270
248 return repo_name
271 return repo_name
249
272
250 def __get_user(self, username):
273 def __get_user(self, username):
251 return User.get_by_username(username)
274 return User.get_by_username(username)
252
275
253 def __get_action(self, environ):
276 def __get_action(self, environ):
254 """
277 """
255 Maps git request commands into a pull or push command.
278 Maps git request commands into a pull or push command.
256
279
257 :param environ:
280 :param environ:
258 """
281 """
259 service = environ['QUERY_STRING'].split('=')
282 service = environ['QUERY_STRING'].split('=')
260
283
261 if len(service) > 1:
284 if len(service) > 1:
262 service_cmd = service[1]
285 service_cmd = service[1]
263 mapping = {
286 mapping = {
264 'git-receive-pack': 'push',
287 'git-receive-pack': 'push',
265 'git-upload-pack': 'pull',
288 'git-upload-pack': 'pull',
266 }
289 }
267 op = mapping[service_cmd]
290 op = mapping[service_cmd]
268 self._git_stored_op = op
291 self._git_stored_op = op
269 return op
292 return op
270 else:
293 else:
271 # try to fallback to stored variable as we don't know if the last
294 # try to fallback to stored variable as we don't know if the last
272 # operation is pull/push
295 # operation is pull/push
273 op = getattr(self, '_git_stored_op', 'pull')
296 op = getattr(self, '_git_stored_op', 'pull')
274 return op
297 return op
275
298
276 def _handle_githooks(self, repo_name, action, baseui, environ):
299 def _handle_githooks(self, repo_name, action, baseui, environ):
277 """
300 """
278 Handles pull action, push is handled by post-receive hook
301 Handles pull action, push is handled by post-receive hook
279 """
302 """
280 from rhodecode.lib.hooks import log_pull_action
303 from rhodecode.lib.hooks import log_pull_action
281 service = environ['QUERY_STRING'].split('=')
304 service = environ['QUERY_STRING'].split('=')
305
282 if len(service) < 2:
306 if len(service) < 2:
283 return
307 return
284
308
285 from rhodecode.model.db import Repository
309 from rhodecode.model.db import Repository
286 _repo = Repository.get_by_repo_name(repo_name)
310 _repo = Repository.get_by_repo_name(repo_name)
287 _repo = _repo.scm_instance
311 _repo = _repo.scm_instance
288 _repo._repo.ui = baseui
312 _repo._repo.ui = baseui
289
313
290 _hooks = dict(baseui.configitems('hooks')) or {}
314 _hooks = dict(baseui.configitems('hooks')) or {}
315 if action == 'pull':
316 # stupid git, emulate pre-pull hook !
317 pre_pull(ui=baseui, repo=_repo._repo)
291 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
318 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
292 log_pull_action(ui=baseui, repo=_repo._repo)
319 log_pull_action(ui=baseui, repo=_repo._repo)
293
320
294 def __inject_extras(self, repo_path, baseui, extras={}):
321 def __inject_extras(self, repo_path, baseui, extras={}):
295 """
322 """
296 Injects some extra params into baseui instance
323 Injects some extra params into baseui instance
297
324
298 :param baseui: baseui instance
325 :param baseui: baseui instance
299 :param extras: dict with extra params to put into baseui
326 :param extras: dict with extra params to put into baseui
300 """
327 """
301
328
302 # make our hgweb quiet so it doesn't print output
329 # make our hgweb quiet so it doesn't print output
303 baseui.setconfig('ui', 'quiet', 'true')
330 baseui.setconfig('ui', 'quiet', 'true')
304
331
305 #inject some additional parameters that will be available in ui
332 #inject some additional parameters that will be available in ui
306 #for hooks
333 #for hooks
307 for k, v in extras.items():
334 for k, v in extras.items():
308 baseui.setconfig('rhodecode_extras', k, v)
335 baseui.setconfig('rhodecode_extras', k, v)
@@ -1,263 +1,283 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 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 modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import urllib
30 import urllib
31
31
32 from mercurial.error import RepoError
32 from mercurial.error import RepoError
33 from mercurial.hgweb import hgweb_mod
33 from mercurial.hgweb import hgweb_mod
34
34
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
36 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
37 HTTPBadRequest, HTTPNotAcceptable
37 HTTPBadRequest, HTTPNotAcceptable
38
38
39 from rhodecode.lib.utils2 import safe_str
39 from rhodecode.lib.utils2 import safe_str
40 from rhodecode.lib.base import BaseVCSController
40 from rhodecode.lib.base import BaseVCSController
41 from rhodecode.lib.auth import get_container_username
41 from rhodecode.lib.auth import get_container_username
42 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
42 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
43 from rhodecode.lib.compat import json
43 from rhodecode.lib.compat import json
44 from rhodecode.model.db import User
44 from rhodecode.model.db import User
45 from rhodecode.lib.exceptions import HTTPLockedRC
45
46
46
47
47 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
48
49
49
50
50 def is_mercurial(environ):
51 def is_mercurial(environ):
51 """
52 """
52 Returns True if request's target is mercurial server - header
53 Returns True if request's target is mercurial server - header
53 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
54 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
54 """
55 """
55 http_accept = environ.get('HTTP_ACCEPT')
56 http_accept = environ.get('HTTP_ACCEPT')
56 path_info = environ['PATH_INFO']
57 path_info = environ['PATH_INFO']
57 if http_accept and http_accept.startswith('application/mercurial'):
58 if http_accept and http_accept.startswith('application/mercurial'):
58 ishg_path = True
59 ishg_path = True
59 else:
60 else:
60 ishg_path = False
61 ishg_path = False
61
62
62 log.debug('pathinfo: %s detected as HG %s' % (
63 log.debug('pathinfo: %s detected as HG %s' % (
63 path_info, ishg_path)
64 path_info, ishg_path)
64 )
65 )
65 return ishg_path
66 return ishg_path
66
67
67
68
68 class SimpleHg(BaseVCSController):
69 class SimpleHg(BaseVCSController):
69
70
70 def _handle_request(self, environ, start_response):
71 def _handle_request(self, environ, start_response):
71 if not is_mercurial(environ):
72 if not is_mercurial(environ):
72 return self.application(environ, start_response)
73 return self.application(environ, start_response)
73 if not self._check_ssl(environ, start_response):
74 if not self._check_ssl(environ, start_response):
74 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
75 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
75
76
76 ipaddr = self._get_ip_addr(environ)
77 ipaddr = self._get_ip_addr(environ)
77 username = None
78 username = None
78 # skip passing error to error controller
79 # skip passing error to error controller
79 environ['pylons.status_code_redirect'] = True
80 environ['pylons.status_code_redirect'] = True
80
81
81 #======================================================================
82 #======================================================================
82 # EXTRACT REPOSITORY NAME FROM ENV
83 # EXTRACT REPOSITORY NAME FROM ENV
83 #======================================================================
84 #======================================================================
84 try:
85 try:
85 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
86 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
86 log.debug('Extracted repo name is %s' % repo_name)
87 log.debug('Extracted repo name is %s' % repo_name)
87 except:
88 except:
88 return HTTPInternalServerError()(environ, start_response)
89 return HTTPInternalServerError()(environ, start_response)
89
90
90 # quick check if that dir exists...
91 # quick check if that dir exists...
91 if is_valid_repo(repo_name, self.basepath, 'hg') is False:
92 if is_valid_repo(repo_name, self.basepath, 'hg') is False:
92 return HTTPNotFound()(environ, start_response)
93 return HTTPNotFound()(environ, start_response)
93
94
94 #======================================================================
95 #======================================================================
95 # GET ACTION PULL or PUSH
96 # GET ACTION PULL or PUSH
96 #======================================================================
97 #======================================================================
97 action = self.__get_action(environ)
98 action = self.__get_action(environ)
98
99
99 #======================================================================
100 #======================================================================
100 # CHECK ANONYMOUS PERMISSION
101 # CHECK ANONYMOUS PERMISSION
101 #======================================================================
102 #======================================================================
102 if action in ['pull', 'push']:
103 if action in ['pull', 'push']:
103 anonymous_user = self.__get_user('default')
104 anonymous_user = self.__get_user('default')
104 username = anonymous_user.username
105 username = anonymous_user.username
105 anonymous_perm = self._check_permission(action, anonymous_user,
106 anonymous_perm = self._check_permission(action, anonymous_user,
106 repo_name)
107 repo_name)
107
108
108 if anonymous_perm is not True or anonymous_user.active is False:
109 if anonymous_perm is not True or anonymous_user.active is False:
109 if anonymous_perm is not True:
110 if anonymous_perm is not True:
110 log.debug('Not enough credentials to access this '
111 log.debug('Not enough credentials to access this '
111 'repository as anonymous user')
112 'repository as anonymous user')
112 if anonymous_user.active is False:
113 if anonymous_user.active is False:
113 log.debug('Anonymous access is disabled, running '
114 log.debug('Anonymous access is disabled, running '
114 'authentication')
115 'authentication')
115 #==============================================================
116 #==============================================================
116 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
117 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
117 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
118 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
118 #==============================================================
119 #==============================================================
119
120
120 # Attempting to retrieve username from the container
121 # Attempting to retrieve username from the container
121 username = get_container_username(environ, self.config)
122 username = get_container_username(environ, self.config)
122
123
123 # If not authenticated by the container, running basic auth
124 # If not authenticated by the container, running basic auth
124 if not username:
125 if not username:
125 self.authenticate.realm = \
126 self.authenticate.realm = \
126 safe_str(self.config['rhodecode_realm'])
127 safe_str(self.config['rhodecode_realm'])
127 result = self.authenticate(environ)
128 result = self.authenticate(environ)
128 if isinstance(result, str):
129 if isinstance(result, str):
129 AUTH_TYPE.update(environ, 'basic')
130 AUTH_TYPE.update(environ, 'basic')
130 REMOTE_USER.update(environ, result)
131 REMOTE_USER.update(environ, result)
131 username = result
132 username = result
132 else:
133 else:
133 return result.wsgi_application(environ, start_response)
134 return result.wsgi_application(environ, start_response)
134
135
135 #==============================================================
136 #==============================================================
136 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
137 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
137 #==============================================================
138 #==============================================================
138 try:
139 try:
139 user = self.__get_user(username)
140 user = self.__get_user(username)
140 if user is None or not user.active:
141 if user is None or not user.active:
141 return HTTPForbidden()(environ, start_response)
142 return HTTPForbidden()(environ, start_response)
142 username = user.username
143 username = user.username
143 except:
144 except:
144 log.error(traceback.format_exc())
145 log.error(traceback.format_exc())
145 return HTTPInternalServerError()(environ, start_response)
146 return HTTPInternalServerError()(environ, start_response)
146
147
147 #check permissions for this repository
148 #check permissions for this repository
148 perm = self._check_permission(action, user, repo_name)
149 perm = self._check_permission(action, user, repo_name)
149 if perm is not True:
150 if perm is not True:
150 return HTTPForbidden()(environ, start_response)
151 return HTTPForbidden()(environ, start_response)
151
152
152 # extras are injected into mercurial UI object and later available
153 # extras are injected into mercurial UI object and later available
153 # in hg hooks executed by rhodecode
154 # in hg hooks executed by rhodecode
154 extras = {
155 extras = {
155 'ip': ipaddr,
156 'ip': ipaddr,
156 'username': username,
157 'username': username,
157 'action': action,
158 'action': action,
158 'repository': repo_name,
159 'repository': repo_name,
159 'scm': 'hg',
160 'scm': 'hg',
161 'make_lock': None,
162 'locked_by': [None, None]
160 }
163 }
161 # set the environ variables for this request
162 os.environ['RC_SCM_DATA'] = json.dumps(extras)
163 #======================================================================
164 #======================================================================
164 # MERCURIAL REQUEST HANDLING
165 # MERCURIAL REQUEST HANDLING
165 #======================================================================
166 #======================================================================
166 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
167 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
167 log.debug('Repository path is %s' % repo_path)
168 log.debug('Repository path is %s' % repo_path)
168
169
170 # CHECK LOCKING only if it's not ANONYMOUS USER
171 if username != User.DEFAULT_USER:
172 log.debug('Checking locking on repository')
173 (make_lock,
174 locked,
175 locked_by) = self._check_locking_state(
176 environ=environ, action=action,
177 repo=repo_name, user_id=user.user_id
178 )
179 # store the make_lock for later evaluation in hooks
180 extras.update({'make_lock': make_lock,
181 'locked_by': locked_by})
182
183 # set the environ variables for this request
184 os.environ['RC_SCM_DATA'] = json.dumps(extras)
185 log.debug('HOOKS extras is %s' % extras)
169 baseui = make_ui('db')
186 baseui = make_ui('db')
170 self.__inject_extras(repo_path, baseui, extras)
187 self.__inject_extras(repo_path, baseui, extras)
171
188
172 try:
189 try:
173 # invalidate cache on push
190 # invalidate cache on push
174 if action == 'push':
191 if action == 'push':
175 self._invalidate_cache(repo_name)
192 self._invalidate_cache(repo_name)
176 log.info('%s action on HG repo "%s"' % (action, repo_name))
193 log.info('%s action on HG repo "%s"' % (action, repo_name))
177 app = self.__make_app(repo_path, baseui, extras)
194 app = self.__make_app(repo_path, baseui, extras)
178 return app(environ, start_response)
195 return app(environ, start_response)
179 except RepoError, e:
196 except RepoError, e:
180 if str(e).find('not found') != -1:
197 if str(e).find('not found') != -1:
181 return HTTPNotFound()(environ, start_response)
198 return HTTPNotFound()(environ, start_response)
199 except HTTPLockedRC, e:
200 log.debug('Repositry LOCKED ret code 423!')
201 return e(environ, start_response)
182 except Exception:
202 except Exception:
183 log.error(traceback.format_exc())
203 log.error(traceback.format_exc())
184 return HTTPInternalServerError()(environ, start_response)
204 return HTTPInternalServerError()(environ, start_response)
185
205
186 def __make_app(self, repo_name, baseui, extras):
206 def __make_app(self, repo_name, baseui, extras):
187 """
207 """
188 Make an wsgi application using hgweb, and inject generated baseui
208 Make an wsgi application using hgweb, and inject generated baseui
189 instance, additionally inject some extras into ui object
209 instance, additionally inject some extras into ui object
190 """
210 """
191 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
211 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
192
212
193 def __get_repository(self, environ):
213 def __get_repository(self, environ):
194 """
214 """
195 Get's repository name out of PATH_INFO header
215 Get's repository name out of PATH_INFO header
196
216
197 :param environ: environ where PATH_INFO is stored
217 :param environ: environ where PATH_INFO is stored
198 """
218 """
199 try:
219 try:
200 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
220 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
201 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
221 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
202 if repo_name.endswith('/'):
222 if repo_name.endswith('/'):
203 repo_name = repo_name.rstrip('/')
223 repo_name = repo_name.rstrip('/')
204 except:
224 except:
205 log.error(traceback.format_exc())
225 log.error(traceback.format_exc())
206 raise
226 raise
207
227
208 return repo_name
228 return repo_name
209
229
210 def __get_user(self, username):
230 def __get_user(self, username):
211 return User.get_by_username(username)
231 return User.get_by_username(username)
212
232
213 def __get_action(self, environ):
233 def __get_action(self, environ):
214 """
234 """
215 Maps mercurial request commands into a clone,pull or push command.
235 Maps mercurial request commands into a clone,pull or push command.
216 This should always return a valid command string
236 This should always return a valid command string
217
237
218 :param environ:
238 :param environ:
219 """
239 """
220 mapping = {'changegroup': 'pull',
240 mapping = {'changegroup': 'pull',
221 'changegroupsubset': 'pull',
241 'changegroupsubset': 'pull',
222 'stream_out': 'pull',
242 'stream_out': 'pull',
223 'listkeys': 'pull',
243 'listkeys': 'pull',
224 'unbundle': 'push',
244 'unbundle': 'push',
225 'pushkey': 'push', }
245 'pushkey': 'push', }
226 for qry in environ['QUERY_STRING'].split('&'):
246 for qry in environ['QUERY_STRING'].split('&'):
227 if qry.startswith('cmd'):
247 if qry.startswith('cmd'):
228 cmd = qry.split('=')[-1]
248 cmd = qry.split('=')[-1]
229 if cmd in mapping:
249 if cmd in mapping:
230 return mapping[cmd]
250 return mapping[cmd]
231
251
232 return 'pull'
252 return 'pull'
233
253
234 raise Exception('Unable to detect pull/push action !!'
254 raise Exception('Unable to detect pull/push action !!'
235 'Are you using non standard command or client ?')
255 'Are you using non standard command or client ?')
236
256
237 def __inject_extras(self, repo_path, baseui, extras={}):
257 def __inject_extras(self, repo_path, baseui, extras={}):
238 """
258 """
239 Injects some extra params into baseui instance
259 Injects some extra params into baseui instance
240
260
241 also overwrites global settings with those takes from local hgrc file
261 also overwrites global settings with those takes from local hgrc file
242
262
243 :param baseui: baseui instance
263 :param baseui: baseui instance
244 :param extras: dict with extra params to put into baseui
264 :param extras: dict with extra params to put into baseui
245 """
265 """
246
266
247 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
267 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
248
268
249 # make our hgweb quiet so it doesn't print output
269 # make our hgweb quiet so it doesn't print output
250 baseui.setconfig('ui', 'quiet', 'true')
270 baseui.setconfig('ui', 'quiet', 'true')
251
271
252 #inject some additional parameters that will be available in ui
272 #inject some additional parameters that will be available in ui
253 #for hooks
273 #for hooks
254 for k, v in extras.items():
274 for k, v in extras.items():
255 baseui.setconfig('rhodecode_extras', k, v)
275 baseui.setconfig('rhodecode_extras', k, v)
256
276
257 repoui = make_ui('file', hgrc, False)
277 repoui = make_ui('file', hgrc, False)
258
278
259 if repoui:
279 if repoui:
260 #overwrite our ui instance with the section from hgrc file
280 #overwrite our ui instance with the section from hgrc file
261 for section in ui_sections:
281 for section in ui_sections:
262 for k, v in repoui.configitems(section):
282 for k, v in repoui.configitems(section):
263 baseui.setconfig(section, k, v)
283 baseui.setconfig(section, k, v)
@@ -1,458 +1,467 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import re
26 import re
27 import time
27 import time
28 from datetime import datetime
28 import datetime
29 from pylons.i18n.translation import _, ungettext
29 from pylons.i18n.translation import _, ungettext
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
31
31
32
32
33 def __get_lem():
33 def __get_lem():
34 """
34 """
35 Get language extension map based on what's inside pygments lexers
35 Get language extension map based on what's inside pygments lexers
36 """
36 """
37 from pygments import lexers
37 from pygments import lexers
38 from string import lower
38 from string import lower
39 from collections import defaultdict
39 from collections import defaultdict
40
40
41 d = defaultdict(lambda: [])
41 d = defaultdict(lambda: [])
42
42
43 def __clean(s):
43 def __clean(s):
44 s = s.lstrip('*')
44 s = s.lstrip('*')
45 s = s.lstrip('.')
45 s = s.lstrip('.')
46
46
47 if s.find('[') != -1:
47 if s.find('[') != -1:
48 exts = []
48 exts = []
49 start, stop = s.find('['), s.find(']')
49 start, stop = s.find('['), s.find(']')
50
50
51 for suffix in s[start + 1:stop]:
51 for suffix in s[start + 1:stop]:
52 exts.append(s[:s.find('[')] + suffix)
52 exts.append(s[:s.find('[')] + suffix)
53 return map(lower, exts)
53 return map(lower, exts)
54 else:
54 else:
55 return map(lower, [s])
55 return map(lower, [s])
56
56
57 for lx, t in sorted(lexers.LEXERS.items()):
57 for lx, t in sorted(lexers.LEXERS.items()):
58 m = map(__clean, t[-2])
58 m = map(__clean, t[-2])
59 if m:
59 if m:
60 m = reduce(lambda x, y: x + y, m)
60 m = reduce(lambda x, y: x + y, m)
61 for ext in m:
61 for ext in m:
62 desc = lx.replace('Lexer', '')
62 desc = lx.replace('Lexer', '')
63 d[ext].append(desc)
63 d[ext].append(desc)
64
64
65 return dict(d)
65 return dict(d)
66
66
67 def str2bool(_str):
67 def str2bool(_str):
68 """
68 """
69 returs True/False value from given string, it tries to translate the
69 returs True/False value from given string, it tries to translate the
70 string into boolean
70 string into boolean
71
71
72 :param _str: string value to translate into boolean
72 :param _str: string value to translate into boolean
73 :rtype: boolean
73 :rtype: boolean
74 :returns: boolean from given string
74 :returns: boolean from given string
75 """
75 """
76 if _str is None:
76 if _str is None:
77 return False
77 return False
78 if _str in (True, False):
78 if _str in (True, False):
79 return _str
79 return _str
80 _str = str(_str).strip().lower()
80 _str = str(_str).strip().lower()
81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
82
82
83
83
84 def convert_line_endings(line, mode):
84 def convert_line_endings(line, mode):
85 """
85 """
86 Converts a given line "line end" accordingly to given mode
86 Converts a given line "line end" accordingly to given mode
87
87
88 Available modes are::
88 Available modes are::
89 0 - Unix
89 0 - Unix
90 1 - Mac
90 1 - Mac
91 2 - DOS
91 2 - DOS
92
92
93 :param line: given line to convert
93 :param line: given line to convert
94 :param mode: mode to convert to
94 :param mode: mode to convert to
95 :rtype: str
95 :rtype: str
96 :return: converted line according to mode
96 :return: converted line according to mode
97 """
97 """
98 from string import replace
98 from string import replace
99
99
100 if mode == 0:
100 if mode == 0:
101 line = replace(line, '\r\n', '\n')
101 line = replace(line, '\r\n', '\n')
102 line = replace(line, '\r', '\n')
102 line = replace(line, '\r', '\n')
103 elif mode == 1:
103 elif mode == 1:
104 line = replace(line, '\r\n', '\r')
104 line = replace(line, '\r\n', '\r')
105 line = replace(line, '\n', '\r')
105 line = replace(line, '\n', '\r')
106 elif mode == 2:
106 elif mode == 2:
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
108 return line
108 return line
109
109
110
110
111 def detect_mode(line, default):
111 def detect_mode(line, default):
112 """
112 """
113 Detects line break for given line, if line break couldn't be found
113 Detects line break for given line, if line break couldn't be found
114 given default value is returned
114 given default value is returned
115
115
116 :param line: str line
116 :param line: str line
117 :param default: default
117 :param default: default
118 :rtype: int
118 :rtype: int
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
120 """
120 """
121 if line.endswith('\r\n'):
121 if line.endswith('\r\n'):
122 return 2
122 return 2
123 elif line.endswith('\n'):
123 elif line.endswith('\n'):
124 return 0
124 return 0
125 elif line.endswith('\r'):
125 elif line.endswith('\r'):
126 return 1
126 return 1
127 else:
127 else:
128 return default
128 return default
129
129
130
130
131 def generate_api_key(username, salt=None):
131 def generate_api_key(username, salt=None):
132 """
132 """
133 Generates unique API key for given username, if salt is not given
133 Generates unique API key for given username, if salt is not given
134 it'll be generated from some random string
134 it'll be generated from some random string
135
135
136 :param username: username as string
136 :param username: username as string
137 :param salt: salt to hash generate KEY
137 :param salt: salt to hash generate KEY
138 :rtype: str
138 :rtype: str
139 :returns: sha1 hash from username+salt
139 :returns: sha1 hash from username+salt
140 """
140 """
141 from tempfile import _RandomNameSequence
141 from tempfile import _RandomNameSequence
142 import hashlib
142 import hashlib
143
143
144 if salt is None:
144 if salt is None:
145 salt = _RandomNameSequence().next()
145 salt = _RandomNameSequence().next()
146
146
147 return hashlib.sha1(username + salt).hexdigest()
147 return hashlib.sha1(username + salt).hexdigest()
148
148
149
149
150 def safe_unicode(str_, from_encoding=None):
150 def safe_unicode(str_, from_encoding=None):
151 """
151 """
152 safe unicode function. Does few trick to turn str_ into unicode
152 safe unicode function. Does few trick to turn str_ into unicode
153
153
154 In case of UnicodeDecode error we try to return it with encoding detected
154 In case of UnicodeDecode error we try to return it with encoding detected
155 by chardet library if it fails fallback to unicode with errors replaced
155 by chardet library if it fails fallback to unicode with errors replaced
156
156
157 :param str_: string to decode
157 :param str_: string to decode
158 :rtype: unicode
158 :rtype: unicode
159 :returns: unicode object
159 :returns: unicode object
160 """
160 """
161 if isinstance(str_, unicode):
161 if isinstance(str_, unicode):
162 return str_
162 return str_
163
163
164 if not from_encoding:
164 if not from_encoding:
165 import rhodecode
165 import rhodecode
166 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
166 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
167 from_encoding = DEFAULT_ENCODING
167 from_encoding = DEFAULT_ENCODING
168
168
169 try:
169 try:
170 return unicode(str_)
170 return unicode(str_)
171 except UnicodeDecodeError:
171 except UnicodeDecodeError:
172 pass
172 pass
173
173
174 try:
174 try:
175 return unicode(str_, from_encoding)
175 return unicode(str_, from_encoding)
176 except UnicodeDecodeError:
176 except UnicodeDecodeError:
177 pass
177 pass
178
178
179 try:
179 try:
180 import chardet
180 import chardet
181 encoding = chardet.detect(str_)['encoding']
181 encoding = chardet.detect(str_)['encoding']
182 if encoding is None:
182 if encoding is None:
183 raise Exception()
183 raise Exception()
184 return str_.decode(encoding)
184 return str_.decode(encoding)
185 except (ImportError, UnicodeDecodeError, Exception):
185 except (ImportError, UnicodeDecodeError, Exception):
186 return unicode(str_, from_encoding, 'replace')
186 return unicode(str_, from_encoding, 'replace')
187
187
188
188
189 def safe_str(unicode_, to_encoding=None):
189 def safe_str(unicode_, to_encoding=None):
190 """
190 """
191 safe str function. Does few trick to turn unicode_ into string
191 safe str function. Does few trick to turn unicode_ into string
192
192
193 In case of UnicodeEncodeError we try to return it with encoding detected
193 In case of UnicodeEncodeError we try to return it with encoding detected
194 by chardet library if it fails fallback to string with errors replaced
194 by chardet library if it fails fallback to string with errors replaced
195
195
196 :param unicode_: unicode to encode
196 :param unicode_: unicode to encode
197 :rtype: str
197 :rtype: str
198 :returns: str object
198 :returns: str object
199 """
199 """
200
200
201 # if it's not basestr cast to str
201 # if it's not basestr cast to str
202 if not isinstance(unicode_, basestring):
202 if not isinstance(unicode_, basestring):
203 return str(unicode_)
203 return str(unicode_)
204
204
205 if isinstance(unicode_, str):
205 if isinstance(unicode_, str):
206 return unicode_
206 return unicode_
207
207
208 if not to_encoding:
208 if not to_encoding:
209 import rhodecode
209 import rhodecode
210 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
210 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
211 to_encoding = DEFAULT_ENCODING
211 to_encoding = DEFAULT_ENCODING
212
212
213 try:
213 try:
214 return unicode_.encode(to_encoding)
214 return unicode_.encode(to_encoding)
215 except UnicodeEncodeError:
215 except UnicodeEncodeError:
216 pass
216 pass
217
217
218 try:
218 try:
219 import chardet
219 import chardet
220 encoding = chardet.detect(unicode_)['encoding']
220 encoding = chardet.detect(unicode_)['encoding']
221 if encoding is None:
221 if encoding is None:
222 raise UnicodeEncodeError()
222 raise UnicodeEncodeError()
223
223
224 return unicode_.encode(encoding)
224 return unicode_.encode(encoding)
225 except (ImportError, UnicodeEncodeError):
225 except (ImportError, UnicodeEncodeError):
226 return unicode_.encode(to_encoding, 'replace')
226 return unicode_.encode(to_encoding, 'replace')
227
227
228 return safe_str
228 return safe_str
229
229
230
230
231 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
231 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
232 """
232 """
233 Custom engine_from_config functions that makes sure we use NullPool for
233 Custom engine_from_config functions that makes sure we use NullPool for
234 file based sqlite databases. This prevents errors on sqlite. This only
234 file based sqlite databases. This prevents errors on sqlite. This only
235 applies to sqlalchemy versions < 0.7.0
235 applies to sqlalchemy versions < 0.7.0
236
236
237 """
237 """
238 import sqlalchemy
238 import sqlalchemy
239 from sqlalchemy import engine_from_config as efc
239 from sqlalchemy import engine_from_config as efc
240 import logging
240 import logging
241
241
242 if int(sqlalchemy.__version__.split('.')[1]) < 7:
242 if int(sqlalchemy.__version__.split('.')[1]) < 7:
243
243
244 # This solution should work for sqlalchemy < 0.7.0, and should use
244 # This solution should work for sqlalchemy < 0.7.0, and should use
245 # proxy=TimerProxy() for execution time profiling
245 # proxy=TimerProxy() for execution time profiling
246
246
247 from sqlalchemy.pool import NullPool
247 from sqlalchemy.pool import NullPool
248 url = configuration[prefix + 'url']
248 url = configuration[prefix + 'url']
249
249
250 if url.startswith('sqlite'):
250 if url.startswith('sqlite'):
251 kwargs.update({'poolclass': NullPool})
251 kwargs.update({'poolclass': NullPool})
252 return efc(configuration, prefix, **kwargs)
252 return efc(configuration, prefix, **kwargs)
253 else:
253 else:
254 import time
254 import time
255 from sqlalchemy import event
255 from sqlalchemy import event
256 from sqlalchemy.engine import Engine
256 from sqlalchemy.engine import Engine
257
257
258 log = logging.getLogger('sqlalchemy.engine')
258 log = logging.getLogger('sqlalchemy.engine')
259 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
259 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
260 engine = efc(configuration, prefix, **kwargs)
260 engine = efc(configuration, prefix, **kwargs)
261
261
262 def color_sql(sql):
262 def color_sql(sql):
263 COLOR_SEQ = "\033[1;%dm"
263 COLOR_SEQ = "\033[1;%dm"
264 COLOR_SQL = YELLOW
264 COLOR_SQL = YELLOW
265 normal = '\x1b[0m'
265 normal = '\x1b[0m'
266 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
266 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
267
267
268 if configuration['debug']:
268 if configuration['debug']:
269 #attach events only for debug configuration
269 #attach events only for debug configuration
270
270
271 def before_cursor_execute(conn, cursor, statement,
271 def before_cursor_execute(conn, cursor, statement,
272 parameters, context, executemany):
272 parameters, context, executemany):
273 context._query_start_time = time.time()
273 context._query_start_time = time.time()
274 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
274 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
275
275
276
276
277 def after_cursor_execute(conn, cursor, statement,
277 def after_cursor_execute(conn, cursor, statement,
278 parameters, context, executemany):
278 parameters, context, executemany):
279 total = time.time() - context._query_start_time
279 total = time.time() - context._query_start_time
280 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
280 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
281
281
282 event.listen(engine, "before_cursor_execute",
282 event.listen(engine, "before_cursor_execute",
283 before_cursor_execute)
283 before_cursor_execute)
284 event.listen(engine, "after_cursor_execute",
284 event.listen(engine, "after_cursor_execute",
285 after_cursor_execute)
285 after_cursor_execute)
286
286
287 return engine
287 return engine
288
288
289
289
290 def age(prevdate):
290 def age(prevdate):
291 """
291 """
292 turns a datetime into an age string.
292 turns a datetime into an age string.
293
293
294 :param prevdate: datetime object
294 :param prevdate: datetime object
295 :rtype: unicode
295 :rtype: unicode
296 :returns: unicode words describing age
296 :returns: unicode words describing age
297 """
297 """
298
298
299 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
299 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
300 deltas = {}
300 deltas = {}
301
301
302 # Get date parts deltas
302 # Get date parts deltas
303 now = datetime.now()
303 now = datetime.datetime.now()
304 for part in order:
304 for part in order:
305 deltas[part] = getattr(now, part) - getattr(prevdate, part)
305 deltas[part] = getattr(now, part) - getattr(prevdate, part)
306
306
307 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
307 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
308 # not 1 hour, -59 minutes and -59 seconds)
308 # not 1 hour, -59 minutes and -59 seconds)
309
309
310 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
310 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
311 part = order[num]
311 part = order[num]
312 carry_part = order[num - 1]
312 carry_part = order[num - 1]
313
313
314 if deltas[part] < 0:
314 if deltas[part] < 0:
315 deltas[part] += length
315 deltas[part] += length
316 deltas[carry_part] -= 1
316 deltas[carry_part] -= 1
317
317
318 # Same thing for days except that the increment depends on the (variable)
318 # Same thing for days except that the increment depends on the (variable)
319 # number of days in the month
319 # number of days in the month
320 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
320 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
321 if deltas['day'] < 0:
321 if deltas['day'] < 0:
322 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
322 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
323 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
323 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
324 deltas['day'] += 29
324 deltas['day'] += 29
325 else:
325 else:
326 deltas['day'] += month_lengths[prevdate.month - 1]
326 deltas['day'] += month_lengths[prevdate.month - 1]
327
327
328 deltas['month'] -= 1
328 deltas['month'] -= 1
329
329
330 if deltas['month'] < 0:
330 if deltas['month'] < 0:
331 deltas['month'] += 12
331 deltas['month'] += 12
332 deltas['year'] -= 1
332 deltas['year'] -= 1
333
333
334 # Format the result
334 # Format the result
335 fmt_funcs = {
335 fmt_funcs = {
336 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
336 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
337 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
337 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
338 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
338 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
339 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
339 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
340 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
340 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
341 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
341 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
342 }
342 }
343
343
344 for i, part in enumerate(order):
344 for i, part in enumerate(order):
345 value = deltas[part]
345 value = deltas[part]
346 if value == 0:
346 if value == 0:
347 continue
347 continue
348
348
349 if i < 5:
349 if i < 5:
350 sub_part = order[i + 1]
350 sub_part = order[i + 1]
351 sub_value = deltas[sub_part]
351 sub_value = deltas[sub_part]
352 else:
352 else:
353 sub_value = 0
353 sub_value = 0
354
354
355 if sub_value == 0:
355 if sub_value == 0:
356 return _(u'%s ago') % fmt_funcs[part](value)
356 return _(u'%s ago') % fmt_funcs[part](value)
357
357
358 return _(u'%s and %s ago') % (fmt_funcs[part](value),
358 return _(u'%s and %s ago') % (fmt_funcs[part](value),
359 fmt_funcs[sub_part](sub_value))
359 fmt_funcs[sub_part](sub_value))
360
360
361 return _(u'just now')
361 return _(u'just now')
362
362
363
363
364 def uri_filter(uri):
364 def uri_filter(uri):
365 """
365 """
366 Removes user:password from given url string
366 Removes user:password from given url string
367
367
368 :param uri:
368 :param uri:
369 :rtype: unicode
369 :rtype: unicode
370 :returns: filtered list of strings
370 :returns: filtered list of strings
371 """
371 """
372 if not uri:
372 if not uri:
373 return ''
373 return ''
374
374
375 proto = ''
375 proto = ''
376
376
377 for pat in ('https://', 'http://'):
377 for pat in ('https://', 'http://'):
378 if uri.startswith(pat):
378 if uri.startswith(pat):
379 uri = uri[len(pat):]
379 uri = uri[len(pat):]
380 proto = pat
380 proto = pat
381 break
381 break
382
382
383 # remove passwords and username
383 # remove passwords and username
384 uri = uri[uri.find('@') + 1:]
384 uri = uri[uri.find('@') + 1:]
385
385
386 # get the port
386 # get the port
387 cred_pos = uri.find(':')
387 cred_pos = uri.find(':')
388 if cred_pos == -1:
388 if cred_pos == -1:
389 host, port = uri, None
389 host, port = uri, None
390 else:
390 else:
391 host, port = uri[:cred_pos], uri[cred_pos + 1:]
391 host, port = uri[:cred_pos], uri[cred_pos + 1:]
392
392
393 return filter(None, [proto, host, port])
393 return filter(None, [proto, host, port])
394
394
395
395
396 def credentials_filter(uri):
396 def credentials_filter(uri):
397 """
397 """
398 Returns a url with removed credentials
398 Returns a url with removed credentials
399
399
400 :param uri:
400 :param uri:
401 """
401 """
402
402
403 uri = uri_filter(uri)
403 uri = uri_filter(uri)
404 #check if we have port
404 #check if we have port
405 if len(uri) > 2 and uri[2]:
405 if len(uri) > 2 and uri[2]:
406 uri[2] = ':' + uri[2]
406 uri[2] = ':' + uri[2]
407
407
408 return ''.join(uri)
408 return ''.join(uri)
409
409
410
410
411 def get_changeset_safe(repo, rev):
411 def get_changeset_safe(repo, rev):
412 """
412 """
413 Safe version of get_changeset if this changeset doesn't exists for a
413 Safe version of get_changeset if this changeset doesn't exists for a
414 repo it returns a Dummy one instead
414 repo it returns a Dummy one instead
415
415
416 :param repo:
416 :param repo:
417 :param rev:
417 :param rev:
418 """
418 """
419 from rhodecode.lib.vcs.backends.base import BaseRepository
419 from rhodecode.lib.vcs.backends.base import BaseRepository
420 from rhodecode.lib.vcs.exceptions import RepositoryError
420 from rhodecode.lib.vcs.exceptions import RepositoryError
421 from rhodecode.lib.vcs.backends.base import EmptyChangeset
421 from rhodecode.lib.vcs.backends.base import EmptyChangeset
422 if not isinstance(repo, BaseRepository):
422 if not isinstance(repo, BaseRepository):
423 raise Exception('You must pass an Repository '
423 raise Exception('You must pass an Repository '
424 'object as first argument got %s', type(repo))
424 'object as first argument got %s', type(repo))
425
425
426 try:
426 try:
427 cs = repo.get_changeset(rev)
427 cs = repo.get_changeset(rev)
428 except RepositoryError:
428 except RepositoryError:
429 cs = EmptyChangeset(requested_revision=rev)
429 cs = EmptyChangeset(requested_revision=rev)
430 return cs
430 return cs
431
431
432
432
433 def datetime_to_time(dt):
433 def datetime_to_time(dt):
434 if dt:
434 if dt:
435 return time.mktime(dt.timetuple())
435 return time.mktime(dt.timetuple())
436
436
437
437
438 def time_to_datetime(tm):
439 if tm:
440 if isinstance(tm, basestring):
441 try:
442 tm = float(tm)
443 except ValueError:
444 return
445 return datetime.datetime.fromtimestamp(tm)
446
438 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
447 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
439
448
440
449
441 def extract_mentioned_users(s):
450 def extract_mentioned_users(s):
442 """
451 """
443 Returns unique usernames from given string s that have @mention
452 Returns unique usernames from given string s that have @mention
444
453
445 :param s: string to get mentions
454 :param s: string to get mentions
446 """
455 """
447 usrs = set()
456 usrs = set()
448 for username in re.findall(MENTIONS_REGEX, s):
457 for username in re.findall(MENTIONS_REGEX, s):
449 usrs.add(username)
458 usrs.add(username)
450
459
451 return sorted(list(usrs), key=lambda k: k.lower())
460 return sorted(list(usrs), key=lambda k: k.lower())
452
461
453
462
454 class AttributeDict(dict):
463 class AttributeDict(dict):
455 def __getattr__(self, attr):
464 def __getattr__(self, attr):
456 return self.get(attr, None)
465 return self.get(attr, None)
457 __setattr__ = dict.__setitem__
466 __setattr__ = dict.__setitem__
458 __delattr__ = dict.__delitem__
467 __delattr__ = dict.__delitem__
@@ -1,1706 +1,1743 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 from collections import defaultdict
32 from collections import defaultdict
32
33
33 from sqlalchemy import *
34 from sqlalchemy import *
34 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
37 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
38 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
39
40
40 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
41
42
42 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46
47
47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 safe_unicode
49 safe_unicode
49 from rhodecode.lib.compat import json
50 from rhodecode.lib.compat import json
50 from rhodecode.lib.caching_query import FromCache
51 from rhodecode.lib.caching_query import FromCache
51
52
52 from rhodecode.model.meta import Base, Session
53 from rhodecode.model.meta import Base, Session
53
54
54 URL_SEP = '/'
55 URL_SEP = '/'
55 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
56
57
57 #==============================================================================
58 #==============================================================================
58 # BASE CLASSES
59 # BASE CLASSES
59 #==============================================================================
60 #==============================================================================
60
61
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62
63
63
64
64 class BaseModel(object):
65 class BaseModel(object):
65 """
66 """
66 Base Model for all classess
67 Base Model for all classess
67 """
68 """
68
69
69 @classmethod
70 @classmethod
70 def _get_keys(cls):
71 def _get_keys(cls):
71 """return column names for this model """
72 """return column names for this model """
72 return class_mapper(cls).c.keys()
73 return class_mapper(cls).c.keys()
73
74
74 def get_dict(self):
75 def get_dict(self):
75 """
76 """
76 return dict with keys and values corresponding
77 return dict with keys and values corresponding
77 to this model data """
78 to this model data """
78
79
79 d = {}
80 d = {}
80 for k in self._get_keys():
81 for k in self._get_keys():
81 d[k] = getattr(self, k)
82 d[k] = getattr(self, k)
82
83
83 # also use __json__() if present to get additional fields
84 # also use __json__() if present to get additional fields
84 _json_attr = getattr(self, '__json__', None)
85 _json_attr = getattr(self, '__json__', None)
85 if _json_attr:
86 if _json_attr:
86 # update with attributes from __json__
87 # update with attributes from __json__
87 if callable(_json_attr):
88 if callable(_json_attr):
88 _json_attr = _json_attr()
89 _json_attr = _json_attr()
89 for k, val in _json_attr.iteritems():
90 for k, val in _json_attr.iteritems():
90 d[k] = val
91 d[k] = val
91 return d
92 return d
92
93
93 def get_appstruct(self):
94 def get_appstruct(self):
94 """return list with keys and values tupples corresponding
95 """return list with keys and values tupples corresponding
95 to this model data """
96 to this model data """
96
97
97 l = []
98 l = []
98 for k in self._get_keys():
99 for k in self._get_keys():
99 l.append((k, getattr(self, k),))
100 l.append((k, getattr(self, k),))
100 return l
101 return l
101
102
102 def populate_obj(self, populate_dict):
103 def populate_obj(self, populate_dict):
103 """populate model with data from given populate_dict"""
104 """populate model with data from given populate_dict"""
104
105
105 for k in self._get_keys():
106 for k in self._get_keys():
106 if k in populate_dict:
107 if k in populate_dict:
107 setattr(self, k, populate_dict[k])
108 setattr(self, k, populate_dict[k])
108
109
109 @classmethod
110 @classmethod
110 def query(cls):
111 def query(cls):
111 return Session().query(cls)
112 return Session().query(cls)
112
113
113 @classmethod
114 @classmethod
114 def get(cls, id_):
115 def get(cls, id_):
115 if id_:
116 if id_:
116 return cls.query().get(id_)
117 return cls.query().get(id_)
117
118
118 @classmethod
119 @classmethod
119 def get_or_404(cls, id_):
120 def get_or_404(cls, id_):
120 if id_:
121 if id_:
121 res = cls.query().get(id_)
122 res = cls.query().get(id_)
122 if not res:
123 if not res:
123 raise HTTPNotFound
124 raise HTTPNotFound
124 return res
125 return res
125
126
126 @classmethod
127 @classmethod
127 def getAll(cls):
128 def getAll(cls):
128 return cls.query().all()
129 return cls.query().all()
129
130
130 @classmethod
131 @classmethod
131 def delete(cls, id_):
132 def delete(cls, id_):
132 obj = cls.query().get(id_)
133 obj = cls.query().get(id_)
133 Session().delete(obj)
134 Session().delete(obj)
134
135
135 def __repr__(self):
136 def __repr__(self):
136 if hasattr(self, '__unicode__'):
137 if hasattr(self, '__unicode__'):
137 # python repr needs to return str
138 # python repr needs to return str
138 return safe_str(self.__unicode__())
139 return safe_str(self.__unicode__())
139 return '<DB:%s>' % (self.__class__.__name__)
140 return '<DB:%s>' % (self.__class__.__name__)
140
141
141
142
142 class RhodeCodeSetting(Base, BaseModel):
143 class RhodeCodeSetting(Base, BaseModel):
143 __tablename__ = 'rhodecode_settings'
144 __tablename__ = 'rhodecode_settings'
144 __table_args__ = (
145 __table_args__ = (
145 UniqueConstraint('app_settings_name'),
146 UniqueConstraint('app_settings_name'),
146 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 'mysql_charset': 'utf8'}
148 'mysql_charset': 'utf8'}
148 )
149 )
149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152
153
153 def __init__(self, k='', v=''):
154 def __init__(self, k='', v=''):
154 self.app_settings_name = k
155 self.app_settings_name = k
155 self.app_settings_value = v
156 self.app_settings_value = v
156
157
157 @validates('_app_settings_value')
158 @validates('_app_settings_value')
158 def validate_settings_value(self, key, val):
159 def validate_settings_value(self, key, val):
159 assert type(val) == unicode
160 assert type(val) == unicode
160 return val
161 return val
161
162
162 @hybrid_property
163 @hybrid_property
163 def app_settings_value(self):
164 def app_settings_value(self):
164 v = self._app_settings_value
165 v = self._app_settings_value
165 if self.app_settings_name == 'ldap_active':
166 if self.app_settings_name == 'ldap_active':
166 v = str2bool(v)
167 v = str2bool(v)
167 return v
168 return v
168
169
169 @app_settings_value.setter
170 @app_settings_value.setter
170 def app_settings_value(self, val):
171 def app_settings_value(self, val):
171 """
172 """
172 Setter that will always make sure we use unicode in app_settings_value
173 Setter that will always make sure we use unicode in app_settings_value
173
174
174 :param val:
175 :param val:
175 """
176 """
176 self._app_settings_value = safe_unicode(val)
177 self._app_settings_value = safe_unicode(val)
177
178
178 def __unicode__(self):
179 def __unicode__(self):
179 return u"<%s('%s:%s')>" % (
180 return u"<%s('%s:%s')>" % (
180 self.__class__.__name__,
181 self.__class__.__name__,
181 self.app_settings_name, self.app_settings_value
182 self.app_settings_name, self.app_settings_value
182 )
183 )
183
184
184 @classmethod
185 @classmethod
185 def get_by_name(cls, key):
186 def get_by_name(cls, key):
186 return cls.query()\
187 return cls.query()\
187 .filter(cls.app_settings_name == key).scalar()
188 .filter(cls.app_settings_name == key).scalar()
188
189
189 @classmethod
190 @classmethod
190 def get_by_name_or_create(cls, key):
191 def get_by_name_or_create(cls, key):
191 res = cls.get_by_name(key)
192 res = cls.get_by_name(key)
192 if not res:
193 if not res:
193 res = cls(key)
194 res = cls(key)
194 return res
195 return res
195
196
196 @classmethod
197 @classmethod
197 def get_app_settings(cls, cache=False):
198 def get_app_settings(cls, cache=False):
198
199
199 ret = cls.query()
200 ret = cls.query()
200
201
201 if cache:
202 if cache:
202 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
203 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
203
204
204 if not ret:
205 if not ret:
205 raise Exception('Could not get application settings !')
206 raise Exception('Could not get application settings !')
206 settings = {}
207 settings = {}
207 for each in ret:
208 for each in ret:
208 settings['rhodecode_' + each.app_settings_name] = \
209 settings['rhodecode_' + each.app_settings_name] = \
209 each.app_settings_value
210 each.app_settings_value
210
211
211 return settings
212 return settings
212
213
213 @classmethod
214 @classmethod
214 def get_ldap_settings(cls, cache=False):
215 def get_ldap_settings(cls, cache=False):
215 ret = cls.query()\
216 ret = cls.query()\
216 .filter(cls.app_settings_name.startswith('ldap_')).all()
217 .filter(cls.app_settings_name.startswith('ldap_')).all()
217 fd = {}
218 fd = {}
218 for row in ret:
219 for row in ret:
219 fd.update({row.app_settings_name: row.app_settings_value})
220 fd.update({row.app_settings_name: row.app_settings_value})
220
221
221 return fd
222 return fd
222
223
223
224
224 class RhodeCodeUi(Base, BaseModel):
225 class RhodeCodeUi(Base, BaseModel):
225 __tablename__ = 'rhodecode_ui'
226 __tablename__ = 'rhodecode_ui'
226 __table_args__ = (
227 __table_args__ = (
227 UniqueConstraint('ui_key'),
228 UniqueConstraint('ui_key'),
228 {'extend_existing': True, 'mysql_engine': 'InnoDB',
229 {'extend_existing': True, 'mysql_engine': 'InnoDB',
229 'mysql_charset': 'utf8'}
230 'mysql_charset': 'utf8'}
230 )
231 )
231
232
232 HOOK_UPDATE = 'changegroup.update'
233 HOOK_UPDATE = 'changegroup.update'
233 HOOK_REPO_SIZE = 'changegroup.repo_size'
234 HOOK_REPO_SIZE = 'changegroup.repo_size'
234 HOOK_PUSH = 'changegroup.push_logger'
235 HOOK_PUSH = 'changegroup.push_logger'
235 HOOK_PULL = 'preoutgoing.pull_logger'
236 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
237 HOOK_PULL = 'outgoing.pull_logger'
238 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
236
239
237 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
240 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
238 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
241 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
239 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
242 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
240 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
243 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
241 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
244 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
242
245
243 @classmethod
246 @classmethod
244 def get_by_key(cls, key):
247 def get_by_key(cls, key):
245 return cls.query().filter(cls.ui_key == key).scalar()
248 return cls.query().filter(cls.ui_key == key).scalar()
246
249
247 @classmethod
250 @classmethod
248 def get_builtin_hooks(cls):
251 def get_builtin_hooks(cls):
249 q = cls.query()
252 q = cls.query()
250 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
253 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
251 cls.HOOK_REPO_SIZE,
254 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
252 cls.HOOK_PUSH, cls.HOOK_PULL]))
255 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
253 return q.all()
256 return q.all()
254
257
255 @classmethod
258 @classmethod
256 def get_custom_hooks(cls):
259 def get_custom_hooks(cls):
257 q = cls.query()
260 q = cls.query()
258 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
261 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
259 cls.HOOK_REPO_SIZE,
262 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
260 cls.HOOK_PUSH, cls.HOOK_PULL]))
263 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
261 q = q.filter(cls.ui_section == 'hooks')
264 q = q.filter(cls.ui_section == 'hooks')
262 return q.all()
265 return q.all()
263
266
264 @classmethod
267 @classmethod
265 def get_repos_location(cls):
268 def get_repos_location(cls):
266 return cls.get_by_key('/').ui_value
269 return cls.get_by_key('/').ui_value
267
270
268 @classmethod
271 @classmethod
269 def create_or_update_hook(cls, key, val):
272 def create_or_update_hook(cls, key, val):
270 new_ui = cls.get_by_key(key) or cls()
273 new_ui = cls.get_by_key(key) or cls()
271 new_ui.ui_section = 'hooks'
274 new_ui.ui_section = 'hooks'
272 new_ui.ui_active = True
275 new_ui.ui_active = True
273 new_ui.ui_key = key
276 new_ui.ui_key = key
274 new_ui.ui_value = val
277 new_ui.ui_value = val
275
278
276 Session().add(new_ui)
279 Session().add(new_ui)
277
280
278
281
279 class User(Base, BaseModel):
282 class User(Base, BaseModel):
280 __tablename__ = 'users'
283 __tablename__ = 'users'
281 __table_args__ = (
284 __table_args__ = (
282 UniqueConstraint('username'), UniqueConstraint('email'),
285 UniqueConstraint('username'), UniqueConstraint('email'),
286 Index('u_username_idx', 'username'),
287 Index('u_email_idx', 'email'),
283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
288 {'extend_existing': True, 'mysql_engine': 'InnoDB',
284 'mysql_charset': 'utf8'}
289 'mysql_charset': 'utf8'}
285 )
290 )
291 DEFAULT_USER = 'default'
292
286 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
293 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
287 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
294 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
288 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
296 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
290 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
297 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
291 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
292 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
293 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
294 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
301 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
295 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
302 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
297 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
304 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
298
305
299 user_log = relationship('UserLog', cascade='all')
306 user_log = relationship('UserLog', cascade='all')
300 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
307 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
301
308
302 repositories = relationship('Repository')
309 repositories = relationship('Repository')
303 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
310 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
304 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
311 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
305 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
312 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
306
313
307 group_member = relationship('UsersGroupMember', cascade='all')
314 group_member = relationship('UsersGroupMember', cascade='all')
308
315
309 notifications = relationship('UserNotification', cascade='all')
316 notifications = relationship('UserNotification', cascade='all')
310 # notifications assigned to this user
317 # notifications assigned to this user
311 user_created_notifications = relationship('Notification', cascade='all')
318 user_created_notifications = relationship('Notification', cascade='all')
312 # comments created by this user
319 # comments created by this user
313 user_comments = relationship('ChangesetComment', cascade='all')
320 user_comments = relationship('ChangesetComment', cascade='all')
314 #extra emails for this user
321 #extra emails for this user
315 user_emails = relationship('UserEmailMap', cascade='all')
322 user_emails = relationship('UserEmailMap', cascade='all')
316
323
317 @hybrid_property
324 @hybrid_property
318 def email(self):
325 def email(self):
319 return self._email
326 return self._email
320
327
321 @email.setter
328 @email.setter
322 def email(self, val):
329 def email(self, val):
323 self._email = val.lower() if val else None
330 self._email = val.lower() if val else None
324
331
325 @property
332 @property
326 def emails(self):
333 def emails(self):
327 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
334 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
328 return [self.email] + [x.email for x in other]
335 return [self.email] + [x.email for x in other]
329
336
330 @property
337 @property
331 def full_name(self):
338 def full_name(self):
332 return '%s %s' % (self.name, self.lastname)
339 return '%s %s' % (self.name, self.lastname)
333
340
334 @property
341 @property
335 def full_name_or_username(self):
342 def full_name_or_username(self):
336 return ('%s %s' % (self.name, self.lastname)
343 return ('%s %s' % (self.name, self.lastname)
337 if (self.name and self.lastname) else self.username)
344 if (self.name and self.lastname) else self.username)
338
345
339 @property
346 @property
340 def full_contact(self):
347 def full_contact(self):
341 return '%s %s <%s>' % (self.name, self.lastname, self.email)
348 return '%s %s <%s>' % (self.name, self.lastname, self.email)
342
349
343 @property
350 @property
344 def short_contact(self):
351 def short_contact(self):
345 return '%s %s' % (self.name, self.lastname)
352 return '%s %s' % (self.name, self.lastname)
346
353
347 @property
354 @property
348 def is_admin(self):
355 def is_admin(self):
349 return self.admin
356 return self.admin
350
357
351 def __unicode__(self):
358 def __unicode__(self):
352 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
359 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
353 self.user_id, self.username)
360 self.user_id, self.username)
354
361
355 @classmethod
362 @classmethod
356 def get_by_username(cls, username, case_insensitive=False, cache=False):
363 def get_by_username(cls, username, case_insensitive=False, cache=False):
357 if case_insensitive:
364 if case_insensitive:
358 q = cls.query().filter(cls.username.ilike(username))
365 q = cls.query().filter(cls.username.ilike(username))
359 else:
366 else:
360 q = cls.query().filter(cls.username == username)
367 q = cls.query().filter(cls.username == username)
361
368
362 if cache:
369 if cache:
363 q = q.options(FromCache(
370 q = q.options(FromCache(
364 "sql_cache_short",
371 "sql_cache_short",
365 "get_user_%s" % _hash_key(username)
372 "get_user_%s" % _hash_key(username)
366 )
373 )
367 )
374 )
368 return q.scalar()
375 return q.scalar()
369
376
370 @classmethod
377 @classmethod
371 def get_by_api_key(cls, api_key, cache=False):
378 def get_by_api_key(cls, api_key, cache=False):
372 q = cls.query().filter(cls.api_key == api_key)
379 q = cls.query().filter(cls.api_key == api_key)
373
380
374 if cache:
381 if cache:
375 q = q.options(FromCache("sql_cache_short",
382 q = q.options(FromCache("sql_cache_short",
376 "get_api_key_%s" % api_key))
383 "get_api_key_%s" % api_key))
377 return q.scalar()
384 return q.scalar()
378
385
379 @classmethod
386 @classmethod
380 def get_by_email(cls, email, case_insensitive=False, cache=False):
387 def get_by_email(cls, email, case_insensitive=False, cache=False):
381 if case_insensitive:
388 if case_insensitive:
382 q = cls.query().filter(cls.email.ilike(email))
389 q = cls.query().filter(cls.email.ilike(email))
383 else:
390 else:
384 q = cls.query().filter(cls.email == email)
391 q = cls.query().filter(cls.email == email)
385
392
386 if cache:
393 if cache:
387 q = q.options(FromCache("sql_cache_short",
394 q = q.options(FromCache("sql_cache_short",
388 "get_email_key_%s" % email))
395 "get_email_key_%s" % email))
389
396
390 ret = q.scalar()
397 ret = q.scalar()
391 if ret is None:
398 if ret is None:
392 q = UserEmailMap.query()
399 q = UserEmailMap.query()
393 # try fetching in alternate email map
400 # try fetching in alternate email map
394 if case_insensitive:
401 if case_insensitive:
395 q = q.filter(UserEmailMap.email.ilike(email))
402 q = q.filter(UserEmailMap.email.ilike(email))
396 else:
403 else:
397 q = q.filter(UserEmailMap.email == email)
404 q = q.filter(UserEmailMap.email == email)
398 q = q.options(joinedload(UserEmailMap.user))
405 q = q.options(joinedload(UserEmailMap.user))
399 if cache:
406 if cache:
400 q = q.options(FromCache("sql_cache_short",
407 q = q.options(FromCache("sql_cache_short",
401 "get_email_map_key_%s" % email))
408 "get_email_map_key_%s" % email))
402 ret = getattr(q.scalar(), 'user', None)
409 ret = getattr(q.scalar(), 'user', None)
403
410
404 return ret
411 return ret
405
412
406 def update_lastlogin(self):
413 def update_lastlogin(self):
407 """Update user lastlogin"""
414 """Update user lastlogin"""
408 self.last_login = datetime.datetime.now()
415 self.last_login = datetime.datetime.now()
409 Session().add(self)
416 Session().add(self)
410 log.debug('updated user %s lastlogin' % self.username)
417 log.debug('updated user %s lastlogin' % self.username)
411
418
412 def get_api_data(self):
419 def get_api_data(self):
413 """
420 """
414 Common function for generating user related data for API
421 Common function for generating user related data for API
415 """
422 """
416 user = self
423 user = self
417 data = dict(
424 data = dict(
418 user_id=user.user_id,
425 user_id=user.user_id,
419 username=user.username,
426 username=user.username,
420 firstname=user.name,
427 firstname=user.name,
421 lastname=user.lastname,
428 lastname=user.lastname,
422 email=user.email,
429 email=user.email,
423 emails=user.emails,
430 emails=user.emails,
424 api_key=user.api_key,
431 api_key=user.api_key,
425 active=user.active,
432 active=user.active,
426 admin=user.admin,
433 admin=user.admin,
427 ldap_dn=user.ldap_dn,
434 ldap_dn=user.ldap_dn,
428 last_login=user.last_login,
435 last_login=user.last_login,
429 )
436 )
430 return data
437 return data
431
438
432 def __json__(self):
439 def __json__(self):
433 data = dict(
440 data = dict(
434 full_name=self.full_name,
441 full_name=self.full_name,
435 full_name_or_username=self.full_name_or_username,
442 full_name_or_username=self.full_name_or_username,
436 short_contact=self.short_contact,
443 short_contact=self.short_contact,
437 full_contact=self.full_contact
444 full_contact=self.full_contact
438 )
445 )
439 data.update(self.get_api_data())
446 data.update(self.get_api_data())
440 return data
447 return data
441
448
442
449
443 class UserEmailMap(Base, BaseModel):
450 class UserEmailMap(Base, BaseModel):
444 __tablename__ = 'user_email_map'
451 __tablename__ = 'user_email_map'
445 __table_args__ = (
452 __table_args__ = (
446 Index('uem_email_idx', 'email'),
453 Index('uem_email_idx', 'email'),
447 UniqueConstraint('email'),
454 UniqueConstraint('email'),
448 {'extend_existing': True, 'mysql_engine': 'InnoDB',
455 {'extend_existing': True, 'mysql_engine': 'InnoDB',
449 'mysql_charset': 'utf8'}
456 'mysql_charset': 'utf8'}
450 )
457 )
451 __mapper_args__ = {}
458 __mapper_args__ = {}
452
459
453 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
460 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
454 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
461 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
455 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
462 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
456
463
457 user = relationship('User', lazy='joined')
464 user = relationship('User', lazy='joined')
458
465
459 @validates('_email')
466 @validates('_email')
460 def validate_email(self, key, email):
467 def validate_email(self, key, email):
461 # check if this email is not main one
468 # check if this email is not main one
462 main_email = Session().query(User).filter(User.email == email).scalar()
469 main_email = Session().query(User).filter(User.email == email).scalar()
463 if main_email is not None:
470 if main_email is not None:
464 raise AttributeError('email %s is present is user table' % email)
471 raise AttributeError('email %s is present is user table' % email)
465 return email
472 return email
466
473
467 @hybrid_property
474 @hybrid_property
468 def email(self):
475 def email(self):
469 return self._email
476 return self._email
470
477
471 @email.setter
478 @email.setter
472 def email(self, val):
479 def email(self, val):
473 self._email = val.lower() if val else None
480 self._email = val.lower() if val else None
474
481
475
482
476 class UserLog(Base, BaseModel):
483 class UserLog(Base, BaseModel):
477 __tablename__ = 'user_logs'
484 __tablename__ = 'user_logs'
478 __table_args__ = (
485 __table_args__ = (
479 {'extend_existing': True, 'mysql_engine': 'InnoDB',
486 {'extend_existing': True, 'mysql_engine': 'InnoDB',
480 'mysql_charset': 'utf8'},
487 'mysql_charset': 'utf8'},
481 )
488 )
482 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
483 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
490 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
484 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
491 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
485 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
492 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
486 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
493 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
487 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
494 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
488 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
495 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
489
496
490 @property
497 @property
491 def action_as_day(self):
498 def action_as_day(self):
492 return datetime.date(*self.action_date.timetuple()[:3])
499 return datetime.date(*self.action_date.timetuple()[:3])
493
500
494 user = relationship('User')
501 user = relationship('User')
495 repository = relationship('Repository', cascade='')
502 repository = relationship('Repository', cascade='')
496
503
497
504
498 class UsersGroup(Base, BaseModel):
505 class UsersGroup(Base, BaseModel):
499 __tablename__ = 'users_groups'
506 __tablename__ = 'users_groups'
500 __table_args__ = (
507 __table_args__ = (
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
508 {'extend_existing': True, 'mysql_engine': 'InnoDB',
502 'mysql_charset': 'utf8'},
509 'mysql_charset': 'utf8'},
503 )
510 )
504
511
505 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
512 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
506 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
513 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
507 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
514 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
508 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
515 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
509
516
510 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
517 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
511 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
518 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
512 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
519 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
513
520
514 def __unicode__(self):
521 def __unicode__(self):
515 return u'<userGroup(%s)>' % (self.users_group_name)
522 return u'<userGroup(%s)>' % (self.users_group_name)
516
523
517 @classmethod
524 @classmethod
518 def get_by_group_name(cls, group_name, cache=False,
525 def get_by_group_name(cls, group_name, cache=False,
519 case_insensitive=False):
526 case_insensitive=False):
520 if case_insensitive:
527 if case_insensitive:
521 q = cls.query().filter(cls.users_group_name.ilike(group_name))
528 q = cls.query().filter(cls.users_group_name.ilike(group_name))
522 else:
529 else:
523 q = cls.query().filter(cls.users_group_name == group_name)
530 q = cls.query().filter(cls.users_group_name == group_name)
524 if cache:
531 if cache:
525 q = q.options(FromCache(
532 q = q.options(FromCache(
526 "sql_cache_short",
533 "sql_cache_short",
527 "get_user_%s" % _hash_key(group_name)
534 "get_user_%s" % _hash_key(group_name)
528 )
535 )
529 )
536 )
530 return q.scalar()
537 return q.scalar()
531
538
532 @classmethod
539 @classmethod
533 def get(cls, users_group_id, cache=False):
540 def get(cls, users_group_id, cache=False):
534 users_group = cls.query()
541 users_group = cls.query()
535 if cache:
542 if cache:
536 users_group = users_group.options(FromCache("sql_cache_short",
543 users_group = users_group.options(FromCache("sql_cache_short",
537 "get_users_group_%s" % users_group_id))
544 "get_users_group_%s" % users_group_id))
538 return users_group.get(users_group_id)
545 return users_group.get(users_group_id)
539
546
540 def get_api_data(self):
547 def get_api_data(self):
541 users_group = self
548 users_group = self
542
549
543 data = dict(
550 data = dict(
544 users_group_id=users_group.users_group_id,
551 users_group_id=users_group.users_group_id,
545 group_name=users_group.users_group_name,
552 group_name=users_group.users_group_name,
546 active=users_group.users_group_active,
553 active=users_group.users_group_active,
547 )
554 )
548
555
549 return data
556 return data
550
557
551
558
552 class UsersGroupMember(Base, BaseModel):
559 class UsersGroupMember(Base, BaseModel):
553 __tablename__ = 'users_groups_members'
560 __tablename__ = 'users_groups_members'
554 __table_args__ = (
561 __table_args__ = (
555 {'extend_existing': True, 'mysql_engine': 'InnoDB',
562 {'extend_existing': True, 'mysql_engine': 'InnoDB',
556 'mysql_charset': 'utf8'},
563 'mysql_charset': 'utf8'},
557 )
564 )
558
565
559 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
566 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
560 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
567 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
561 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
568 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
562
569
563 user = relationship('User', lazy='joined')
570 user = relationship('User', lazy='joined')
564 users_group = relationship('UsersGroup')
571 users_group = relationship('UsersGroup')
565
572
566 def __init__(self, gr_id='', u_id=''):
573 def __init__(self, gr_id='', u_id=''):
567 self.users_group_id = gr_id
574 self.users_group_id = gr_id
568 self.user_id = u_id
575 self.user_id = u_id
569
576
570
577
571 class Repository(Base, BaseModel):
578 class Repository(Base, BaseModel):
572 __tablename__ = 'repositories'
579 __tablename__ = 'repositories'
573 __table_args__ = (
580 __table_args__ = (
574 UniqueConstraint('repo_name'),
581 UniqueConstraint('repo_name'),
582 Index('r_repo_name_idx', 'repo_name'),
575 {'extend_existing': True, 'mysql_engine': 'InnoDB',
583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
576 'mysql_charset': 'utf8'},
584 'mysql_charset': 'utf8'},
577 )
585 )
578
586
579 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
587 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
580 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
588 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
581 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
589 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
582 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
590 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
583 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
591 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
584 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
592 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
585 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
593 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
586 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
594 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
587 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
595 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
588 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
596 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
589 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
597 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
598 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
599 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
590
600
591 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
601 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
592 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
602 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
593
603
594 user = relationship('User')
604 user = relationship('User')
595 fork = relationship('Repository', remote_side=repo_id)
605 fork = relationship('Repository', remote_side=repo_id)
596 group = relationship('RepoGroup')
606 group = relationship('RepoGroup')
597 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
607 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
598 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
608 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
599 stats = relationship('Statistics', cascade='all', uselist=False)
609 stats = relationship('Statistics', cascade='all', uselist=False)
600
610
601 followers = relationship('UserFollowing',
611 followers = relationship('UserFollowing',
602 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
612 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
603 cascade='all')
613 cascade='all')
604
614
605 logs = relationship('UserLog')
615 logs = relationship('UserLog')
606 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
616 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
607
617
608 pull_requests_org = relationship('PullRequest',
618 pull_requests_org = relationship('PullRequest',
609 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
619 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
610 cascade="all, delete, delete-orphan")
620 cascade="all, delete, delete-orphan")
611
621
612 pull_requests_other = relationship('PullRequest',
622 pull_requests_other = relationship('PullRequest',
613 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
623 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
614 cascade="all, delete, delete-orphan")
624 cascade="all, delete, delete-orphan")
615
625
616 def __unicode__(self):
626 def __unicode__(self):
617 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
627 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
618 self.repo_name)
628 self.repo_name)
619
629
630 @hybrid_property
631 def locked(self):
632 # always should return [user_id, timelocked]
633 if self._locked:
634 _lock_info = self._locked.split(':')
635 return int(_lock_info[0]), _lock_info[1]
636 return [None, None]
637
638 @locked.setter
639 def locked(self, val):
640 if val and isinstance(val, (list, tuple)):
641 self._locked = ':'.join(map(str, val))
642 else:
643 self._locked = None
644
620 @classmethod
645 @classmethod
621 def url_sep(cls):
646 def url_sep(cls):
622 return URL_SEP
647 return URL_SEP
623
648
624 @classmethod
649 @classmethod
625 def get_by_repo_name(cls, repo_name):
650 def get_by_repo_name(cls, repo_name):
626 q = Session().query(cls).filter(cls.repo_name == repo_name)
651 q = Session().query(cls).filter(cls.repo_name == repo_name)
627 q = q.options(joinedload(Repository.fork))\
652 q = q.options(joinedload(Repository.fork))\
628 .options(joinedload(Repository.user))\
653 .options(joinedload(Repository.user))\
629 .options(joinedload(Repository.group))
654 .options(joinedload(Repository.group))
630 return q.scalar()
655 return q.scalar()
631
656
632 @classmethod
657 @classmethod
633 def get_by_full_path(cls, repo_full_path):
658 def get_by_full_path(cls, repo_full_path):
634 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
659 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
635 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
660 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
636
661
637 @classmethod
662 @classmethod
638 def get_repo_forks(cls, repo_id):
663 def get_repo_forks(cls, repo_id):
639 return cls.query().filter(Repository.fork_id == repo_id)
664 return cls.query().filter(Repository.fork_id == repo_id)
640
665
641 @classmethod
666 @classmethod
642 def base_path(cls):
667 def base_path(cls):
643 """
668 """
644 Returns base path when all repos are stored
669 Returns base path when all repos are stored
645
670
646 :param cls:
671 :param cls:
647 """
672 """
648 q = Session().query(RhodeCodeUi)\
673 q = Session().query(RhodeCodeUi)\
649 .filter(RhodeCodeUi.ui_key == cls.url_sep())
674 .filter(RhodeCodeUi.ui_key == cls.url_sep())
650 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
675 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
651 return q.one().ui_value
676 return q.one().ui_value
652
677
653 @property
678 @property
654 def forks(self):
679 def forks(self):
655 """
680 """
656 Return forks of this repo
681 Return forks of this repo
657 """
682 """
658 return Repository.get_repo_forks(self.repo_id)
683 return Repository.get_repo_forks(self.repo_id)
659
684
660 @property
685 @property
661 def parent(self):
686 def parent(self):
662 """
687 """
663 Returns fork parent
688 Returns fork parent
664 """
689 """
665 return self.fork
690 return self.fork
666
691
667 @property
692 @property
668 def just_name(self):
693 def just_name(self):
669 return self.repo_name.split(Repository.url_sep())[-1]
694 return self.repo_name.split(Repository.url_sep())[-1]
670
695
671 @property
696 @property
672 def groups_with_parents(self):
697 def groups_with_parents(self):
673 groups = []
698 groups = []
674 if self.group is None:
699 if self.group is None:
675 return groups
700 return groups
676
701
677 cur_gr = self.group
702 cur_gr = self.group
678 groups.insert(0, cur_gr)
703 groups.insert(0, cur_gr)
679 while 1:
704 while 1:
680 gr = getattr(cur_gr, 'parent_group', None)
705 gr = getattr(cur_gr, 'parent_group', None)
681 cur_gr = cur_gr.parent_group
706 cur_gr = cur_gr.parent_group
682 if gr is None:
707 if gr is None:
683 break
708 break
684 groups.insert(0, gr)
709 groups.insert(0, gr)
685
710
686 return groups
711 return groups
687
712
688 @property
713 @property
689 def groups_and_repo(self):
714 def groups_and_repo(self):
690 return self.groups_with_parents, self.just_name
715 return self.groups_with_parents, self.just_name
691
716
692 @LazyProperty
717 @LazyProperty
693 def repo_path(self):
718 def repo_path(self):
694 """
719 """
695 Returns base full path for that repository means where it actually
720 Returns base full path for that repository means where it actually
696 exists on a filesystem
721 exists on a filesystem
697 """
722 """
698 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
723 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
699 Repository.url_sep())
724 Repository.url_sep())
700 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
725 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
701 return q.one().ui_value
726 return q.one().ui_value
702
727
703 @property
728 @property
704 def repo_full_path(self):
729 def repo_full_path(self):
705 p = [self.repo_path]
730 p = [self.repo_path]
706 # we need to split the name by / since this is how we store the
731 # we need to split the name by / since this is how we store the
707 # names in the database, but that eventually needs to be converted
732 # names in the database, but that eventually needs to be converted
708 # into a valid system path
733 # into a valid system path
709 p += self.repo_name.split(Repository.url_sep())
734 p += self.repo_name.split(Repository.url_sep())
710 return os.path.join(*p)
735 return os.path.join(*p)
711
736
712 def get_new_name(self, repo_name):
737 def get_new_name(self, repo_name):
713 """
738 """
714 returns new full repository name based on assigned group and new new
739 returns new full repository name based on assigned group and new new
715
740
716 :param group_name:
741 :param group_name:
717 """
742 """
718 path_prefix = self.group.full_path_splitted if self.group else []
743 path_prefix = self.group.full_path_splitted if self.group else []
719 return Repository.url_sep().join(path_prefix + [repo_name])
744 return Repository.url_sep().join(path_prefix + [repo_name])
720
745
721 @property
746 @property
722 def _ui(self):
747 def _ui(self):
723 """
748 """
724 Creates an db based ui object for this repository
749 Creates an db based ui object for this repository
725 """
750 """
726 from mercurial import ui
751 from mercurial import ui
727 from mercurial import config
752 from mercurial import config
728 baseui = ui.ui()
753 baseui = ui.ui()
729
754
730 #clean the baseui object
755 #clean the baseui object
731 baseui._ocfg = config.config()
756 baseui._ocfg = config.config()
732 baseui._ucfg = config.config()
757 baseui._ucfg = config.config()
733 baseui._tcfg = config.config()
758 baseui._tcfg = config.config()
734
759
735 ret = RhodeCodeUi.query()\
760 ret = RhodeCodeUi.query()\
736 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
761 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
737
762
738 hg_ui = ret
763 hg_ui = ret
739 for ui_ in hg_ui:
764 for ui_ in hg_ui:
740 if ui_.ui_active:
765 if ui_.ui_active:
741 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
766 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
742 ui_.ui_key, ui_.ui_value)
767 ui_.ui_key, ui_.ui_value)
743 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
768 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
744 if ui_.ui_key == 'push_ssl':
769 if ui_.ui_key == 'push_ssl':
745 # force set push_ssl requirement to False, rhodecode
770 # force set push_ssl requirement to False, rhodecode
746 # handles that
771 # handles that
747 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
772 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
748
773
749 return baseui
774 return baseui
750
775
751 @classmethod
776 @classmethod
752 def inject_ui(cls, repo, extras={}):
777 def inject_ui(cls, repo, extras={}):
753 from rhodecode.lib.vcs.backends.hg import MercurialRepository
778 from rhodecode.lib.vcs.backends.hg import MercurialRepository
754 from rhodecode.lib.vcs.backends.git import GitRepository
779 from rhodecode.lib.vcs.backends.git import GitRepository
755 required = (MercurialRepository, GitRepository)
780 required = (MercurialRepository, GitRepository)
756 if not isinstance(repo, required):
781 if not isinstance(repo, required):
757 raise Exception('repo must be instance of %s' % required)
782 raise Exception('repo must be instance of %s' % required)
758
783
759 # inject ui extra param to log this action via push logger
784 # inject ui extra param to log this action via push logger
760 for k, v in extras.items():
785 for k, v in extras.items():
761 repo._repo.ui.setconfig('rhodecode_extras', k, v)
786 repo._repo.ui.setconfig('rhodecode_extras', k, v)
762
787
763 @classmethod
788 @classmethod
764 def is_valid(cls, repo_name):
789 def is_valid(cls, repo_name):
765 """
790 """
766 returns True if given repo name is a valid filesystem repository
791 returns True if given repo name is a valid filesystem repository
767
792
768 :param cls:
793 :param cls:
769 :param repo_name:
794 :param repo_name:
770 """
795 """
771 from rhodecode.lib.utils import is_valid_repo
796 from rhodecode.lib.utils import is_valid_repo
772
797
773 return is_valid_repo(repo_name, cls.base_path())
798 return is_valid_repo(repo_name, cls.base_path())
774
799
775 def get_api_data(self):
800 def get_api_data(self):
776 """
801 """
777 Common function for generating repo api data
802 Common function for generating repo api data
778
803
779 """
804 """
780 repo = self
805 repo = self
781 data = dict(
806 data = dict(
782 repo_id=repo.repo_id,
807 repo_id=repo.repo_id,
783 repo_name=repo.repo_name,
808 repo_name=repo.repo_name,
784 repo_type=repo.repo_type,
809 repo_type=repo.repo_type,
785 clone_uri=repo.clone_uri,
810 clone_uri=repo.clone_uri,
786 private=repo.private,
811 private=repo.private,
787 created_on=repo.created_on,
812 created_on=repo.created_on,
788 description=repo.description,
813 description=repo.description,
789 landing_rev=repo.landing_rev,
814 landing_rev=repo.landing_rev,
790 owner=repo.user.username,
815 owner=repo.user.username,
791 fork_of=repo.fork.repo_name if repo.fork else None
816 fork_of=repo.fork.repo_name if repo.fork else None
792 )
817 )
793
818
794 return data
819 return data
795
820
821 @classmethod
822 def lock(cls, repo, user_id):
823 repo.locked = [user_id, time.time()]
824 Session().add(repo)
825 Session().commit()
826
827 @classmethod
828 def unlock(cls, repo):
829 repo.locked = None
830 Session().add(repo)
831 Session().commit()
832
796 #==========================================================================
833 #==========================================================================
797 # SCM PROPERTIES
834 # SCM PROPERTIES
798 #==========================================================================
835 #==========================================================================
799
836
800 def get_changeset(self, rev=None):
837 def get_changeset(self, rev=None):
801 return get_changeset_safe(self.scm_instance, rev)
838 return get_changeset_safe(self.scm_instance, rev)
802
839
803 def get_landing_changeset(self):
840 def get_landing_changeset(self):
804 """
841 """
805 Returns landing changeset, or if that doesn't exist returns the tip
842 Returns landing changeset, or if that doesn't exist returns the tip
806 """
843 """
807 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
844 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
808 return cs
845 return cs
809
846
810 @property
847 @property
811 def tip(self):
848 def tip(self):
812 return self.get_changeset('tip')
849 return self.get_changeset('tip')
813
850
814 @property
851 @property
815 def author(self):
852 def author(self):
816 return self.tip.author
853 return self.tip.author
817
854
818 @property
855 @property
819 def last_change(self):
856 def last_change(self):
820 return self.scm_instance.last_change
857 return self.scm_instance.last_change
821
858
822 def get_comments(self, revisions=None):
859 def get_comments(self, revisions=None):
823 """
860 """
824 Returns comments for this repository grouped by revisions
861 Returns comments for this repository grouped by revisions
825
862
826 :param revisions: filter query by revisions only
863 :param revisions: filter query by revisions only
827 """
864 """
828 cmts = ChangesetComment.query()\
865 cmts = ChangesetComment.query()\
829 .filter(ChangesetComment.repo == self)
866 .filter(ChangesetComment.repo == self)
830 if revisions:
867 if revisions:
831 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
868 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
832 grouped = defaultdict(list)
869 grouped = defaultdict(list)
833 for cmt in cmts.all():
870 for cmt in cmts.all():
834 grouped[cmt.revision].append(cmt)
871 grouped[cmt.revision].append(cmt)
835 return grouped
872 return grouped
836
873
837 def statuses(self, revisions=None):
874 def statuses(self, revisions=None):
838 """
875 """
839 Returns statuses for this repository
876 Returns statuses for this repository
840
877
841 :param revisions: list of revisions to get statuses for
878 :param revisions: list of revisions to get statuses for
842 :type revisions: list
879 :type revisions: list
843 """
880 """
844
881
845 statuses = ChangesetStatus.query()\
882 statuses = ChangesetStatus.query()\
846 .filter(ChangesetStatus.repo == self)\
883 .filter(ChangesetStatus.repo == self)\
847 .filter(ChangesetStatus.version == 0)
884 .filter(ChangesetStatus.version == 0)
848 if revisions:
885 if revisions:
849 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
886 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
850 grouped = {}
887 grouped = {}
851
888
852 #maybe we have open new pullrequest without a status ?
889 #maybe we have open new pullrequest without a status ?
853 stat = ChangesetStatus.STATUS_UNDER_REVIEW
890 stat = ChangesetStatus.STATUS_UNDER_REVIEW
854 status_lbl = ChangesetStatus.get_status_lbl(stat)
891 status_lbl = ChangesetStatus.get_status_lbl(stat)
855 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
892 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
856 for rev in pr.revisions:
893 for rev in pr.revisions:
857 pr_id = pr.pull_request_id
894 pr_id = pr.pull_request_id
858 pr_repo = pr.other_repo.repo_name
895 pr_repo = pr.other_repo.repo_name
859 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
896 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
860
897
861 for stat in statuses.all():
898 for stat in statuses.all():
862 pr_id = pr_repo = None
899 pr_id = pr_repo = None
863 if stat.pull_request:
900 if stat.pull_request:
864 pr_id = stat.pull_request.pull_request_id
901 pr_id = stat.pull_request.pull_request_id
865 pr_repo = stat.pull_request.other_repo.repo_name
902 pr_repo = stat.pull_request.other_repo.repo_name
866 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
903 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
867 pr_id, pr_repo]
904 pr_id, pr_repo]
868 return grouped
905 return grouped
869
906
870 #==========================================================================
907 #==========================================================================
871 # SCM CACHE INSTANCE
908 # SCM CACHE INSTANCE
872 #==========================================================================
909 #==========================================================================
873
910
874 @property
911 @property
875 def invalidate(self):
912 def invalidate(self):
876 return CacheInvalidation.invalidate(self.repo_name)
913 return CacheInvalidation.invalidate(self.repo_name)
877
914
878 def set_invalidate(self):
915 def set_invalidate(self):
879 """
916 """
880 set a cache for invalidation for this instance
917 set a cache for invalidation for this instance
881 """
918 """
882 CacheInvalidation.set_invalidate(self.repo_name)
919 CacheInvalidation.set_invalidate(self.repo_name)
883
920
884 @LazyProperty
921 @LazyProperty
885 def scm_instance(self):
922 def scm_instance(self):
886 return self.__get_instance()
923 return self.__get_instance()
887
924
888 def scm_instance_cached(self, cache_map=None):
925 def scm_instance_cached(self, cache_map=None):
889 @cache_region('long_term')
926 @cache_region('long_term')
890 def _c(repo_name):
927 def _c(repo_name):
891 return self.__get_instance()
928 return self.__get_instance()
892 rn = self.repo_name
929 rn = self.repo_name
893 log.debug('Getting cached instance of repo')
930 log.debug('Getting cached instance of repo')
894
931
895 if cache_map:
932 if cache_map:
896 # get using prefilled cache_map
933 # get using prefilled cache_map
897 invalidate_repo = cache_map[self.repo_name]
934 invalidate_repo = cache_map[self.repo_name]
898 if invalidate_repo:
935 if invalidate_repo:
899 invalidate_repo = (None if invalidate_repo.cache_active
936 invalidate_repo = (None if invalidate_repo.cache_active
900 else invalidate_repo)
937 else invalidate_repo)
901 else:
938 else:
902 # get from invalidate
939 # get from invalidate
903 invalidate_repo = self.invalidate
940 invalidate_repo = self.invalidate
904
941
905 if invalidate_repo is not None:
942 if invalidate_repo is not None:
906 region_invalidate(_c, None, rn)
943 region_invalidate(_c, None, rn)
907 # update our cache
944 # update our cache
908 CacheInvalidation.set_valid(invalidate_repo.cache_key)
945 CacheInvalidation.set_valid(invalidate_repo.cache_key)
909 return _c(rn)
946 return _c(rn)
910
947
911 def __get_instance(self):
948 def __get_instance(self):
912 repo_full_path = self.repo_full_path
949 repo_full_path = self.repo_full_path
913 try:
950 try:
914 alias = get_scm(repo_full_path)[0]
951 alias = get_scm(repo_full_path)[0]
915 log.debug('Creating instance of %s repository' % alias)
952 log.debug('Creating instance of %s repository' % alias)
916 backend = get_backend(alias)
953 backend = get_backend(alias)
917 except VCSError:
954 except VCSError:
918 log.error(traceback.format_exc())
955 log.error(traceback.format_exc())
919 log.error('Perhaps this repository is in db and not in '
956 log.error('Perhaps this repository is in db and not in '
920 'filesystem run rescan repositories with '
957 'filesystem run rescan repositories with '
921 '"destroy old data " option from admin panel')
958 '"destroy old data " option from admin panel')
922 return
959 return
923
960
924 if alias == 'hg':
961 if alias == 'hg':
925
962
926 repo = backend(safe_str(repo_full_path), create=False,
963 repo = backend(safe_str(repo_full_path), create=False,
927 baseui=self._ui)
964 baseui=self._ui)
928 # skip hidden web repository
965 # skip hidden web repository
929 if repo._get_hidden():
966 if repo._get_hidden():
930 return
967 return
931 else:
968 else:
932 repo = backend(repo_full_path, create=False)
969 repo = backend(repo_full_path, create=False)
933
970
934 return repo
971 return repo
935
972
936
973
937 class RepoGroup(Base, BaseModel):
974 class RepoGroup(Base, BaseModel):
938 __tablename__ = 'groups'
975 __tablename__ = 'groups'
939 __table_args__ = (
976 __table_args__ = (
940 UniqueConstraint('group_name', 'group_parent_id'),
977 UniqueConstraint('group_name', 'group_parent_id'),
941 CheckConstraint('group_id != group_parent_id'),
978 CheckConstraint('group_id != group_parent_id'),
942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
943 'mysql_charset': 'utf8'},
980 'mysql_charset': 'utf8'},
944 )
981 )
945 __mapper_args__ = {'order_by': 'group_name'}
982 __mapper_args__ = {'order_by': 'group_name'}
946
983
947 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
984 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
948 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
985 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
949 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
986 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
950 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
987 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
951
988
952 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
989 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
953 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
990 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
954
991
955 parent_group = relationship('RepoGroup', remote_side=group_id)
992 parent_group = relationship('RepoGroup', remote_side=group_id)
956
993
957 def __init__(self, group_name='', parent_group=None):
994 def __init__(self, group_name='', parent_group=None):
958 self.group_name = group_name
995 self.group_name = group_name
959 self.parent_group = parent_group
996 self.parent_group = parent_group
960
997
961 def __unicode__(self):
998 def __unicode__(self):
962 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
999 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
963 self.group_name)
1000 self.group_name)
964
1001
965 @classmethod
1002 @classmethod
966 def groups_choices(cls):
1003 def groups_choices(cls):
967 from webhelpers.html import literal as _literal
1004 from webhelpers.html import literal as _literal
968 repo_groups = [('', '')]
1005 repo_groups = [('', '')]
969 sep = ' &raquo; '
1006 sep = ' &raquo; '
970 _name = lambda k: _literal(sep.join(k))
1007 _name = lambda k: _literal(sep.join(k))
971
1008
972 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1009 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
973 for x in cls.query().all()])
1010 for x in cls.query().all()])
974
1011
975 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1012 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
976 return repo_groups
1013 return repo_groups
977
1014
978 @classmethod
1015 @classmethod
979 def url_sep(cls):
1016 def url_sep(cls):
980 return URL_SEP
1017 return URL_SEP
981
1018
982 @classmethod
1019 @classmethod
983 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1020 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
984 if case_insensitive:
1021 if case_insensitive:
985 gr = cls.query()\
1022 gr = cls.query()\
986 .filter(cls.group_name.ilike(group_name))
1023 .filter(cls.group_name.ilike(group_name))
987 else:
1024 else:
988 gr = cls.query()\
1025 gr = cls.query()\
989 .filter(cls.group_name == group_name)
1026 .filter(cls.group_name == group_name)
990 if cache:
1027 if cache:
991 gr = gr.options(FromCache(
1028 gr = gr.options(FromCache(
992 "sql_cache_short",
1029 "sql_cache_short",
993 "get_group_%s" % _hash_key(group_name)
1030 "get_group_%s" % _hash_key(group_name)
994 )
1031 )
995 )
1032 )
996 return gr.scalar()
1033 return gr.scalar()
997
1034
998 @property
1035 @property
999 def parents(self):
1036 def parents(self):
1000 parents_recursion_limit = 5
1037 parents_recursion_limit = 5
1001 groups = []
1038 groups = []
1002 if self.parent_group is None:
1039 if self.parent_group is None:
1003 return groups
1040 return groups
1004 cur_gr = self.parent_group
1041 cur_gr = self.parent_group
1005 groups.insert(0, cur_gr)
1042 groups.insert(0, cur_gr)
1006 cnt = 0
1043 cnt = 0
1007 while 1:
1044 while 1:
1008 cnt += 1
1045 cnt += 1
1009 gr = getattr(cur_gr, 'parent_group', None)
1046 gr = getattr(cur_gr, 'parent_group', None)
1010 cur_gr = cur_gr.parent_group
1047 cur_gr = cur_gr.parent_group
1011 if gr is None:
1048 if gr is None:
1012 break
1049 break
1013 if cnt == parents_recursion_limit:
1050 if cnt == parents_recursion_limit:
1014 # this will prevent accidental infinit loops
1051 # this will prevent accidental infinit loops
1015 log.error('group nested more than %s' %
1052 log.error('group nested more than %s' %
1016 parents_recursion_limit)
1053 parents_recursion_limit)
1017 break
1054 break
1018
1055
1019 groups.insert(0, gr)
1056 groups.insert(0, gr)
1020 return groups
1057 return groups
1021
1058
1022 @property
1059 @property
1023 def children(self):
1060 def children(self):
1024 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1061 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1025
1062
1026 @property
1063 @property
1027 def name(self):
1064 def name(self):
1028 return self.group_name.split(RepoGroup.url_sep())[-1]
1065 return self.group_name.split(RepoGroup.url_sep())[-1]
1029
1066
1030 @property
1067 @property
1031 def full_path(self):
1068 def full_path(self):
1032 return self.group_name
1069 return self.group_name
1033
1070
1034 @property
1071 @property
1035 def full_path_splitted(self):
1072 def full_path_splitted(self):
1036 return self.group_name.split(RepoGroup.url_sep())
1073 return self.group_name.split(RepoGroup.url_sep())
1037
1074
1038 @property
1075 @property
1039 def repositories(self):
1076 def repositories(self):
1040 return Repository.query()\
1077 return Repository.query()\
1041 .filter(Repository.group == self)\
1078 .filter(Repository.group == self)\
1042 .order_by(Repository.repo_name)
1079 .order_by(Repository.repo_name)
1043
1080
1044 @property
1081 @property
1045 def repositories_recursive_count(self):
1082 def repositories_recursive_count(self):
1046 cnt = self.repositories.count()
1083 cnt = self.repositories.count()
1047
1084
1048 def children_count(group):
1085 def children_count(group):
1049 cnt = 0
1086 cnt = 0
1050 for child in group.children:
1087 for child in group.children:
1051 cnt += child.repositories.count()
1088 cnt += child.repositories.count()
1052 cnt += children_count(child)
1089 cnt += children_count(child)
1053 return cnt
1090 return cnt
1054
1091
1055 return cnt + children_count(self)
1092 return cnt + children_count(self)
1056
1093
1057 def get_new_name(self, group_name):
1094 def get_new_name(self, group_name):
1058 """
1095 """
1059 returns new full group name based on parent and new name
1096 returns new full group name based on parent and new name
1060
1097
1061 :param group_name:
1098 :param group_name:
1062 """
1099 """
1063 path_prefix = (self.parent_group.full_path_splitted if
1100 path_prefix = (self.parent_group.full_path_splitted if
1064 self.parent_group else [])
1101 self.parent_group else [])
1065 return RepoGroup.url_sep().join(path_prefix + [group_name])
1102 return RepoGroup.url_sep().join(path_prefix + [group_name])
1066
1103
1067
1104
1068 class Permission(Base, BaseModel):
1105 class Permission(Base, BaseModel):
1069 __tablename__ = 'permissions'
1106 __tablename__ = 'permissions'
1070 __table_args__ = (
1107 __table_args__ = (
1071 Index('p_perm_name_idx', 'permission_name'),
1108 Index('p_perm_name_idx', 'permission_name'),
1072 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1109 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1073 'mysql_charset': 'utf8'},
1110 'mysql_charset': 'utf8'},
1074 )
1111 )
1075 PERMS = [
1112 PERMS = [
1076 ('repository.none', _('Repository no access')),
1113 ('repository.none', _('Repository no access')),
1077 ('repository.read', _('Repository read access')),
1114 ('repository.read', _('Repository read access')),
1078 ('repository.write', _('Repository write access')),
1115 ('repository.write', _('Repository write access')),
1079 ('repository.admin', _('Repository admin access')),
1116 ('repository.admin', _('Repository admin access')),
1080
1117
1081 ('group.none', _('Repositories Group no access')),
1118 ('group.none', _('Repositories Group no access')),
1082 ('group.read', _('Repositories Group read access')),
1119 ('group.read', _('Repositories Group read access')),
1083 ('group.write', _('Repositories Group write access')),
1120 ('group.write', _('Repositories Group write access')),
1084 ('group.admin', _('Repositories Group admin access')),
1121 ('group.admin', _('Repositories Group admin access')),
1085
1122
1086 ('hg.admin', _('RhodeCode Administrator')),
1123 ('hg.admin', _('RhodeCode Administrator')),
1087 ('hg.create.none', _('Repository creation disabled')),
1124 ('hg.create.none', _('Repository creation disabled')),
1088 ('hg.create.repository', _('Repository creation enabled')),
1125 ('hg.create.repository', _('Repository creation enabled')),
1089 ('hg.fork.none', _('Repository forking disabled')),
1126 ('hg.fork.none', _('Repository forking disabled')),
1090 ('hg.fork.repository', _('Repository forking enabled')),
1127 ('hg.fork.repository', _('Repository forking enabled')),
1091 ('hg.register.none', _('Register disabled')),
1128 ('hg.register.none', _('Register disabled')),
1092 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1129 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1093 'with manual activation')),
1130 'with manual activation')),
1094
1131
1095 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1132 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1096 'with auto activation')),
1133 'with auto activation')),
1097 ]
1134 ]
1098
1135
1099 # defines which permissions are more important higher the more important
1136 # defines which permissions are more important higher the more important
1100 PERM_WEIGHTS = {
1137 PERM_WEIGHTS = {
1101 'repository.none': 0,
1138 'repository.none': 0,
1102 'repository.read': 1,
1139 'repository.read': 1,
1103 'repository.write': 3,
1140 'repository.write': 3,
1104 'repository.admin': 4,
1141 'repository.admin': 4,
1105
1142
1106 'group.none': 0,
1143 'group.none': 0,
1107 'group.read': 1,
1144 'group.read': 1,
1108 'group.write': 3,
1145 'group.write': 3,
1109 'group.admin': 4,
1146 'group.admin': 4,
1110
1147
1111 'hg.fork.none': 0,
1148 'hg.fork.none': 0,
1112 'hg.fork.repository': 1,
1149 'hg.fork.repository': 1,
1113 'hg.create.none': 0,
1150 'hg.create.none': 0,
1114 'hg.create.repository':1
1151 'hg.create.repository':1
1115 }
1152 }
1116
1153
1117 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1154 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1118 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1155 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1119 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1156 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1120
1157
1121 def __unicode__(self):
1158 def __unicode__(self):
1122 return u"<%s('%s:%s')>" % (
1159 return u"<%s('%s:%s')>" % (
1123 self.__class__.__name__, self.permission_id, self.permission_name
1160 self.__class__.__name__, self.permission_id, self.permission_name
1124 )
1161 )
1125
1162
1126 @classmethod
1163 @classmethod
1127 def get_by_key(cls, key):
1164 def get_by_key(cls, key):
1128 return cls.query().filter(cls.permission_name == key).scalar()
1165 return cls.query().filter(cls.permission_name == key).scalar()
1129
1166
1130 @classmethod
1167 @classmethod
1131 def get_default_perms(cls, default_user_id):
1168 def get_default_perms(cls, default_user_id):
1132 q = Session().query(UserRepoToPerm, Repository, cls)\
1169 q = Session().query(UserRepoToPerm, Repository, cls)\
1133 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1170 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1134 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1171 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1135 .filter(UserRepoToPerm.user_id == default_user_id)
1172 .filter(UserRepoToPerm.user_id == default_user_id)
1136
1173
1137 return q.all()
1174 return q.all()
1138
1175
1139 @classmethod
1176 @classmethod
1140 def get_default_group_perms(cls, default_user_id):
1177 def get_default_group_perms(cls, default_user_id):
1141 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1178 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1142 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1179 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1143 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1180 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1144 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1181 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1145
1182
1146 return q.all()
1183 return q.all()
1147
1184
1148
1185
1149 class UserRepoToPerm(Base, BaseModel):
1186 class UserRepoToPerm(Base, BaseModel):
1150 __tablename__ = 'repo_to_perm'
1187 __tablename__ = 'repo_to_perm'
1151 __table_args__ = (
1188 __table_args__ = (
1152 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1189 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1153 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1190 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1154 'mysql_charset': 'utf8'}
1191 'mysql_charset': 'utf8'}
1155 )
1192 )
1156 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1193 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1157 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1194 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1158 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1195 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1159 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1196 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1160
1197
1161 user = relationship('User')
1198 user = relationship('User')
1162 repository = relationship('Repository')
1199 repository = relationship('Repository')
1163 permission = relationship('Permission')
1200 permission = relationship('Permission')
1164
1201
1165 @classmethod
1202 @classmethod
1166 def create(cls, user, repository, permission):
1203 def create(cls, user, repository, permission):
1167 n = cls()
1204 n = cls()
1168 n.user = user
1205 n.user = user
1169 n.repository = repository
1206 n.repository = repository
1170 n.permission = permission
1207 n.permission = permission
1171 Session().add(n)
1208 Session().add(n)
1172 return n
1209 return n
1173
1210
1174 def __unicode__(self):
1211 def __unicode__(self):
1175 return u'<user:%s => %s >' % (self.user, self.repository)
1212 return u'<user:%s => %s >' % (self.user, self.repository)
1176
1213
1177
1214
1178 class UserToPerm(Base, BaseModel):
1215 class UserToPerm(Base, BaseModel):
1179 __tablename__ = 'user_to_perm'
1216 __tablename__ = 'user_to_perm'
1180 __table_args__ = (
1217 __table_args__ = (
1181 UniqueConstraint('user_id', 'permission_id'),
1218 UniqueConstraint('user_id', 'permission_id'),
1182 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1219 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1183 'mysql_charset': 'utf8'}
1220 'mysql_charset': 'utf8'}
1184 )
1221 )
1185 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1222 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1186 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1223 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1187 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1224 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1188
1225
1189 user = relationship('User')
1226 user = relationship('User')
1190 permission = relationship('Permission', lazy='joined')
1227 permission = relationship('Permission', lazy='joined')
1191
1228
1192
1229
1193 class UsersGroupRepoToPerm(Base, BaseModel):
1230 class UsersGroupRepoToPerm(Base, BaseModel):
1194 __tablename__ = 'users_group_repo_to_perm'
1231 __tablename__ = 'users_group_repo_to_perm'
1195 __table_args__ = (
1232 __table_args__ = (
1196 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1233 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1197 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1234 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1198 'mysql_charset': 'utf8'}
1235 'mysql_charset': 'utf8'}
1199 )
1236 )
1200 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1237 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1201 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1238 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1202 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1239 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1203 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1240 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1204
1241
1205 users_group = relationship('UsersGroup')
1242 users_group = relationship('UsersGroup')
1206 permission = relationship('Permission')
1243 permission = relationship('Permission')
1207 repository = relationship('Repository')
1244 repository = relationship('Repository')
1208
1245
1209 @classmethod
1246 @classmethod
1210 def create(cls, users_group, repository, permission):
1247 def create(cls, users_group, repository, permission):
1211 n = cls()
1248 n = cls()
1212 n.users_group = users_group
1249 n.users_group = users_group
1213 n.repository = repository
1250 n.repository = repository
1214 n.permission = permission
1251 n.permission = permission
1215 Session().add(n)
1252 Session().add(n)
1216 return n
1253 return n
1217
1254
1218 def __unicode__(self):
1255 def __unicode__(self):
1219 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1256 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1220
1257
1221
1258
1222 class UsersGroupToPerm(Base, BaseModel):
1259 class UsersGroupToPerm(Base, BaseModel):
1223 __tablename__ = 'users_group_to_perm'
1260 __tablename__ = 'users_group_to_perm'
1224 __table_args__ = (
1261 __table_args__ = (
1225 UniqueConstraint('users_group_id', 'permission_id',),
1262 UniqueConstraint('users_group_id', 'permission_id',),
1226 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1227 'mysql_charset': 'utf8'}
1264 'mysql_charset': 'utf8'}
1228 )
1265 )
1229 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1266 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1230 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1267 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1231 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1268 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1232
1269
1233 users_group = relationship('UsersGroup')
1270 users_group = relationship('UsersGroup')
1234 permission = relationship('Permission')
1271 permission = relationship('Permission')
1235
1272
1236
1273
1237 class UserRepoGroupToPerm(Base, BaseModel):
1274 class UserRepoGroupToPerm(Base, BaseModel):
1238 __tablename__ = 'user_repo_group_to_perm'
1275 __tablename__ = 'user_repo_group_to_perm'
1239 __table_args__ = (
1276 __table_args__ = (
1240 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1277 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1241 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1278 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1242 'mysql_charset': 'utf8'}
1279 'mysql_charset': 'utf8'}
1243 )
1280 )
1244
1281
1245 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1282 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1246 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1283 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1247 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1284 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1248 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1285 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1249
1286
1250 user = relationship('User')
1287 user = relationship('User')
1251 group = relationship('RepoGroup')
1288 group = relationship('RepoGroup')
1252 permission = relationship('Permission')
1289 permission = relationship('Permission')
1253
1290
1254
1291
1255 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1292 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1256 __tablename__ = 'users_group_repo_group_to_perm'
1293 __tablename__ = 'users_group_repo_group_to_perm'
1257 __table_args__ = (
1294 __table_args__ = (
1258 UniqueConstraint('users_group_id', 'group_id'),
1295 UniqueConstraint('users_group_id', 'group_id'),
1259 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1296 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1260 'mysql_charset': 'utf8'}
1297 'mysql_charset': 'utf8'}
1261 )
1298 )
1262
1299
1263 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1300 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1264 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1301 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1265 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1302 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1266 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1303 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1267
1304
1268 users_group = relationship('UsersGroup')
1305 users_group = relationship('UsersGroup')
1269 permission = relationship('Permission')
1306 permission = relationship('Permission')
1270 group = relationship('RepoGroup')
1307 group = relationship('RepoGroup')
1271
1308
1272
1309
1273 class Statistics(Base, BaseModel):
1310 class Statistics(Base, BaseModel):
1274 __tablename__ = 'statistics'
1311 __tablename__ = 'statistics'
1275 __table_args__ = (
1312 __table_args__ = (
1276 UniqueConstraint('repository_id'),
1313 UniqueConstraint('repository_id'),
1277 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1278 'mysql_charset': 'utf8'}
1315 'mysql_charset': 'utf8'}
1279 )
1316 )
1280 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1317 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1281 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1318 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1282 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1319 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1283 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1320 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1284 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1321 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1285 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1322 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1286
1323
1287 repository = relationship('Repository', single_parent=True)
1324 repository = relationship('Repository', single_parent=True)
1288
1325
1289
1326
1290 class UserFollowing(Base, BaseModel):
1327 class UserFollowing(Base, BaseModel):
1291 __tablename__ = 'user_followings'
1328 __tablename__ = 'user_followings'
1292 __table_args__ = (
1329 __table_args__ = (
1293 UniqueConstraint('user_id', 'follows_repository_id'),
1330 UniqueConstraint('user_id', 'follows_repository_id'),
1294 UniqueConstraint('user_id', 'follows_user_id'),
1331 UniqueConstraint('user_id', 'follows_user_id'),
1295 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1332 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1296 'mysql_charset': 'utf8'}
1333 'mysql_charset': 'utf8'}
1297 )
1334 )
1298
1335
1299 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1336 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1300 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1337 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1301 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1338 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1302 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1339 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1303 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1340 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1304
1341
1305 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1342 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1306
1343
1307 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1344 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1308 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1345 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1309
1346
1310 @classmethod
1347 @classmethod
1311 def get_repo_followers(cls, repo_id):
1348 def get_repo_followers(cls, repo_id):
1312 return cls.query().filter(cls.follows_repo_id == repo_id)
1349 return cls.query().filter(cls.follows_repo_id == repo_id)
1313
1350
1314
1351
1315 class CacheInvalidation(Base, BaseModel):
1352 class CacheInvalidation(Base, BaseModel):
1316 __tablename__ = 'cache_invalidation'
1353 __tablename__ = 'cache_invalidation'
1317 __table_args__ = (
1354 __table_args__ = (
1318 UniqueConstraint('cache_key'),
1355 UniqueConstraint('cache_key'),
1319 Index('key_idx', 'cache_key'),
1356 Index('key_idx', 'cache_key'),
1320 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1357 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1321 'mysql_charset': 'utf8'},
1358 'mysql_charset': 'utf8'},
1322 )
1359 )
1323 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1360 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1324 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1361 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1325 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1362 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1326 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1363 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1327
1364
1328 def __init__(self, cache_key, cache_args=''):
1365 def __init__(self, cache_key, cache_args=''):
1329 self.cache_key = cache_key
1366 self.cache_key = cache_key
1330 self.cache_args = cache_args
1367 self.cache_args = cache_args
1331 self.cache_active = False
1368 self.cache_active = False
1332
1369
1333 def __unicode__(self):
1370 def __unicode__(self):
1334 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1371 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1335 self.cache_id, self.cache_key)
1372 self.cache_id, self.cache_key)
1336
1373
1337 @classmethod
1374 @classmethod
1338 def clear_cache(cls):
1375 def clear_cache(cls):
1339 cls.query().delete()
1376 cls.query().delete()
1340
1377
1341 @classmethod
1378 @classmethod
1342 def _get_key(cls, key):
1379 def _get_key(cls, key):
1343 """
1380 """
1344 Wrapper for generating a key, together with a prefix
1381 Wrapper for generating a key, together with a prefix
1345
1382
1346 :param key:
1383 :param key:
1347 """
1384 """
1348 import rhodecode
1385 import rhodecode
1349 prefix = ''
1386 prefix = ''
1350 iid = rhodecode.CONFIG.get('instance_id')
1387 iid = rhodecode.CONFIG.get('instance_id')
1351 if iid:
1388 if iid:
1352 prefix = iid
1389 prefix = iid
1353 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1390 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1354
1391
1355 @classmethod
1392 @classmethod
1356 def get_by_key(cls, key):
1393 def get_by_key(cls, key):
1357 return cls.query().filter(cls.cache_key == key).scalar()
1394 return cls.query().filter(cls.cache_key == key).scalar()
1358
1395
1359 @classmethod
1396 @classmethod
1360 def _get_or_create_key(cls, key, prefix, org_key):
1397 def _get_or_create_key(cls, key, prefix, org_key):
1361 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1398 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1362 if not inv_obj:
1399 if not inv_obj:
1363 try:
1400 try:
1364 inv_obj = CacheInvalidation(key, org_key)
1401 inv_obj = CacheInvalidation(key, org_key)
1365 Session().add(inv_obj)
1402 Session().add(inv_obj)
1366 Session().commit()
1403 Session().commit()
1367 except Exception:
1404 except Exception:
1368 log.error(traceback.format_exc())
1405 log.error(traceback.format_exc())
1369 Session().rollback()
1406 Session().rollback()
1370 return inv_obj
1407 return inv_obj
1371
1408
1372 @classmethod
1409 @classmethod
1373 def invalidate(cls, key):
1410 def invalidate(cls, key):
1374 """
1411 """
1375 Returns Invalidation object if this given key should be invalidated
1412 Returns Invalidation object if this given key should be invalidated
1376 None otherwise. `cache_active = False` means that this cache
1413 None otherwise. `cache_active = False` means that this cache
1377 state is not valid and needs to be invalidated
1414 state is not valid and needs to be invalidated
1378
1415
1379 :param key:
1416 :param key:
1380 """
1417 """
1381
1418
1382 key, _prefix, _org_key = cls._get_key(key)
1419 key, _prefix, _org_key = cls._get_key(key)
1383 inv = cls._get_or_create_key(key, _prefix, _org_key)
1420 inv = cls._get_or_create_key(key, _prefix, _org_key)
1384
1421
1385 if inv and inv.cache_active is False:
1422 if inv and inv.cache_active is False:
1386 return inv
1423 return inv
1387
1424
1388 @classmethod
1425 @classmethod
1389 def set_invalidate(cls, key):
1426 def set_invalidate(cls, key):
1390 """
1427 """
1391 Mark this Cache key for invalidation
1428 Mark this Cache key for invalidation
1392
1429
1393 :param key:
1430 :param key:
1394 """
1431 """
1395
1432
1396 key, _prefix, _org_key = cls._get_key(key)
1433 key, _prefix, _org_key = cls._get_key(key)
1397 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1434 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1398 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1435 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1399 _org_key))
1436 _org_key))
1400 try:
1437 try:
1401 for inv_obj in inv_objs:
1438 for inv_obj in inv_objs:
1402 if inv_obj:
1439 if inv_obj:
1403 inv_obj.cache_active = False
1440 inv_obj.cache_active = False
1404
1441
1405 Session().add(inv_obj)
1442 Session().add(inv_obj)
1406 Session().commit()
1443 Session().commit()
1407 except Exception:
1444 except Exception:
1408 log.error(traceback.format_exc())
1445 log.error(traceback.format_exc())
1409 Session().rollback()
1446 Session().rollback()
1410
1447
1411 @classmethod
1448 @classmethod
1412 def set_valid(cls, key):
1449 def set_valid(cls, key):
1413 """
1450 """
1414 Mark this cache key as active and currently cached
1451 Mark this cache key as active and currently cached
1415
1452
1416 :param key:
1453 :param key:
1417 """
1454 """
1418 inv_obj = cls.get_by_key(key)
1455 inv_obj = cls.get_by_key(key)
1419 inv_obj.cache_active = True
1456 inv_obj.cache_active = True
1420 Session().add(inv_obj)
1457 Session().add(inv_obj)
1421 Session().commit()
1458 Session().commit()
1422
1459
1423 @classmethod
1460 @classmethod
1424 def get_cache_map(cls):
1461 def get_cache_map(cls):
1425
1462
1426 class cachemapdict(dict):
1463 class cachemapdict(dict):
1427
1464
1428 def __init__(self, *args, **kwargs):
1465 def __init__(self, *args, **kwargs):
1429 fixkey = kwargs.get('fixkey')
1466 fixkey = kwargs.get('fixkey')
1430 if fixkey:
1467 if fixkey:
1431 del kwargs['fixkey']
1468 del kwargs['fixkey']
1432 self.fixkey = fixkey
1469 self.fixkey = fixkey
1433 super(cachemapdict, self).__init__(*args, **kwargs)
1470 super(cachemapdict, self).__init__(*args, **kwargs)
1434
1471
1435 def __getattr__(self, name):
1472 def __getattr__(self, name):
1436 key = name
1473 key = name
1437 if self.fixkey:
1474 if self.fixkey:
1438 key, _prefix, _org_key = cls._get_key(key)
1475 key, _prefix, _org_key = cls._get_key(key)
1439 if key in self.__dict__:
1476 if key in self.__dict__:
1440 return self.__dict__[key]
1477 return self.__dict__[key]
1441 else:
1478 else:
1442 return self[key]
1479 return self[key]
1443
1480
1444 def __getitem__(self, key):
1481 def __getitem__(self, key):
1445 if self.fixkey:
1482 if self.fixkey:
1446 key, _prefix, _org_key = cls._get_key(key)
1483 key, _prefix, _org_key = cls._get_key(key)
1447 try:
1484 try:
1448 return super(cachemapdict, self).__getitem__(key)
1485 return super(cachemapdict, self).__getitem__(key)
1449 except KeyError:
1486 except KeyError:
1450 return
1487 return
1451
1488
1452 cache_map = cachemapdict(fixkey=True)
1489 cache_map = cachemapdict(fixkey=True)
1453 for obj in cls.query().all():
1490 for obj in cls.query().all():
1454 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1491 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1455 return cache_map
1492 return cache_map
1456
1493
1457
1494
1458 class ChangesetComment(Base, BaseModel):
1495 class ChangesetComment(Base, BaseModel):
1459 __tablename__ = 'changeset_comments'
1496 __tablename__ = 'changeset_comments'
1460 __table_args__ = (
1497 __table_args__ = (
1461 Index('cc_revision_idx', 'revision'),
1498 Index('cc_revision_idx', 'revision'),
1462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1499 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1463 'mysql_charset': 'utf8'},
1500 'mysql_charset': 'utf8'},
1464 )
1501 )
1465 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1502 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1466 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1503 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1467 revision = Column('revision', String(40), nullable=True)
1504 revision = Column('revision', String(40), nullable=True)
1468 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1505 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1469 line_no = Column('line_no', Unicode(10), nullable=True)
1506 line_no = Column('line_no', Unicode(10), nullable=True)
1470 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1507 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1471 f_path = Column('f_path', Unicode(1000), nullable=True)
1508 f_path = Column('f_path', Unicode(1000), nullable=True)
1472 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1509 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1473 text = Column('text', Unicode(25000), nullable=False)
1510 text = Column('text', Unicode(25000), nullable=False)
1474 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1511 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1475 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1512 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1476
1513
1477 author = relationship('User', lazy='joined')
1514 author = relationship('User', lazy='joined')
1478 repo = relationship('Repository')
1515 repo = relationship('Repository')
1479 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1516 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1480 pull_request = relationship('PullRequest', lazy='joined')
1517 pull_request = relationship('PullRequest', lazy='joined')
1481
1518
1482 @classmethod
1519 @classmethod
1483 def get_users(cls, revision=None, pull_request_id=None):
1520 def get_users(cls, revision=None, pull_request_id=None):
1484 """
1521 """
1485 Returns user associated with this ChangesetComment. ie those
1522 Returns user associated with this ChangesetComment. ie those
1486 who actually commented
1523 who actually commented
1487
1524
1488 :param cls:
1525 :param cls:
1489 :param revision:
1526 :param revision:
1490 """
1527 """
1491 q = Session().query(User)\
1528 q = Session().query(User)\
1492 .join(ChangesetComment.author)
1529 .join(ChangesetComment.author)
1493 if revision:
1530 if revision:
1494 q = q.filter(cls.revision == revision)
1531 q = q.filter(cls.revision == revision)
1495 elif pull_request_id:
1532 elif pull_request_id:
1496 q = q.filter(cls.pull_request_id == pull_request_id)
1533 q = q.filter(cls.pull_request_id == pull_request_id)
1497 return q.all()
1534 return q.all()
1498
1535
1499
1536
1500 class ChangesetStatus(Base, BaseModel):
1537 class ChangesetStatus(Base, BaseModel):
1501 __tablename__ = 'changeset_statuses'
1538 __tablename__ = 'changeset_statuses'
1502 __table_args__ = (
1539 __table_args__ = (
1503 Index('cs_revision_idx', 'revision'),
1540 Index('cs_revision_idx', 'revision'),
1504 Index('cs_version_idx', 'version'),
1541 Index('cs_version_idx', 'version'),
1505 UniqueConstraint('repo_id', 'revision', 'version'),
1542 UniqueConstraint('repo_id', 'revision', 'version'),
1506 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1543 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1507 'mysql_charset': 'utf8'}
1544 'mysql_charset': 'utf8'}
1508 )
1545 )
1509 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1546 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1510 STATUS_APPROVED = 'approved'
1547 STATUS_APPROVED = 'approved'
1511 STATUS_REJECTED = 'rejected'
1548 STATUS_REJECTED = 'rejected'
1512 STATUS_UNDER_REVIEW = 'under_review'
1549 STATUS_UNDER_REVIEW = 'under_review'
1513
1550
1514 STATUSES = [
1551 STATUSES = [
1515 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1552 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1516 (STATUS_APPROVED, _("Approved")),
1553 (STATUS_APPROVED, _("Approved")),
1517 (STATUS_REJECTED, _("Rejected")),
1554 (STATUS_REJECTED, _("Rejected")),
1518 (STATUS_UNDER_REVIEW, _("Under Review")),
1555 (STATUS_UNDER_REVIEW, _("Under Review")),
1519 ]
1556 ]
1520
1557
1521 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1558 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1522 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1559 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1523 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1560 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1524 revision = Column('revision', String(40), nullable=False)
1561 revision = Column('revision', String(40), nullable=False)
1525 status = Column('status', String(128), nullable=False, default=DEFAULT)
1562 status = Column('status', String(128), nullable=False, default=DEFAULT)
1526 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1563 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1527 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1564 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1528 version = Column('version', Integer(), nullable=False, default=0)
1565 version = Column('version', Integer(), nullable=False, default=0)
1529 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1566 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1530
1567
1531 author = relationship('User', lazy='joined')
1568 author = relationship('User', lazy='joined')
1532 repo = relationship('Repository')
1569 repo = relationship('Repository')
1533 comment = relationship('ChangesetComment', lazy='joined')
1570 comment = relationship('ChangesetComment', lazy='joined')
1534 pull_request = relationship('PullRequest', lazy='joined')
1571 pull_request = relationship('PullRequest', lazy='joined')
1535
1572
1536 def __unicode__(self):
1573 def __unicode__(self):
1537 return u"<%s('%s:%s')>" % (
1574 return u"<%s('%s:%s')>" % (
1538 self.__class__.__name__,
1575 self.__class__.__name__,
1539 self.status, self.author
1576 self.status, self.author
1540 )
1577 )
1541
1578
1542 @classmethod
1579 @classmethod
1543 def get_status_lbl(cls, value):
1580 def get_status_lbl(cls, value):
1544 return dict(cls.STATUSES).get(value)
1581 return dict(cls.STATUSES).get(value)
1545
1582
1546 @property
1583 @property
1547 def status_lbl(self):
1584 def status_lbl(self):
1548 return ChangesetStatus.get_status_lbl(self.status)
1585 return ChangesetStatus.get_status_lbl(self.status)
1549
1586
1550
1587
1551 class PullRequest(Base, BaseModel):
1588 class PullRequest(Base, BaseModel):
1552 __tablename__ = 'pull_requests'
1589 __tablename__ = 'pull_requests'
1553 __table_args__ = (
1590 __table_args__ = (
1554 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1591 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1555 'mysql_charset': 'utf8'},
1592 'mysql_charset': 'utf8'},
1556 )
1593 )
1557
1594
1558 STATUS_NEW = u'new'
1595 STATUS_NEW = u'new'
1559 STATUS_OPEN = u'open'
1596 STATUS_OPEN = u'open'
1560 STATUS_CLOSED = u'closed'
1597 STATUS_CLOSED = u'closed'
1561
1598
1562 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1599 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1563 title = Column('title', Unicode(256), nullable=True)
1600 title = Column('title', Unicode(256), nullable=True)
1564 description = Column('description', UnicodeText(10240), nullable=True)
1601 description = Column('description', UnicodeText(10240), nullable=True)
1565 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1602 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1566 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1603 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1567 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1604 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1568 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1605 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1569 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1606 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1570 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1607 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1571 org_ref = Column('org_ref', Unicode(256), nullable=False)
1608 org_ref = Column('org_ref', Unicode(256), nullable=False)
1572 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1609 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1573 other_ref = Column('other_ref', Unicode(256), nullable=False)
1610 other_ref = Column('other_ref', Unicode(256), nullable=False)
1574
1611
1575 @hybrid_property
1612 @hybrid_property
1576 def revisions(self):
1613 def revisions(self):
1577 return self._revisions.split(':')
1614 return self._revisions.split(':')
1578
1615
1579 @revisions.setter
1616 @revisions.setter
1580 def revisions(self, val):
1617 def revisions(self, val):
1581 self._revisions = ':'.join(val)
1618 self._revisions = ':'.join(val)
1582
1619
1583 author = relationship('User', lazy='joined')
1620 author = relationship('User', lazy='joined')
1584 reviewers = relationship('PullRequestReviewers',
1621 reviewers = relationship('PullRequestReviewers',
1585 cascade="all, delete, delete-orphan")
1622 cascade="all, delete, delete-orphan")
1586 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1623 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1587 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1624 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1588 statuses = relationship('ChangesetStatus')
1625 statuses = relationship('ChangesetStatus')
1589 comments = relationship('ChangesetComment',
1626 comments = relationship('ChangesetComment',
1590 cascade="all, delete, delete-orphan")
1627 cascade="all, delete, delete-orphan")
1591
1628
1592 def is_closed(self):
1629 def is_closed(self):
1593 return self.status == self.STATUS_CLOSED
1630 return self.status == self.STATUS_CLOSED
1594
1631
1595 def __json__(self):
1632 def __json__(self):
1596 return dict(
1633 return dict(
1597 revisions=self.revisions
1634 revisions=self.revisions
1598 )
1635 )
1599
1636
1600
1637
1601 class PullRequestReviewers(Base, BaseModel):
1638 class PullRequestReviewers(Base, BaseModel):
1602 __tablename__ = 'pull_request_reviewers'
1639 __tablename__ = 'pull_request_reviewers'
1603 __table_args__ = (
1640 __table_args__ = (
1604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1605 'mysql_charset': 'utf8'},
1642 'mysql_charset': 'utf8'},
1606 )
1643 )
1607
1644
1608 def __init__(self, user=None, pull_request=None):
1645 def __init__(self, user=None, pull_request=None):
1609 self.user = user
1646 self.user = user
1610 self.pull_request = pull_request
1647 self.pull_request = pull_request
1611
1648
1612 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1649 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1613 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1650 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1614 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1651 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1615
1652
1616 user = relationship('User')
1653 user = relationship('User')
1617 pull_request = relationship('PullRequest')
1654 pull_request = relationship('PullRequest')
1618
1655
1619
1656
1620 class Notification(Base, BaseModel):
1657 class Notification(Base, BaseModel):
1621 __tablename__ = 'notifications'
1658 __tablename__ = 'notifications'
1622 __table_args__ = (
1659 __table_args__ = (
1623 Index('notification_type_idx', 'type'),
1660 Index('notification_type_idx', 'type'),
1624 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1661 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1625 'mysql_charset': 'utf8'},
1662 'mysql_charset': 'utf8'},
1626 )
1663 )
1627
1664
1628 TYPE_CHANGESET_COMMENT = u'cs_comment'
1665 TYPE_CHANGESET_COMMENT = u'cs_comment'
1629 TYPE_MESSAGE = u'message'
1666 TYPE_MESSAGE = u'message'
1630 TYPE_MENTION = u'mention'
1667 TYPE_MENTION = u'mention'
1631 TYPE_REGISTRATION = u'registration'
1668 TYPE_REGISTRATION = u'registration'
1632 TYPE_PULL_REQUEST = u'pull_request'
1669 TYPE_PULL_REQUEST = u'pull_request'
1633 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1670 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1634
1671
1635 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1672 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1636 subject = Column('subject', Unicode(512), nullable=True)
1673 subject = Column('subject', Unicode(512), nullable=True)
1637 body = Column('body', UnicodeText(50000), nullable=True)
1674 body = Column('body', UnicodeText(50000), nullable=True)
1638 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1675 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1639 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1676 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1640 type_ = Column('type', Unicode(256))
1677 type_ = Column('type', Unicode(256))
1641
1678
1642 created_by_user = relationship('User')
1679 created_by_user = relationship('User')
1643 notifications_to_users = relationship('UserNotification', lazy='joined',
1680 notifications_to_users = relationship('UserNotification', lazy='joined',
1644 cascade="all, delete, delete-orphan")
1681 cascade="all, delete, delete-orphan")
1645
1682
1646 @property
1683 @property
1647 def recipients(self):
1684 def recipients(self):
1648 return [x.user for x in UserNotification.query()\
1685 return [x.user for x in UserNotification.query()\
1649 .filter(UserNotification.notification == self)\
1686 .filter(UserNotification.notification == self)\
1650 .order_by(UserNotification.user_id.asc()).all()]
1687 .order_by(UserNotification.user_id.asc()).all()]
1651
1688
1652 @classmethod
1689 @classmethod
1653 def create(cls, created_by, subject, body, recipients, type_=None):
1690 def create(cls, created_by, subject, body, recipients, type_=None):
1654 if type_ is None:
1691 if type_ is None:
1655 type_ = Notification.TYPE_MESSAGE
1692 type_ = Notification.TYPE_MESSAGE
1656
1693
1657 notification = cls()
1694 notification = cls()
1658 notification.created_by_user = created_by
1695 notification.created_by_user = created_by
1659 notification.subject = subject
1696 notification.subject = subject
1660 notification.body = body
1697 notification.body = body
1661 notification.type_ = type_
1698 notification.type_ = type_
1662 notification.created_on = datetime.datetime.now()
1699 notification.created_on = datetime.datetime.now()
1663
1700
1664 for u in recipients:
1701 for u in recipients:
1665 assoc = UserNotification()
1702 assoc = UserNotification()
1666 assoc.notification = notification
1703 assoc.notification = notification
1667 u.notifications.append(assoc)
1704 u.notifications.append(assoc)
1668 Session().add(notification)
1705 Session().add(notification)
1669 return notification
1706 return notification
1670
1707
1671 @property
1708 @property
1672 def description(self):
1709 def description(self):
1673 from rhodecode.model.notification import NotificationModel
1710 from rhodecode.model.notification import NotificationModel
1674 return NotificationModel().make_description(self)
1711 return NotificationModel().make_description(self)
1675
1712
1676
1713
1677 class UserNotification(Base, BaseModel):
1714 class UserNotification(Base, BaseModel):
1678 __tablename__ = 'user_to_notification'
1715 __tablename__ = 'user_to_notification'
1679 __table_args__ = (
1716 __table_args__ = (
1680 UniqueConstraint('user_id', 'notification_id'),
1717 UniqueConstraint('user_id', 'notification_id'),
1681 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1718 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1682 'mysql_charset': 'utf8'}
1719 'mysql_charset': 'utf8'}
1683 )
1720 )
1684 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1721 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1685 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1722 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1686 read = Column('read', Boolean, default=False)
1723 read = Column('read', Boolean, default=False)
1687 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1724 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1688
1725
1689 user = relationship('User', lazy="joined")
1726 user = relationship('User', lazy="joined")
1690 notification = relationship('Notification', lazy="joined",
1727 notification = relationship('Notification', lazy="joined",
1691 order_by=lambda: Notification.created_on.desc(),)
1728 order_by=lambda: Notification.created_on.desc(),)
1692
1729
1693 def mark_as_read(self):
1730 def mark_as_read(self):
1694 self.read = True
1731 self.read = True
1695 Session().add(self)
1732 Session().add(self)
1696
1733
1697
1734
1698 class DbMigrateVersion(Base, BaseModel):
1735 class DbMigrateVersion(Base, BaseModel):
1699 __tablename__ = 'db_migrate_version'
1736 __tablename__ = 'db_migrate_version'
1700 __table_args__ = (
1737 __table_args__ = (
1701 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1738 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1702 'mysql_charset': 'utf8'},
1739 'mysql_charset': 'utf8'},
1703 )
1740 )
1704 repository_id = Column('repository_id', String(250), primary_key=True)
1741 repository_id = Column('repository_id', String(250), primary_key=True)
1705 repository_path = Column('repository_path', Text)
1742 repository_path = Column('repository_path', Text)
1706 version = Column('version', Integer)
1743 version = Column('version', Integer)
@@ -1,342 +1,343 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import logging
22 import logging
23
23
24 import formencode
24 import formencode
25 from formencode import All
25 from formencode import All
26
26
27 from pylons.i18n.translation import _
27 from pylons.i18n.translation import _
28
28
29 from rhodecode.model import validators as v
29 from rhodecode.model import validators as v
30 from rhodecode import BACKENDS
30 from rhodecode import BACKENDS
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class LoginForm(formencode.Schema):
35 class LoginForm(formencode.Schema):
36 allow_extra_fields = True
36 allow_extra_fields = True
37 filter_extra_fields = True
37 filter_extra_fields = True
38 username = v.UnicodeString(
38 username = v.UnicodeString(
39 strip=True,
39 strip=True,
40 min=1,
40 min=1,
41 not_empty=True,
41 not_empty=True,
42 messages={
42 messages={
43 'empty': _(u'Please enter a login'),
43 'empty': _(u'Please enter a login'),
44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 )
45 )
46
46
47 password = v.UnicodeString(
47 password = v.UnicodeString(
48 strip=False,
48 strip=False,
49 min=3,
49 min=3,
50 not_empty=True,
50 not_empty=True,
51 messages={
51 messages={
52 'empty': _(u'Please enter a password'),
52 'empty': _(u'Please enter a password'),
53 'tooShort': _(u'Enter %(min)i characters or more')}
53 'tooShort': _(u'Enter %(min)i characters or more')}
54 )
54 )
55
55
56 remember = v.StringBoolean(if_missing=False)
56 remember = v.StringBoolean(if_missing=False)
57
57
58 chained_validators = [v.ValidAuth()]
58 chained_validators = [v.ValidAuth()]
59
59
60
60
61 def UserForm(edit=False, old_data={}):
61 def UserForm(edit=False, old_data={}):
62 class _UserForm(formencode.Schema):
62 class _UserForm(formencode.Schema):
63 allow_extra_fields = True
63 allow_extra_fields = True
64 filter_extra_fields = True
64 filter_extra_fields = True
65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 v.ValidUsername(edit, old_data))
66 v.ValidUsername(edit, old_data))
67 if edit:
67 if edit:
68 new_password = All(
68 new_password = All(
69 v.ValidPassword(),
69 v.ValidPassword(),
70 v.UnicodeString(strip=False, min=6, not_empty=False)
70 v.UnicodeString(strip=False, min=6, not_empty=False)
71 )
71 )
72 password_confirmation = All(
72 password_confirmation = All(
73 v.ValidPassword(),
73 v.ValidPassword(),
74 v.UnicodeString(strip=False, min=6, not_empty=False),
74 v.UnicodeString(strip=False, min=6, not_empty=False),
75 )
75 )
76 admin = v.StringBoolean(if_missing=False)
76 admin = v.StringBoolean(if_missing=False)
77 else:
77 else:
78 password = All(
78 password = All(
79 v.ValidPassword(),
79 v.ValidPassword(),
80 v.UnicodeString(strip=False, min=6, not_empty=True)
80 v.UnicodeString(strip=False, min=6, not_empty=True)
81 )
81 )
82 password_confirmation = All(
82 password_confirmation = All(
83 v.ValidPassword(),
83 v.ValidPassword(),
84 v.UnicodeString(strip=False, min=6, not_empty=False)
84 v.UnicodeString(strip=False, min=6, not_empty=False)
85 )
85 )
86
86
87 active = v.StringBoolean(if_missing=False)
87 active = v.StringBoolean(if_missing=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
91
91
92 chained_validators = [v.ValidPasswordsMatch()]
92 chained_validators = [v.ValidPasswordsMatch()]
93
93
94 return _UserForm
94 return _UserForm
95
95
96
96
97 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
97 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
98 class _UsersGroupForm(formencode.Schema):
98 class _UsersGroupForm(formencode.Schema):
99 allow_extra_fields = True
99 allow_extra_fields = True
100 filter_extra_fields = True
100 filter_extra_fields = True
101
101
102 users_group_name = All(
102 users_group_name = All(
103 v.UnicodeString(strip=True, min=1, not_empty=True),
103 v.UnicodeString(strip=True, min=1, not_empty=True),
104 v.ValidUsersGroup(edit, old_data)
104 v.ValidUsersGroup(edit, old_data)
105 )
105 )
106
106
107 users_group_active = v.StringBoolean(if_missing=False)
107 users_group_active = v.StringBoolean(if_missing=False)
108
108
109 if edit:
109 if edit:
110 users_group_members = v.OneOf(
110 users_group_members = v.OneOf(
111 available_members, hideList=False, testValueList=True,
111 available_members, hideList=False, testValueList=True,
112 if_missing=None, not_empty=False
112 if_missing=None, not_empty=False
113 )
113 )
114
114
115 return _UsersGroupForm
115 return _UsersGroupForm
116
116
117
117
118 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
118 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
119 class _ReposGroupForm(formencode.Schema):
119 class _ReposGroupForm(formencode.Schema):
120 allow_extra_fields = True
120 allow_extra_fields = True
121 filter_extra_fields = False
121 filter_extra_fields = False
122
122
123 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
123 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
124 v.SlugifyName())
124 v.SlugifyName())
125 group_description = v.UnicodeString(strip=True, min=1,
125 group_description = v.UnicodeString(strip=True, min=1,
126 not_empty=True)
126 not_empty=True)
127 group_parent_id = v.OneOf(available_groups, hideList=False,
127 group_parent_id = v.OneOf(available_groups, hideList=False,
128 testValueList=True,
128 testValueList=True,
129 if_missing=None, not_empty=False)
129 if_missing=None, not_empty=False)
130
130
131 chained_validators = [v.ValidReposGroup(edit, old_data),
131 chained_validators = [v.ValidReposGroup(edit, old_data),
132 v.ValidPerms('group')]
132 v.ValidPerms('group')]
133
133
134 return _ReposGroupForm
134 return _ReposGroupForm
135
135
136
136
137 def RegisterForm(edit=False, old_data={}):
137 def RegisterForm(edit=False, old_data={}):
138 class _RegisterForm(formencode.Schema):
138 class _RegisterForm(formencode.Schema):
139 allow_extra_fields = True
139 allow_extra_fields = True
140 filter_extra_fields = True
140 filter_extra_fields = True
141 username = All(
141 username = All(
142 v.ValidUsername(edit, old_data),
142 v.ValidUsername(edit, old_data),
143 v.UnicodeString(strip=True, min=1, not_empty=True)
143 v.UnicodeString(strip=True, min=1, not_empty=True)
144 )
144 )
145 password = All(
145 password = All(
146 v.ValidPassword(),
146 v.ValidPassword(),
147 v.UnicodeString(strip=False, min=6, not_empty=True)
147 v.UnicodeString(strip=False, min=6, not_empty=True)
148 )
148 )
149 password_confirmation = All(
149 password_confirmation = All(
150 v.ValidPassword(),
150 v.ValidPassword(),
151 v.UnicodeString(strip=False, min=6, not_empty=True)
151 v.UnicodeString(strip=False, min=6, not_empty=True)
152 )
152 )
153 active = v.StringBoolean(if_missing=False)
153 active = v.StringBoolean(if_missing=False)
154 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
154 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
155 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
155 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
156 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
156 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
157
157
158 chained_validators = [v.ValidPasswordsMatch()]
158 chained_validators = [v.ValidPasswordsMatch()]
159
159
160 return _RegisterForm
160 return _RegisterForm
161
161
162
162
163 def PasswordResetForm():
163 def PasswordResetForm():
164 class _PasswordResetForm(formencode.Schema):
164 class _PasswordResetForm(formencode.Schema):
165 allow_extra_fields = True
165 allow_extra_fields = True
166 filter_extra_fields = True
166 filter_extra_fields = True
167 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
167 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
168 return _PasswordResetForm
168 return _PasswordResetForm
169
169
170
170
171 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
171 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
172 repo_groups=[], landing_revs=[]):
172 repo_groups=[], landing_revs=[]):
173 class _RepoForm(formencode.Schema):
173 class _RepoForm(formencode.Schema):
174 allow_extra_fields = True
174 allow_extra_fields = True
175 filter_extra_fields = False
175 filter_extra_fields = False
176 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
176 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
177 v.SlugifyName())
177 v.SlugifyName())
178 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
178 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
179 repo_group = v.OneOf(repo_groups, hideList=True)
179 repo_group = v.OneOf(repo_groups, hideList=True)
180 repo_type = v.OneOf(supported_backends)
180 repo_type = v.OneOf(supported_backends)
181 description = v.UnicodeString(strip=True, min=1, not_empty=False)
181 description = v.UnicodeString(strip=True, min=1, not_empty=False)
182 private = v.StringBoolean(if_missing=False)
182 private = v.StringBoolean(if_missing=False)
183 enable_statistics = v.StringBoolean(if_missing=False)
183 enable_statistics = v.StringBoolean(if_missing=False)
184 enable_downloads = v.StringBoolean(if_missing=False)
184 enable_downloads = v.StringBoolean(if_missing=False)
185 enable_locking = v.StringBoolean(if_missing=False)
185 landing_rev = v.OneOf(landing_revs, hideList=True)
186 landing_rev = v.OneOf(landing_revs, hideList=True)
186
187
187 if edit:
188 if edit:
188 #this is repo owner
189 #this is repo owner
189 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
190 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
190
191
191 chained_validators = [v.ValidCloneUri(),
192 chained_validators = [v.ValidCloneUri(),
192 v.ValidRepoName(edit, old_data),
193 v.ValidRepoName(edit, old_data),
193 v.ValidPerms()]
194 v.ValidPerms()]
194 return _RepoForm
195 return _RepoForm
195
196
196
197
197 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
198 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
198 repo_groups=[], landing_revs=[]):
199 repo_groups=[], landing_revs=[]):
199 class _RepoForkForm(formencode.Schema):
200 class _RepoForkForm(formencode.Schema):
200 allow_extra_fields = True
201 allow_extra_fields = True
201 filter_extra_fields = False
202 filter_extra_fields = False
202 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
203 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
203 v.SlugifyName())
204 v.SlugifyName())
204 repo_group = v.OneOf(repo_groups, hideList=True)
205 repo_group = v.OneOf(repo_groups, hideList=True)
205 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
206 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
206 description = v.UnicodeString(strip=True, min=1, not_empty=True)
207 description = v.UnicodeString(strip=True, min=1, not_empty=True)
207 private = v.StringBoolean(if_missing=False)
208 private = v.StringBoolean(if_missing=False)
208 copy_permissions = v.StringBoolean(if_missing=False)
209 copy_permissions = v.StringBoolean(if_missing=False)
209 update_after_clone = v.StringBoolean(if_missing=False)
210 update_after_clone = v.StringBoolean(if_missing=False)
210 fork_parent_id = v.UnicodeString()
211 fork_parent_id = v.UnicodeString()
211 chained_validators = [v.ValidForkName(edit, old_data)]
212 chained_validators = [v.ValidForkName(edit, old_data)]
212 landing_rev = v.OneOf(landing_revs, hideList=True)
213 landing_rev = v.OneOf(landing_revs, hideList=True)
213
214
214 return _RepoForkForm
215 return _RepoForkForm
215
216
216
217
217 def RepoSettingsForm(edit=False, old_data={},
218 def RepoSettingsForm(edit=False, old_data={},
218 supported_backends=BACKENDS.keys(), repo_groups=[],
219 supported_backends=BACKENDS.keys(), repo_groups=[],
219 landing_revs=[]):
220 landing_revs=[]):
220 class _RepoForm(formencode.Schema):
221 class _RepoForm(formencode.Schema):
221 allow_extra_fields = True
222 allow_extra_fields = True
222 filter_extra_fields = False
223 filter_extra_fields = False
223 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
224 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
224 v.SlugifyName())
225 v.SlugifyName())
225 description = v.UnicodeString(strip=True, min=1, not_empty=True)
226 description = v.UnicodeString(strip=True, min=1, not_empty=True)
226 repo_group = v.OneOf(repo_groups, hideList=True)
227 repo_group = v.OneOf(repo_groups, hideList=True)
227 private = v.StringBoolean(if_missing=False)
228 private = v.StringBoolean(if_missing=False)
228 landing_rev = v.OneOf(landing_revs, hideList=True)
229 landing_rev = v.OneOf(landing_revs, hideList=True)
229 chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(),
230 chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(),
230 v.ValidSettings()]
231 v.ValidSettings()]
231 return _RepoForm
232 return _RepoForm
232
233
233
234
234 def ApplicationSettingsForm():
235 def ApplicationSettingsForm():
235 class _ApplicationSettingsForm(formencode.Schema):
236 class _ApplicationSettingsForm(formencode.Schema):
236 allow_extra_fields = True
237 allow_extra_fields = True
237 filter_extra_fields = False
238 filter_extra_fields = False
238 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
239 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
239 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
240 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
240 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
241 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
241
242
242 return _ApplicationSettingsForm
243 return _ApplicationSettingsForm
243
244
244
245
245 def ApplicationVisualisationForm():
246 def ApplicationVisualisationForm():
246 class _ApplicationVisualisationForm(formencode.Schema):
247 class _ApplicationVisualisationForm(formencode.Schema):
247 allow_extra_fields = True
248 allow_extra_fields = True
248 filter_extra_fields = False
249 filter_extra_fields = False
249 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
250 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
250 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
251 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
251 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
252 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
252
253
253 return _ApplicationVisualisationForm
254 return _ApplicationVisualisationForm
254
255
255
256
256 def ApplicationUiSettingsForm():
257 def ApplicationUiSettingsForm():
257 class _ApplicationUiSettingsForm(formencode.Schema):
258 class _ApplicationUiSettingsForm(formencode.Schema):
258 allow_extra_fields = True
259 allow_extra_fields = True
259 filter_extra_fields = False
260 filter_extra_fields = False
260 web_push_ssl = v.StringBoolean(if_missing=False)
261 web_push_ssl = v.StringBoolean(if_missing=False)
261 paths_root_path = All(
262 paths_root_path = All(
262 v.ValidPath(),
263 v.ValidPath(),
263 v.UnicodeString(strip=True, min=1, not_empty=True)
264 v.UnicodeString(strip=True, min=1, not_empty=True)
264 )
265 )
265 hooks_changegroup_update = v.StringBoolean(if_missing=False)
266 hooks_changegroup_update = v.StringBoolean(if_missing=False)
266 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
267 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
267 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
268 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
268 hooks_preoutgoing_pull_logger = v.StringBoolean(if_missing=False)
269 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
269
270
270 extensions_largefiles = v.StringBoolean(if_missing=False)
271 extensions_largefiles = v.StringBoolean(if_missing=False)
271 extensions_hgsubversion = v.StringBoolean(if_missing=False)
272 extensions_hgsubversion = v.StringBoolean(if_missing=False)
272 extensions_hggit = v.StringBoolean(if_missing=False)
273 extensions_hggit = v.StringBoolean(if_missing=False)
273
274
274 return _ApplicationUiSettingsForm
275 return _ApplicationUiSettingsForm
275
276
276
277
277 def DefaultPermissionsForm(perms_choices, register_choices, create_choices,
278 def DefaultPermissionsForm(perms_choices, register_choices, create_choices,
278 fork_choices):
279 fork_choices):
279 class _DefaultPermissionsForm(formencode.Schema):
280 class _DefaultPermissionsForm(formencode.Schema):
280 allow_extra_fields = True
281 allow_extra_fields = True
281 filter_extra_fields = True
282 filter_extra_fields = True
282 overwrite_default = v.StringBoolean(if_missing=False)
283 overwrite_default = v.StringBoolean(if_missing=False)
283 anonymous = v.StringBoolean(if_missing=False)
284 anonymous = v.StringBoolean(if_missing=False)
284 default_perm = v.OneOf(perms_choices)
285 default_perm = v.OneOf(perms_choices)
285 default_register = v.OneOf(register_choices)
286 default_register = v.OneOf(register_choices)
286 default_create = v.OneOf(create_choices)
287 default_create = v.OneOf(create_choices)
287 default_fork = v.OneOf(fork_choices)
288 default_fork = v.OneOf(fork_choices)
288
289
289 return _DefaultPermissionsForm
290 return _DefaultPermissionsForm
290
291
291
292
292 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
293 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
293 tls_kind_choices):
294 tls_kind_choices):
294 class _LdapSettingsForm(formencode.Schema):
295 class _LdapSettingsForm(formencode.Schema):
295 allow_extra_fields = True
296 allow_extra_fields = True
296 filter_extra_fields = True
297 filter_extra_fields = True
297 #pre_validators = [LdapLibValidator]
298 #pre_validators = [LdapLibValidator]
298 ldap_active = v.StringBoolean(if_missing=False)
299 ldap_active = v.StringBoolean(if_missing=False)
299 ldap_host = v.UnicodeString(strip=True,)
300 ldap_host = v.UnicodeString(strip=True,)
300 ldap_port = v.Number(strip=True,)
301 ldap_port = v.Number(strip=True,)
301 ldap_tls_kind = v.OneOf(tls_kind_choices)
302 ldap_tls_kind = v.OneOf(tls_kind_choices)
302 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
303 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
303 ldap_dn_user = v.UnicodeString(strip=True,)
304 ldap_dn_user = v.UnicodeString(strip=True,)
304 ldap_dn_pass = v.UnicodeString(strip=True,)
305 ldap_dn_pass = v.UnicodeString(strip=True,)
305 ldap_base_dn = v.UnicodeString(strip=True,)
306 ldap_base_dn = v.UnicodeString(strip=True,)
306 ldap_filter = v.UnicodeString(strip=True,)
307 ldap_filter = v.UnicodeString(strip=True,)
307 ldap_search_scope = v.OneOf(search_scope_choices)
308 ldap_search_scope = v.OneOf(search_scope_choices)
308 ldap_attr_login = All(
309 ldap_attr_login = All(
309 v.AttrLoginValidator(),
310 v.AttrLoginValidator(),
310 v.UnicodeString(strip=True,)
311 v.UnicodeString(strip=True,)
311 )
312 )
312 ldap_attr_firstname = v.UnicodeString(strip=True,)
313 ldap_attr_firstname = v.UnicodeString(strip=True,)
313 ldap_attr_lastname = v.UnicodeString(strip=True,)
314 ldap_attr_lastname = v.UnicodeString(strip=True,)
314 ldap_attr_email = v.UnicodeString(strip=True,)
315 ldap_attr_email = v.UnicodeString(strip=True,)
315
316
316 return _LdapSettingsForm
317 return _LdapSettingsForm
317
318
318
319
319 def UserExtraEmailForm():
320 def UserExtraEmailForm():
320 class _UserExtraEmailForm(formencode.Schema):
321 class _UserExtraEmailForm(formencode.Schema):
321 email = All(v.UniqSystemEmail(), v.Email)
322 email = All(v.UniqSystemEmail(), v.Email)
322
323
323 return _UserExtraEmailForm
324 return _UserExtraEmailForm
324
325
325
326
326 def PullRequestForm():
327 def PullRequestForm():
327 class _PullRequestForm(formencode.Schema):
328 class _PullRequestForm(formencode.Schema):
328 allow_extra_fields = True
329 allow_extra_fields = True
329 filter_extra_fields = True
330 filter_extra_fields = True
330
331
331 user = v.UnicodeString(strip=True, required=True)
332 user = v.UnicodeString(strip=True, required=True)
332 org_repo = v.UnicodeString(strip=True, required=True)
333 org_repo = v.UnicodeString(strip=True, required=True)
333 org_ref = v.UnicodeString(strip=True, required=True)
334 org_ref = v.UnicodeString(strip=True, required=True)
334 other_repo = v.UnicodeString(strip=True, required=True)
335 other_repo = v.UnicodeString(strip=True, required=True)
335 other_ref = v.UnicodeString(strip=True, required=True)
336 other_ref = v.UnicodeString(strip=True, required=True)
336 revisions = All(v.NotReviewedRevisions()(), v.UniqueList(not_empty=True))
337 revisions = All(v.NotReviewedRevisions()(), v.UniqueList(not_empty=True))
337 review_members = v.UniqueList(not_empty=True)
338 review_members = v.UniqueList(not_empty=True)
338
339
339 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
340 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
340 pullrequest_desc = v.UnicodeString(strip=True, required=False)
341 pullrequest_desc = v.UnicodeString(strip=True, required=False)
341
342
342 return _PullRequestForm No newline at end of file
343 return _PullRequestForm
@@ -1,604 +1,611 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import re
27 import re
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31 import cStringIO
31 import cStringIO
32 import pkg_resources
32 import pkg_resources
33 from os.path import dirname as dn, join as jn
33 from os.path import dirname as dn, join as jn
34
34
35 from sqlalchemy import func
35 from sqlalchemy import func
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.lib.vcs import get_backend
39 from rhodecode.lib.vcs import get_backend
40 from rhodecode.lib.vcs.exceptions import RepositoryError
40 from rhodecode.lib.vcs.exceptions import RepositoryError
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 from rhodecode.lib.vcs.nodes import FileNode
42 from rhodecode.lib.vcs.nodes import FileNode
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44
44
45 from rhodecode import BACKENDS
45 from rhodecode import BACKENDS
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.utils2 import safe_str, safe_unicode
47 from rhodecode.lib.utils2 import safe_str, safe_unicode
48 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
48 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
49 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
49 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
50 action_logger, REMOVED_REPO_PAT
50 action_logger, REMOVED_REPO_PAT
51 from rhodecode.model import BaseModel
51 from rhodecode.model import BaseModel
52 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
52 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
53 UserFollowing, UserLog, User, RepoGroup, PullRequest
53 UserFollowing, UserLog, User, RepoGroup, PullRequest
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class UserTemp(object):
58 class UserTemp(object):
59 def __init__(self, user_id):
59 def __init__(self, user_id):
60 self.user_id = user_id
60 self.user_id = user_id
61
61
62 def __repr__(self):
62 def __repr__(self):
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64
64
65
65
66 class RepoTemp(object):
66 class RepoTemp(object):
67 def __init__(self, repo_id):
67 def __init__(self, repo_id):
68 self.repo_id = repo_id
68 self.repo_id = repo_id
69
69
70 def __repr__(self):
70 def __repr__(self):
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72
72
73
73
74 class CachedRepoList(object):
74 class CachedRepoList(object):
75 """
75 """
76 Cached repo list, uses in-memory cache after initialization, that is
76 Cached repo list, uses in-memory cache after initialization, that is
77 super fast
77 super fast
78 """
78 """
79
79
80 def __init__(self, db_repo_list, repos_path, order_by=None):
80 def __init__(self, db_repo_list, repos_path, order_by=None):
81 self.db_repo_list = db_repo_list
81 self.db_repo_list = db_repo_list
82 self.repos_path = repos_path
82 self.repos_path = repos_path
83 self.order_by = order_by
83 self.order_by = order_by
84 self.reversed = (order_by or '').startswith('-')
84 self.reversed = (order_by or '').startswith('-')
85
85
86 def __len__(self):
86 def __len__(self):
87 return len(self.db_repo_list)
87 return len(self.db_repo_list)
88
88
89 def __repr__(self):
89 def __repr__(self):
90 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
90 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
91
91
92 def __iter__(self):
92 def __iter__(self):
93 # pre-propagated cache_map to save executing select statements
93 # pre-propagated cache_map to save executing select statements
94 # for each repo
94 # for each repo
95 cache_map = CacheInvalidation.get_cache_map()
95 cache_map = CacheInvalidation.get_cache_map()
96
96
97 for dbr in self.db_repo_list:
97 for dbr in self.db_repo_list:
98 scmr = dbr.scm_instance_cached(cache_map)
98 scmr = dbr.scm_instance_cached(cache_map)
99 # check permission at this level
99 # check permission at this level
100 if not HasRepoPermissionAny(
100 if not HasRepoPermissionAny(
101 'repository.read', 'repository.write', 'repository.admin'
101 'repository.read', 'repository.write', 'repository.admin'
102 )(dbr.repo_name, 'get repo check'):
102 )(dbr.repo_name, 'get repo check'):
103 continue
103 continue
104
104
105 if scmr is None:
105 if scmr is None:
106 log.error(
106 log.error(
107 '%s this repository is present in database but it '
107 '%s this repository is present in database but it '
108 'cannot be created as an scm instance' % dbr.repo_name
108 'cannot be created as an scm instance' % dbr.repo_name
109 )
109 )
110 continue
110 continue
111
111
112 last_change = scmr.last_change
112 last_change = scmr.last_change
113 tip = h.get_changeset_safe(scmr, 'tip')
113 tip = h.get_changeset_safe(scmr, 'tip')
114
114
115 tmp_d = {}
115 tmp_d = {}
116 tmp_d['name'] = dbr.repo_name
116 tmp_d['name'] = dbr.repo_name
117 tmp_d['name_sort'] = tmp_d['name'].lower()
117 tmp_d['name_sort'] = tmp_d['name'].lower()
118 tmp_d['description'] = dbr.description
118 tmp_d['description'] = dbr.description
119 tmp_d['description_sort'] = tmp_d['description'].lower()
119 tmp_d['description_sort'] = tmp_d['description'].lower()
120 tmp_d['last_change'] = last_change
120 tmp_d['last_change'] = last_change
121 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
121 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
122 tmp_d['tip'] = tip.raw_id
122 tmp_d['tip'] = tip.raw_id
123 tmp_d['tip_sort'] = tip.revision
123 tmp_d['tip_sort'] = tip.revision
124 tmp_d['rev'] = tip.revision
124 tmp_d['rev'] = tip.revision
125 tmp_d['contact'] = dbr.user.full_contact
125 tmp_d['contact'] = dbr.user.full_contact
126 tmp_d['contact_sort'] = tmp_d['contact']
126 tmp_d['contact_sort'] = tmp_d['contact']
127 tmp_d['owner_sort'] = tmp_d['contact']
127 tmp_d['owner_sort'] = tmp_d['contact']
128 tmp_d['repo_archives'] = list(scmr._get_archives())
128 tmp_d['repo_archives'] = list(scmr._get_archives())
129 tmp_d['last_msg'] = tip.message
129 tmp_d['last_msg'] = tip.message
130 tmp_d['author'] = tip.author
130 tmp_d['author'] = tip.author
131 tmp_d['dbrepo'] = dbr.get_dict()
131 tmp_d['dbrepo'] = dbr.get_dict()
132 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
132 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
133 yield tmp_d
133 yield tmp_d
134
134
135
135
136 class SimpleCachedRepoList(CachedRepoList):
136 class SimpleCachedRepoList(CachedRepoList):
137 """
137 """
138 Lighter version of CachedRepoList without the scm initialisation
138 Lighter version of CachedRepoList without the scm initialisation
139 """
139 """
140
140
141 def __iter__(self):
141 def __iter__(self):
142 for dbr in self.db_repo_list:
142 for dbr in self.db_repo_list:
143 # check permission at this level
143 # check permission at this level
144 if not HasRepoPermissionAny(
144 if not HasRepoPermissionAny(
145 'repository.read', 'repository.write', 'repository.admin'
145 'repository.read', 'repository.write', 'repository.admin'
146 )(dbr.repo_name, 'get repo check'):
146 )(dbr.repo_name, 'get repo check'):
147 continue
147 continue
148
148
149 tmp_d = {}
149 tmp_d = {}
150 tmp_d['name'] = dbr.repo_name
150 tmp_d['name'] = dbr.repo_name
151 tmp_d['name_sort'] = tmp_d['name'].lower()
151 tmp_d['name_sort'] = tmp_d['name'].lower()
152 tmp_d['description'] = dbr.description
152 tmp_d['description'] = dbr.description
153 tmp_d['description_sort'] = tmp_d['description'].lower()
153 tmp_d['description_sort'] = tmp_d['description'].lower()
154 tmp_d['dbrepo'] = dbr.get_dict()
154 tmp_d['dbrepo'] = dbr.get_dict()
155 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
155 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
156 yield tmp_d
156 yield tmp_d
157
157
158
158
159 class GroupList(object):
159 class GroupList(object):
160
160
161 def __init__(self, db_repo_group_list):
161 def __init__(self, db_repo_group_list):
162 self.db_repo_group_list = db_repo_group_list
162 self.db_repo_group_list = db_repo_group_list
163
163
164 def __len__(self):
164 def __len__(self):
165 return len(self.db_repo_group_list)
165 return len(self.db_repo_group_list)
166
166
167 def __repr__(self):
167 def __repr__(self):
168 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
168 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
169
169
170 def __iter__(self):
170 def __iter__(self):
171 for dbgr in self.db_repo_group_list:
171 for dbgr in self.db_repo_group_list:
172 # check permission at this level
172 # check permission at this level
173 if not HasReposGroupPermissionAny(
173 if not HasReposGroupPermissionAny(
174 'group.read', 'group.write', 'group.admin'
174 'group.read', 'group.write', 'group.admin'
175 )(dbgr.group_name, 'get group repo check'):
175 )(dbgr.group_name, 'get group repo check'):
176 continue
176 continue
177
177
178 yield dbgr
178 yield dbgr
179
179
180
180
181 class ScmModel(BaseModel):
181 class ScmModel(BaseModel):
182 """
182 """
183 Generic Scm Model
183 Generic Scm Model
184 """
184 """
185
185
186 def __get_repo(self, instance):
186 def __get_repo(self, instance):
187 cls = Repository
187 cls = Repository
188 if isinstance(instance, cls):
188 if isinstance(instance, cls):
189 return instance
189 return instance
190 elif isinstance(instance, int) or safe_str(instance).isdigit():
190 elif isinstance(instance, int) or safe_str(instance).isdigit():
191 return cls.get(instance)
191 return cls.get(instance)
192 elif isinstance(instance, basestring):
192 elif isinstance(instance, basestring):
193 return cls.get_by_repo_name(instance)
193 return cls.get_by_repo_name(instance)
194 elif instance:
194 elif instance:
195 raise Exception('given object must be int, basestr or Instance'
195 raise Exception('given object must be int, basestr or Instance'
196 ' of %s got %s' % (type(cls), type(instance)))
196 ' of %s got %s' % (type(cls), type(instance)))
197
197
198 @LazyProperty
198 @LazyProperty
199 def repos_path(self):
199 def repos_path(self):
200 """
200 """
201 Get's the repositories root path from database
201 Get's the repositories root path from database
202 """
202 """
203
203
204 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
204 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
205
205
206 return q.ui_value
206 return q.ui_value
207
207
208 def repo_scan(self, repos_path=None):
208 def repo_scan(self, repos_path=None):
209 """
209 """
210 Listing of repositories in given path. This path should not be a
210 Listing of repositories in given path. This path should not be a
211 repository itself. Return a dictionary of repository objects
211 repository itself. Return a dictionary of repository objects
212
212
213 :param repos_path: path to directory containing repositories
213 :param repos_path: path to directory containing repositories
214 """
214 """
215
215
216 if repos_path is None:
216 if repos_path is None:
217 repos_path = self.repos_path
217 repos_path = self.repos_path
218
218
219 log.info('scanning for repositories in %s' % repos_path)
219 log.info('scanning for repositories in %s' % repos_path)
220
220
221 baseui = make_ui('db')
221 baseui = make_ui('db')
222 repos = {}
222 repos = {}
223
223
224 for name, path in get_filesystem_repos(repos_path, recursive=True):
224 for name, path in get_filesystem_repos(repos_path, recursive=True):
225 # skip removed repos
225 # skip removed repos
226 if REMOVED_REPO_PAT.match(name):
226 if REMOVED_REPO_PAT.match(name):
227 continue
227 continue
228
228
229 # name need to be decomposed and put back together using the /
229 # name need to be decomposed and put back together using the /
230 # since this is internal storage separator for rhodecode
230 # since this is internal storage separator for rhodecode
231 name = Repository.url_sep().join(name.split(os.sep))
231 name = Repository.url_sep().join(name.split(os.sep))
232
232
233 try:
233 try:
234 if name in repos:
234 if name in repos:
235 raise RepositoryError('Duplicate repository name %s '
235 raise RepositoryError('Duplicate repository name %s '
236 'found in %s' % (name, path))
236 'found in %s' % (name, path))
237 else:
237 else:
238
238
239 klass = get_backend(path[0])
239 klass = get_backend(path[0])
240
240
241 if path[0] == 'hg' and path[0] in BACKENDS.keys():
241 if path[0] == 'hg' and path[0] in BACKENDS.keys():
242 repos[name] = klass(safe_str(path[1]), baseui=baseui)
242 repos[name] = klass(safe_str(path[1]), baseui=baseui)
243
243
244 if path[0] == 'git' and path[0] in BACKENDS.keys():
244 if path[0] == 'git' and path[0] in BACKENDS.keys():
245 repos[name] = klass(path[1])
245 repos[name] = klass(path[1])
246 except OSError:
246 except OSError:
247 continue
247 continue
248
248
249 return repos
249 return repos
250
250
251 def get_repos(self, all_repos=None, sort_key=None, simple=False):
251 def get_repos(self, all_repos=None, sort_key=None, simple=False):
252 """
252 """
253 Get all repos from db and for each repo create it's
253 Get all repos from db and for each repo create it's
254 backend instance and fill that backed with information from database
254 backend instance and fill that backed with information from database
255
255
256 :param all_repos: list of repository names as strings
256 :param all_repos: list of repository names as strings
257 give specific repositories list, good for filtering
257 give specific repositories list, good for filtering
258
258
259 :param sort_key: initial sorting of repos
259 :param sort_key: initial sorting of repos
260 :param simple: use SimpleCachedList - one without the SCM info
260 :param simple: use SimpleCachedList - one without the SCM info
261 """
261 """
262 if all_repos is None:
262 if all_repos is None:
263 all_repos = self.sa.query(Repository)\
263 all_repos = self.sa.query(Repository)\
264 .filter(Repository.group_id == None)\
264 .filter(Repository.group_id == None)\
265 .order_by(func.lower(Repository.repo_name)).all()
265 .order_by(func.lower(Repository.repo_name)).all()
266 if simple:
266 if simple:
267 repo_iter = SimpleCachedRepoList(all_repos,
267 repo_iter = SimpleCachedRepoList(all_repos,
268 repos_path=self.repos_path,
268 repos_path=self.repos_path,
269 order_by=sort_key)
269 order_by=sort_key)
270 else:
270 else:
271 repo_iter = CachedRepoList(all_repos,
271 repo_iter = CachedRepoList(all_repos,
272 repos_path=self.repos_path,
272 repos_path=self.repos_path,
273 order_by=sort_key)
273 order_by=sort_key)
274
274
275 return repo_iter
275 return repo_iter
276
276
277 def get_repos_groups(self, all_groups=None):
277 def get_repos_groups(self, all_groups=None):
278 if all_groups is None:
278 if all_groups is None:
279 all_groups = RepoGroup.query()\
279 all_groups = RepoGroup.query()\
280 .filter(RepoGroup.group_parent_id == None).all()
280 .filter(RepoGroup.group_parent_id == None).all()
281 group_iter = GroupList(all_groups)
281 group_iter = GroupList(all_groups)
282
282
283 return group_iter
283 return group_iter
284
284
285 def mark_for_invalidation(self, repo_name):
285 def mark_for_invalidation(self, repo_name):
286 """
286 """
287 Puts cache invalidation task into db for
287 Puts cache invalidation task into db for
288 further global cache invalidation
288 further global cache invalidation
289
289
290 :param repo_name: this repo that should invalidation take place
290 :param repo_name: this repo that should invalidation take place
291 """
291 """
292 CacheInvalidation.set_invalidate(repo_name)
292 CacheInvalidation.set_invalidate(repo_name)
293
293
294 def toggle_following_repo(self, follow_repo_id, user_id):
294 def toggle_following_repo(self, follow_repo_id, user_id):
295
295
296 f = self.sa.query(UserFollowing)\
296 f = self.sa.query(UserFollowing)\
297 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
297 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
298 .filter(UserFollowing.user_id == user_id).scalar()
298 .filter(UserFollowing.user_id == user_id).scalar()
299
299
300 if f is not None:
300 if f is not None:
301 try:
301 try:
302 self.sa.delete(f)
302 self.sa.delete(f)
303 action_logger(UserTemp(user_id),
303 action_logger(UserTemp(user_id),
304 'stopped_following_repo',
304 'stopped_following_repo',
305 RepoTemp(follow_repo_id))
305 RepoTemp(follow_repo_id))
306 return
306 return
307 except:
307 except:
308 log.error(traceback.format_exc())
308 log.error(traceback.format_exc())
309 raise
309 raise
310
310
311 try:
311 try:
312 f = UserFollowing()
312 f = UserFollowing()
313 f.user_id = user_id
313 f.user_id = user_id
314 f.follows_repo_id = follow_repo_id
314 f.follows_repo_id = follow_repo_id
315 self.sa.add(f)
315 self.sa.add(f)
316
316
317 action_logger(UserTemp(user_id),
317 action_logger(UserTemp(user_id),
318 'started_following_repo',
318 'started_following_repo',
319 RepoTemp(follow_repo_id))
319 RepoTemp(follow_repo_id))
320 except:
320 except:
321 log.error(traceback.format_exc())
321 log.error(traceback.format_exc())
322 raise
322 raise
323
323
324 def toggle_following_user(self, follow_user_id, user_id):
324 def toggle_following_user(self, follow_user_id, user_id):
325 f = self.sa.query(UserFollowing)\
325 f = self.sa.query(UserFollowing)\
326 .filter(UserFollowing.follows_user_id == follow_user_id)\
326 .filter(UserFollowing.follows_user_id == follow_user_id)\
327 .filter(UserFollowing.user_id == user_id).scalar()
327 .filter(UserFollowing.user_id == user_id).scalar()
328
328
329 if f is not None:
329 if f is not None:
330 try:
330 try:
331 self.sa.delete(f)
331 self.sa.delete(f)
332 return
332 return
333 except:
333 except:
334 log.error(traceback.format_exc())
334 log.error(traceback.format_exc())
335 raise
335 raise
336
336
337 try:
337 try:
338 f = UserFollowing()
338 f = UserFollowing()
339 f.user_id = user_id
339 f.user_id = user_id
340 f.follows_user_id = follow_user_id
340 f.follows_user_id = follow_user_id
341 self.sa.add(f)
341 self.sa.add(f)
342 except:
342 except:
343 log.error(traceback.format_exc())
343 log.error(traceback.format_exc())
344 raise
344 raise
345
345
346 def is_following_repo(self, repo_name, user_id, cache=False):
346 def is_following_repo(self, repo_name, user_id, cache=False):
347 r = self.sa.query(Repository)\
347 r = self.sa.query(Repository)\
348 .filter(Repository.repo_name == repo_name).scalar()
348 .filter(Repository.repo_name == repo_name).scalar()
349
349
350 f = self.sa.query(UserFollowing)\
350 f = self.sa.query(UserFollowing)\
351 .filter(UserFollowing.follows_repository == r)\
351 .filter(UserFollowing.follows_repository == r)\
352 .filter(UserFollowing.user_id == user_id).scalar()
352 .filter(UserFollowing.user_id == user_id).scalar()
353
353
354 return f is not None
354 return f is not None
355
355
356 def is_following_user(self, username, user_id, cache=False):
356 def is_following_user(self, username, user_id, cache=False):
357 u = User.get_by_username(username)
357 u = User.get_by_username(username)
358
358
359 f = self.sa.query(UserFollowing)\
359 f = self.sa.query(UserFollowing)\
360 .filter(UserFollowing.follows_user == u)\
360 .filter(UserFollowing.follows_user == u)\
361 .filter(UserFollowing.user_id == user_id).scalar()
361 .filter(UserFollowing.user_id == user_id).scalar()
362
362
363 return f is not None
363 return f is not None
364
364
365 def get_followers(self, repo):
365 def get_followers(self, repo):
366 repo = self._get_repo(repo)
366 repo = self._get_repo(repo)
367
367
368 return self.sa.query(UserFollowing)\
368 return self.sa.query(UserFollowing)\
369 .filter(UserFollowing.follows_repository == repo).count()
369 .filter(UserFollowing.follows_repository == repo).count()
370
370
371 def get_forks(self, repo):
371 def get_forks(self, repo):
372 repo = self._get_repo(repo)
372 repo = self._get_repo(repo)
373 return self.sa.query(Repository)\
373 return self.sa.query(Repository)\
374 .filter(Repository.fork == repo).count()
374 .filter(Repository.fork == repo).count()
375
375
376 def get_pull_requests(self, repo):
376 def get_pull_requests(self, repo):
377 repo = self._get_repo(repo)
377 repo = self._get_repo(repo)
378 return self.sa.query(PullRequest)\
378 return self.sa.query(PullRequest)\
379 .filter(PullRequest.other_repo == repo).count()
379 .filter(PullRequest.other_repo == repo).count()
380
380
381 def mark_as_fork(self, repo, fork, user):
381 def mark_as_fork(self, repo, fork, user):
382 repo = self.__get_repo(repo)
382 repo = self.__get_repo(repo)
383 fork = self.__get_repo(fork)
383 fork = self.__get_repo(fork)
384 if fork and repo.repo_id == fork.repo_id:
384 if fork and repo.repo_id == fork.repo_id:
385 raise Exception("Cannot set repository as fork of itself")
385 raise Exception("Cannot set repository as fork of itself")
386 repo.fork = fork
386 repo.fork = fork
387 self.sa.add(repo)
387 self.sa.add(repo)
388 return repo
388 return repo
389
389
390 def pull_changes(self, repo, username):
390 def pull_changes(self, repo, username):
391 dbrepo = self.__get_repo(repo)
391 dbrepo = self.__get_repo(repo)
392 clone_uri = dbrepo.clone_uri
392 clone_uri = dbrepo.clone_uri
393 if not clone_uri:
393 if not clone_uri:
394 raise Exception("This repository doesn't have a clone uri")
394 raise Exception("This repository doesn't have a clone uri")
395
395
396 repo = dbrepo.scm_instance
396 repo = dbrepo.scm_instance
397 try:
397 try:
398 extras = {
398 extras = {
399 'ip': '',
399 'ip': '',
400 'username': username,
400 'username': username,
401 'action': 'push_remote',
401 'action': 'push_remote',
402 'repository': dbrepo.repo_name,
402 'repository': dbrepo.repo_name,
403 'scm': repo.alias,
403 'scm': repo.alias,
404 }
404 }
405 Repository.inject_ui(repo, extras=extras)
405 Repository.inject_ui(repo, extras=extras)
406
406
407 if repo.alias == 'git':
407 if repo.alias == 'git':
408 repo.fetch(clone_uri)
408 repo.fetch(clone_uri)
409 else:
409 else:
410 repo.pull(clone_uri)
410 repo.pull(clone_uri)
411 self.mark_for_invalidation(dbrepo.repo_name)
411 self.mark_for_invalidation(dbrepo.repo_name)
412 except:
412 except:
413 log.error(traceback.format_exc())
413 log.error(traceback.format_exc())
414 raise
414 raise
415
415
416 def commit_change(self, repo, repo_name, cs, user, author, message,
416 def commit_change(self, repo, repo_name, cs, user, author, message,
417 content, f_path):
417 content, f_path):
418 """
418 """
419 Commits changes
419 Commits changes
420
420
421 :param repo: SCM instance
421 :param repo: SCM instance
422
422
423 """
423 """
424
424
425 if repo.alias == 'hg':
425 if repo.alias == 'hg':
426 from rhodecode.lib.vcs.backends.hg import \
426 from rhodecode.lib.vcs.backends.hg import \
427 MercurialInMemoryChangeset as IMC
427 MercurialInMemoryChangeset as IMC
428 elif repo.alias == 'git':
428 elif repo.alias == 'git':
429 from rhodecode.lib.vcs.backends.git import \
429 from rhodecode.lib.vcs.backends.git import \
430 GitInMemoryChangeset as IMC
430 GitInMemoryChangeset as IMC
431
431
432 # decoding here will force that we have proper encoded values
432 # decoding here will force that we have proper encoded values
433 # in any other case this will throw exceptions and deny commit
433 # in any other case this will throw exceptions and deny commit
434 content = safe_str(content)
434 content = safe_str(content)
435 path = safe_str(f_path)
435 path = safe_str(f_path)
436 # message and author needs to be unicode
436 # message and author needs to be unicode
437 # proper backend should then translate that into required type
437 # proper backend should then translate that into required type
438 message = safe_unicode(message)
438 message = safe_unicode(message)
439 author = safe_unicode(author)
439 author = safe_unicode(author)
440 m = IMC(repo)
440 m = IMC(repo)
441 m.change(FileNode(path, content))
441 m.change(FileNode(path, content))
442 tip = m.commit(message=message,
442 tip = m.commit(message=message,
443 author=author,
443 author=author,
444 parents=[cs], branch=cs.branch)
444 parents=[cs], branch=cs.branch)
445
445
446 action = 'push_local:%s' % tip.raw_id
446 action = 'push_local:%s' % tip.raw_id
447 action_logger(user, action, repo_name)
447 action_logger(user, action, repo_name)
448 self.mark_for_invalidation(repo_name)
448 self.mark_for_invalidation(repo_name)
449 return tip
449 return tip
450
450
451 def create_node(self, repo, repo_name, cs, user, author, message, content,
451 def create_node(self, repo, repo_name, cs, user, author, message, content,
452 f_path):
452 f_path):
453 if repo.alias == 'hg':
453 if repo.alias == 'hg':
454 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
454 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
455 elif repo.alias == 'git':
455 elif repo.alias == 'git':
456 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
456 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
457 # decoding here will force that we have proper encoded values
457 # decoding here will force that we have proper encoded values
458 # in any other case this will throw exceptions and deny commit
458 # in any other case this will throw exceptions and deny commit
459
459
460 if isinstance(content, (basestring,)):
460 if isinstance(content, (basestring,)):
461 content = safe_str(content)
461 content = safe_str(content)
462 elif isinstance(content, (file, cStringIO.OutputType,)):
462 elif isinstance(content, (file, cStringIO.OutputType,)):
463 content = content.read()
463 content = content.read()
464 else:
464 else:
465 raise Exception('Content is of unrecognized type %s' % (
465 raise Exception('Content is of unrecognized type %s' % (
466 type(content)
466 type(content)
467 ))
467 ))
468
468
469 message = safe_unicode(message)
469 message = safe_unicode(message)
470 author = safe_unicode(author)
470 author = safe_unicode(author)
471 path = safe_str(f_path)
471 path = safe_str(f_path)
472 m = IMC(repo)
472 m = IMC(repo)
473
473
474 if isinstance(cs, EmptyChangeset):
474 if isinstance(cs, EmptyChangeset):
475 # EmptyChangeset means we we're editing empty repository
475 # EmptyChangeset means we we're editing empty repository
476 parents = None
476 parents = None
477 else:
477 else:
478 parents = [cs]
478 parents = [cs]
479
479
480 m.add(FileNode(path, content=content))
480 m.add(FileNode(path, content=content))
481 tip = m.commit(message=message,
481 tip = m.commit(message=message,
482 author=author,
482 author=author,
483 parents=parents, branch=cs.branch)
483 parents=parents, branch=cs.branch)
484
484
485 action = 'push_local:%s' % tip.raw_id
485 action = 'push_local:%s' % tip.raw_id
486 action_logger(user, action, repo_name)
486 action_logger(user, action, repo_name)
487 self.mark_for_invalidation(repo_name)
487 self.mark_for_invalidation(repo_name)
488 return tip
488 return tip
489
489
490 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
490 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
491 """
491 """
492 recursive walk in root dir and return a set of all path in that dir
492 recursive walk in root dir and return a set of all path in that dir
493 based on repository walk function
493 based on repository walk function
494
494
495 :param repo_name: name of repository
495 :param repo_name: name of repository
496 :param revision: revision for which to list nodes
496 :param revision: revision for which to list nodes
497 :param root_path: root path to list
497 :param root_path: root path to list
498 :param flat: return as a list, if False returns a dict with decription
498 :param flat: return as a list, if False returns a dict with decription
499
499
500 """
500 """
501 _files = list()
501 _files = list()
502 _dirs = list()
502 _dirs = list()
503 try:
503 try:
504 _repo = self.__get_repo(repo_name)
504 _repo = self.__get_repo(repo_name)
505 changeset = _repo.scm_instance.get_changeset(revision)
505 changeset = _repo.scm_instance.get_changeset(revision)
506 root_path = root_path.lstrip('/')
506 root_path = root_path.lstrip('/')
507 for topnode, dirs, files in changeset.walk(root_path):
507 for topnode, dirs, files in changeset.walk(root_path):
508 for f in files:
508 for f in files:
509 _files.append(f.path if flat else {"name": f.path,
509 _files.append(f.path if flat else {"name": f.path,
510 "type": "file"})
510 "type": "file"})
511 for d in dirs:
511 for d in dirs:
512 _dirs.append(d.path if flat else {"name": d.path,
512 _dirs.append(d.path if flat else {"name": d.path,
513 "type": "dir"})
513 "type": "dir"})
514 except RepositoryError:
514 except RepositoryError:
515 log.debug(traceback.format_exc())
515 log.debug(traceback.format_exc())
516 raise
516 raise
517
517
518 return _dirs, _files
518 return _dirs, _files
519
519
520 def get_unread_journal(self):
520 def get_unread_journal(self):
521 return self.sa.query(UserLog).count()
521 return self.sa.query(UserLog).count()
522
522
523 def get_repo_landing_revs(self, repo=None):
523 def get_repo_landing_revs(self, repo=None):
524 """
524 """
525 Generates select option with tags branches and bookmarks (for hg only)
525 Generates select option with tags branches and bookmarks (for hg only)
526 grouped by type
526 grouped by type
527
527
528 :param repo:
528 :param repo:
529 :type repo:
529 :type repo:
530 """
530 """
531
531
532 hist_l = []
532 hist_l = []
533 choices = []
533 choices = []
534 repo = self.__get_repo(repo)
534 repo = self.__get_repo(repo)
535 hist_l.append(['tip', _('latest tip')])
535 hist_l.append(['tip', _('latest tip')])
536 choices.append('tip')
536 choices.append('tip')
537 if not repo:
537 if not repo:
538 return choices, hist_l
538 return choices, hist_l
539
539
540 repo = repo.scm_instance
540 repo = repo.scm_instance
541
541
542 branches_group = ([(k, k) for k, v in
542 branches_group = ([(k, k) for k, v in
543 repo.branches.iteritems()], _("Branches"))
543 repo.branches.iteritems()], _("Branches"))
544 hist_l.append(branches_group)
544 hist_l.append(branches_group)
545 choices.extend([x[0] for x in branches_group[0]])
545 choices.extend([x[0] for x in branches_group[0]])
546
546
547 if repo.alias == 'hg':
547 if repo.alias == 'hg':
548 bookmarks_group = ([(k, k) for k, v in
548 bookmarks_group = ([(k, k) for k, v in
549 repo.bookmarks.iteritems()], _("Bookmarks"))
549 repo.bookmarks.iteritems()], _("Bookmarks"))
550 hist_l.append(bookmarks_group)
550 hist_l.append(bookmarks_group)
551 choices.extend([x[0] for x in bookmarks_group[0]])
551 choices.extend([x[0] for x in bookmarks_group[0]])
552
552
553 tags_group = ([(k, k) for k, v in
553 tags_group = ([(k, k) for k, v in
554 repo.tags.iteritems()], _("Tags"))
554 repo.tags.iteritems()], _("Tags"))
555 hist_l.append(tags_group)
555 hist_l.append(tags_group)
556 choices.extend([x[0] for x in tags_group[0]])
556 choices.extend([x[0] for x in tags_group[0]])
557
557
558 return choices, hist_l
558 return choices, hist_l
559
559
560 def install_git_hook(self, repo, force_create=False):
560 def install_git_hook(self, repo, force_create=False):
561 """
561 """
562 Creates a rhodecode hook inside a git repository
562 Creates a rhodecode hook inside a git repository
563
563
564 :param repo: Instance of VCS repo
564 :param repo: Instance of VCS repo
565 :param force_create: Create even if same name hook exists
565 :param force_create: Create even if same name hook exists
566 """
566 """
567
567
568 loc = jn(repo.path, 'hooks')
568 loc = jn(repo.path, 'hooks')
569 if not repo.bare:
569 if not repo.bare:
570 loc = jn(repo.path, '.git', 'hooks')
570 loc = jn(repo.path, '.git', 'hooks')
571 if not os.path.isdir(loc):
571 if not os.path.isdir(loc):
572 os.makedirs(loc)
572 os.makedirs(loc)
573
573
574 tmpl = pkg_resources.resource_string(
574 tmpl_post = pkg_resources.resource_string(
575 'rhodecode', jn('config', 'post_receive_tmpl.py')
575 'rhodecode', jn('config', 'post_receive_tmpl.py')
576 )
576 )
577 tmpl_pre = pkg_resources.resource_string(
578 'rhodecode', jn('config', 'pre_receive_tmpl.py')
579 )
577
580
578 _hook_file = jn(loc, 'post-receive')
581 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
579 _rhodecode_hook = False
582 _hook_file = jn(loc, '%s-receive' % h_type)
580 log.debug('Installing git hook in repo %s' % repo)
583 _rhodecode_hook = False
581 if os.path.exists(_hook_file):
584 log.debug('Installing git hook in repo %s' % repo)
582 # let's take a look at this hook, maybe it's rhodecode ?
585 if os.path.exists(_hook_file):
583 log.debug('hook exists, checking if it is from rhodecode')
586 # let's take a look at this hook, maybe it's rhodecode ?
584 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
587 log.debug('hook exists, checking if it is from rhodecode')
585 with open(_hook_file, 'rb') as f:
588 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
586 data = f.read()
589 with open(_hook_file, 'rb') as f:
587 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
590 data = f.read()
588 % 'RC_HOOK_VER').search(data)
591 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
589 if matches:
592 % 'RC_HOOK_VER').search(data)
590 try:
593 if matches:
591 ver = matches.groups()[0]
594 try:
592 log.debug('got %s it is rhodecode' % (ver))
595 ver = matches.groups()[0]
593 _rhodecode_hook = True
596 log.debug('got %s it is rhodecode' % (ver))
594 except:
597 _rhodecode_hook = True
595 log.error(traceback.format_exc())
598 except:
599 log.error(traceback.format_exc())
600 else:
601 # there is no hook in this dir, so we want to create one
602 _rhodecode_hook = True
596
603
597 if _rhodecode_hook or force_create:
604 if _rhodecode_hook or force_create:
598 log.debug('writing hook file !')
605 log.debug('writing %s hook file !' % h_type)
599 with open(_hook_file, 'wb') as f:
606 with open(_hook_file, 'wb') as f:
600 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
607 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
601 f.write(tmpl)
608 f.write(tmpl)
602 os.chmod(_hook_file, 0755)
609 os.chmod(_hook_file, 0755)
603 else:
610 else:
604 log.debug('skipping writing hook file')
611 log.debug('skipping writing hook file')
@@ -1,240 +1,268 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name}
5 ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(_('Repositories'),h.url('repos'))}
11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 &raquo;
12 &raquo;
13 ${_('edit')} &raquo; ${h.link_to(c.repo_info.just_name,h.url('summary_home',repo_name=c.repo_name))}
13 ${_('edit')} &raquo; ${h.link_to(c.repo_info.just_name,h.url('summary_home',repo_name=c.repo_name))}
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="repo_name">${_('Name')}:</label>
32 <label for="repo_name">${_('Name')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('repo_name',class_="medium")}
35 ${h.text('repo_name',class_="medium")}
36 </div>
36 </div>
37 </div>
37 </div>
38 <div class="field">
38 <div class="field">
39 <div class="label">
39 <div class="label">
40 <label for="clone_uri">${_('Clone uri')}:</label>
40 <label for="clone_uri">${_('Clone uri')}:</label>
41 </div>
41 </div>
42 <div class="input">
42 <div class="input">
43 ${h.text('clone_uri',class_="medium")}
43 ${h.text('clone_uri',class_="medium")}
44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
45 </div>
45 </div>
46 </div>
46 </div>
47 <div class="field">
47 <div class="field">
48 <div class="label">
48 <div class="label">
49 <label for="repo_group">${_('Repository group')}:</label>
49 <label for="repo_group">${_('Repository group')}:</label>
50 </div>
50 </div>
51 <div class="input">
51 <div class="input">
52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
54 </div>
54 </div>
55 </div>
55 </div>
56 <div class="field">
56 <div class="field">
57 <div class="label">
57 <div class="label">
58 <label for="repo_type">${_('Type')}:</label>
58 <label for="repo_type">${_('Type')}:</label>
59 </div>
59 </div>
60 <div class="input">
60 <div class="input">
61 ${h.select('repo_type','hg',c.backends,class_="medium")}
61 ${h.select('repo_type','hg',c.backends,class_="medium")}
62 </div>
62 </div>
63 </div>
63 </div>
64 <div class="field">
64 <div class="field">
65 <div class="label">
65 <div class="label">
66 <label for="landing_rev">${_('Landing revision')}:</label>
66 <label for="landing_rev">${_('Landing revision')}:</label>
67 </div>
67 </div>
68 <div class="input">
68 <div class="input">
69 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
69 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
71 </div>
71 </div>
72 </div>
72 </div>
73 <div class="field">
73 <div class="field">
74 <div class="label label-textarea">
74 <div class="label label-textarea">
75 <label for="description">${_('Description')}:</label>
75 <label for="description">${_('Description')}:</label>
76 </div>
76 </div>
77 <div class="textarea text-area editor">
77 <div class="textarea text-area editor">
78 ${h.textarea('description')}
78 ${h.textarea('description')}
79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
80 </div>
80 </div>
81 </div>
81 </div>
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label label-checkbox">
84 <div class="label label-checkbox">
85 <label for="private">${_('Private repository')}:</label>
85 <label for="private">${_('Private repository')}:</label>
86 </div>
86 </div>
87 <div class="checkboxes">
87 <div class="checkboxes">
88 ${h.checkbox('private',value="True")}
88 ${h.checkbox('private',value="True")}
89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
90 </div>
90 </div>
91 </div>
91 </div>
92 <div class="field">
92 <div class="field">
93 <div class="label label-checkbox">
93 <div class="label label-checkbox">
94 <label for="enable_statistics">${_('Enable statistics')}:</label>
94 <label for="enable_statistics">${_('Enable statistics')}:</label>
95 </div>
95 </div>
96 <div class="checkboxes">
96 <div class="checkboxes">
97 ${h.checkbox('enable_statistics',value="True")}
97 ${h.checkbox('enable_statistics',value="True")}
98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
99 </div>
99 </div>
100 </div>
100 </div>
101 <div class="field">
101 <div class="field">
102 <div class="label label-checkbox">
102 <div class="label label-checkbox">
103 <label for="enable_downloads">${_('Enable downloads')}:</label>
103 <label for="enable_downloads">${_('Enable downloads')}:</label>
104 </div>
104 </div>
105 <div class="checkboxes">
105 <div class="checkboxes">
106 ${h.checkbox('enable_downloads',value="True")}
106 ${h.checkbox('enable_downloads',value="True")}
107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
108 </div>
108 </div>
109 </div>
109 </div>
110 <div class="field">
110 <div class="field">
111 <div class="label label-checkbox">
112 <label for="enable_locking">${_('Enable locking')}:</label>
113 </div>
114 <div class="checkboxes">
115 ${h.checkbox('enable_locking',value="True")}
116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
117 </div>
118 </div>
119 <div class="field">
111 <div class="label">
120 <div class="label">
112 <label for="user">${_('Owner')}:</label>
121 <label for="user">${_('Owner')}:</label>
113 </div>
122 </div>
114 <div class="input input-medium ac">
123 <div class="input input-medium ac">
115 <div class="perm_ac">
124 <div class="perm_ac">
116 ${h.text('user',class_='yui-ac-input')}
125 ${h.text('user',class_='yui-ac-input')}
117 <span class="help-block">${_('Change owner of this repository.')}</span>
126 <span class="help-block">${_('Change owner of this repository.')}</span>
118 <div id="owner_container"></div>
127 <div id="owner_container"></div>
119 </div>
128 </div>
120 </div>
129 </div>
121 </div>
130 </div>
122
131
123 <div class="field">
132 <div class="field">
124 <div class="label">
133 <div class="label">
125 <label for="input">${_('Permissions')}:</label>
134 <label for="input">${_('Permissions')}:</label>
126 </div>
135 </div>
127 <div class="input">
136 <div class="input">
128 <%include file="repo_edit_perms.html"/>
137 <%include file="repo_edit_perms.html"/>
129 </div>
138 </div>
130
139
131 <div class="buttons">
140 <div class="buttons">
132 ${h.submit('save',_('Save'),class_="ui-btn large")}
141 ${h.submit('save',_('Save'),class_="ui-btn large")}
133 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
142 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
134 </div>
143 </div>
135 </div>
144 </div>
136 </div>
145 </div>
137 </div>
146 </div>
138 ${h.end_form()}
147 ${h.end_form()}
139 </div>
148 </div>
140
149
141 <div class="box box-right">
150 <div class="box box-right">
142 <div class="title">
151 <div class="title">
143 <h5>${_('Administration')}</h5>
152 <h5>${_('Administration')}</h5>
144 </div>
153 </div>
145
154
146 <h3>${_('Statistics')}</h3>
155 <h3>${_('Statistics')}</h3>
147 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
156 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
148 <div class="form">
157 <div class="form">
149 <div class="fields">
158 <div class="fields">
150 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
159 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
151 <div class="field" style="border:none;color:#888">
160 <div class="field" style="border:none;color:#888">
152 <ul>
161 <ul>
153 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
162 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
154 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
163 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
155 </ul>
164 </ul>
156 </div>
165 </div>
157 </div>
166 </div>
158 </div>
167 </div>
159 ${h.end_form()}
168 ${h.end_form()}
160
169
161 %if c.repo_info.clone_uri:
170 %if c.repo_info.clone_uri:
162 <h3>${_('Remote')}</h3>
171 <h3>${_('Remote')}</h3>
163 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
172 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
164 <div class="form">
173 <div class="form">
165 <div class="fields">
174 <div class="fields">
166 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
175 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
167 <div class="field" style="border:none">
176 <div class="field" style="border:none">
168 <ul>
177 <ul>
169 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
178 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
170 </ul>
179 </ul>
171 </div>
180 </div>
172 </div>
181 </div>
173 </div>
182 </div>
174 ${h.end_form()}
183 ${h.end_form()}
175 %endif
184 %endif
176
185
177 <h3>${_('Cache')}</h3>
186 <h3>${_('Cache')}</h3>
178 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
187 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
179 <div class="form">
188 <div class="form">
180 <div class="fields">
189 <div class="fields">
181 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
190 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
182 </div>
191 </div>
183 </div>
192 </div>
184 ${h.end_form()}
193 ${h.end_form()}
185
194
186 <h3>${_('Public journal')}</h3>
195 <h3>${_('Public journal')}</h3>
187 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
196 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
188 <div class="form">
197 <div class="form">
189 ${h.hidden('auth_token',str(h.get_token()))}
198 ${h.hidden('auth_token',str(h.get_token()))}
190 <div class="field">
199 <div class="field">
191 %if c.in_public_journal:
200 %if c.in_public_journal:
192 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
201 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
193 %else:
202 %else:
194 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
203 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
195 %endif
204 %endif
196 </div>
205 </div>
197 <div class="field" style="border:none;color:#888">
206 <div class="field" style="border:none;color:#888">
198 <ul>
207 <ul>
199 <li>${_('''All actions made on this repository will be accessible to everyone in public journal''')}
208 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
200 </li>
209 </li>
201 </ul>
210 </ul>
202 </div>
211 </div>
203 </div>
212 </div>
204 ${h.end_form()}
213 ${h.end_form()}
205
214
206 <h3>${_('Delete')}</h3>
215 <h3>${_('Locking')}</h3>
207 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
216 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
208 <div class="form">
217 <div class="form">
209 <div class="fields">
218 <div class="fields">
210 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
219 %if c.repo_info.locked[0]:
220 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
221 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
222 %else:
223 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
224 ${_('Repository is not locked')}
225 %endif
211 </div>
226 </div>
212 <div class="field" style="border:none;color:#888">
227 <div class="field" style="border:none;color:#888">
213 <ul>
228 <ul>
214 <li>${_('''This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems.
229 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
215 If you need fully delete it from filesystem please do it manually''')}
216 </li>
230 </li>
217 </ul>
231 </ul>
218 </div>
232 </div>
219 </div>
233 </div>
220 ${h.end_form()}
234 ${h.end_form()}
221
235
222 <h3>${_('Set as fork of')}</h3>
236 <h3>${_('Set as fork of')}</h3>
223 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
237 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
224 <div class="form">
238 <div class="form">
225 <div class="fields">
239 <div class="fields">
226 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
240 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
227 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
241 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
228 </div>
242 </div>
229 <div class="field" style="border:none;color:#888">
243 <div class="field" style="border:none;color:#888">
230 <ul>
244 <ul>
231 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
245 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
232 </ul>
246 </ul>
233 </div>
247 </div>
234 </div>
248 </div>
235 ${h.end_form()}
249 ${h.end_form()}
236
250
251 <h3>${_('Delete')}</h3>
252 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
253 <div class="form">
254 <div class="fields">
255 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
256 </div>
257 <div class="field" style="border:none;color:#888">
258 <ul>
259 <li>${_('''This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems.
260 If you need fully delete it from filesystem please do it manually''')}
261 </li>
262 </ul>
263 </div>
264 </div>
265 ${h.end_form()}
237 </div>
266 </div>
238
267
239
240 </%def>
268 </%def>
@@ -1,327 +1,327 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Settings administration')} - ${c.rhodecode_name}
5 ${_('Settings administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 <!-- end box / title -->
22 <!-- end box / title -->
23
23
24 <h3>${_('Remap and rescan repositories')}</h3>
24 <h3>${_('Remap and rescan repositories')}</h3>
25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 <div class="form">
26 <div class="form">
27 <!-- fields -->
27 <!-- fields -->
28
28
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label label-checkbox">
31 <div class="label label-checkbox">
32 <label for="destroy">${_('rescan option')}:</label>
32 <label for="destroy">${_('rescan option')}:</label>
33 </div>
33 </div>
34 <div class="checkboxes">
34 <div class="checkboxes">
35 <div class="checkbox">
35 <div class="checkbox">
36 ${h.checkbox('destroy',True)}
36 ${h.checkbox('destroy',True)}
37 <label for="destroy">
37 <label for="destroy">
38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
39 ${_('destroy old data')}</span> </label>
39 ${_('destroy old data')}</span> </label>
40 </div>
40 </div>
41 <span class="help-block">${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}</span>
41 <span class="help-block">${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}</span>
42 </div>
42 </div>
43 </div>
43 </div>
44
44
45 <div class="buttons">
45 <div class="buttons">
46 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
46 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
47 </div>
47 </div>
48 </div>
48 </div>
49 </div>
49 </div>
50 ${h.end_form()}
50 ${h.end_form()}
51
51
52 <h3>${_('Whoosh indexing')}</h3>
52 <h3>${_('Whoosh indexing')}</h3>
53 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
53 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
54 <div class="form">
54 <div class="form">
55 <!-- fields -->
55 <!-- fields -->
56
56
57 <div class="fields">
57 <div class="fields">
58 <div class="field">
58 <div class="field">
59 <div class="label label-checkbox">
59 <div class="label label-checkbox">
60 <label>${_('index build option')}:</label>
60 <label>${_('index build option')}:</label>
61 </div>
61 </div>
62 <div class="checkboxes">
62 <div class="checkboxes">
63 <div class="checkbox">
63 <div class="checkbox">
64 ${h.checkbox('full_index',True)}
64 ${h.checkbox('full_index',True)}
65 <label for="full_index">${_('build from scratch')}</label>
65 <label for="full_index">${_('build from scratch')}</label>
66 </div>
66 </div>
67 </div>
67 </div>
68 </div>
68 </div>
69
69
70 <div class="buttons">
70 <div class="buttons">
71 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
71 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
72 </div>
72 </div>
73 </div>
73 </div>
74 </div>
74 </div>
75 ${h.end_form()}
75 ${h.end_form()}
76
76
77 <h3>${_('Global application settings')}</h3>
77 <h3>${_('Global application settings')}</h3>
78 ${h.form(url('admin_setting', setting_id='global'),method='put')}
78 ${h.form(url('admin_setting', setting_id='global'),method='put')}
79 <div class="form">
79 <div class="form">
80 <!-- fields -->
80 <!-- fields -->
81
81
82 <div class="fields">
82 <div class="fields">
83
83
84 <div class="field">
84 <div class="field">
85 <div class="label">
85 <div class="label">
86 <label for="rhodecode_title">${_('Application name')}:</label>
86 <label for="rhodecode_title">${_('Application name')}:</label>
87 </div>
87 </div>
88 <div class="input">
88 <div class="input">
89 ${h.text('rhodecode_title',size=30)}
89 ${h.text('rhodecode_title',size=30)}
90 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div class="field">
93 <div class="field">
94 <div class="label">
94 <div class="label">
95 <label for="rhodecode_realm">${_('Realm text')}:</label>
95 <label for="rhodecode_realm">${_('Realm text')}:</label>
96 </div>
96 </div>
97 <div class="input">
97 <div class="input">
98 ${h.text('rhodecode_realm',size=30)}
98 ${h.text('rhodecode_realm',size=30)}
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102 <div class="field">
102 <div class="field">
103 <div class="label">
103 <div class="label">
104 <label for="rhodecode_ga_code">${_('GA code')}:</label>
104 <label for="rhodecode_ga_code">${_('GA code')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 ${h.text('rhodecode_ga_code',size=30)}
107 ${h.text('rhodecode_ga_code',size=30)}
108 </div>
108 </div>
109 </div>
109 </div>
110
110
111 <div class="buttons">
111 <div class="buttons">
112 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
112 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
113 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
113 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
114 </div>
114 </div>
115 </div>
115 </div>
116 </div>
116 </div>
117 ${h.end_form()}
117 ${h.end_form()}
118
118
119 <h3>${_('Visualisation settings')}</h3>
119 <h3>${_('Visualisation settings')}</h3>
120 ${h.form(url('admin_setting', setting_id='visual'),method='put')}
120 ${h.form(url('admin_setting', setting_id='visual'),method='put')}
121 <div class="form">
121 <div class="form">
122 <!-- fields -->
122 <!-- fields -->
123
123
124 <div class="fields">
124 <div class="fields">
125
125
126 <div class="field">
126 <div class="field">
127 <div class="label label-checkbox">
127 <div class="label label-checkbox">
128 <label>${_('Icons')}:</label>
128 <label>${_('Icons')}:</label>
129 </div>
129 </div>
130 <div class="checkboxes">
130 <div class="checkboxes">
131 <div class="checkbox">
131 <div class="checkbox">
132 ${h.checkbox('rhodecode_show_public_icon','True')}
132 ${h.checkbox('rhodecode_show_public_icon','True')}
133 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
133 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
134 </div>
134 </div>
135 <div class="checkbox">
135 <div class="checkbox">
136 ${h.checkbox('rhodecode_show_private_icon','True')}
136 ${h.checkbox('rhodecode_show_private_icon','True')}
137 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
137 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
138 </div>
138 </div>
139 </div>
139 </div>
140 </div>
140 </div>
141
141
142 <div class="field">
142 <div class="field">
143 <div class="label label-checkbox">
143 <div class="label label-checkbox">
144 <label>${_('Meta-Tagging')}:</label>
144 <label>${_('Meta-Tagging')}:</label>
145 </div>
145 </div>
146 <div class="checkboxes">
146 <div class="checkboxes">
147 <div class="checkbox">
147 <div class="checkbox">
148 ${h.checkbox('rhodecode_stylify_metatags','True')}
148 ${h.checkbox('rhodecode_stylify_metatags','True')}
149 <label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
149 <label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
150 </div>
150 </div>
151 <div style="padding-left: 20px;">
151 <div style="padding-left: 20px;">
152 <ul> <!-- Fix style here -->
152 <ul> <!-- Fix style here -->
153 <li>[featured] <span class="metatag" tag="featured">featured</span></li>
153 <li>[featured] <span class="metatag" tag="featured">featured</span></li>
154 <li>[stale] <span class="metatag" tag="stale">stale</span></li>
154 <li>[stale] <span class="metatag" tag="stale">stale</span></li>
155 <li>[dead] <span class="metatag" tag="dead">dead</span></li>
155 <li>[dead] <span class="metatag" tag="dead">dead</span></li>
156 <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
156 <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
157 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
157 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
158 <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
158 <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
159 <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
159 <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
160 <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
160 <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
161 </ul>
161 </ul>
162 </div>
162 </div>
163 </div>
163 </div>
164 </div>
164 </div>
165
165
166 <div class="buttons">
166 <div class="buttons">
167 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
167 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
168 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
168 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
169 </div>
169 </div>
170
170
171 </div>
171 </div>
172 </div>
172 </div>
173 ${h.end_form()}
173 ${h.end_form()}
174
174
175
175
176 <h3>${_('VCS settings')}</h3>
176 <h3>${_('VCS settings')}</h3>
177 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
177 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
178 <div class="form">
178 <div class="form">
179 <!-- fields -->
179 <!-- fields -->
180
180
181 <div class="fields">
181 <div class="fields">
182
182
183 <div class="field">
183 <div class="field">
184 <div class="label label-checkbox">
184 <div class="label label-checkbox">
185 <label>${_('Web')}:</label>
185 <label>${_('Web')}:</label>
186 </div>
186 </div>
187 <div class="checkboxes">
187 <div class="checkboxes">
188 <div class="checkbox">
188 <div class="checkbox">
189 ${h.checkbox('web_push_ssl','true')}
189 ${h.checkbox('web_push_ssl','true')}
190 <label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
190 <label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
191 </div>
191 </div>
192 <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
192 <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
193 </div>
193 </div>
194 </div>
194 </div>
195
195
196 <div class="field">
196 <div class="field">
197 <div class="label label-checkbox">
197 <div class="label label-checkbox">
198 <label>${_('Hooks')}:</label>
198 <label>${_('Hooks')}:</label>
199 </div>
199 </div>
200 <div class="checkboxes">
200 <div class="checkboxes">
201 <div class="checkbox">
201 <div class="checkbox">
202 ${h.checkbox('hooks_changegroup_update','True')}
202 ${h.checkbox('hooks_changegroup_update','True')}
203 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
203 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
204 </div>
204 </div>
205 <div class="checkbox">
205 <div class="checkbox">
206 ${h.checkbox('hooks_changegroup_repo_size','True')}
206 ${h.checkbox('hooks_changegroup_repo_size','True')}
207 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
207 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
208 </div>
208 </div>
209 <div class="checkbox">
209 <div class="checkbox">
210 ${h.checkbox('hooks_changegroup_push_logger','True')}
210 ${h.checkbox('hooks_changegroup_push_logger','True')}
211 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
211 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
212 </div>
212 </div>
213 <div class="checkbox">
213 <div class="checkbox">
214 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
214 ${h.checkbox('hooks_outgoing_pull_logger','True')}
215 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
215 <label for="hooks_outgoing_pull_logger">${_('Log user pull commands')}</label>
216 </div>
216 </div>
217 </div>
217 </div>
218 <div class="input" style="margin-top:10px">
218 <div class="input" style="margin-top:10px">
219 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
219 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
220 </div>
220 </div>
221 </div>
221 </div>
222 <div class="field">
222 <div class="field">
223 <div class="label label-checkbox">
223 <div class="label label-checkbox">
224 <label>${_('Mercurial Extensions')}:</label>
224 <label>${_('Mercurial Extensions')}:</label>
225 </div>
225 </div>
226 <div class="checkboxes">
226 <div class="checkboxes">
227 <div class="checkbox">
227 <div class="checkbox">
228 ${h.checkbox('extensions_largefiles','True')}
228 ${h.checkbox('extensions_largefiles','True')}
229 <label for="extensions_hgsubversion">${_('largefiles extensions')}</label>
229 <label for="extensions_hgsubversion">${_('largefiles extensions')}</label>
230 </div>
230 </div>
231 <div class="checkbox">
231 <div class="checkbox">
232 ${h.checkbox('extensions_hgsubversion','True')}
232 ${h.checkbox('extensions_hgsubversion','True')}
233 <label for="extensions_hgsubversion">${_('hgsubversion extensions')}</label>
233 <label for="extensions_hgsubversion">${_('hgsubversion extensions')}</label>
234 </div>
234 </div>
235 <span class="help-block">${_('Requires hgsubversion library installed. Allows clonning from svn remote locations')}</span>
235 <span class="help-block">${_('Requires hgsubversion library installed. Allows clonning from svn remote locations')}</span>
236 ##<div class="checkbox">
236 ##<div class="checkbox">
237 ## ${h.checkbox('extensions_hggit','True')}
237 ## ${h.checkbox('extensions_hggit','True')}
238 ## <label for="extensions_hggit">${_('hg-git extensions')}</label>
238 ## <label for="extensions_hggit">${_('hg-git extensions')}</label>
239 ##</div>
239 ##</div>
240 ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
240 ##<span class="help-block">${_('Requires hg-git library installed. Allows clonning from git remote locations')}</span>
241 </div>
241 </div>
242 </div>
242 </div>
243 <div class="field">
243 <div class="field">
244 <div class="label">
244 <div class="label">
245 <label for="paths_root_path">${_('Repositories location')}:</label>
245 <label for="paths_root_path">${_('Repositories location')}:</label>
246 </div>
246 </div>
247 <div class="input">
247 <div class="input">
248 ${h.text('paths_root_path',size=30,readonly="readonly")}
248 ${h.text('paths_root_path',size=30,readonly="readonly")}
249 <span id="path_unlock" class="tooltip"
249 <span id="path_unlock" class="tooltip"
250 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
250 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
251 ${_('unlock')}</span>
251 ${_('unlock')}</span>
252 <span class="help-block">${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}</span>
252 <span class="help-block">${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}</span>
253 </div>
253 </div>
254 </div>
254 </div>
255
255
256 <div class="buttons">
256 <div class="buttons">
257 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
257 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
258 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
258 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
259 </div>
259 </div>
260 </div>
260 </div>
261 </div>
261 </div>
262 ${h.end_form()}
262 ${h.end_form()}
263
263
264 <script type="text/javascript">
264 <script type="text/javascript">
265 YAHOO.util.Event.onDOMReady(function(){
265 YAHOO.util.Event.onDOMReady(function(){
266 YAHOO.util.Event.addListener('path_unlock','click',function(){
266 YAHOO.util.Event.addListener('path_unlock','click',function(){
267 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
267 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
268 });
268 });
269 });
269 });
270 </script>
270 </script>
271
271
272 <h3>${_('Test Email')}</h3>
272 <h3>${_('Test Email')}</h3>
273 ${h.form(url('admin_setting', setting_id='email'),method='put')}
273 ${h.form(url('admin_setting', setting_id='email'),method='put')}
274 <div class="form">
274 <div class="form">
275 <!-- fields -->
275 <!-- fields -->
276
276
277 <div class="fields">
277 <div class="fields">
278 <div class="field">
278 <div class="field">
279 <div class="label">
279 <div class="label">
280 <label for="test_email">${_('Email to')}:</label>
280 <label for="test_email">${_('Email to')}:</label>
281 </div>
281 </div>
282 <div class="input">
282 <div class="input">
283 ${h.text('test_email',size=30)}
283 ${h.text('test_email',size=30)}
284 </div>
284 </div>
285 </div>
285 </div>
286
286
287 <div class="buttons">
287 <div class="buttons">
288 ${h.submit('send',_('Send'),class_="ui-btn large")}
288 ${h.submit('send',_('Send'),class_="ui-btn large")}
289 </div>
289 </div>
290 </div>
290 </div>
291 </div>
291 </div>
292 ${h.end_form()}
292 ${h.end_form()}
293
293
294 <h3>${_('System Info and Packages')}</h3>
294 <h3>${_('System Info and Packages')}</h3>
295 <div class="form">
295 <div class="form">
296 <div>
296 <div>
297 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
297 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
298 </div>
298 </div>
299 <div id="expand_modules_table" style="display:none">
299 <div id="expand_modules_table" style="display:none">
300 <h5>Python - ${c.py_version}</h5>
300 <h5>Python - ${c.py_version}</h5>
301 <h5>System - ${c.platform}</h5>
301 <h5>System - ${c.platform}</h5>
302
302
303 <table class="table" style="margin:0px 0px 0px 20px">
303 <table class="table" style="margin:0px 0px 0px 20px">
304 <colgroup>
304 <colgroup>
305 <col style="width:220px">
305 <col style="width:220px">
306 </colgroup>
306 </colgroup>
307 <tbody>
307 <tbody>
308 %for key, value in c.modules:
308 %for key, value in c.modules:
309 <tr>
309 <tr>
310 <th style="text-align: right;padding-right:5px;">${key}</th>
310 <th style="text-align: right;padding-right:5px;">${key}</th>
311 <td>${value}</td>
311 <td>${value}</td>
312 </tr>
312 </tr>
313 %endfor
313 %endfor
314 </tbody>
314 </tbody>
315 </table>
315 </table>
316 </div>
316 </div>
317 </div>
317 </div>
318
318
319 <script type="text/javascript">
319 <script type="text/javascript">
320 YUE.on('expand_modules','click',function(e){
320 YUE.on('expand_modules','click',function(e){
321 YUD.setStyle('expand_modules_table','display','');
321 YUD.setStyle('expand_modules_table','display','');
322 YUD.setStyle('expand_modules','display','none');
322 YUD.setStyle('expand_modules','display','none');
323 })
323 })
324 </script>
324 </script>
325
325
326 </div>
326 </div>
327 </%def>
327 </%def>
General Comments 0
You need to be logged in to leave comments. Login now