##// END OF EJS Templates
- pull request generates overview based on it's params...
marcink -
r2440:1bc579bc codereview
parent child Browse files
Show More
@@ -0,0 +1,31 b''
1 <%inherit file="/base/base.html"/>
2
3 <%def name="title()">
4 ${c.repo_name} ${_('All pull requests')}
5 </%def>
6
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 &raquo;
12 ${_('All pull requests')}
13 </%def>
14
15 <%def name="main()">
16
17 <div class="box">
18 <!-- box / title -->
19 <div class="title">
20 ${self.breadcrumbs()}
21 </div>
22
23 %for pr in c.pull_requests:
24 <a href="${h.url('pullrequest_show',repo_name=c.repo_name,pull_request_id=pr.pull_request_id)}">#${pr.pull_request_id}</a>
25 %endfor
26
27 </div>
28
29 <script type="text/javascript"></script>
30
31 </%def>
@@ -1,546 +1,552 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
142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
143 controller='admin/repos_groups') as m:
143 controller='admin/repos_groups') as m:
144 m.connect("repos_groups", "/repos_groups",
144 m.connect("repos_groups", "/repos_groups",
145 action="create", conditions=dict(method=["POST"]))
145 action="create", conditions=dict(method=["POST"]))
146 m.connect("repos_groups", "/repos_groups",
146 m.connect("repos_groups", "/repos_groups",
147 action="index", conditions=dict(method=["GET"]))
147 action="index", conditions=dict(method=["GET"]))
148 m.connect("formatted_repos_groups", "/repos_groups.{format}",
148 m.connect("formatted_repos_groups", "/repos_groups.{format}",
149 action="index", conditions=dict(method=["GET"]))
149 action="index", conditions=dict(method=["GET"]))
150 m.connect("new_repos_group", "/repos_groups/new",
150 m.connect("new_repos_group", "/repos_groups/new",
151 action="new", conditions=dict(method=["GET"]))
151 action="new", conditions=dict(method=["GET"]))
152 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
152 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
153 action="new", conditions=dict(method=["GET"]))
153 action="new", conditions=dict(method=["GET"]))
154 m.connect("update_repos_group", "/repos_groups/{id}",
154 m.connect("update_repos_group", "/repos_groups/{id}",
155 action="update", conditions=dict(method=["PUT"],
155 action="update", conditions=dict(method=["PUT"],
156 function=check_int))
156 function=check_int))
157 m.connect("delete_repos_group", "/repos_groups/{id}",
157 m.connect("delete_repos_group", "/repos_groups/{id}",
158 action="delete", conditions=dict(method=["DELETE"],
158 action="delete", conditions=dict(method=["DELETE"],
159 function=check_int))
159 function=check_int))
160 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
160 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
161 action="edit", conditions=dict(method=["GET"],
161 action="edit", conditions=dict(method=["GET"],
162 function=check_int))
162 function=check_int))
163 m.connect("formatted_edit_repos_group",
163 m.connect("formatted_edit_repos_group",
164 "/repos_groups/{id}.{format}/edit",
164 "/repos_groups/{id}.{format}/edit",
165 action="edit", conditions=dict(method=["GET"],
165 action="edit", conditions=dict(method=["GET"],
166 function=check_int))
166 function=check_int))
167 m.connect("repos_group", "/repos_groups/{id}",
167 m.connect("repos_group", "/repos_groups/{id}",
168 action="show", conditions=dict(method=["GET"],
168 action="show", conditions=dict(method=["GET"],
169 function=check_int))
169 function=check_int))
170 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
170 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
171 action="show", conditions=dict(method=["GET"],
171 action="show", conditions=dict(method=["GET"],
172 function=check_int))
172 function=check_int))
173 # ajax delete repos group perm user
173 # ajax delete repos group perm user
174 m.connect('delete_repos_group_user_perm',
174 m.connect('delete_repos_group_user_perm',
175 "/delete_repos_group_user_perm/{group_name:.*}",
175 "/delete_repos_group_user_perm/{group_name:.*}",
176 action="delete_repos_group_user_perm",
176 action="delete_repos_group_user_perm",
177 conditions=dict(method=["DELETE"], function=check_group))
177 conditions=dict(method=["DELETE"], function=check_group))
178
178
179 # ajax delete repos group perm users_group
179 # ajax delete repos group perm users_group
180 m.connect('delete_repos_group_users_group_perm',
180 m.connect('delete_repos_group_users_group_perm',
181 "/delete_repos_group_users_group_perm/{group_name:.*}",
181 "/delete_repos_group_users_group_perm/{group_name:.*}",
182 action="delete_repos_group_users_group_perm",
182 action="delete_repos_group_users_group_perm",
183 conditions=dict(method=["DELETE"], function=check_group))
183 conditions=dict(method=["DELETE"], function=check_group))
184
184
185 #ADMIN USER REST ROUTES
185 #ADMIN USER REST ROUTES
186 with rmap.submapper(path_prefix=ADMIN_PREFIX,
186 with rmap.submapper(path_prefix=ADMIN_PREFIX,
187 controller='admin/users') as m:
187 controller='admin/users') as m:
188 m.connect("users", "/users",
188 m.connect("users", "/users",
189 action="create", conditions=dict(method=["POST"]))
189 action="create", conditions=dict(method=["POST"]))
190 m.connect("users", "/users",
190 m.connect("users", "/users",
191 action="index", conditions=dict(method=["GET"]))
191 action="index", conditions=dict(method=["GET"]))
192 m.connect("formatted_users", "/users.{format}",
192 m.connect("formatted_users", "/users.{format}",
193 action="index", conditions=dict(method=["GET"]))
193 action="index", conditions=dict(method=["GET"]))
194 m.connect("new_user", "/users/new",
194 m.connect("new_user", "/users/new",
195 action="new", conditions=dict(method=["GET"]))
195 action="new", conditions=dict(method=["GET"]))
196 m.connect("formatted_new_user", "/users/new.{format}",
196 m.connect("formatted_new_user", "/users/new.{format}",
197 action="new", conditions=dict(method=["GET"]))
197 action="new", conditions=dict(method=["GET"]))
198 m.connect("update_user", "/users/{id}",
198 m.connect("update_user", "/users/{id}",
199 action="update", conditions=dict(method=["PUT"]))
199 action="update", conditions=dict(method=["PUT"]))
200 m.connect("delete_user", "/users/{id}",
200 m.connect("delete_user", "/users/{id}",
201 action="delete", conditions=dict(method=["DELETE"]))
201 action="delete", conditions=dict(method=["DELETE"]))
202 m.connect("edit_user", "/users/{id}/edit",
202 m.connect("edit_user", "/users/{id}/edit",
203 action="edit", conditions=dict(method=["GET"]))
203 action="edit", conditions=dict(method=["GET"]))
204 m.connect("formatted_edit_user",
204 m.connect("formatted_edit_user",
205 "/users/{id}.{format}/edit",
205 "/users/{id}.{format}/edit",
206 action="edit", conditions=dict(method=["GET"]))
206 action="edit", conditions=dict(method=["GET"]))
207 m.connect("user", "/users/{id}",
207 m.connect("user", "/users/{id}",
208 action="show", conditions=dict(method=["GET"]))
208 action="show", conditions=dict(method=["GET"]))
209 m.connect("formatted_user", "/users/{id}.{format}",
209 m.connect("formatted_user", "/users/{id}.{format}",
210 action="show", conditions=dict(method=["GET"]))
210 action="show", conditions=dict(method=["GET"]))
211
211
212 #EXTRAS USER ROUTES
212 #EXTRAS USER ROUTES
213 m.connect("user_perm", "/users_perm/{id}",
213 m.connect("user_perm", "/users_perm/{id}",
214 action="update_perm", conditions=dict(method=["PUT"]))
214 action="update_perm", conditions=dict(method=["PUT"]))
215 m.connect("user_emails", "/users_emails/{id}",
215 m.connect("user_emails", "/users_emails/{id}",
216 action="add_email", conditions=dict(method=["PUT"]))
216 action="add_email", conditions=dict(method=["PUT"]))
217 m.connect("user_emails_delete", "/users_emails/{id}",
217 m.connect("user_emails_delete", "/users_emails/{id}",
218 action="delete_email", conditions=dict(method=["DELETE"]))
218 action="delete_email", conditions=dict(method=["DELETE"]))
219
219
220 #ADMIN USERS GROUPS REST ROUTES
220 #ADMIN USERS GROUPS REST ROUTES
221 with rmap.submapper(path_prefix=ADMIN_PREFIX,
221 with rmap.submapper(path_prefix=ADMIN_PREFIX,
222 controller='admin/users_groups') as m:
222 controller='admin/users_groups') as m:
223 m.connect("users_groups", "/users_groups",
223 m.connect("users_groups", "/users_groups",
224 action="create", conditions=dict(method=["POST"]))
224 action="create", conditions=dict(method=["POST"]))
225 m.connect("users_groups", "/users_groups",
225 m.connect("users_groups", "/users_groups",
226 action="index", conditions=dict(method=["GET"]))
226 action="index", conditions=dict(method=["GET"]))
227 m.connect("formatted_users_groups", "/users_groups.{format}",
227 m.connect("formatted_users_groups", "/users_groups.{format}",
228 action="index", conditions=dict(method=["GET"]))
228 action="index", conditions=dict(method=["GET"]))
229 m.connect("new_users_group", "/users_groups/new",
229 m.connect("new_users_group", "/users_groups/new",
230 action="new", conditions=dict(method=["GET"]))
230 action="new", conditions=dict(method=["GET"]))
231 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
231 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
232 action="new", conditions=dict(method=["GET"]))
232 action="new", conditions=dict(method=["GET"]))
233 m.connect("update_users_group", "/users_groups/{id}",
233 m.connect("update_users_group", "/users_groups/{id}",
234 action="update", conditions=dict(method=["PUT"]))
234 action="update", conditions=dict(method=["PUT"]))
235 m.connect("delete_users_group", "/users_groups/{id}",
235 m.connect("delete_users_group", "/users_groups/{id}",
236 action="delete", conditions=dict(method=["DELETE"]))
236 action="delete", conditions=dict(method=["DELETE"]))
237 m.connect("edit_users_group", "/users_groups/{id}/edit",
237 m.connect("edit_users_group", "/users_groups/{id}/edit",
238 action="edit", conditions=dict(method=["GET"]))
238 action="edit", conditions=dict(method=["GET"]))
239 m.connect("formatted_edit_users_group",
239 m.connect("formatted_edit_users_group",
240 "/users_groups/{id}.{format}/edit",
240 "/users_groups/{id}.{format}/edit",
241 action="edit", conditions=dict(method=["GET"]))
241 action="edit", conditions=dict(method=["GET"]))
242 m.connect("users_group", "/users_groups/{id}",
242 m.connect("users_group", "/users_groups/{id}",
243 action="show", conditions=dict(method=["GET"]))
243 action="show", conditions=dict(method=["GET"]))
244 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
244 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
245 action="show", conditions=dict(method=["GET"]))
245 action="show", conditions=dict(method=["GET"]))
246
246
247 #EXTRAS USER ROUTES
247 #EXTRAS USER ROUTES
248 m.connect("users_group_perm", "/users_groups_perm/{id}",
248 m.connect("users_group_perm", "/users_groups_perm/{id}",
249 action="update_perm", conditions=dict(method=["PUT"]))
249 action="update_perm", conditions=dict(method=["PUT"]))
250
250
251 #ADMIN GROUP REST ROUTES
251 #ADMIN GROUP REST ROUTES
252 rmap.resource('group', 'groups',
252 rmap.resource('group', 'groups',
253 controller='admin/groups', path_prefix=ADMIN_PREFIX)
253 controller='admin/groups', path_prefix=ADMIN_PREFIX)
254
254
255 #ADMIN PERMISSIONS REST ROUTES
255 #ADMIN PERMISSIONS REST ROUTES
256 rmap.resource('permission', 'permissions',
256 rmap.resource('permission', 'permissions',
257 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
257 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
258
258
259 ##ADMIN LDAP SETTINGS
259 ##ADMIN LDAP SETTINGS
260 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
260 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
261 controller='admin/ldap_settings', action='ldap_settings',
261 controller='admin/ldap_settings', action='ldap_settings',
262 conditions=dict(method=["POST"]))
262 conditions=dict(method=["POST"]))
263
263
264 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
264 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
265 controller='admin/ldap_settings')
265 controller='admin/ldap_settings')
266
266
267 #ADMIN SETTINGS REST ROUTES
267 #ADMIN SETTINGS REST ROUTES
268 with rmap.submapper(path_prefix=ADMIN_PREFIX,
268 with rmap.submapper(path_prefix=ADMIN_PREFIX,
269 controller='admin/settings') as m:
269 controller='admin/settings') as m:
270 m.connect("admin_settings", "/settings",
270 m.connect("admin_settings", "/settings",
271 action="create", conditions=dict(method=["POST"]))
271 action="create", conditions=dict(method=["POST"]))
272 m.connect("admin_settings", "/settings",
272 m.connect("admin_settings", "/settings",
273 action="index", conditions=dict(method=["GET"]))
273 action="index", conditions=dict(method=["GET"]))
274 m.connect("formatted_admin_settings", "/settings.{format}",
274 m.connect("formatted_admin_settings", "/settings.{format}",
275 action="index", conditions=dict(method=["GET"]))
275 action="index", conditions=dict(method=["GET"]))
276 m.connect("admin_new_setting", "/settings/new",
276 m.connect("admin_new_setting", "/settings/new",
277 action="new", conditions=dict(method=["GET"]))
277 action="new", conditions=dict(method=["GET"]))
278 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
278 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
279 action="new", conditions=dict(method=["GET"]))
279 action="new", conditions=dict(method=["GET"]))
280 m.connect("/settings/{setting_id}",
280 m.connect("/settings/{setting_id}",
281 action="update", conditions=dict(method=["PUT"]))
281 action="update", conditions=dict(method=["PUT"]))
282 m.connect("/settings/{setting_id}",
282 m.connect("/settings/{setting_id}",
283 action="delete", conditions=dict(method=["DELETE"]))
283 action="delete", conditions=dict(method=["DELETE"]))
284 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
284 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
285 action="edit", conditions=dict(method=["GET"]))
285 action="edit", conditions=dict(method=["GET"]))
286 m.connect("formatted_admin_edit_setting",
286 m.connect("formatted_admin_edit_setting",
287 "/settings/{setting_id}.{format}/edit",
287 "/settings/{setting_id}.{format}/edit",
288 action="edit", conditions=dict(method=["GET"]))
288 action="edit", conditions=dict(method=["GET"]))
289 m.connect("admin_setting", "/settings/{setting_id}",
289 m.connect("admin_setting", "/settings/{setting_id}",
290 action="show", conditions=dict(method=["GET"]))
290 action="show", conditions=dict(method=["GET"]))
291 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
291 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
292 action="show", conditions=dict(method=["GET"]))
292 action="show", conditions=dict(method=["GET"]))
293 m.connect("admin_settings_my_account", "/my_account",
293 m.connect("admin_settings_my_account", "/my_account",
294 action="my_account", conditions=dict(method=["GET"]))
294 action="my_account", conditions=dict(method=["GET"]))
295 m.connect("admin_settings_my_account_update", "/my_account_update",
295 m.connect("admin_settings_my_account_update", "/my_account_update",
296 action="my_account_update", conditions=dict(method=["PUT"]))
296 action="my_account_update", conditions=dict(method=["PUT"]))
297 m.connect("admin_settings_create_repository", "/create_repository",
297 m.connect("admin_settings_create_repository", "/create_repository",
298 action="create_repository", conditions=dict(method=["GET"]))
298 action="create_repository", conditions=dict(method=["GET"]))
299
299
300 #NOTIFICATION REST ROUTES
300 #NOTIFICATION REST ROUTES
301 with rmap.submapper(path_prefix=ADMIN_PREFIX,
301 with rmap.submapper(path_prefix=ADMIN_PREFIX,
302 controller='admin/notifications') as m:
302 controller='admin/notifications') as m:
303 m.connect("notifications", "/notifications",
303 m.connect("notifications", "/notifications",
304 action="create", conditions=dict(method=["POST"]))
304 action="create", conditions=dict(method=["POST"]))
305 m.connect("notifications", "/notifications",
305 m.connect("notifications", "/notifications",
306 action="index", conditions=dict(method=["GET"]))
306 action="index", conditions=dict(method=["GET"]))
307 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
307 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
308 action="mark_all_read", conditions=dict(method=["GET"]))
308 action="mark_all_read", conditions=dict(method=["GET"]))
309 m.connect("formatted_notifications", "/notifications.{format}",
309 m.connect("formatted_notifications", "/notifications.{format}",
310 action="index", conditions=dict(method=["GET"]))
310 action="index", conditions=dict(method=["GET"]))
311 m.connect("new_notification", "/notifications/new",
311 m.connect("new_notification", "/notifications/new",
312 action="new", conditions=dict(method=["GET"]))
312 action="new", conditions=dict(method=["GET"]))
313 m.connect("formatted_new_notification", "/notifications/new.{format}",
313 m.connect("formatted_new_notification", "/notifications/new.{format}",
314 action="new", conditions=dict(method=["GET"]))
314 action="new", conditions=dict(method=["GET"]))
315 m.connect("/notification/{notification_id}",
315 m.connect("/notification/{notification_id}",
316 action="update", conditions=dict(method=["PUT"]))
316 action="update", conditions=dict(method=["PUT"]))
317 m.connect("/notification/{notification_id}",
317 m.connect("/notification/{notification_id}",
318 action="delete", conditions=dict(method=["DELETE"]))
318 action="delete", conditions=dict(method=["DELETE"]))
319 m.connect("edit_notification", "/notification/{notification_id}/edit",
319 m.connect("edit_notification", "/notification/{notification_id}/edit",
320 action="edit", conditions=dict(method=["GET"]))
320 action="edit", conditions=dict(method=["GET"]))
321 m.connect("formatted_edit_notification",
321 m.connect("formatted_edit_notification",
322 "/notification/{notification_id}.{format}/edit",
322 "/notification/{notification_id}.{format}/edit",
323 action="edit", conditions=dict(method=["GET"]))
323 action="edit", conditions=dict(method=["GET"]))
324 m.connect("notification", "/notification/{notification_id}",
324 m.connect("notification", "/notification/{notification_id}",
325 action="show", conditions=dict(method=["GET"]))
325 action="show", conditions=dict(method=["GET"]))
326 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
326 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
327 action="show", conditions=dict(method=["GET"]))
327 action="show", conditions=dict(method=["GET"]))
328
328
329 #ADMIN MAIN PAGES
329 #ADMIN MAIN PAGES
330 with rmap.submapper(path_prefix=ADMIN_PREFIX,
330 with rmap.submapper(path_prefix=ADMIN_PREFIX,
331 controller='admin/admin') as m:
331 controller='admin/admin') as m:
332 m.connect('admin_home', '', action='index')
332 m.connect('admin_home', '', action='index')
333 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
333 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
334 action='add_repo')
334 action='add_repo')
335
335
336 #==========================================================================
336 #==========================================================================
337 # API V2
337 # API V2
338 #==========================================================================
338 #==========================================================================
339 with rmap.submapper(path_prefix=ADMIN_PREFIX,
339 with rmap.submapper(path_prefix=ADMIN_PREFIX,
340 controller='api/api') as m:
340 controller='api/api') as m:
341 m.connect('api', '/api')
341 m.connect('api', '/api')
342
342
343 #USER JOURNAL
343 #USER JOURNAL
344 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
344 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
345 controller='journal', action='index')
345 controller='journal', action='index')
346 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
346 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
347 controller='journal', action='journal_rss')
347 controller='journal', action='journal_rss')
348 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
348 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
349 controller='journal', action='journal_atom')
349 controller='journal', action='journal_atom')
350
350
351 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
351 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
352 controller='journal', action="public_journal")
352 controller='journal', action="public_journal")
353
353
354 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
354 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
355 controller='journal', action="public_journal_rss")
355 controller='journal', action="public_journal_rss")
356
356
357 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
357 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
358 controller='journal', action="public_journal_rss")
358 controller='journal', action="public_journal_rss")
359
359
360 rmap.connect('public_journal_atom',
360 rmap.connect('public_journal_atom',
361 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
361 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
362 action="public_journal_atom")
362 action="public_journal_atom")
363
363
364 rmap.connect('public_journal_atom_old',
364 rmap.connect('public_journal_atom_old',
365 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
365 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
366 action="public_journal_atom")
366 action="public_journal_atom")
367
367
368 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
368 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
369 controller='journal', action='toggle_following',
369 controller='journal', action='toggle_following',
370 conditions=dict(method=["POST"]))
370 conditions=dict(method=["POST"]))
371
371
372 #SEARCH
372 #SEARCH
373 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
373 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
374 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
374 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
375 controller='search')
375 controller='search')
376
376
377 #LOGIN/LOGOUT/REGISTER/SIGN IN
377 #LOGIN/LOGOUT/REGISTER/SIGN IN
378 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
378 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
379 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
379 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
380 action='logout')
380 action='logout')
381
381
382 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
382 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
383 action='register')
383 action='register')
384
384
385 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
385 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
386 controller='login', action='password_reset')
386 controller='login', action='password_reset')
387
387
388 rmap.connect('reset_password_confirmation',
388 rmap.connect('reset_password_confirmation',
389 '%s/password_reset_confirmation' % ADMIN_PREFIX,
389 '%s/password_reset_confirmation' % ADMIN_PREFIX,
390 controller='login', action='password_reset_confirmation')
390 controller='login', action='password_reset_confirmation')
391
391
392 #FEEDS
392 #FEEDS
393 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
393 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
394 controller='feed', action='rss',
394 controller='feed', action='rss',
395 conditions=dict(function=check_repo))
395 conditions=dict(function=check_repo))
396
396
397 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
397 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
398 controller='feed', action='atom',
398 controller='feed', action='atom',
399 conditions=dict(function=check_repo))
399 conditions=dict(function=check_repo))
400
400
401 #==========================================================================
401 #==========================================================================
402 # REPOSITORY ROUTES
402 # REPOSITORY ROUTES
403 #==========================================================================
403 #==========================================================================
404 rmap.connect('summary_home', '/{repo_name:.*}',
404 rmap.connect('summary_home', '/{repo_name:.*}',
405 controller='summary',
405 controller='summary',
406 conditions=dict(function=check_repo))
406 conditions=dict(function=check_repo))
407
407
408 rmap.connect('repos_group_home', '/{group_name:.*}',
408 rmap.connect('repos_group_home', '/{group_name:.*}',
409 controller='admin/repos_groups', action="show_by_name",
409 controller='admin/repos_groups', action="show_by_name",
410 conditions=dict(function=check_group))
410 conditions=dict(function=check_group))
411
411
412 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
412 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
413 controller='changeset', revision='tip',
413 controller='changeset', revision='tip',
414 conditions=dict(function=check_repo))
414 conditions=dict(function=check_repo))
415
415
416 rmap.connect('changeset_comment',
416 rmap.connect('changeset_comment',
417 '/{repo_name:.*}/changeset/{revision}/comment',
417 '/{repo_name:.*}/changeset/{revision}/comment',
418 controller='changeset', revision='tip', action='comment',
418 controller='changeset', revision='tip', action='comment',
419 conditions=dict(function=check_repo))
419 conditions=dict(function=check_repo))
420
420
421 rmap.connect('changeset_comment_delete',
421 rmap.connect('changeset_comment_delete',
422 '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
422 '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
423 controller='changeset', action='delete_comment',
423 controller='changeset', action='delete_comment',
424 conditions=dict(function=check_repo, method=["DELETE"]))
424 conditions=dict(function=check_repo, method=["DELETE"]))
425
425
426 rmap.connect('raw_changeset_home',
426 rmap.connect('raw_changeset_home',
427 '/{repo_name:.*}/raw-changeset/{revision}',
427 '/{repo_name:.*}/raw-changeset/{revision}',
428 controller='changeset', action='raw_changeset',
428 controller='changeset', action='raw_changeset',
429 revision='tip', conditions=dict(function=check_repo))
429 revision='tip', conditions=dict(function=check_repo))
430
430
431 rmap.connect('compare_url',
431 rmap.connect('compare_url',
432 '/{repo_name:.*}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}',
432 '/{repo_name:.*}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}',
433 controller='compare', action='index',
433 controller='compare', action='index',
434 conditions=dict(function=check_repo),
434 conditions=dict(function=check_repo),
435 requirements=dict(org_ref_type='(branch|book|tag)',
435 requirements=dict(org_ref_type='(branch|book|tag)',
436 other_ref_type='(branch|book|tag)'))
436 other_ref_type='(branch|book|tag)'))
437
437
438 rmap.connect('pullrequest_home',
438 rmap.connect('pullrequest_home',
439 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
439 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
440 action='index', conditions=dict(function=check_repo,
440 action='index', conditions=dict(function=check_repo,
441 method=["GET"]))
441 method=["GET"]))
442
442
443 rmap.connect('pullrequest',
443 rmap.connect('pullrequest',
444 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
444 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
445 action='create', conditions=dict(function=check_repo,
445 action='create', conditions=dict(function=check_repo,
446 method=["POST"]))
446 method=["POST"]))
447
447
448 rmap.connect('pullrequest_show',
448 rmap.connect('pullrequest_show',
449 '/{repo_name:.*}/pull-request/{pull_request_id}',
449 '/{repo_name:.*}/pull-request/{pull_request_id}',
450 controller='pullrequests',
450 controller='pullrequests',
451 action='show', conditions=dict(function=check_repo,
451 action='show', conditions=dict(function=check_repo,
452 method=["GET"]))
452 method=["GET"]))
453
453
454 rmap.connect('pullrequest_show_all',
455 '/{repo_name:.*}/pull-request',
456 controller='pullrequests',
457 action='show_all', conditions=dict(function=check_repo,
458 method=["GET"]))
459
454 rmap.connect('summary_home', '/{repo_name:.*}/summary',
460 rmap.connect('summary_home', '/{repo_name:.*}/summary',
455 controller='summary', conditions=dict(function=check_repo))
461 controller='summary', conditions=dict(function=check_repo))
456
462
457 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
463 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
458 controller='shortlog', conditions=dict(function=check_repo))
464 controller='shortlog', conditions=dict(function=check_repo))
459
465
460 rmap.connect('branches_home', '/{repo_name:.*}/branches',
466 rmap.connect('branches_home', '/{repo_name:.*}/branches',
461 controller='branches', conditions=dict(function=check_repo))
467 controller='branches', conditions=dict(function=check_repo))
462
468
463 rmap.connect('tags_home', '/{repo_name:.*}/tags',
469 rmap.connect('tags_home', '/{repo_name:.*}/tags',
464 controller='tags', conditions=dict(function=check_repo))
470 controller='tags', conditions=dict(function=check_repo))
465
471
466 rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
472 rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
467 controller='bookmarks', conditions=dict(function=check_repo))
473 controller='bookmarks', conditions=dict(function=check_repo))
468
474
469 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
475 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
470 controller='changelog', conditions=dict(function=check_repo))
476 controller='changelog', conditions=dict(function=check_repo))
471
477
472 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
478 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
473 controller='changelog', action='changelog_details',
479 controller='changelog', action='changelog_details',
474 conditions=dict(function=check_repo))
480 conditions=dict(function=check_repo))
475
481
476 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
482 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
477 controller='files', revision='tip', f_path='',
483 controller='files', revision='tip', f_path='',
478 conditions=dict(function=check_repo))
484 conditions=dict(function=check_repo))
479
485
480 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
486 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
481 controller='files', action='diff', revision='tip', f_path='',
487 controller='files', action='diff', revision='tip', f_path='',
482 conditions=dict(function=check_repo))
488 conditions=dict(function=check_repo))
483
489
484 rmap.connect('files_rawfile_home',
490 rmap.connect('files_rawfile_home',
485 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
491 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
486 controller='files', action='rawfile', revision='tip',
492 controller='files', action='rawfile', revision='tip',
487 f_path='', conditions=dict(function=check_repo))
493 f_path='', conditions=dict(function=check_repo))
488
494
489 rmap.connect('files_raw_home',
495 rmap.connect('files_raw_home',
490 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
496 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
491 controller='files', action='raw', revision='tip', f_path='',
497 controller='files', action='raw', revision='tip', f_path='',
492 conditions=dict(function=check_repo))
498 conditions=dict(function=check_repo))
493
499
494 rmap.connect('files_annotate_home',
500 rmap.connect('files_annotate_home',
495 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
501 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
496 controller='files', action='index', revision='tip',
502 controller='files', action='index', revision='tip',
497 f_path='', annotate=True, conditions=dict(function=check_repo))
503 f_path='', annotate=True, conditions=dict(function=check_repo))
498
504
499 rmap.connect('files_edit_home',
505 rmap.connect('files_edit_home',
500 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
506 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
501 controller='files', action='edit', revision='tip',
507 controller='files', action='edit', revision='tip',
502 f_path='', conditions=dict(function=check_repo))
508 f_path='', conditions=dict(function=check_repo))
503
509
504 rmap.connect('files_add_home',
510 rmap.connect('files_add_home',
505 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
511 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
506 controller='files', action='add', revision='tip',
512 controller='files', action='add', revision='tip',
507 f_path='', conditions=dict(function=check_repo))
513 f_path='', conditions=dict(function=check_repo))
508
514
509 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
515 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
510 controller='files', action='archivefile',
516 controller='files', action='archivefile',
511 conditions=dict(function=check_repo))
517 conditions=dict(function=check_repo))
512
518
513 rmap.connect('files_nodelist_home',
519 rmap.connect('files_nodelist_home',
514 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
520 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
515 controller='files', action='nodelist',
521 controller='files', action='nodelist',
516 conditions=dict(function=check_repo))
522 conditions=dict(function=check_repo))
517
523
518 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
524 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
519 controller='settings', action="delete",
525 controller='settings', action="delete",
520 conditions=dict(method=["DELETE"], function=check_repo))
526 conditions=dict(method=["DELETE"], function=check_repo))
521
527
522 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
528 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
523 controller='settings', action="update",
529 controller='settings', action="update",
524 conditions=dict(method=["PUT"], function=check_repo))
530 conditions=dict(method=["PUT"], function=check_repo))
525
531
526 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
532 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
527 controller='settings', action='index',
533 controller='settings', action='index',
528 conditions=dict(function=check_repo))
534 conditions=dict(function=check_repo))
529
535
530 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
536 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
531 controller='forks', action='fork_create',
537 controller='forks', action='fork_create',
532 conditions=dict(function=check_repo, method=["POST"]))
538 conditions=dict(function=check_repo, method=["POST"]))
533
539
534 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
540 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
535 controller='forks', action='fork',
541 controller='forks', action='fork',
536 conditions=dict(function=check_repo))
542 conditions=dict(function=check_repo))
537
543
538 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
544 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
539 controller='forks', action='forks',
545 controller='forks', action='forks',
540 conditions=dict(function=check_repo))
546 conditions=dict(function=check_repo))
541
547
542 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
548 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
543 controller='followers', action='followers',
549 controller='followers', action='followers',
544 conditions=dict(function=check_repo))
550 conditions=dict(function=check_repo))
545
551
546 return rmap
552 return rmap
@@ -1,428 +1,428 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 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 import logging
26 import logging
27 import traceback
27 import traceback
28 from collections import defaultdict
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden
29 from webob.exc import HTTPForbidden
30
30
31 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.decorators import jsonify
34 from pylons.decorators import jsonify
35
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
39
39
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import EmptyChangeset, action_logger
43 from rhodecode.lib.utils import EmptyChangeset, action_logger
44 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib.compat import OrderedDict
45 from rhodecode.lib import diffs
45 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 from rhodecode.model.comment import ChangesetCommentsModel
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
50 from rhodecode.lib.diffs import wrapped_diff
50 from rhodecode.lib.diffs import wrapped_diff
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 def _update_with_GET(params, GET):
56 def _update_with_GET(params, GET):
57 for k in ['diff1', 'diff2', 'diff']:
57 for k in ['diff1', 'diff2', 'diff']:
58 params[k] += GET.getall(k)
58 params[k] += GET.getall(k)
59
59
60
60
61 def anchor_url(revision, path, GET):
61 def anchor_url(revision, path, GET):
62 fid = h.FID(revision, path)
62 fid = h.FID(revision, path)
63 return h.url.current(anchor=fid, **dict(GET))
63 return h.url.current(anchor=fid, **dict(GET))
64
64
65
65
66 def get_ignore_ws(fid, GET):
66 def get_ignore_ws(fid, GET):
67 ig_ws_global = GET.get('ignorews')
67 ig_ws_global = GET.get('ignorews')
68 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
68 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
69 if ig_ws:
69 if ig_ws:
70 try:
70 try:
71 return int(ig_ws[0].split(':')[-1])
71 return int(ig_ws[0].split(':')[-1])
72 except:
72 except:
73 pass
73 pass
74 return ig_ws_global
74 return ig_ws_global
75
75
76
76
77 def _ignorews_url(GET, fileid=None):
77 def _ignorews_url(GET, fileid=None):
78 fileid = str(fileid) if fileid else None
78 fileid = str(fileid) if fileid else None
79 params = defaultdict(list)
79 params = defaultdict(list)
80 _update_with_GET(params, GET)
80 _update_with_GET(params, GET)
81 lbl = _('show white space')
81 lbl = _('show white space')
82 ig_ws = get_ignore_ws(fileid, GET)
82 ig_ws = get_ignore_ws(fileid, GET)
83 ln_ctx = get_line_ctx(fileid, GET)
83 ln_ctx = get_line_ctx(fileid, GET)
84 # global option
84 # global option
85 if fileid is None:
85 if fileid is None:
86 if ig_ws is None:
86 if ig_ws is None:
87 params['ignorews'] += [1]
87 params['ignorews'] += [1]
88 lbl = _('ignore white space')
88 lbl = _('ignore white space')
89 ctx_key = 'context'
89 ctx_key = 'context'
90 ctx_val = ln_ctx
90 ctx_val = ln_ctx
91 # per file options
91 # per file options
92 else:
92 else:
93 if ig_ws is None:
93 if ig_ws is None:
94 params[fileid] += ['WS:1']
94 params[fileid] += ['WS:1']
95 lbl = _('ignore white space')
95 lbl = _('ignore white space')
96
96
97 ctx_key = fileid
97 ctx_key = fileid
98 ctx_val = 'C:%s' % ln_ctx
98 ctx_val = 'C:%s' % ln_ctx
99 # if we have passed in ln_ctx pass it along to our params
99 # if we have passed in ln_ctx pass it along to our params
100 if ln_ctx:
100 if ln_ctx:
101 params[ctx_key] += [ctx_val]
101 params[ctx_key] += [ctx_val]
102
102
103 params['anchor'] = fileid
103 params['anchor'] = fileid
104 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
104 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
105 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
105 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
106
106
107
107
108 def get_line_ctx(fid, GET):
108 def get_line_ctx(fid, GET):
109 ln_ctx_global = GET.get('context')
109 ln_ctx_global = GET.get('context')
110 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
110 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
111
111
112 if ln_ctx:
112 if ln_ctx:
113 retval = ln_ctx[0].split(':')[-1]
113 retval = ln_ctx[0].split(':')[-1]
114 else:
114 else:
115 retval = ln_ctx_global
115 retval = ln_ctx_global
116
116
117 try:
117 try:
118 return int(retval)
118 return int(retval)
119 except:
119 except:
120 return
120 return
121
121
122
122
123 def _context_url(GET, fileid=None):
123 def _context_url(GET, fileid=None):
124 """
124 """
125 Generates url for context lines
125 Generates url for context lines
126
126
127 :param fileid:
127 :param fileid:
128 """
128 """
129
129
130 fileid = str(fileid) if fileid else None
130 fileid = str(fileid) if fileid else None
131 ig_ws = get_ignore_ws(fileid, GET)
131 ig_ws = get_ignore_ws(fileid, GET)
132 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
132 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
133
133
134 params = defaultdict(list)
134 params = defaultdict(list)
135 _update_with_GET(params, GET)
135 _update_with_GET(params, GET)
136
136
137 # global option
137 # global option
138 if fileid is None:
138 if fileid is None:
139 if ln_ctx > 0:
139 if ln_ctx > 0:
140 params['context'] += [ln_ctx]
140 params['context'] += [ln_ctx]
141
141
142 if ig_ws:
142 if ig_ws:
143 ig_ws_key = 'ignorews'
143 ig_ws_key = 'ignorews'
144 ig_ws_val = 1
144 ig_ws_val = 1
145
145
146 # per file option
146 # per file option
147 else:
147 else:
148 params[fileid] += ['C:%s' % ln_ctx]
148 params[fileid] += ['C:%s' % ln_ctx]
149 ig_ws_key = fileid
149 ig_ws_key = fileid
150 ig_ws_val = 'WS:%s' % 1
150 ig_ws_val = 'WS:%s' % 1
151
151
152 if ig_ws:
152 if ig_ws:
153 params[ig_ws_key] += [ig_ws_val]
153 params[ig_ws_key] += [ig_ws_val]
154
154
155 lbl = _('%s line context') % ln_ctx
155 lbl = _('%s line context') % ln_ctx
156
156
157 params['anchor'] = fileid
157 params['anchor'] = fileid
158 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
158 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
159 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
159 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
160
160
161
161
162 class ChangesetController(BaseRepoController):
162 class ChangesetController(BaseRepoController):
163
163
164 @LoginRequired()
164 @LoginRequired()
165 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
165 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
166 'repository.admin')
166 'repository.admin')
167 def __before__(self):
167 def __before__(self):
168 super(ChangesetController, self).__before__()
168 super(ChangesetController, self).__before__()
169 c.affected_files_cut_off = 60
169 c.affected_files_cut_off = 60
170 repo_model = RepoModel()
170 repo_model = RepoModel()
171 c.users_array = repo_model.get_users_js()
171 c.users_array = repo_model.get_users_js()
172 c.users_groups_array = repo_model.get_users_groups_js()
172 c.users_groups_array = repo_model.get_users_groups_js()
173
173
174 def index(self, revision):
174 def index(self, revision):
175
175
176 c.anchor_url = anchor_url
176 c.anchor_url = anchor_url
177 c.ignorews_url = _ignorews_url
177 c.ignorews_url = _ignorews_url
178 c.context_url = _context_url
178 c.context_url = _context_url
179 limit_off = request.GET.get('fulldiff')
179 limit_off = request.GET.get('fulldiff')
180 #get ranges of revisions if preset
180 #get ranges of revisions if preset
181 rev_range = revision.split('...')[:2]
181 rev_range = revision.split('...')[:2]
182 enable_comments = True
182 enable_comments = True
183 try:
183 try:
184 if len(rev_range) == 2:
184 if len(rev_range) == 2:
185 enable_comments = False
185 enable_comments = False
186 rev_start = rev_range[0]
186 rev_start = rev_range[0]
187 rev_end = rev_range[1]
187 rev_end = rev_range[1]
188 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
188 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
189 end=rev_end)
189 end=rev_end)
190 else:
190 else:
191 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
191 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
192
192
193 c.cs_ranges = list(rev_ranges)
193 c.cs_ranges = list(rev_ranges)
194 if not c.cs_ranges:
194 if not c.cs_ranges:
195 raise RepositoryError('Changeset range returned empty result')
195 raise RepositoryError('Changeset range returned empty result')
196
196
197 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
197 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
198 log.error(traceback.format_exc())
198 log.error(traceback.format_exc())
199 h.flash(str(e), category='warning')
199 h.flash(str(e), category='warning')
200 return redirect(url('home'))
200 return redirect(url('home'))
201
201
202 c.changes = OrderedDict()
202 c.changes = OrderedDict()
203
203
204 c.lines_added = 0 # count of lines added
204 c.lines_added = 0 # count of lines added
205 c.lines_deleted = 0 # count of lines removes
205 c.lines_deleted = 0 # count of lines removes
206
206
207 cumulative_diff = 0
207 cumulative_diff = 0
208 c.cut_off = False # defines if cut off limit is reached
208 c.cut_off = False # defines if cut off limit is reached
209 c.changeset_statuses = ChangesetStatus.STATUSES
209 c.changeset_statuses = ChangesetStatus.STATUSES
210 c.comments = []
210 c.comments = []
211 c.statuses = []
211 c.statuses = []
212 c.inline_comments = []
212 c.inline_comments = []
213 c.inline_cnt = 0
213 c.inline_cnt = 0
214 # Iterate over ranges (default changeset view is always one changeset)
214 # Iterate over ranges (default changeset view is always one changeset)
215 for changeset in c.cs_ranges:
215 for changeset in c.cs_ranges:
216
216
217 c.statuses.extend([ChangesetStatusModel()\
217 c.statuses.extend([ChangesetStatusModel()\
218 .get_status(c.rhodecode_db_repo.repo_id,
218 .get_status(c.rhodecode_db_repo.repo_id,
219 changeset.raw_id)])
219 changeset.raw_id)])
220
220
221 c.comments.extend(ChangesetCommentsModel()\
221 c.comments.extend(ChangesetCommentsModel()\
222 .get_comments(c.rhodecode_db_repo.repo_id,
222 .get_comments(c.rhodecode_db_repo.repo_id,
223 revision=changeset.raw_id))
223 revision=changeset.raw_id))
224 inlines = ChangesetCommentsModel()\
224 inlines = ChangesetCommentsModel()\
225 .get_inline_comments(c.rhodecode_db_repo.repo_id,
225 .get_inline_comments(c.rhodecode_db_repo.repo_id,
226 revision=changeset.raw_id)
226 revision=changeset.raw_id)
227 c.inline_comments.extend(inlines)
227 c.inline_comments.extend(inlines)
228 c.changes[changeset.raw_id] = []
228 c.changes[changeset.raw_id] = []
229 try:
229 try:
230 changeset_parent = changeset.parents[0]
230 changeset_parent = changeset.parents[0]
231 except IndexError:
231 except IndexError:
232 changeset_parent = None
232 changeset_parent = None
233
233
234 #==================================================================
234 #==================================================================
235 # ADDED FILES
235 # ADDED FILES
236 #==================================================================
236 #==================================================================
237 for node in changeset.added:
237 for node in changeset.added:
238 fid = h.FID(revision, node.path)
238 fid = h.FID(revision, node.path)
239 line_context_lcl = get_line_ctx(fid, request.GET)
239 line_context_lcl = get_line_ctx(fid, request.GET)
240 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
240 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
241 lim = self.cut_off_limit
241 lim = self.cut_off_limit
242 if cumulative_diff > self.cut_off_limit:
242 if cumulative_diff > self.cut_off_limit:
243 lim = -1 if limit_off is None else None
243 lim = -1 if limit_off is None else None
244 size, cs1, cs2, diff, st = wrapped_diff(
244 size, cs1, cs2, diff, st = wrapped_diff(
245 filenode_old=None,
245 filenode_old=None,
246 filenode_new=node,
246 filenode_new=node,
247 cut_off_limit=lim,
247 cut_off_limit=lim,
248 ignore_whitespace=ign_whitespace_lcl,
248 ignore_whitespace=ign_whitespace_lcl,
249 line_context=line_context_lcl,
249 line_context=line_context_lcl,
250 enable_comments=enable_comments
250 enable_comments=enable_comments
251 )
251 )
252 cumulative_diff += size
252 cumulative_diff += size
253 c.lines_added += st[0]
253 c.lines_added += st[0]
254 c.lines_deleted += st[1]
254 c.lines_deleted += st[1]
255 c.changes[changeset.raw_id].append(
255 c.changes[changeset.raw_id].append(
256 ('added', node, diff, cs1, cs2, st)
256 ('added', node, diff, cs1, cs2, st)
257 )
257 )
258
258
259 #==================================================================
259 #==================================================================
260 # CHANGED FILES
260 # CHANGED FILES
261 #==================================================================
261 #==================================================================
262 for node in changeset.changed:
262 for node in changeset.changed:
263 try:
263 try:
264 filenode_old = changeset_parent.get_node(node.path)
264 filenode_old = changeset_parent.get_node(node.path)
265 except ChangesetError:
265 except ChangesetError:
266 log.warning('Unable to fetch parent node for diff')
266 log.warning('Unable to fetch parent node for diff')
267 filenode_old = FileNode(node.path, '', EmptyChangeset())
267 filenode_old = FileNode(node.path, '', EmptyChangeset())
268
268
269 fid = h.FID(revision, node.path)
269 fid = h.FID(revision, node.path)
270 line_context_lcl = get_line_ctx(fid, request.GET)
270 line_context_lcl = get_line_ctx(fid, request.GET)
271 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
271 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
272 lim = self.cut_off_limit
272 lim = self.cut_off_limit
273 if cumulative_diff > self.cut_off_limit:
273 if cumulative_diff > self.cut_off_limit:
274 lim = -1 if limit_off is None else None
274 lim = -1 if limit_off is None else None
275 size, cs1, cs2, diff, st = wrapped_diff(
275 size, cs1, cs2, diff, st = wrapped_diff(
276 filenode_old=filenode_old,
276 filenode_old=filenode_old,
277 filenode_new=node,
277 filenode_new=node,
278 cut_off_limit=lim,
278 cut_off_limit=lim,
279 ignore_whitespace=ign_whitespace_lcl,
279 ignore_whitespace=ign_whitespace_lcl,
280 line_context=line_context_lcl,
280 line_context=line_context_lcl,
281 enable_comments=enable_comments
281 enable_comments=enable_comments
282 )
282 )
283 cumulative_diff += size
283 cumulative_diff += size
284 c.lines_added += st[0]
284 c.lines_added += st[0]
285 c.lines_deleted += st[1]
285 c.lines_deleted += st[1]
286 c.changes[changeset.raw_id].append(
286 c.changes[changeset.raw_id].append(
287 ('changed', node, diff, cs1, cs2, st)
287 ('changed', node, diff, cs1, cs2, st)
288 )
288 )
289 #==================================================================
289 #==================================================================
290 # REMOVED FILES
290 # REMOVED FILES
291 #==================================================================
291 #==================================================================
292 for node in changeset.removed:
292 for node in changeset.removed:
293 c.changes[changeset.raw_id].append(
293 c.changes[changeset.raw_id].append(
294 ('removed', node, None, None, None, (0, 0))
294 ('removed', node, None, None, None, (0, 0))
295 )
295 )
296
296
297 # count inline comments
297 # count inline comments
298 for _, lines in c.inline_comments:
298 for __, lines in c.inline_comments:
299 for comments in lines.values():
299 for comments in lines.values():
300 c.inline_cnt += len(comments)
300 c.inline_cnt += len(comments)
301
301
302 if len(c.cs_ranges) == 1:
302 if len(c.cs_ranges) == 1:
303 c.changeset = c.cs_ranges[0]
303 c.changeset = c.cs_ranges[0]
304 c.changes = c.changes[c.changeset.raw_id]
304 c.changes = c.changes[c.changeset.raw_id]
305
305
306 return render('changeset/changeset.html')
306 return render('changeset/changeset.html')
307 else:
307 else:
308 return render('changeset/changeset_range.html')
308 return render('changeset/changeset_range.html')
309
309
310 def raw_changeset(self, revision):
310 def raw_changeset(self, revision):
311
311
312 method = request.GET.get('diff', 'show')
312 method = request.GET.get('diff', 'show')
313 ignore_whitespace = request.GET.get('ignorews') == '1'
313 ignore_whitespace = request.GET.get('ignorews') == '1'
314 line_context = request.GET.get('context', 3)
314 line_context = request.GET.get('context', 3)
315 try:
315 try:
316 c.scm_type = c.rhodecode_repo.alias
316 c.scm_type = c.rhodecode_repo.alias
317 c.changeset = c.rhodecode_repo.get_changeset(revision)
317 c.changeset = c.rhodecode_repo.get_changeset(revision)
318 except RepositoryError:
318 except RepositoryError:
319 log.error(traceback.format_exc())
319 log.error(traceback.format_exc())
320 return redirect(url('home'))
320 return redirect(url('home'))
321 else:
321 else:
322 try:
322 try:
323 c.changeset_parent = c.changeset.parents[0]
323 c.changeset_parent = c.changeset.parents[0]
324 except IndexError:
324 except IndexError:
325 c.changeset_parent = None
325 c.changeset_parent = None
326 c.changes = []
326 c.changes = []
327
327
328 for node in c.changeset.added:
328 for node in c.changeset.added:
329 filenode_old = FileNode(node.path, '')
329 filenode_old = FileNode(node.path, '')
330 if filenode_old.is_binary or node.is_binary:
330 if filenode_old.is_binary or node.is_binary:
331 diff = _('binary file') + '\n'
331 diff = _('binary file') + '\n'
332 else:
332 else:
333 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
333 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
334 ignore_whitespace=ignore_whitespace,
334 ignore_whitespace=ignore_whitespace,
335 context=line_context)
335 context=line_context)
336 diff = diffs.DiffProcessor(f_gitdiff,
336 diff = diffs.DiffProcessor(f_gitdiff,
337 format='gitdiff').raw_diff()
337 format='gitdiff').raw_diff()
338
338
339 cs1 = None
339 cs1 = None
340 cs2 = node.changeset.raw_id
340 cs2 = node.changeset.raw_id
341 c.changes.append(('added', node, diff, cs1, cs2))
341 c.changes.append(('added', node, diff, cs1, cs2))
342
342
343 for node in c.changeset.changed:
343 for node in c.changeset.changed:
344 filenode_old = c.changeset_parent.get_node(node.path)
344 filenode_old = c.changeset_parent.get_node(node.path)
345 if filenode_old.is_binary or node.is_binary:
345 if filenode_old.is_binary or node.is_binary:
346 diff = _('binary file')
346 diff = _('binary file')
347 else:
347 else:
348 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
348 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
349 ignore_whitespace=ignore_whitespace,
349 ignore_whitespace=ignore_whitespace,
350 context=line_context)
350 context=line_context)
351 diff = diffs.DiffProcessor(f_gitdiff,
351 diff = diffs.DiffProcessor(f_gitdiff,
352 format='gitdiff').raw_diff()
352 format='gitdiff').raw_diff()
353
353
354 cs1 = filenode_old.changeset.raw_id
354 cs1 = filenode_old.changeset.raw_id
355 cs2 = node.changeset.raw_id
355 cs2 = node.changeset.raw_id
356 c.changes.append(('changed', node, diff, cs1, cs2))
356 c.changes.append(('changed', node, diff, cs1, cs2))
357
357
358 response.content_type = 'text/plain'
358 response.content_type = 'text/plain'
359
359
360 if method == 'download':
360 if method == 'download':
361 response.content_disposition = 'attachment; filename=%s.patch' \
361 response.content_disposition = 'attachment; filename=%s.patch' \
362 % revision
362 % revision
363
363
364 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
364 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
365 for x in c.changeset.parents])
365 for x in c.changeset.parents])
366
366
367 c.diffs = ''
367 c.diffs = ''
368 for x in c.changes:
368 for x in c.changes:
369 c.diffs += x[2]
369 c.diffs += x[2]
370
370
371 return render('changeset/raw_changeset.html')
371 return render('changeset/raw_changeset.html')
372
372
373 @jsonify
373 @jsonify
374 def comment(self, repo_name, revision):
374 def comment(self, repo_name, revision):
375 status = request.POST.get('changeset_status')
375 status = request.POST.get('changeset_status')
376 change_status = request.POST.get('change_changeset_status')
376 change_status = request.POST.get('change_changeset_status')
377
377
378 comm = ChangesetCommentsModel().create(
378 comm = ChangesetCommentsModel().create(
379 text=request.POST.get('text'),
379 text=request.POST.get('text'),
380 repo_id=c.rhodecode_db_repo.repo_id,
380 repo_id=c.rhodecode_db_repo.repo_id,
381 user_id=c.rhodecode_user.user_id,
381 user_id=c.rhodecode_user.user_id,
382 revision=revision,
382 revision=revision,
383 f_path=request.POST.get('f_path'),
383 f_path=request.POST.get('f_path'),
384 line_no=request.POST.get('line'),
384 line_no=request.POST.get('line'),
385 status_change=(ChangesetStatus.get_status_lbl(status)
385 status_change=(ChangesetStatus.get_status_lbl(status)
386 if status and change_status else None)
386 if status and change_status else None)
387 )
387 )
388
388
389 # get status if set !
389 # get status if set !
390 if status and change_status:
390 if status and change_status:
391 ChangesetStatusModel().set_status(
391 ChangesetStatusModel().set_status(
392 c.rhodecode_db_repo.repo_id,
392 c.rhodecode_db_repo.repo_id,
393 revision,
393 revision,
394 status,
394 status,
395 c.rhodecode_user.user_id,
395 c.rhodecode_user.user_id,
396 comm,
396 comm,
397 )
397 )
398 action_logger(self.rhodecode_user,
398 action_logger(self.rhodecode_user,
399 'user_commented_revision:%s' % revision,
399 'user_commented_revision:%s' % revision,
400 c.rhodecode_db_repo, self.ip_addr, self.sa)
400 c.rhodecode_db_repo, self.ip_addr, self.sa)
401
401
402 Session.commit()
402 Session.commit()
403
403
404 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
404 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
405 return redirect(h.url('changeset_home', repo_name=repo_name,
405 return redirect(h.url('changeset_home', repo_name=repo_name,
406 revision=revision))
406 revision=revision))
407
407
408 data = {
408 data = {
409 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
409 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
410 }
410 }
411 if comm:
411 if comm:
412 c.co = comm
412 c.co = comm
413 data.update(comm.get_dict())
413 data.update(comm.get_dict())
414 data.update({'rendered_text':
414 data.update({'rendered_text':
415 render('changeset/changeset_comment_block.html')})
415 render('changeset/changeset_comment_block.html')})
416
416
417 return data
417 return data
418
418
419 @jsonify
419 @jsonify
420 def delete_comment(self, repo_name, comment_id):
420 def delete_comment(self, repo_name, comment_id):
421 co = ChangesetComment.get(comment_id)
421 co = ChangesetComment.get(comment_id)
422 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
422 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
423 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
423 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
424 ChangesetCommentsModel().delete(comment=co)
424 ChangesetCommentsModel().delete(comment=co)
425 Session.commit()
425 Session.commit()
426 return True
426 return True
427 else:
427 else:
428 raise HTTPForbidden()
428 raise HTTPForbidden()
@@ -1,133 +1,257 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
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 logging
25 import logging
26 import traceback
26 import traceback
27 import binascii
28
29 from webob.exc import HTTPNotFound
27
30
28 from pylons import request, response, session, tmpl_context as c, url
31 from pylons import request, response, session, tmpl_context as c, url
29 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import abort, redirect
30 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
31
34
32 from rhodecode.lib.base import BaseRepoController, render
35 from rhodecode.lib.base import BaseRepoController, render
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
35 from rhodecode.model.db import User, PullRequest
38 from rhodecode.lib import diffs
39 from rhodecode.model.db import User, PullRequest, Repository, ChangesetStatus
36 from rhodecode.model.pull_request import PullRequestModel
40 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.comment import ChangesetCommentsModel
44 from rhodecode.model.changeset_status import ChangesetStatusModel
38
45
39 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
40
47
41
48
42 class PullrequestsController(BaseRepoController):
49 class PullrequestsController(BaseRepoController):
43
50
44 @LoginRequired()
51 @LoginRequired()
45 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
46 'repository.admin')
53 'repository.admin')
47 def __before__(self):
54 def __before__(self):
48 super(PullrequestsController, self).__before__()
55 super(PullrequestsController, self).__before__()
49
56
50 def _get_repo_refs(self, repo):
57 def _get_repo_refs(self, repo):
51 hist_l = []
58 hist_l = []
52
59
53 branches_group = ([('branch:' + k, k) for k in repo.branches.keys()],
60 branches_group = ([('branch:%s:%s' % (k, v), k) for
54 _("Branches"))
61 k, v in repo.branches.iteritems()], _("Branches"))
55 bookmarks_group = ([('book:' + k, k) for k in repo.bookmarks.keys()],
62 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
56 _("Bookmarks"))
63 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
57 tags_group = ([('tag:' + k, k) for k in repo.tags.keys()],
64 tags_group = ([('tag:%s:%s' % (k, v), k) for
58 _("Tags"))
65 k, v in repo.tags.iteritems()], _("Tags"))
59
66
60 hist_l.append(bookmarks_group)
67 hist_l.append(bookmarks_group)
61 hist_l.append(branches_group)
68 hist_l.append(branches_group)
62 hist_l.append(tags_group)
69 hist_l.append(tags_group)
63
70
64 return hist_l
71 return hist_l
65
72
73 def show_all(self, repo_name):
74 c.pull_requests = PullRequestModel().get_all(repo_name)
75 c.repo_name = repo_name
76 return render('/pullrequests/pullrequest_show_all.html')
77
66 def index(self):
78 def index(self):
67 org_repo = c.rhodecode_db_repo
79 org_repo = c.rhodecode_db_repo
68 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
80 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
69 c.org_repos = []
81 c.org_repos = []
70 c.other_repos = []
82 c.other_repos = []
71 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
83 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
72 org_repo.user.username, c.repo_name))
84 org_repo.user.username, c.repo_name))
73 )
85 )
74
86
75 c.other_refs = c.org_refs
87 c.other_refs = c.org_refs
76 c.other_repos.extend(c.org_repos)
88 c.other_repos.extend(c.org_repos)
77 c.default_pull_request = org_repo.repo_name
89 c.default_pull_request = org_repo.repo_name
78 #gather forks and add to this list
90 #gather forks and add to this list
79 for fork in org_repo.forks:
91 for fork in org_repo.forks:
80 c.other_repos.append((fork.repo_name, '%s/%s' % (
92 c.other_repos.append((fork.repo_name, '%s/%s' % (
81 fork.user.username, fork.repo_name))
93 fork.user.username, fork.repo_name))
82 )
94 )
83 #add parents of this fork also
95 #add parents of this fork also
84 if org_repo.parent:
96 if org_repo.parent:
85 c.default_pull_request = org_repo.parent.repo_name
97 c.default_pull_request = org_repo.parent.repo_name
86 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
98 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
87 org_repo.parent.user.username,
99 org_repo.parent.user.username,
88 org_repo.parent.repo_name))
100 org_repo.parent.repo_name))
89 )
101 )
90
102
91 #TODO: maybe the owner should be default ?
103 #TODO: maybe the owner should be default ?
92 c.review_members = []
104 c.review_members = []
93 c.available_members = []
105 c.available_members = []
94 for u in User.query().filter(User.username != 'default').all():
106 for u in User.query().filter(User.username != 'default').all():
95 uname = u.username
107 uname = u.username
96 if org_repo.user == u:
108 if org_repo.user == u:
97 uname = _('%s (owner)' % u.username)
109 uname = _('%s (owner)' % u.username)
98 # auto add owner to pull-request recipients
110 # auto add owner to pull-request recipients
99 c.review_members.append([u.user_id, uname])
111 c.review_members.append([u.user_id, uname])
100 c.available_members.append([u.user_id, uname])
112 c.available_members.append([u.user_id, uname])
101 return render('/pullrequests/pullrequest.html')
113 return render('/pullrequests/pullrequest.html')
102
114
103 def create(self, repo_name):
115 def create(self, repo_name):
104 req_p = request.POST
116 req_p = request.POST
105 org_repo = req_p['org_repo']
117 org_repo = req_p['org_repo']
106 org_ref = req_p['org_ref']
118 org_ref = req_p['org_ref']
107 other_repo = req_p['other_repo']
119 other_repo = req_p['other_repo']
108 other_ref = req_p['other_ref']
120 other_ref = req_p['other_ref']
109 revisions = req_p.getall('revisions')
121 revisions = req_p.getall('revisions')
110 reviewers = req_p.getall('review_members')
122 reviewers = req_p.getall('review_members')
111 #TODO: wrap this into a FORM !!!
123 #TODO: wrap this into a FORM !!!
112
124
113 title = req_p['pullrequest_title']
125 title = req_p['pullrequest_title']
114 description = req_p['pullrequest_desc']
126 description = req_p['pullrequest_desc']
115
127
116 try:
128 try:
117 model = PullRequestModel()
129 model = PullRequestModel()
118 model.create(self.rhodecode_user.user_id, org_repo,
130 model.create(self.rhodecode_user.user_id, org_repo,
119 org_ref, other_repo, other_ref, revisions,
131 org_ref, other_repo, other_ref, revisions,
120 reviewers, title, description)
132 reviewers, title, description)
121 Session.commit()
133 Session.commit()
122 h.flash(_('Pull request send'), category='success')
134 h.flash(_('Pull request send'), category='success')
123 except Exception:
135 except Exception:
124 raise
136 raise
125 h.flash(_('Error occured during sending pull request'),
137 h.flash(_('Error occured during sending pull request'),
126 category='error')
138 category='error')
127 log.error(traceback.format_exc())
139 log.error(traceback.format_exc())
128
140
129 return redirect(url('changelog_home', repo_name=repo_name))
141 return redirect(url('changelog_home', repo_name=repo_name))
130
142
143 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, tmp):
144 changesets = []
145 #case two independent repos
146 if org_repo != other_repo:
147 common, incoming, rheads = tmp
148
149 if not incoming:
150 revs = []
151 else:
152 revs = org_repo._repo.changelog.findmissing(common, rheads)
153
154 for cs in reversed(map(binascii.hexlify, revs)):
155 changesets.append(org_repo.get_changeset(cs))
156 else:
157 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
158 other_ref[1])]
159 from mercurial import scmutil
160 out = scmutil.revrange(org_repo._repo, revs)
161 for cs in reversed(out):
162 changesets.append(org_repo.get_changeset(cs))
163
164 return changesets
165
166 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
167 from mercurial import discovery
168 other = org_repo._repo
169 repo = other_repo._repo
170 tip = other[org_ref[1]]
171 log.debug('Doing discovery for %s@%s vs %s@%s' % (
172 org_repo, org_ref, other_repo, other_ref)
173 )
174 log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
175 tmp = discovery.findcommonincoming(
176 repo=repo, # other_repo we check for incoming
177 remote=other, # org_repo source for incoming
178 heads=[tip.node()],
179 force=False
180 )
181 return tmp
182
183 def _compare(self, pull_request):
184
185 org_repo = pull_request.org_repo
186 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
187 other_repo = pull_request.other_repo
188 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
189
190 org_ref = (org_ref_type, org_ref)
191 other_ref = (other_ref_type, other_ref)
192
193 c.org_repo = org_repo
194 c.other_repo = other_repo
195
196 discovery_data = self._get_discovery(org_repo.scm_instance,
197 org_ref,
198 other_repo.scm_instance,
199 other_ref)
200 c.cs_ranges = self._get_changesets(org_repo.scm_instance,
201 org_ref,
202 other_repo.scm_instance,
203 other_ref,
204 discovery_data)
205
206 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
207 c.cs_ranges])
208 # defines that we need hidden inputs with changesets
209 c.as_form = request.GET.get('as_form', False)
210 if request.environ.get('HTTP_X_PARTIAL_XHR'):
211 return render('compare/compare_cs.html')
212
213 c.org_ref = org_ref[1]
214 c.other_ref = other_ref[1]
215 # diff needs to have swapped org with other to generate proper diff
216 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
217 discovery_data)
218 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
219 _parsed = diff_processor.prepare()
220
221 c.files = []
222 c.changes = {}
223
224 for f in _parsed:
225 fid = h.FID('', f['filename'])
226 c.files.append([fid, f['operation'], f['filename'], f['stats']])
227 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
228 c.changes[fid] = [f['operation'], f['filename'], diff]
229
131 def show(self, repo_name, pull_request_id):
230 def show(self, repo_name, pull_request_id):
231 repo_model = RepoModel()
232 c.users_array = repo_model.get_users_js()
233 c.users_groups_array = repo_model.get_users_groups_js()
132 c.pull_request = PullRequest.get(pull_request_id)
234 c.pull_request = PullRequest.get(pull_request_id)
235 ##TODO: need more generic solution
236 self._compare(c.pull_request)
237
238 # inline comments
239 c.inline_cnt = 0
240 c.inline_comments = ChangesetCommentsModel()\
241 .get_inline_comments(c.rhodecode_db_repo.repo_id,
242 pull_request=pull_request_id)
243 # count inline comments
244 for __, lines in c.inline_comments:
245 for comments in lines.values():
246 c.inline_cnt += len(comments)
247 # comments
248 c.comments = ChangesetCommentsModel()\
249 .get_comments(c.rhodecode_db_repo.repo_id,
250 pull_request=pull_request_id)
251
252 # changeset(pull-request) statuse
253 c.current_changeset_status = ChangesetStatusModel()\
254 .get_status(c.rhodecode_db_repo.repo_id,
255 pull_request=pull_request_id)
256 c.changeset_statuses = ChangesetStatus.STATUSES
133 return render('/pullrequests/pullrequest_show.html')
257 return render('/pullrequests/pullrequest_show.html')
@@ -1,217 +1,219 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 paste.httpheaders import WWW_AUTHENTICATE
11 from paste.httpheaders import WWW_AUTHENTICATE
12
12
13 from pylons import config, tmpl_context as c, request, session, url
13 from pylons import config, tmpl_context as c, request, session, url
14 from pylons.controllers import WSGIController
14 from pylons.controllers import WSGIController
15 from pylons.controllers.util import redirect
15 from pylons.controllers.util import redirect
16 from pylons.templating import render_mako as render
16 from pylons.templating import render_mako as render
17
17
18 from rhodecode import __version__, BACKENDS
18 from rhodecode import __version__, BACKENDS
19
19
20 from rhodecode.lib.utils2 import str2bool, safe_unicode
20 from rhodecode.lib.utils2 import str2bool, safe_unicode
21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 HasPermissionAnyMiddleware, CookieStoreWrapper
22 HasPermissionAnyMiddleware, CookieStoreWrapper
23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 from rhodecode.model import meta
24 from rhodecode.model import meta
25
25
26 from rhodecode.model.db import Repository
26 from rhodecode.model.db import Repository
27 from rhodecode.model.notification import NotificationModel
27 from rhodecode.model.notification import NotificationModel
28 from rhodecode.model.scm import ScmModel
28 from rhodecode.model.scm import ScmModel
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 def _get_ip_addr(environ):
33 def _get_ip_addr(environ):
34 proxy_key = 'HTTP_X_REAL_IP'
34 proxy_key = 'HTTP_X_REAL_IP'
35 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
35 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
36 def_key = 'REMOTE_ADDR'
36 def_key = 'REMOTE_ADDR'
37
37
38 return environ.get(proxy_key2,
38 return environ.get(proxy_key2,
39 environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
39 environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
40 )
40 )
41
41
42
42
43 class BasicAuth(AuthBasicAuthenticator):
43 class BasicAuth(AuthBasicAuthenticator):
44
44
45 def __init__(self, realm, authfunc, auth_http_code=None):
45 def __init__(self, realm, authfunc, auth_http_code=None):
46 self.realm = realm
46 self.realm = realm
47 self.authfunc = authfunc
47 self.authfunc = authfunc
48 self._rc_auth_http_code = auth_http_code
48 self._rc_auth_http_code = auth_http_code
49
49
50 def build_authentication(self):
50 def build_authentication(self):
51 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
51 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
52 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
52 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
53 # return 403 if alternative http return code is specified in
53 # return 403 if alternative http return code is specified in
54 # RhodeCode config
54 # RhodeCode config
55 return HTTPForbidden(headers=head)
55 return HTTPForbidden(headers=head)
56 return HTTPUnauthorized(headers=head)
56 return HTTPUnauthorized(headers=head)
57
57
58
58
59 class BaseVCSController(object):
59 class BaseVCSController(object):
60
60
61 def __init__(self, application, config):
61 def __init__(self, application, config):
62 self.application = application
62 self.application = application
63 self.config = config
63 self.config = config
64 # base path of repo locations
64 # base path of repo locations
65 self.basepath = self.config['base_path']
65 self.basepath = self.config['base_path']
66 #authenticate this mercurial request using authfunc
66 #authenticate this mercurial request using authfunc
67 self.authenticate = BasicAuth('', authfunc,
67 self.authenticate = BasicAuth('', authfunc,
68 config.get('auth_ret_code'))
68 config.get('auth_ret_code'))
69 self.ipaddr = '0.0.0.0'
69 self.ipaddr = '0.0.0.0'
70
70
71 def _handle_request(self, environ, start_response):
71 def _handle_request(self, environ, start_response):
72 raise NotImplementedError()
72 raise NotImplementedError()
73
73
74 def _get_by_id(self, repo_name):
74 def _get_by_id(self, repo_name):
75 """
75 """
76 Get's a special pattern _<ID> from clone url and tries to replace it
76 Get's a special pattern _<ID> from clone url and tries to replace it
77 with a repository_name for support of _<ID> non changable urls
77 with a repository_name for support of _<ID> non changable urls
78
78
79 :param repo_name:
79 :param repo_name:
80 """
80 """
81 try:
81 try:
82 data = repo_name.split('/')
82 data = repo_name.split('/')
83 if len(data) >= 2:
83 if len(data) >= 2:
84 by_id = data[1].split('_')
84 by_id = data[1].split('_')
85 if len(by_id) == 2 and by_id[1].isdigit():
85 if len(by_id) == 2 and by_id[1].isdigit():
86 _repo_name = Repository.get(by_id[1]).repo_name
86 _repo_name = Repository.get(by_id[1]).repo_name
87 data[1] = _repo_name
87 data[1] = _repo_name
88 except:
88 except:
89 log.debug('Failed to extract repo_name from id %s' % (
89 log.debug('Failed to extract repo_name from id %s' % (
90 traceback.format_exc()
90 traceback.format_exc()
91 )
91 )
92 )
92 )
93
93
94 return '/'.join(data)
94 return '/'.join(data)
95
95
96 def _invalidate_cache(self, repo_name):
96 def _invalidate_cache(self, repo_name):
97 """
97 """
98 Set's cache for this repository for invalidation on next access
98 Set's cache for this repository for invalidation on next access
99
99
100 :param repo_name: full repo name, also a cache key
100 :param repo_name: full repo name, also a cache key
101 """
101 """
102 invalidate_cache('get_repo_cached_%s' % repo_name)
102 invalidate_cache('get_repo_cached_%s' % repo_name)
103
103
104 def _check_permission(self, action, user, repo_name):
104 def _check_permission(self, action, user, repo_name):
105 """
105 """
106 Checks permissions using action (push/pull) user and repository
106 Checks permissions using action (push/pull) user and repository
107 name
107 name
108
108
109 :param action: push or pull action
109 :param action: push or pull action
110 :param user: user instance
110 :param user: user instance
111 :param repo_name: repository name
111 :param repo_name: repository name
112 """
112 """
113 if action == 'push':
113 if action == 'push':
114 if not HasPermissionAnyMiddleware('repository.write',
114 if not HasPermissionAnyMiddleware('repository.write',
115 'repository.admin')(user,
115 'repository.admin')(user,
116 repo_name):
116 repo_name):
117 return False
117 return False
118
118
119 else:
119 else:
120 #any other action need at least read permission
120 #any other action need at least read permission
121 if not HasPermissionAnyMiddleware('repository.read',
121 if not HasPermissionAnyMiddleware('repository.read',
122 'repository.write',
122 'repository.write',
123 'repository.admin')(user,
123 'repository.admin')(user,
124 repo_name):
124 repo_name):
125 return False
125 return False
126
126
127 return True
127 return True
128
128
129 def _get_ip_addr(self, environ):
129 def _get_ip_addr(self, environ):
130 return _get_ip_addr(environ)
130 return _get_ip_addr(environ)
131
131
132 def __call__(self, environ, start_response):
132 def __call__(self, environ, start_response):
133 start = time.time()
133 start = time.time()
134 try:
134 try:
135 return self._handle_request(environ, start_response)
135 return self._handle_request(environ, start_response)
136 finally:
136 finally:
137 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
137 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
138 log.debug('Request time: %.3fs' % (time.time() - start))
138 log.debug('Request time: %.3fs' % (time.time() - start))
139 meta.Session.remove()
139 meta.Session.remove()
140
140
141
141
142 class BaseController(WSGIController):
142 class BaseController(WSGIController):
143
143
144 def __before__(self):
144 def __before__(self):
145 c.rhodecode_version = __version__
145 c.rhodecode_version = __version__
146 c.rhodecode_instanceid = config.get('instance_id')
146 c.rhodecode_instanceid = config.get('instance_id')
147 c.rhodecode_name = config.get('rhodecode_title')
147 c.rhodecode_name = config.get('rhodecode_title')
148 c.use_gravatar = str2bool(config.get('use_gravatar'))
148 c.use_gravatar = str2bool(config.get('use_gravatar'))
149 c.ga_code = config.get('rhodecode_ga_code')
149 c.ga_code = config.get('rhodecode_ga_code')
150 c.repo_name = get_repo_slug(request)
150 c.repo_name = get_repo_slug(request)
151 c.backends = BACKENDS.keys()
151 c.backends = BACKENDS.keys()
152 c.unread_notifications = NotificationModel()\
152 c.unread_notifications = NotificationModel()\
153 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
153 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
154 self.cut_off_limit = int(config.get('cut_off_limit'))
154 self.cut_off_limit = int(config.get('cut_off_limit'))
155
155
156 self.sa = meta.Session
156 self.sa = meta.Session
157 self.scm_model = ScmModel(self.sa)
157 self.scm_model = ScmModel(self.sa)
158 self.ip_addr = ''
158 self.ip_addr = ''
159
159
160 def __call__(self, environ, start_response):
160 def __call__(self, environ, start_response):
161 """Invoke the Controller"""
161 """Invoke the Controller"""
162 # WSGIController.__call__ dispatches to the Controller method
162 # WSGIController.__call__ dispatches to the Controller method
163 # the request is routed to. This routing information is
163 # the request is routed to. This routing information is
164 # available in environ['pylons.routes_dict']
164 # available in environ['pylons.routes_dict']
165 start = time.time()
165 start = time.time()
166 try:
166 try:
167 self.ip_addr = _get_ip_addr(environ)
167 self.ip_addr = _get_ip_addr(environ)
168 # make sure that we update permissions each time we call controller
168 # make sure that we update permissions each time we call controller
169 api_key = request.GET.get('api_key')
169 api_key = request.GET.get('api_key')
170 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
170 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
171 user_id = cookie_store.get('user_id', None)
171 user_id = cookie_store.get('user_id', None)
172 username = get_container_username(environ, config)
172 username = get_container_username(environ, config)
173 auth_user = AuthUser(user_id, api_key, username)
173 auth_user = AuthUser(user_id, api_key, username)
174 request.user = auth_user
174 request.user = auth_user
175 self.rhodecode_user = c.rhodecode_user = auth_user
175 self.rhodecode_user = c.rhodecode_user = auth_user
176 if not self.rhodecode_user.is_authenticated and \
176 if not self.rhodecode_user.is_authenticated and \
177 self.rhodecode_user.user_id is not None:
177 self.rhodecode_user.user_id is not None:
178 self.rhodecode_user.set_authenticated(
178 self.rhodecode_user.set_authenticated(
179 cookie_store.get('is_authenticated')
179 cookie_store.get('is_authenticated')
180 )
180 )
181 log.info('User: %s accessed %s' % (
181 log.info('User: %s accessed %s' % (
182 auth_user, safe_unicode(environ.get('PATH_INFO')))
182 auth_user, safe_unicode(environ.get('PATH_INFO')))
183 )
183 )
184 return WSGIController.__call__(self, environ, start_response)
184 return WSGIController.__call__(self, environ, start_response)
185 finally:
185 finally:
186 log.info('Request to %s time: %.3fs' % (
186 log.info('Request to %s time: %.3fs' % (
187 safe_unicode(environ.get('PATH_INFO')), time.time() - start)
187 safe_unicode(environ.get('PATH_INFO')), time.time() - start)
188 )
188 )
189 meta.Session.remove()
189 meta.Session.remove()
190
190
191
191
192 class BaseRepoController(BaseController):
192 class BaseRepoController(BaseController):
193 """
193 """
194 Base class for controllers responsible for loading all needed data for
194 Base class for controllers responsible for loading all needed data for
195 repository loaded items are
195 repository loaded items are
196
196
197 c.rhodecode_repo: instance of scm repository
197 c.rhodecode_repo: instance of scm repository
198 c.rhodecode_db_repo: instance of db
198 c.rhodecode_db_repo: instance of db
199 c.repository_followers: number of followers
199 c.repository_followers: number of followers
200 c.repository_forks: number of forks
200 c.repository_forks: number of forks
201 """
201 """
202
202
203 def __before__(self):
203 def __before__(self):
204 super(BaseRepoController, self).__before__()
204 super(BaseRepoController, self).__before__()
205 if c.repo_name:
205 if c.repo_name:
206
206
207 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
207 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
208 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
208 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
209
209
210 if c.rhodecode_repo is None:
210 if c.rhodecode_repo is None:
211 log.error('%s this repository is present in database but it '
211 log.error('%s this repository is present in database but it '
212 'cannot be created as an scm instance', c.repo_name)
212 'cannot be created as an scm instance', c.repo_name)
213
213
214 redirect(url('home'))
214 redirect(url('home'))
215
215
216 c.repository_followers = self.scm_model.get_followers(c.repo_name)
216 # some globals counter for menu
217 c.repository_forks = self.scm_model.get_forks(c.repo_name)
217 c.repository_followers = self.scm_model.get_followers(dbr)
218 c.repository_forks = self.scm_model.get_forks(dbr)
219 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr) No newline at end of file
@@ -1,94 +1,108 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.changeset_status
3 rhodecode.model.changeset_status
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6
6
7 :created_on: Apr 30, 2012
7 :created_on: Apr 30, 2012
8 :author: marcink
8 :author: marcink
9 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
9 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25
25
26 import logging
26 import logging
27
27
28 from rhodecode.model import BaseModel
28 from rhodecode.model import BaseModel
29 from rhodecode.model.db import ChangesetStatus
29 from rhodecode.model.db import ChangesetStatus, PullRequest
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class ChangesetStatusModel(BaseModel):
34 class ChangesetStatusModel(BaseModel):
35
35
36 def __get_changeset_status(self, changeset_status):
36 def __get_changeset_status(self, changeset_status):
37 return self._get_instance(ChangesetStatus, changeset_status)
37 return self._get_instance(ChangesetStatus, changeset_status)
38
38
39 def get_status(self, repo, revision):
39 def __get_pull_request(self, pull_request):
40 return self._get_instance(PullRequest, pull_request)
41
42 def get_status(self, repo, revision=None, pull_request=None):
40 """
43 """
41 Returns status of changeset for given revision and version 0
44 Returns latest status of changeset for given revision or for given
42 versioning makes a history of statuses, and version == 0 is always the
45 pull request. Statuses are versioned inside a table itself and
43 current one
46 version == 0 is always the current one
44
47
45 :param repo:
48 :param repo:
46 :type repo:
49 :type repo:
47 :param revision: 40char hash
50 :param revision: 40char hash or None
48 :type revision: str
51 :type revision: str
52 :param pull_request: pull_request reference
53 :type:
49 """
54 """
50 repo = self._get_repo(repo)
55 repo = self._get_repo(repo)
51
56
52 status = ChangesetStatus.query()\
57 q = ChangesetStatus.query()\
53 .filter(ChangesetStatus.repo == repo)\
58 .filter(ChangesetStatus.repo == repo)\
54 .filter(ChangesetStatus.revision == revision)\
59 .filter(ChangesetStatus.version == 0)
55 .filter(ChangesetStatus.version == 0).scalar()
60
61 if revision:
62 q = q.filter(ChangesetStatus.revision == revision)
63 elif pull_request:
64 pull_request = self.__get_pull_request(pull_request)
65 q = q.filter(ChangesetStatus.pull_request == pull_request)
66 else:
67 raise Exception('Please specify revision or pull_request')
68
69 status = q.scalar()
56 status = status.status if status else status
70 status = status.status if status else status
57 st = status or ChangesetStatus.DEFAULT
71 st = status or ChangesetStatus.DEFAULT
58 return str(st)
72 return str(st)
59
73
60 def set_status(self, repo, revision, status, user, comment):
74 def set_status(self, repo, revision, status, user, comment):
61 """
75 """
62 Creates new status for changeset or updates the old ones bumping their
76 Creates new status for changeset or updates the old ones bumping their
63 version, leaving the current status at
77 version, leaving the current status at
64
78
65 :param repo:
79 :param repo:
66 :type repo:
80 :type repo:
67 :param revision:
81 :param revision:
68 :type revision:
82 :type revision:
69 :param status:
83 :param status:
70 :type status:
84 :type status:
71 :param user:
85 :param user:
72 :type user:
86 :type user:
73 :param comment:
87 :param comment:
74 :type comment:
88 :type comment:
75 """
89 """
76 repo = self._get_repo(repo)
90 repo = self._get_repo(repo)
77
91
78 cur_statuses = ChangesetStatus.query()\
92 cur_statuses = ChangesetStatus.query()\
79 .filter(ChangesetStatus.repo == repo)\
93 .filter(ChangesetStatus.repo == repo)\
80 .filter(ChangesetStatus.revision == revision)\
94 .filter(ChangesetStatus.revision == revision)\
81 .all()
95 .all()
82 if cur_statuses:
96 if cur_statuses:
83 for st in cur_statuses:
97 for st in cur_statuses:
84 st.version += 1
98 st.version += 1
85 self.sa.add(st)
99 self.sa.add(st)
86 new_status = ChangesetStatus()
100 new_status = ChangesetStatus()
87 new_status.author = self._get_user(user)
101 new_status.author = self._get_user(user)
88 new_status.repo = self._get_repo(repo)
102 new_status.repo = self._get_repo(repo)
89 new_status.status = status
103 new_status.status = status
90 new_status.revision = revision
104 new_status.revision = revision
91 new_status.comment = comment
105 new_status.comment = comment
92 self.sa.add(new_status)
106 self.sa.add(new_status)
93 return new_status
107 return new_status
94
108
@@ -1,181 +1,188 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.comment
3 rhodecode.model.comment
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 comments model for RhodeCode
6 comments model for RhodeCode
7
7
8 :created_on: Nov 11, 2011
8 :created_on: Nov 11, 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 logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from sqlalchemy.util.compat import defaultdict
30 from sqlalchemy.util.compat import defaultdict
31
31
32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
35 from rhodecode.model.db import ChangesetComment, User, Repository, \
36 Notification, PullRequest
36 from rhodecode.model.notification import NotificationModel
37 from rhodecode.model.notification import NotificationModel
37
38
38 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
39
40
40
41
41 class ChangesetCommentsModel(BaseModel):
42 class ChangesetCommentsModel(BaseModel):
42
43
43 def __get_changeset_comment(self, changeset_comment):
44 def __get_changeset_comment(self, changeset_comment):
44 return self._get_instance(ChangesetComment, changeset_comment)
45 return self._get_instance(ChangesetComment, changeset_comment)
45
46
47 def __get_pull_request(self, pull_request):
48 return self._get_instance(PullRequest, pull_request)
49
46 def _extract_mentions(self, s):
50 def _extract_mentions(self, s):
47 user_objects = []
51 user_objects = []
48 for username in extract_mentioned_users(s):
52 for username in extract_mentioned_users(s):
49 user_obj = User.get_by_username(username, case_insensitive=True)
53 user_obj = User.get_by_username(username, case_insensitive=True)
50 if user_obj:
54 if user_obj:
51 user_objects.append(user_obj)
55 user_objects.append(user_obj)
52 return user_objects
56 return user_objects
53
57
54 def create(self, text, repo_id, user_id, revision, f_path=None,
58 def create(self, text, repo_id, user_id, revision, f_path=None,
55 line_no=None, status_change=None):
59 line_no=None, status_change=None):
56 """
60 """
57 Creates new comment for changeset. IF status_change is not none
61 Creates new comment for changeset. IF status_change is not none
58 this comment is associated with a status change of changeset
62 this comment is associated with a status change of changeset
59
63
60 :param text:
64 :param text:
61 :param repo_id:
65 :param repo_id:
62 :param user_id:
66 :param user_id:
63 :param revision:
67 :param revision:
64 :param f_path:
68 :param f_path:
65 :param line_no:
69 :param line_no:
66 :param status_change:
70 :param status_change:
67 """
71 """
68
72
69 if text:
73 if text:
70 repo = Repository.get(repo_id)
74 repo = Repository.get(repo_id)
71 cs = repo.scm_instance.get_changeset(revision)
75 cs = repo.scm_instance.get_changeset(revision)
72 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
76 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
73 author_email = cs.author_email
77 author_email = cs.author_email
74 comment = ChangesetComment()
78 comment = ChangesetComment()
75 comment.repo = repo
79 comment.repo = repo
76 comment.user_id = user_id
80 comment.user_id = user_id
77 comment.revision = revision
81 comment.revision = revision
78 comment.text = text
82 comment.text = text
79 comment.f_path = f_path
83 comment.f_path = f_path
80 comment.line_no = line_no
84 comment.line_no = line_no
81
85
82 self.sa.add(comment)
86 self.sa.add(comment)
83 self.sa.flush()
87 self.sa.flush()
84 # make notification
88 # make notification
85 line = ''
89 line = ''
86 if line_no:
90 if line_no:
87 line = _('on line %s') % line_no
91 line = _('on line %s') % line_no
88 subj = safe_unicode(
92 subj = safe_unicode(
89 h.link_to('Re commit: %(commit_desc)s %(line)s' % \
93 h.link_to('Re commit: %(commit_desc)s %(line)s' % \
90 {'commit_desc': desc, 'line': line},
94 {'commit_desc': desc, 'line': line},
91 h.url('changeset_home', repo_name=repo.repo_name,
95 h.url('changeset_home', repo_name=repo.repo_name,
92 revision=revision,
96 revision=revision,
93 anchor='comment-%s' % comment.comment_id,
97 anchor='comment-%s' % comment.comment_id,
94 qualified=True,
98 qualified=True,
95 )
99 )
96 )
100 )
97 )
101 )
98
102
99 body = text
103 body = text
100
104
101 # get the current participants of this changeset
105 # get the current participants of this changeset
102 recipients = ChangesetComment.get_users(revision=revision)
106 recipients = ChangesetComment.get_users(revision=revision)
103
107
104 # add changeset author if it's in rhodecode system
108 # add changeset author if it's in rhodecode system
105 recipients += [User.get_by_email(author_email)]
109 recipients += [User.get_by_email(author_email)]
106
110
107 # create notification objects, and emails
111 # create notification objects, and emails
108 NotificationModel().create(
112 NotificationModel().create(
109 created_by=user_id, subject=subj, body=body,
113 created_by=user_id, subject=subj, body=body,
110 recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT,
114 recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT,
111 email_kwargs={'status_change': status_change}
115 email_kwargs={'status_change': status_change}
112 )
116 )
113
117
114 mention_recipients = set(self._extract_mentions(body))\
118 mention_recipients = set(self._extract_mentions(body))\
115 .difference(recipients)
119 .difference(recipients)
116 if mention_recipients:
120 if mention_recipients:
117 subj = _('[Mention]') + ' ' + subj
121 subj = _('[Mention]') + ' ' + subj
118 NotificationModel().create(
122 NotificationModel().create(
119 created_by=user_id, subject=subj, body=body,
123 created_by=user_id, subject=subj, body=body,
120 recipients=mention_recipients,
124 recipients=mention_recipients,
121 type_=Notification.TYPE_CHANGESET_COMMENT,
125 type_=Notification.TYPE_CHANGESET_COMMENT,
122 email_kwargs={'status_change': status_change}
126 email_kwargs={'status_change': status_change}
123 )
127 )
124
128
125 return comment
129 return comment
126
130
127 def delete(self, comment):
131 def delete(self, comment):
128 """
132 """
129 Deletes given comment
133 Deletes given comment
130
134
131 :param comment_id:
135 :param comment_id:
132 """
136 """
133 comment = self.__get_changeset_comment(comment)
137 comment = self.__get_changeset_comment(comment)
134 self.sa.delete(comment)
138 self.sa.delete(comment)
135
139
136 return comment
140 return comment
137
141
138 def get_comments(self, repo_id, revision=None, pull_request_id=None):
142 def get_comments(self, repo_id, revision=None, pull_request=None):
139 """
143 """
140 Get's main comments based on revision or pull_request_id
144 Get's main comments based on revision or pull_request_id
141
145
142 :param repo_id:
146 :param repo_id:
143 :type repo_id:
147 :type repo_id:
144 :param revision:
148 :param revision:
145 :type revision:
149 :type revision:
146 :param pull_request_id:
150 :param pull_request:
147 :type pull_request_id:
151 :type pull_request:
148 """
152 """
153
149 q = ChangesetComment.query()\
154 q = ChangesetComment.query()\
150 .filter(ChangesetComment.repo_id == repo_id)\
155 .filter(ChangesetComment.repo_id == repo_id)\
151 .filter(ChangesetComment.line_no == None)\
156 .filter(ChangesetComment.line_no == None)\
152 .filter(ChangesetComment.f_path == None)
157 .filter(ChangesetComment.f_path == None)
153 if revision:
158 if revision:
154 q = q.filter(ChangesetComment.revision == revision)
159 q = q.filter(ChangesetComment.revision == revision)
155 elif pull_request_id:
160 elif pull_request:
156 q = q.filter(ChangesetComment.pull_request_id == pull_request_id)
161 pull_request = self.__get_pull_request(pull_request)
162 q = q.filter(ChangesetComment.pull_request == pull_request)
157 else:
163 else:
158 raise Exception('Please specify revision or pull_request_id')
164 raise Exception('Please specify revision or pull_request')
159 return q.all()
165 return q.all()
160
166
161 def get_inline_comments(self, repo_id, revision=None, pull_request_id=None):
167 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
162 q = self.sa.query(ChangesetComment)\
168 q = self.sa.query(ChangesetComment)\
163 .filter(ChangesetComment.repo_id == repo_id)\
169 .filter(ChangesetComment.repo_id == repo_id)\
164 .filter(ChangesetComment.line_no != None)\
170 .filter(ChangesetComment.line_no != None)\
165 .filter(ChangesetComment.f_path != None)\
171 .filter(ChangesetComment.f_path != None)\
166 .order_by(ChangesetComment.comment_id.asc())\
172 .order_by(ChangesetComment.comment_id.asc())\
167
173
168 if revision:
174 if revision:
169 q = q.filter(ChangesetComment.revision == revision)
175 q = q.filter(ChangesetComment.revision == revision)
170 elif pull_request_id:
176 elif pull_request:
171 q = q.filter(ChangesetComment.pull_request_id == pull_request_id)
177 pull_request = self.__get_pull_request(pull_request)
178 q = q.filter(ChangesetComment.pull_request == pull_request)
172 else:
179 else:
173 raise Exception('Please specify revision or pull_request_id')
180 raise Exception('Please specify revision or pull_request_id')
174
181
175 comments = q.all()
182 comments = q.all()
176
183
177 paths = defaultdict(lambda: defaultdict(list))
184 paths = defaultdict(lambda: defaultdict(list))
178
185
179 for co in comments:
186 for co in comments:
180 paths[co.f_path][co.line_no].append(co)
187 paths[co.f_path][co.line_no].append(co)
181 return paths.items()
188 return paths.items()
@@ -1,1527 +1,1532 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 from collections import defaultdict
31 from collections import defaultdict
32
32
33 from sqlalchemy import *
33 from sqlalchemy import *
34 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.exc import DatabaseError
36 from sqlalchemy.exc import DatabaseError
37 from beaker.cache import cache_region, region_invalidate
37 from beaker.cache import cache_region, region_invalidate
38
38
39 from pylons.i18n.translation import lazy_ugettext as _
39 from pylons.i18n.translation import lazy_ugettext as _
40
40
41 from rhodecode.lib.vcs import get_backend
41 from rhodecode.lib.vcs import get_backend
42 from rhodecode.lib.vcs.utils.helpers import get_scm
42 from rhodecode.lib.vcs.utils.helpers import get_scm
43 from rhodecode.lib.vcs.exceptions import VCSError
43 from rhodecode.lib.vcs.exceptions import VCSError
44 from rhodecode.lib.vcs.utils.lazy import LazyProperty
44 from rhodecode.lib.vcs.utils.lazy import LazyProperty
45
45
46 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
46 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
47 safe_unicode
47 safe_unicode
48 from rhodecode.lib.compat import json
48 from rhodecode.lib.compat import json
49 from rhodecode.lib.caching_query import FromCache
49 from rhodecode.lib.caching_query import FromCache
50
50
51 from rhodecode.model.meta import Base, Session
51 from rhodecode.model.meta import Base, Session
52
52
53
53
54 URL_SEP = '/'
54 URL_SEP = '/'
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 #==============================================================================
57 #==============================================================================
58 # BASE CLASSES
58 # BASE CLASSES
59 #==============================================================================
59 #==============================================================================
60
60
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62
62
63
63
64 class ModelSerializer(json.JSONEncoder):
64 class ModelSerializer(json.JSONEncoder):
65 """
65 """
66 Simple Serializer for JSON,
66 Simple Serializer for JSON,
67
67
68 usage::
68 usage::
69
69
70 to make object customized for serialization implement a __json__
70 to make object customized for serialization implement a __json__
71 method that will return a dict for serialization into json
71 method that will return a dict for serialization into json
72
72
73 example::
73 example::
74
74
75 class Task(object):
75 class Task(object):
76
76
77 def __init__(self, name, value):
77 def __init__(self, name, value):
78 self.name = name
78 self.name = name
79 self.value = value
79 self.value = value
80
80
81 def __json__(self):
81 def __json__(self):
82 return dict(name=self.name,
82 return dict(name=self.name,
83 value=self.value)
83 value=self.value)
84
84
85 """
85 """
86
86
87 def default(self, obj):
87 def default(self, obj):
88
88
89 if hasattr(obj, '__json__'):
89 if hasattr(obj, '__json__'):
90 return obj.__json__()
90 return obj.__json__()
91 else:
91 else:
92 return json.JSONEncoder.default(self, obj)
92 return json.JSONEncoder.default(self, obj)
93
93
94
94
95 class BaseModel(object):
95 class BaseModel(object):
96 """
96 """
97 Base Model for all classess
97 Base Model for all classess
98 """
98 """
99
99
100 @classmethod
100 @classmethod
101 def _get_keys(cls):
101 def _get_keys(cls):
102 """return column names for this model """
102 """return column names for this model """
103 return class_mapper(cls).c.keys()
103 return class_mapper(cls).c.keys()
104
104
105 def get_dict(self):
105 def get_dict(self):
106 """
106 """
107 return dict with keys and values corresponding
107 return dict with keys and values corresponding
108 to this model data """
108 to this model data """
109
109
110 d = {}
110 d = {}
111 for k in self._get_keys():
111 for k in self._get_keys():
112 d[k] = getattr(self, k)
112 d[k] = getattr(self, k)
113
113
114 # also use __json__() if present to get additional fields
114 # also use __json__() if present to get additional fields
115 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
115 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
116 d[k] = val
116 d[k] = val
117 return d
117 return d
118
118
119 def get_appstruct(self):
119 def get_appstruct(self):
120 """return list with keys and values tupples corresponding
120 """return list with keys and values tupples corresponding
121 to this model data """
121 to this model data """
122
122
123 l = []
123 l = []
124 for k in self._get_keys():
124 for k in self._get_keys():
125 l.append((k, getattr(self, k),))
125 l.append((k, getattr(self, k),))
126 return l
126 return l
127
127
128 def populate_obj(self, populate_dict):
128 def populate_obj(self, populate_dict):
129 """populate model with data from given populate_dict"""
129 """populate model with data from given populate_dict"""
130
130
131 for k in self._get_keys():
131 for k in self._get_keys():
132 if k in populate_dict:
132 if k in populate_dict:
133 setattr(self, k, populate_dict[k])
133 setattr(self, k, populate_dict[k])
134
134
135 @classmethod
135 @classmethod
136 def query(cls):
136 def query(cls):
137 return Session.query(cls)
137 return Session.query(cls)
138
138
139 @classmethod
139 @classmethod
140 def get(cls, id_):
140 def get(cls, id_):
141 if id_:
141 if id_:
142 return cls.query().get(id_)
142 return cls.query().get(id_)
143
143
144 @classmethod
144 @classmethod
145 def getAll(cls):
145 def getAll(cls):
146 return cls.query().all()
146 return cls.query().all()
147
147
148 @classmethod
148 @classmethod
149 def delete(cls, id_):
149 def delete(cls, id_):
150 obj = cls.query().get(id_)
150 obj = cls.query().get(id_)
151 Session.delete(obj)
151 Session.delete(obj)
152
152
153 def __repr__(self):
153 def __repr__(self):
154 if hasattr(self, '__unicode__'):
154 if hasattr(self, '__unicode__'):
155 # python repr needs to return str
155 # python repr needs to return str
156 return safe_str(self.__unicode__())
156 return safe_str(self.__unicode__())
157 return '<DB:%s>' % (self.__class__.__name__)
157 return '<DB:%s>' % (self.__class__.__name__)
158
158
159
159
160 class RhodeCodeSetting(Base, BaseModel):
160 class RhodeCodeSetting(Base, BaseModel):
161 __tablename__ = 'rhodecode_settings'
161 __tablename__ = 'rhodecode_settings'
162 __table_args__ = (
162 __table_args__ = (
163 UniqueConstraint('app_settings_name'),
163 UniqueConstraint('app_settings_name'),
164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
165 'mysql_charset': 'utf8'}
165 'mysql_charset': 'utf8'}
166 )
166 )
167 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
167 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
168 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
168 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
169 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
169 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
170
170
171 def __init__(self, k='', v=''):
171 def __init__(self, k='', v=''):
172 self.app_settings_name = k
172 self.app_settings_name = k
173 self.app_settings_value = v
173 self.app_settings_value = v
174
174
175 @validates('_app_settings_value')
175 @validates('_app_settings_value')
176 def validate_settings_value(self, key, val):
176 def validate_settings_value(self, key, val):
177 assert type(val) == unicode
177 assert type(val) == unicode
178 return val
178 return val
179
179
180 @hybrid_property
180 @hybrid_property
181 def app_settings_value(self):
181 def app_settings_value(self):
182 v = self._app_settings_value
182 v = self._app_settings_value
183 if self.app_settings_name == 'ldap_active':
183 if self.app_settings_name == 'ldap_active':
184 v = str2bool(v)
184 v = str2bool(v)
185 return v
185 return v
186
186
187 @app_settings_value.setter
187 @app_settings_value.setter
188 def app_settings_value(self, val):
188 def app_settings_value(self, val):
189 """
189 """
190 Setter that will always make sure we use unicode in app_settings_value
190 Setter that will always make sure we use unicode in app_settings_value
191
191
192 :param val:
192 :param val:
193 """
193 """
194 self._app_settings_value = safe_unicode(val)
194 self._app_settings_value = safe_unicode(val)
195
195
196 def __unicode__(self):
196 def __unicode__(self):
197 return u"<%s('%s:%s')>" % (
197 return u"<%s('%s:%s')>" % (
198 self.__class__.__name__,
198 self.__class__.__name__,
199 self.app_settings_name, self.app_settings_value
199 self.app_settings_name, self.app_settings_value
200 )
200 )
201
201
202 @classmethod
202 @classmethod
203 def get_by_name(cls, ldap_key):
203 def get_by_name(cls, ldap_key):
204 return cls.query()\
204 return cls.query()\
205 .filter(cls.app_settings_name == ldap_key).scalar()
205 .filter(cls.app_settings_name == ldap_key).scalar()
206
206
207 @classmethod
207 @classmethod
208 def get_app_settings(cls, cache=False):
208 def get_app_settings(cls, cache=False):
209
209
210 ret = cls.query()
210 ret = cls.query()
211
211
212 if cache:
212 if cache:
213 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
213 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
214
214
215 if not ret:
215 if not ret:
216 raise Exception('Could not get application settings !')
216 raise Exception('Could not get application settings !')
217 settings = {}
217 settings = {}
218 for each in ret:
218 for each in ret:
219 settings['rhodecode_' + each.app_settings_name] = \
219 settings['rhodecode_' + each.app_settings_name] = \
220 each.app_settings_value
220 each.app_settings_value
221
221
222 return settings
222 return settings
223
223
224 @classmethod
224 @classmethod
225 def get_ldap_settings(cls, cache=False):
225 def get_ldap_settings(cls, cache=False):
226 ret = cls.query()\
226 ret = cls.query()\
227 .filter(cls.app_settings_name.startswith('ldap_')).all()
227 .filter(cls.app_settings_name.startswith('ldap_')).all()
228 fd = {}
228 fd = {}
229 for row in ret:
229 for row in ret:
230 fd.update({row.app_settings_name: row.app_settings_value})
230 fd.update({row.app_settings_name: row.app_settings_value})
231
231
232 return fd
232 return fd
233
233
234
234
235 class RhodeCodeUi(Base, BaseModel):
235 class RhodeCodeUi(Base, BaseModel):
236 __tablename__ = 'rhodecode_ui'
236 __tablename__ = 'rhodecode_ui'
237 __table_args__ = (
237 __table_args__ = (
238 UniqueConstraint('ui_key'),
238 UniqueConstraint('ui_key'),
239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
240 'mysql_charset': 'utf8'}
240 'mysql_charset': 'utf8'}
241 )
241 )
242
242
243 HOOK_UPDATE = 'changegroup.update'
243 HOOK_UPDATE = 'changegroup.update'
244 HOOK_REPO_SIZE = 'changegroup.repo_size'
244 HOOK_REPO_SIZE = 'changegroup.repo_size'
245 HOOK_PUSH = 'changegroup.push_logger'
245 HOOK_PUSH = 'changegroup.push_logger'
246 HOOK_PULL = 'preoutgoing.pull_logger'
246 HOOK_PULL = 'preoutgoing.pull_logger'
247
247
248 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
248 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
249 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
249 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
252 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
252 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
253
253
254 @classmethod
254 @classmethod
255 def get_by_key(cls, key):
255 def get_by_key(cls, key):
256 return cls.query().filter(cls.ui_key == key)
256 return cls.query().filter(cls.ui_key == key)
257
257
258 @classmethod
258 @classmethod
259 def get_builtin_hooks(cls):
259 def get_builtin_hooks(cls):
260 q = cls.query()
260 q = cls.query()
261 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
261 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
262 cls.HOOK_REPO_SIZE,
262 cls.HOOK_REPO_SIZE,
263 cls.HOOK_PUSH, cls.HOOK_PULL]))
263 cls.HOOK_PUSH, cls.HOOK_PULL]))
264 return q.all()
264 return q.all()
265
265
266 @classmethod
266 @classmethod
267 def get_custom_hooks(cls):
267 def get_custom_hooks(cls):
268 q = cls.query()
268 q = cls.query()
269 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
269 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
270 cls.HOOK_REPO_SIZE,
270 cls.HOOK_REPO_SIZE,
271 cls.HOOK_PUSH, cls.HOOK_PULL]))
271 cls.HOOK_PUSH, cls.HOOK_PULL]))
272 q = q.filter(cls.ui_section == 'hooks')
272 q = q.filter(cls.ui_section == 'hooks')
273 return q.all()
273 return q.all()
274
274
275 @classmethod
275 @classmethod
276 def get_repos_location(cls):
276 def get_repos_location(cls):
277 return cls.get_by_key('/').one().ui_value
277 return cls.get_by_key('/').one().ui_value
278
278
279 @classmethod
279 @classmethod
280 def create_or_update_hook(cls, key, val):
280 def create_or_update_hook(cls, key, val):
281 new_ui = cls.get_by_key(key).scalar() or cls()
281 new_ui = cls.get_by_key(key).scalar() or cls()
282 new_ui.ui_section = 'hooks'
282 new_ui.ui_section = 'hooks'
283 new_ui.ui_active = True
283 new_ui.ui_active = True
284 new_ui.ui_key = key
284 new_ui.ui_key = key
285 new_ui.ui_value = val
285 new_ui.ui_value = val
286
286
287 Session.add(new_ui)
287 Session.add(new_ui)
288
288
289
289
290 class User(Base, BaseModel):
290 class User(Base, BaseModel):
291 __tablename__ = 'users'
291 __tablename__ = 'users'
292 __table_args__ = (
292 __table_args__ = (
293 UniqueConstraint('username'), UniqueConstraint('email'),
293 UniqueConstraint('username'), UniqueConstraint('email'),
294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
295 'mysql_charset': 'utf8'}
295 'mysql_charset': 'utf8'}
296 )
296 )
297 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
297 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
298 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
300 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
301 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
301 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
302 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
302 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
305 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
305 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
306 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
308
308
309 user_log = relationship('UserLog', cascade='all')
309 user_log = relationship('UserLog', cascade='all')
310 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
310 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
311
311
312 repositories = relationship('Repository')
312 repositories = relationship('Repository')
313 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
313 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
314 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
314 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
315 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
315 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
316
316
317 group_member = relationship('UsersGroupMember', cascade='all')
317 group_member = relationship('UsersGroupMember', cascade='all')
318
318
319 notifications = relationship('UserNotification', cascade='all')
319 notifications = relationship('UserNotification', cascade='all')
320 # notifications assigned to this user
320 # notifications assigned to this user
321 user_created_notifications = relationship('Notification', cascade='all')
321 user_created_notifications = relationship('Notification', cascade='all')
322 # comments created by this user
322 # comments created by this user
323 user_comments = relationship('ChangesetComment', cascade='all')
323 user_comments = relationship('ChangesetComment', cascade='all')
324
324
325 @hybrid_property
325 @hybrid_property
326 def email(self):
326 def email(self):
327 return self._email
327 return self._email
328
328
329 @email.setter
329 @email.setter
330 def email(self, val):
330 def email(self, val):
331 self._email = val.lower() if val else None
331 self._email = val.lower() if val else None
332
332
333 @property
333 @property
334 def full_name(self):
334 def full_name(self):
335 return '%s %s' % (self.name, self.lastname)
335 return '%s %s' % (self.name, self.lastname)
336
336
337 @property
337 @property
338 def full_name_or_username(self):
338 def full_name_or_username(self):
339 return ('%s %s' % (self.name, self.lastname)
339 return ('%s %s' % (self.name, self.lastname)
340 if (self.name and self.lastname) else self.username)
340 if (self.name and self.lastname) else self.username)
341
341
342 @property
342 @property
343 def full_contact(self):
343 def full_contact(self):
344 return '%s %s <%s>' % (self.name, self.lastname, self.email)
344 return '%s %s <%s>' % (self.name, self.lastname, self.email)
345
345
346 @property
346 @property
347 def short_contact(self):
347 def short_contact(self):
348 return '%s %s' % (self.name, self.lastname)
348 return '%s %s' % (self.name, self.lastname)
349
349
350 @property
350 @property
351 def is_admin(self):
351 def is_admin(self):
352 return self.admin
352 return self.admin
353
353
354 def __unicode__(self):
354 def __unicode__(self):
355 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
355 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
356 self.user_id, self.username)
356 self.user_id, self.username)
357
357
358 @classmethod
358 @classmethod
359 def get_by_username(cls, username, case_insensitive=False, cache=False):
359 def get_by_username(cls, username, case_insensitive=False, cache=False):
360 if case_insensitive:
360 if case_insensitive:
361 q = cls.query().filter(cls.username.ilike(username))
361 q = cls.query().filter(cls.username.ilike(username))
362 else:
362 else:
363 q = cls.query().filter(cls.username == username)
363 q = cls.query().filter(cls.username == username)
364
364
365 if cache:
365 if cache:
366 q = q.options(FromCache(
366 q = q.options(FromCache(
367 "sql_cache_short",
367 "sql_cache_short",
368 "get_user_%s" % _hash_key(username)
368 "get_user_%s" % _hash_key(username)
369 )
369 )
370 )
370 )
371 return q.scalar()
371 return q.scalar()
372
372
373 @classmethod
373 @classmethod
374 def get_by_api_key(cls, api_key, cache=False):
374 def get_by_api_key(cls, api_key, cache=False):
375 q = cls.query().filter(cls.api_key == api_key)
375 q = cls.query().filter(cls.api_key == api_key)
376
376
377 if cache:
377 if cache:
378 q = q.options(FromCache("sql_cache_short",
378 q = q.options(FromCache("sql_cache_short",
379 "get_api_key_%s" % api_key))
379 "get_api_key_%s" % api_key))
380 return q.scalar()
380 return q.scalar()
381
381
382 @classmethod
382 @classmethod
383 def get_by_email(cls, email, case_insensitive=False, cache=False):
383 def get_by_email(cls, email, case_insensitive=False, cache=False):
384 if case_insensitive:
384 if case_insensitive:
385 q = cls.query().filter(cls.email.ilike(email))
385 q = cls.query().filter(cls.email.ilike(email))
386 else:
386 else:
387 q = cls.query().filter(cls.email == email)
387 q = cls.query().filter(cls.email == email)
388
388
389 if cache:
389 if cache:
390 q = q.options(FromCache("sql_cache_short",
390 q = q.options(FromCache("sql_cache_short",
391 "get_email_key_%s" % email))
391 "get_email_key_%s" % email))
392
392
393 ret = q.scalar()
393 ret = q.scalar()
394 if ret is None:
394 if ret is None:
395 q = UserEmailMap.query()
395 q = UserEmailMap.query()
396 # try fetching in alternate email map
396 # try fetching in alternate email map
397 if case_insensitive:
397 if case_insensitive:
398 q = q.filter(UserEmailMap.email.ilike(email))
398 q = q.filter(UserEmailMap.email.ilike(email))
399 else:
399 else:
400 q = q.filter(UserEmailMap.email == email)
400 q = q.filter(UserEmailMap.email == email)
401 q = q.options(joinedload(UserEmailMap.user))
401 q = q.options(joinedload(UserEmailMap.user))
402 if cache:
402 if cache:
403 q = q.options(FromCache("sql_cache_short",
403 q = q.options(FromCache("sql_cache_short",
404 "get_email_map_key_%s" % email))
404 "get_email_map_key_%s" % email))
405 ret = getattr(q.scalar(), 'user', None)
405 ret = getattr(q.scalar(), 'user', None)
406
406
407 return ret
407 return ret
408
408
409 def update_lastlogin(self):
409 def update_lastlogin(self):
410 """Update user lastlogin"""
410 """Update user lastlogin"""
411 self.last_login = datetime.datetime.now()
411 self.last_login = datetime.datetime.now()
412 Session.add(self)
412 Session.add(self)
413 log.debug('updated user %s lastlogin' % self.username)
413 log.debug('updated user %s lastlogin' % self.username)
414
414
415 def __json__(self):
415 def __json__(self):
416 return dict(
416 return dict(
417 user_id=self.user_id,
417 user_id=self.user_id,
418 first_name=self.name,
418 first_name=self.name,
419 last_name=self.lastname,
419 last_name=self.lastname,
420 email=self.email,
420 email=self.email,
421 full_name=self.full_name,
421 full_name=self.full_name,
422 full_name_or_username=self.full_name_or_username,
422 full_name_or_username=self.full_name_or_username,
423 short_contact=self.short_contact,
423 short_contact=self.short_contact,
424 full_contact=self.full_contact
424 full_contact=self.full_contact
425 )
425 )
426
426
427
427
428 class UserEmailMap(Base, BaseModel):
428 class UserEmailMap(Base, BaseModel):
429 __tablename__ = 'user_email_map'
429 __tablename__ = 'user_email_map'
430 __table_args__ = (
430 __table_args__ = (
431 UniqueConstraint('email'),
431 UniqueConstraint('email'),
432 {'extend_existing': True, 'mysql_engine':'InnoDB',
432 {'extend_existing': True, 'mysql_engine':'InnoDB',
433 'mysql_charset': 'utf8'}
433 'mysql_charset': 'utf8'}
434 )
434 )
435 __mapper_args__ = {}
435 __mapper_args__ = {}
436
436
437 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
437 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
438 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
438 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
439 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
439 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
440
440
441 user = relationship('User')
441 user = relationship('User')
442
442
443 @validates('_email')
443 @validates('_email')
444 def validate_email(self, key, email):
444 def validate_email(self, key, email):
445 # check if this email is not main one
445 # check if this email is not main one
446 main_email = Session.query(User).filter(User.email == email).scalar()
446 main_email = Session.query(User).filter(User.email == email).scalar()
447 if main_email is not None:
447 if main_email is not None:
448 raise AttributeError('email %s is present is user table' % email)
448 raise AttributeError('email %s is present is user table' % email)
449 return email
449 return email
450
450
451 @hybrid_property
451 @hybrid_property
452 def email(self):
452 def email(self):
453 return self._email
453 return self._email
454
454
455 @email.setter
455 @email.setter
456 def email(self, val):
456 def email(self, val):
457 self._email = val.lower() if val else None
457 self._email = val.lower() if val else None
458
458
459
459
460 class UserLog(Base, BaseModel):
460 class UserLog(Base, BaseModel):
461 __tablename__ = 'user_logs'
461 __tablename__ = 'user_logs'
462 __table_args__ = (
462 __table_args__ = (
463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
464 'mysql_charset': 'utf8'},
464 'mysql_charset': 'utf8'},
465 )
465 )
466 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
466 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
468 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
468 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
469 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
469 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
470 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
470 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
471 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
471 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
472 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
472 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
473
473
474 @property
474 @property
475 def action_as_day(self):
475 def action_as_day(self):
476 return datetime.date(*self.action_date.timetuple()[:3])
476 return datetime.date(*self.action_date.timetuple()[:3])
477
477
478 user = relationship('User')
478 user = relationship('User')
479 repository = relationship('Repository', cascade='')
479 repository = relationship('Repository', cascade='')
480
480
481
481
482 class UsersGroup(Base, BaseModel):
482 class UsersGroup(Base, BaseModel):
483 __tablename__ = 'users_groups'
483 __tablename__ = 'users_groups'
484 __table_args__ = (
484 __table_args__ = (
485 {'extend_existing': True, 'mysql_engine': 'InnoDB',
485 {'extend_existing': True, 'mysql_engine': 'InnoDB',
486 'mysql_charset': 'utf8'},
486 'mysql_charset': 'utf8'},
487 )
487 )
488
488
489 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
490 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
490 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
491 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
491 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
492
492
493 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
493 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
494 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
494 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
495 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
495 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
496
496
497 def __unicode__(self):
497 def __unicode__(self):
498 return u'<userGroup(%s)>' % (self.users_group_name)
498 return u'<userGroup(%s)>' % (self.users_group_name)
499
499
500 @classmethod
500 @classmethod
501 def get_by_group_name(cls, group_name, cache=False,
501 def get_by_group_name(cls, group_name, cache=False,
502 case_insensitive=False):
502 case_insensitive=False):
503 if case_insensitive:
503 if case_insensitive:
504 q = cls.query().filter(cls.users_group_name.ilike(group_name))
504 q = cls.query().filter(cls.users_group_name.ilike(group_name))
505 else:
505 else:
506 q = cls.query().filter(cls.users_group_name == group_name)
506 q = cls.query().filter(cls.users_group_name == group_name)
507 if cache:
507 if cache:
508 q = q.options(FromCache(
508 q = q.options(FromCache(
509 "sql_cache_short",
509 "sql_cache_short",
510 "get_user_%s" % _hash_key(group_name)
510 "get_user_%s" % _hash_key(group_name)
511 )
511 )
512 )
512 )
513 return q.scalar()
513 return q.scalar()
514
514
515 @classmethod
515 @classmethod
516 def get(cls, users_group_id, cache=False):
516 def get(cls, users_group_id, cache=False):
517 users_group = cls.query()
517 users_group = cls.query()
518 if cache:
518 if cache:
519 users_group = users_group.options(FromCache("sql_cache_short",
519 users_group = users_group.options(FromCache("sql_cache_short",
520 "get_users_group_%s" % users_group_id))
520 "get_users_group_%s" % users_group_id))
521 return users_group.get(users_group_id)
521 return users_group.get(users_group_id)
522
522
523
523
524 class UsersGroupMember(Base, BaseModel):
524 class UsersGroupMember(Base, BaseModel):
525 __tablename__ = 'users_groups_members'
525 __tablename__ = 'users_groups_members'
526 __table_args__ = (
526 __table_args__ = (
527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
528 'mysql_charset': 'utf8'},
528 'mysql_charset': 'utf8'},
529 )
529 )
530
530
531 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
531 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
532 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
532 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
533 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
533 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
534
534
535 user = relationship('User', lazy='joined')
535 user = relationship('User', lazy='joined')
536 users_group = relationship('UsersGroup')
536 users_group = relationship('UsersGroup')
537
537
538 def __init__(self, gr_id='', u_id=''):
538 def __init__(self, gr_id='', u_id=''):
539 self.users_group_id = gr_id
539 self.users_group_id = gr_id
540 self.user_id = u_id
540 self.user_id = u_id
541
541
542
542
543 class Repository(Base, BaseModel):
543 class Repository(Base, BaseModel):
544 __tablename__ = 'repositories'
544 __tablename__ = 'repositories'
545 __table_args__ = (
545 __table_args__ = (
546 UniqueConstraint('repo_name'),
546 UniqueConstraint('repo_name'),
547 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 {'extend_existing': True, 'mysql_engine': 'InnoDB',
548 'mysql_charset': 'utf8'},
548 'mysql_charset': 'utf8'},
549 )
549 )
550
550
551 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
551 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
552 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
553 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
553 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
554 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
554 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
555 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
555 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
556 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
556 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
557 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
557 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
558 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
558 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
559 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
559 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
561
561
562 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
562 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
563 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
563 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
564
564
565 user = relationship('User')
565 user = relationship('User')
566 fork = relationship('Repository', remote_side=repo_id)
566 fork = relationship('Repository', remote_side=repo_id)
567 group = relationship('RepoGroup')
567 group = relationship('RepoGroup')
568 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
568 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
569 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
569 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
570 stats = relationship('Statistics', cascade='all', uselist=False)
570 stats = relationship('Statistics', cascade='all', uselist=False)
571
571
572 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
572 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
573
573
574 logs = relationship('UserLog')
574 logs = relationship('UserLog')
575 comments = relationship('ChangesetComment')
575 comments = relationship('ChangesetComment')
576
576
577 def __unicode__(self):
577 def __unicode__(self):
578 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
578 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
579 self.repo_name)
579 self.repo_name)
580
580
581 @classmethod
581 @classmethod
582 def url_sep(cls):
582 def url_sep(cls):
583 return URL_SEP
583 return URL_SEP
584
584
585 @classmethod
585 @classmethod
586 def get_by_repo_name(cls, repo_name):
586 def get_by_repo_name(cls, repo_name):
587 q = Session.query(cls).filter(cls.repo_name == repo_name)
587 q = Session.query(cls).filter(cls.repo_name == repo_name)
588 q = q.options(joinedload(Repository.fork))\
588 q = q.options(joinedload(Repository.fork))\
589 .options(joinedload(Repository.user))\
589 .options(joinedload(Repository.user))\
590 .options(joinedload(Repository.group))
590 .options(joinedload(Repository.group))
591 return q.scalar()
591 return q.scalar()
592
592
593 @classmethod
593 @classmethod
594 def get_by_full_path(cls, repo_full_path):
594 def get_by_full_path(cls, repo_full_path):
595 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
595 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
596 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
596 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
597
597
598 @classmethod
598 @classmethod
599 def get_repo_forks(cls, repo_id):
599 def get_repo_forks(cls, repo_id):
600 return cls.query().filter(Repository.fork_id == repo_id)
600 return cls.query().filter(Repository.fork_id == repo_id)
601
601
602 @classmethod
602 @classmethod
603 def base_path(cls):
603 def base_path(cls):
604 """
604 """
605 Returns base path when all repos are stored
605 Returns base path when all repos are stored
606
606
607 :param cls:
607 :param cls:
608 """
608 """
609 q = Session.query(RhodeCodeUi)\
609 q = Session.query(RhodeCodeUi)\
610 .filter(RhodeCodeUi.ui_key == cls.url_sep())
610 .filter(RhodeCodeUi.ui_key == cls.url_sep())
611 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
611 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
612 return q.one().ui_value
612 return q.one().ui_value
613
613
614 @property
614 @property
615 def forks(self):
615 def forks(self):
616 """
616 """
617 Return forks of this repo
617 Return forks of this repo
618 """
618 """
619 return Repository.get_repo_forks(self.repo_id)
619 return Repository.get_repo_forks(self.repo_id)
620
620
621 @property
621 @property
622 def parent(self):
622 def parent(self):
623 """
623 """
624 Returns fork parent
624 Returns fork parent
625 """
625 """
626 return self.fork
626 return self.fork
627
627
628 @property
628 @property
629 def just_name(self):
629 def just_name(self):
630 return self.repo_name.split(Repository.url_sep())[-1]
630 return self.repo_name.split(Repository.url_sep())[-1]
631
631
632 @property
632 @property
633 def groups_with_parents(self):
633 def groups_with_parents(self):
634 groups = []
634 groups = []
635 if self.group is None:
635 if self.group is None:
636 return groups
636 return groups
637
637
638 cur_gr = self.group
638 cur_gr = self.group
639 groups.insert(0, cur_gr)
639 groups.insert(0, cur_gr)
640 while 1:
640 while 1:
641 gr = getattr(cur_gr, 'parent_group', None)
641 gr = getattr(cur_gr, 'parent_group', None)
642 cur_gr = cur_gr.parent_group
642 cur_gr = cur_gr.parent_group
643 if gr is None:
643 if gr is None:
644 break
644 break
645 groups.insert(0, gr)
645 groups.insert(0, gr)
646
646
647 return groups
647 return groups
648
648
649 @property
649 @property
650 def groups_and_repo(self):
650 def groups_and_repo(self):
651 return self.groups_with_parents, self.just_name
651 return self.groups_with_parents, self.just_name
652
652
653 @LazyProperty
653 @LazyProperty
654 def repo_path(self):
654 def repo_path(self):
655 """
655 """
656 Returns base full path for that repository means where it actually
656 Returns base full path for that repository means where it actually
657 exists on a filesystem
657 exists on a filesystem
658 """
658 """
659 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
659 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
660 Repository.url_sep())
660 Repository.url_sep())
661 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
661 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
662 return q.one().ui_value
662 return q.one().ui_value
663
663
664 @property
664 @property
665 def repo_full_path(self):
665 def repo_full_path(self):
666 p = [self.repo_path]
666 p = [self.repo_path]
667 # we need to split the name by / since this is how we store the
667 # we need to split the name by / since this is how we store the
668 # names in the database, but that eventually needs to be converted
668 # names in the database, but that eventually needs to be converted
669 # into a valid system path
669 # into a valid system path
670 p += self.repo_name.split(Repository.url_sep())
670 p += self.repo_name.split(Repository.url_sep())
671 return os.path.join(*p)
671 return os.path.join(*p)
672
672
673 def get_new_name(self, repo_name):
673 def get_new_name(self, repo_name):
674 """
674 """
675 returns new full repository name based on assigned group and new new
675 returns new full repository name based on assigned group and new new
676
676
677 :param group_name:
677 :param group_name:
678 """
678 """
679 path_prefix = self.group.full_path_splitted if self.group else []
679 path_prefix = self.group.full_path_splitted if self.group else []
680 return Repository.url_sep().join(path_prefix + [repo_name])
680 return Repository.url_sep().join(path_prefix + [repo_name])
681
681
682 @property
682 @property
683 def _ui(self):
683 def _ui(self):
684 """
684 """
685 Creates an db based ui object for this repository
685 Creates an db based ui object for this repository
686 """
686 """
687 from mercurial import ui
687 from mercurial import ui
688 from mercurial import config
688 from mercurial import config
689 baseui = ui.ui()
689 baseui = ui.ui()
690
690
691 #clean the baseui object
691 #clean the baseui object
692 baseui._ocfg = config.config()
692 baseui._ocfg = config.config()
693 baseui._ucfg = config.config()
693 baseui._ucfg = config.config()
694 baseui._tcfg = config.config()
694 baseui._tcfg = config.config()
695
695
696 ret = RhodeCodeUi.query()\
696 ret = RhodeCodeUi.query()\
697 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
697 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
698
698
699 hg_ui = ret
699 hg_ui = ret
700 for ui_ in hg_ui:
700 for ui_ in hg_ui:
701 if ui_.ui_active:
701 if ui_.ui_active:
702 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
702 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
703 ui_.ui_key, ui_.ui_value)
703 ui_.ui_key, ui_.ui_value)
704 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
704 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
705
705
706 return baseui
706 return baseui
707
707
708 @classmethod
708 @classmethod
709 def is_valid(cls, repo_name):
709 def is_valid(cls, repo_name):
710 """
710 """
711 returns True if given repo name is a valid filesystem repository
711 returns True if given repo name is a valid filesystem repository
712
712
713 :param cls:
713 :param cls:
714 :param repo_name:
714 :param repo_name:
715 """
715 """
716 from rhodecode.lib.utils import is_valid_repo
716 from rhodecode.lib.utils import is_valid_repo
717
717
718 return is_valid_repo(repo_name, cls.base_path())
718 return is_valid_repo(repo_name, cls.base_path())
719
719
720 #==========================================================================
720 #==========================================================================
721 # SCM PROPERTIES
721 # SCM PROPERTIES
722 #==========================================================================
722 #==========================================================================
723
723
724 def get_changeset(self, rev=None):
724 def get_changeset(self, rev=None):
725 return get_changeset_safe(self.scm_instance, rev)
725 return get_changeset_safe(self.scm_instance, rev)
726
726
727 @property
727 @property
728 def tip(self):
728 def tip(self):
729 return self.get_changeset('tip')
729 return self.get_changeset('tip')
730
730
731 @property
731 @property
732 def author(self):
732 def author(self):
733 return self.tip.author
733 return self.tip.author
734
734
735 @property
735 @property
736 def last_change(self):
736 def last_change(self):
737 return self.scm_instance.last_change
737 return self.scm_instance.last_change
738
738
739 def comments(self, revisions=None):
739 def comments(self, revisions=None):
740 """
740 """
741 Returns comments for this repository grouped by revisions
741 Returns comments for this repository grouped by revisions
742
742
743 :param revisions: filter query by revisions only
743 :param revisions: filter query by revisions only
744 """
744 """
745 cmts = ChangesetComment.query()\
745 cmts = ChangesetComment.query()\
746 .filter(ChangesetComment.repo == self)
746 .filter(ChangesetComment.repo == self)
747 if revisions:
747 if revisions:
748 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
748 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
749 grouped = defaultdict(list)
749 grouped = defaultdict(list)
750 for cmt in cmts.all():
750 for cmt in cmts.all():
751 grouped[cmt.revision].append(cmt)
751 grouped[cmt.revision].append(cmt)
752 return grouped
752 return grouped
753
753
754 def statuses(self, revisions=None):
754 def statuses(self, revisions=None):
755 """
755 """
756 Returns statuses for this repository
756 Returns statuses for this repository
757
757
758 :param revisions: list of revisions to get statuses for
758 :param revisions: list of revisions to get statuses for
759 :type revisions: list
759 :type revisions: list
760 """
760 """
761
761
762 statuses = ChangesetStatus.query()\
762 statuses = ChangesetStatus.query()\
763 .filter(ChangesetStatus.repo == self)\
763 .filter(ChangesetStatus.repo == self)\
764 .filter(ChangesetStatus.version == 0)
764 .filter(ChangesetStatus.version == 0)
765 if revisions:
765 if revisions:
766 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
766 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
767 grouped = {}
767 grouped = {}
768 for stat in statuses.all():
768 for stat in statuses.all():
769 grouped[stat.revision] = [str(stat.status), stat.status_lbl]
769 grouped[stat.revision] = [str(stat.status), stat.status_lbl]
770 return grouped
770 return grouped
771
771
772 #==========================================================================
772 #==========================================================================
773 # SCM CACHE INSTANCE
773 # SCM CACHE INSTANCE
774 #==========================================================================
774 #==========================================================================
775
775
776 @property
776 @property
777 def invalidate(self):
777 def invalidate(self):
778 return CacheInvalidation.invalidate(self.repo_name)
778 return CacheInvalidation.invalidate(self.repo_name)
779
779
780 def set_invalidate(self):
780 def set_invalidate(self):
781 """
781 """
782 set a cache for invalidation for this instance
782 set a cache for invalidation for this instance
783 """
783 """
784 CacheInvalidation.set_invalidate(self.repo_name)
784 CacheInvalidation.set_invalidate(self.repo_name)
785
785
786 @LazyProperty
786 @LazyProperty
787 def scm_instance(self):
787 def scm_instance(self):
788 return self.__get_instance()
788 return self.__get_instance()
789
789
790 def scm_instance_cached(self, cache_map=None):
790 def scm_instance_cached(self, cache_map=None):
791 @cache_region('long_term')
791 @cache_region('long_term')
792 def _c(repo_name):
792 def _c(repo_name):
793 return self.__get_instance()
793 return self.__get_instance()
794 rn = self.repo_name
794 rn = self.repo_name
795 log.debug('Getting cached instance of repo')
795 log.debug('Getting cached instance of repo')
796
796
797 if cache_map:
797 if cache_map:
798 # get using prefilled cache_map
798 # get using prefilled cache_map
799 invalidate_repo = cache_map[self.repo_name]
799 invalidate_repo = cache_map[self.repo_name]
800 if invalidate_repo:
800 if invalidate_repo:
801 invalidate_repo = (None if invalidate_repo.cache_active
801 invalidate_repo = (None if invalidate_repo.cache_active
802 else invalidate_repo)
802 else invalidate_repo)
803 else:
803 else:
804 # get from invalidate
804 # get from invalidate
805 invalidate_repo = self.invalidate
805 invalidate_repo = self.invalidate
806
806
807 if invalidate_repo is not None:
807 if invalidate_repo is not None:
808 region_invalidate(_c, None, rn)
808 region_invalidate(_c, None, rn)
809 # update our cache
809 # update our cache
810 CacheInvalidation.set_valid(invalidate_repo.cache_key)
810 CacheInvalidation.set_valid(invalidate_repo.cache_key)
811 return _c(rn)
811 return _c(rn)
812
812
813 def __get_instance(self):
813 def __get_instance(self):
814 repo_full_path = self.repo_full_path
814 repo_full_path = self.repo_full_path
815 try:
815 try:
816 alias = get_scm(repo_full_path)[0]
816 alias = get_scm(repo_full_path)[0]
817 log.debug('Creating instance of %s repository' % alias)
817 log.debug('Creating instance of %s repository' % alias)
818 backend = get_backend(alias)
818 backend = get_backend(alias)
819 except VCSError:
819 except VCSError:
820 log.error(traceback.format_exc())
820 log.error(traceback.format_exc())
821 log.error('Perhaps this repository is in db and not in '
821 log.error('Perhaps this repository is in db and not in '
822 'filesystem run rescan repositories with '
822 'filesystem run rescan repositories with '
823 '"destroy old data " option from admin panel')
823 '"destroy old data " option from admin panel')
824 return
824 return
825
825
826 if alias == 'hg':
826 if alias == 'hg':
827
827
828 repo = backend(safe_str(repo_full_path), create=False,
828 repo = backend(safe_str(repo_full_path), create=False,
829 baseui=self._ui)
829 baseui=self._ui)
830 # skip hidden web repository
830 # skip hidden web repository
831 if repo._get_hidden():
831 if repo._get_hidden():
832 return
832 return
833 else:
833 else:
834 repo = backend(repo_full_path, create=False)
834 repo = backend(repo_full_path, create=False)
835
835
836 return repo
836 return repo
837
837
838
838
839 class RepoGroup(Base, BaseModel):
839 class RepoGroup(Base, BaseModel):
840 __tablename__ = 'groups'
840 __tablename__ = 'groups'
841 __table_args__ = (
841 __table_args__ = (
842 UniqueConstraint('group_name', 'group_parent_id'),
842 UniqueConstraint('group_name', 'group_parent_id'),
843 CheckConstraint('group_id != group_parent_id'),
843 CheckConstraint('group_id != group_parent_id'),
844 {'extend_existing': True, 'mysql_engine': 'InnoDB',
844 {'extend_existing': True, 'mysql_engine': 'InnoDB',
845 'mysql_charset': 'utf8'},
845 'mysql_charset': 'utf8'},
846 )
846 )
847 __mapper_args__ = {'order_by': 'group_name'}
847 __mapper_args__ = {'order_by': 'group_name'}
848
848
849 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
849 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
850 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
850 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
851 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
851 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
852 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
852 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
853
853
854 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
854 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
855 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
855 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
856
856
857 parent_group = relationship('RepoGroup', remote_side=group_id)
857 parent_group = relationship('RepoGroup', remote_side=group_id)
858
858
859 def __init__(self, group_name='', parent_group=None):
859 def __init__(self, group_name='', parent_group=None):
860 self.group_name = group_name
860 self.group_name = group_name
861 self.parent_group = parent_group
861 self.parent_group = parent_group
862
862
863 def __unicode__(self):
863 def __unicode__(self):
864 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
864 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
865 self.group_name)
865 self.group_name)
866
866
867 @classmethod
867 @classmethod
868 def groups_choices(cls):
868 def groups_choices(cls):
869 from webhelpers.html import literal as _literal
869 from webhelpers.html import literal as _literal
870 repo_groups = [('', '')]
870 repo_groups = [('', '')]
871 sep = ' &raquo; '
871 sep = ' &raquo; '
872 _name = lambda k: _literal(sep.join(k))
872 _name = lambda k: _literal(sep.join(k))
873
873
874 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
874 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
875 for x in cls.query().all()])
875 for x in cls.query().all()])
876
876
877 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
877 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
878 return repo_groups
878 return repo_groups
879
879
880 @classmethod
880 @classmethod
881 def url_sep(cls):
881 def url_sep(cls):
882 return URL_SEP
882 return URL_SEP
883
883
884 @classmethod
884 @classmethod
885 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
885 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
886 if case_insensitive:
886 if case_insensitive:
887 gr = cls.query()\
887 gr = cls.query()\
888 .filter(cls.group_name.ilike(group_name))
888 .filter(cls.group_name.ilike(group_name))
889 else:
889 else:
890 gr = cls.query()\
890 gr = cls.query()\
891 .filter(cls.group_name == group_name)
891 .filter(cls.group_name == group_name)
892 if cache:
892 if cache:
893 gr = gr.options(FromCache(
893 gr = gr.options(FromCache(
894 "sql_cache_short",
894 "sql_cache_short",
895 "get_group_%s" % _hash_key(group_name)
895 "get_group_%s" % _hash_key(group_name)
896 )
896 )
897 )
897 )
898 return gr.scalar()
898 return gr.scalar()
899
899
900 @property
900 @property
901 def parents(self):
901 def parents(self):
902 parents_recursion_limit = 5
902 parents_recursion_limit = 5
903 groups = []
903 groups = []
904 if self.parent_group is None:
904 if self.parent_group is None:
905 return groups
905 return groups
906 cur_gr = self.parent_group
906 cur_gr = self.parent_group
907 groups.insert(0, cur_gr)
907 groups.insert(0, cur_gr)
908 cnt = 0
908 cnt = 0
909 while 1:
909 while 1:
910 cnt += 1
910 cnt += 1
911 gr = getattr(cur_gr, 'parent_group', None)
911 gr = getattr(cur_gr, 'parent_group', None)
912 cur_gr = cur_gr.parent_group
912 cur_gr = cur_gr.parent_group
913 if gr is None:
913 if gr is None:
914 break
914 break
915 if cnt == parents_recursion_limit:
915 if cnt == parents_recursion_limit:
916 # this will prevent accidental infinit loops
916 # this will prevent accidental infinit loops
917 log.error('group nested more than %s' %
917 log.error('group nested more than %s' %
918 parents_recursion_limit)
918 parents_recursion_limit)
919 break
919 break
920
920
921 groups.insert(0, gr)
921 groups.insert(0, gr)
922 return groups
922 return groups
923
923
924 @property
924 @property
925 def children(self):
925 def children(self):
926 return RepoGroup.query().filter(RepoGroup.parent_group == self)
926 return RepoGroup.query().filter(RepoGroup.parent_group == self)
927
927
928 @property
928 @property
929 def name(self):
929 def name(self):
930 return self.group_name.split(RepoGroup.url_sep())[-1]
930 return self.group_name.split(RepoGroup.url_sep())[-1]
931
931
932 @property
932 @property
933 def full_path(self):
933 def full_path(self):
934 return self.group_name
934 return self.group_name
935
935
936 @property
936 @property
937 def full_path_splitted(self):
937 def full_path_splitted(self):
938 return self.group_name.split(RepoGroup.url_sep())
938 return self.group_name.split(RepoGroup.url_sep())
939
939
940 @property
940 @property
941 def repositories(self):
941 def repositories(self):
942 return Repository.query()\
942 return Repository.query()\
943 .filter(Repository.group == self)\
943 .filter(Repository.group == self)\
944 .order_by(Repository.repo_name)
944 .order_by(Repository.repo_name)
945
945
946 @property
946 @property
947 def repositories_recursive_count(self):
947 def repositories_recursive_count(self):
948 cnt = self.repositories.count()
948 cnt = self.repositories.count()
949
949
950 def children_count(group):
950 def children_count(group):
951 cnt = 0
951 cnt = 0
952 for child in group.children:
952 for child in group.children:
953 cnt += child.repositories.count()
953 cnt += child.repositories.count()
954 cnt += children_count(child)
954 cnt += children_count(child)
955 return cnt
955 return cnt
956
956
957 return cnt + children_count(self)
957 return cnt + children_count(self)
958
958
959 def get_new_name(self, group_name):
959 def get_new_name(self, group_name):
960 """
960 """
961 returns new full group name based on parent and new name
961 returns new full group name based on parent and new name
962
962
963 :param group_name:
963 :param group_name:
964 """
964 """
965 path_prefix = (self.parent_group.full_path_splitted if
965 path_prefix = (self.parent_group.full_path_splitted if
966 self.parent_group else [])
966 self.parent_group else [])
967 return RepoGroup.url_sep().join(path_prefix + [group_name])
967 return RepoGroup.url_sep().join(path_prefix + [group_name])
968
968
969
969
970 class Permission(Base, BaseModel):
970 class Permission(Base, BaseModel):
971 __tablename__ = 'permissions'
971 __tablename__ = 'permissions'
972 __table_args__ = (
972 __table_args__ = (
973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
974 'mysql_charset': 'utf8'},
974 'mysql_charset': 'utf8'},
975 )
975 )
976 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
976 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
977 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
977 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
978 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
978 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
979
979
980 def __unicode__(self):
980 def __unicode__(self):
981 return u"<%s('%s:%s')>" % (
981 return u"<%s('%s:%s')>" % (
982 self.__class__.__name__, self.permission_id, self.permission_name
982 self.__class__.__name__, self.permission_id, self.permission_name
983 )
983 )
984
984
985 @classmethod
985 @classmethod
986 def get_by_key(cls, key):
986 def get_by_key(cls, key):
987 return cls.query().filter(cls.permission_name == key).scalar()
987 return cls.query().filter(cls.permission_name == key).scalar()
988
988
989 @classmethod
989 @classmethod
990 def get_default_perms(cls, default_user_id):
990 def get_default_perms(cls, default_user_id):
991 q = Session.query(UserRepoToPerm, Repository, cls)\
991 q = Session.query(UserRepoToPerm, Repository, cls)\
992 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
992 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
993 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
993 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
994 .filter(UserRepoToPerm.user_id == default_user_id)
994 .filter(UserRepoToPerm.user_id == default_user_id)
995
995
996 return q.all()
996 return q.all()
997
997
998 @classmethod
998 @classmethod
999 def get_default_group_perms(cls, default_user_id):
999 def get_default_group_perms(cls, default_user_id):
1000 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
1000 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
1001 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1001 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1002 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1002 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1003 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1003 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1004
1004
1005 return q.all()
1005 return q.all()
1006
1006
1007
1007
1008 class UserRepoToPerm(Base, BaseModel):
1008 class UserRepoToPerm(Base, BaseModel):
1009 __tablename__ = 'repo_to_perm'
1009 __tablename__ = 'repo_to_perm'
1010 __table_args__ = (
1010 __table_args__ = (
1011 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1011 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1012 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1012 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1013 'mysql_charset': 'utf8'}
1013 'mysql_charset': 'utf8'}
1014 )
1014 )
1015 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1015 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1016 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1016 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1017 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1017 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1018 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1018 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1019
1019
1020 user = relationship('User')
1020 user = relationship('User')
1021 repository = relationship('Repository')
1021 repository = relationship('Repository')
1022 permission = relationship('Permission')
1022 permission = relationship('Permission')
1023
1023
1024 @classmethod
1024 @classmethod
1025 def create(cls, user, repository, permission):
1025 def create(cls, user, repository, permission):
1026 n = cls()
1026 n = cls()
1027 n.user = user
1027 n.user = user
1028 n.repository = repository
1028 n.repository = repository
1029 n.permission = permission
1029 n.permission = permission
1030 Session.add(n)
1030 Session.add(n)
1031 return n
1031 return n
1032
1032
1033 def __unicode__(self):
1033 def __unicode__(self):
1034 return u'<user:%s => %s >' % (self.user, self.repository)
1034 return u'<user:%s => %s >' % (self.user, self.repository)
1035
1035
1036
1036
1037 class UserToPerm(Base, BaseModel):
1037 class UserToPerm(Base, BaseModel):
1038 __tablename__ = 'user_to_perm'
1038 __tablename__ = 'user_to_perm'
1039 __table_args__ = (
1039 __table_args__ = (
1040 UniqueConstraint('user_id', 'permission_id'),
1040 UniqueConstraint('user_id', 'permission_id'),
1041 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1041 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1042 'mysql_charset': 'utf8'}
1042 'mysql_charset': 'utf8'}
1043 )
1043 )
1044 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1044 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1046 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1046 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1047
1047
1048 user = relationship('User')
1048 user = relationship('User')
1049 permission = relationship('Permission', lazy='joined')
1049 permission = relationship('Permission', lazy='joined')
1050
1050
1051
1051
1052 class UsersGroupRepoToPerm(Base, BaseModel):
1052 class UsersGroupRepoToPerm(Base, BaseModel):
1053 __tablename__ = 'users_group_repo_to_perm'
1053 __tablename__ = 'users_group_repo_to_perm'
1054 __table_args__ = (
1054 __table_args__ = (
1055 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1055 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1056 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1056 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1057 'mysql_charset': 'utf8'}
1057 'mysql_charset': 'utf8'}
1058 )
1058 )
1059 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1059 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1060 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1060 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1061 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1061 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1062 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1062 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1063
1063
1064 users_group = relationship('UsersGroup')
1064 users_group = relationship('UsersGroup')
1065 permission = relationship('Permission')
1065 permission = relationship('Permission')
1066 repository = relationship('Repository')
1066 repository = relationship('Repository')
1067
1067
1068 @classmethod
1068 @classmethod
1069 def create(cls, users_group, repository, permission):
1069 def create(cls, users_group, repository, permission):
1070 n = cls()
1070 n = cls()
1071 n.users_group = users_group
1071 n.users_group = users_group
1072 n.repository = repository
1072 n.repository = repository
1073 n.permission = permission
1073 n.permission = permission
1074 Session.add(n)
1074 Session.add(n)
1075 return n
1075 return n
1076
1076
1077 def __unicode__(self):
1077 def __unicode__(self):
1078 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1078 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1079
1079
1080
1080
1081 class UsersGroupToPerm(Base, BaseModel):
1081 class UsersGroupToPerm(Base, BaseModel):
1082 __tablename__ = 'users_group_to_perm'
1082 __tablename__ = 'users_group_to_perm'
1083 __table_args__ = (
1083 __table_args__ = (
1084 UniqueConstraint('users_group_id', 'permission_id',),
1084 UniqueConstraint('users_group_id', 'permission_id',),
1085 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1085 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1086 'mysql_charset': 'utf8'}
1086 'mysql_charset': 'utf8'}
1087 )
1087 )
1088 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1088 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1089 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1089 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1090 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1090 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1091
1091
1092 users_group = relationship('UsersGroup')
1092 users_group = relationship('UsersGroup')
1093 permission = relationship('Permission')
1093 permission = relationship('Permission')
1094
1094
1095
1095
1096 class UserRepoGroupToPerm(Base, BaseModel):
1096 class UserRepoGroupToPerm(Base, BaseModel):
1097 __tablename__ = 'user_repo_group_to_perm'
1097 __tablename__ = 'user_repo_group_to_perm'
1098 __table_args__ = (
1098 __table_args__ = (
1099 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1099 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1101 'mysql_charset': 'utf8'}
1101 'mysql_charset': 'utf8'}
1102 )
1102 )
1103
1103
1104 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1104 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1105 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1105 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1106 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1106 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1107 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1107 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1108
1108
1109 user = relationship('User')
1109 user = relationship('User')
1110 group = relationship('RepoGroup')
1110 group = relationship('RepoGroup')
1111 permission = relationship('Permission')
1111 permission = relationship('Permission')
1112
1112
1113
1113
1114 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1114 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1115 __tablename__ = 'users_group_repo_group_to_perm'
1115 __tablename__ = 'users_group_repo_group_to_perm'
1116 __table_args__ = (
1116 __table_args__ = (
1117 UniqueConstraint('users_group_id', 'group_id'),
1117 UniqueConstraint('users_group_id', 'group_id'),
1118 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1118 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1119 'mysql_charset': 'utf8'}
1119 'mysql_charset': 'utf8'}
1120 )
1120 )
1121
1121
1122 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)
1122 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)
1123 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1123 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1124 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1124 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1125 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1125 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1126
1126
1127 users_group = relationship('UsersGroup')
1127 users_group = relationship('UsersGroup')
1128 permission = relationship('Permission')
1128 permission = relationship('Permission')
1129 group = relationship('RepoGroup')
1129 group = relationship('RepoGroup')
1130
1130
1131
1131
1132 class Statistics(Base, BaseModel):
1132 class Statistics(Base, BaseModel):
1133 __tablename__ = 'statistics'
1133 __tablename__ = 'statistics'
1134 __table_args__ = (
1134 __table_args__ = (
1135 UniqueConstraint('repository_id'),
1135 UniqueConstraint('repository_id'),
1136 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1136 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1137 'mysql_charset': 'utf8'}
1137 'mysql_charset': 'utf8'}
1138 )
1138 )
1139 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1139 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1140 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1140 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1141 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1141 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1142 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1142 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1143 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1143 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1144 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1144 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1145
1145
1146 repository = relationship('Repository', single_parent=True)
1146 repository = relationship('Repository', single_parent=True)
1147
1147
1148
1148
1149 class UserFollowing(Base, BaseModel):
1149 class UserFollowing(Base, BaseModel):
1150 __tablename__ = 'user_followings'
1150 __tablename__ = 'user_followings'
1151 __table_args__ = (
1151 __table_args__ = (
1152 UniqueConstraint('user_id', 'follows_repository_id'),
1152 UniqueConstraint('user_id', 'follows_repository_id'),
1153 UniqueConstraint('user_id', 'follows_user_id'),
1153 UniqueConstraint('user_id', 'follows_user_id'),
1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1155 'mysql_charset': 'utf8'}
1155 'mysql_charset': 'utf8'}
1156 )
1156 )
1157
1157
1158 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1158 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1159 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1159 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1160 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1160 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1161 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1161 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1162 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1162 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1163
1163
1164 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1164 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1165
1165
1166 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1166 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1167 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1167 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1168
1168
1169 @classmethod
1169 @classmethod
1170 def get_repo_followers(cls, repo_id):
1170 def get_repo_followers(cls, repo_id):
1171 return cls.query().filter(cls.follows_repo_id == repo_id)
1171 return cls.query().filter(cls.follows_repo_id == repo_id)
1172
1172
1173
1173
1174 class CacheInvalidation(Base, BaseModel):
1174 class CacheInvalidation(Base, BaseModel):
1175 __tablename__ = 'cache_invalidation'
1175 __tablename__ = 'cache_invalidation'
1176 __table_args__ = (
1176 __table_args__ = (
1177 UniqueConstraint('cache_key'),
1177 UniqueConstraint('cache_key'),
1178 Index('key_idx', 'cache_key'),
1178 Index('key_idx', 'cache_key'),
1179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1180 'mysql_charset': 'utf8'},
1180 'mysql_charset': 'utf8'},
1181 )
1181 )
1182 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1182 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1183 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1183 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1184 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1184 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1185 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1185 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1186
1186
1187 def __init__(self, cache_key, cache_args=''):
1187 def __init__(self, cache_key, cache_args=''):
1188 self.cache_key = cache_key
1188 self.cache_key = cache_key
1189 self.cache_args = cache_args
1189 self.cache_args = cache_args
1190 self.cache_active = False
1190 self.cache_active = False
1191
1191
1192 def __unicode__(self):
1192 def __unicode__(self):
1193 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1193 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1194 self.cache_id, self.cache_key)
1194 self.cache_id, self.cache_key)
1195
1195
1196 @classmethod
1196 @classmethod
1197 def clear_cache(cls):
1197 def clear_cache(cls):
1198 cls.query().delete()
1198 cls.query().delete()
1199
1199
1200 @classmethod
1200 @classmethod
1201 def _get_key(cls, key):
1201 def _get_key(cls, key):
1202 """
1202 """
1203 Wrapper for generating a key, together with a prefix
1203 Wrapper for generating a key, together with a prefix
1204
1204
1205 :param key:
1205 :param key:
1206 """
1206 """
1207 import rhodecode
1207 import rhodecode
1208 prefix = ''
1208 prefix = ''
1209 iid = rhodecode.CONFIG.get('instance_id')
1209 iid = rhodecode.CONFIG.get('instance_id')
1210 if iid:
1210 if iid:
1211 prefix = iid
1211 prefix = iid
1212 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1212 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1213
1213
1214 @classmethod
1214 @classmethod
1215 def get_by_key(cls, key):
1215 def get_by_key(cls, key):
1216 return cls.query().filter(cls.cache_key == key).scalar()
1216 return cls.query().filter(cls.cache_key == key).scalar()
1217
1217
1218 @classmethod
1218 @classmethod
1219 def _get_or_create_key(cls, key, prefix, org_key):
1219 def _get_or_create_key(cls, key, prefix, org_key):
1220 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1220 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1221 if not inv_obj:
1221 if not inv_obj:
1222 try:
1222 try:
1223 inv_obj = CacheInvalidation(key, org_key)
1223 inv_obj = CacheInvalidation(key, org_key)
1224 Session.add(inv_obj)
1224 Session.add(inv_obj)
1225 Session.commit()
1225 Session.commit()
1226 except Exception:
1226 except Exception:
1227 log.error(traceback.format_exc())
1227 log.error(traceback.format_exc())
1228 Session.rollback()
1228 Session.rollback()
1229 return inv_obj
1229 return inv_obj
1230
1230
1231 @classmethod
1231 @classmethod
1232 def invalidate(cls, key):
1232 def invalidate(cls, key):
1233 """
1233 """
1234 Returns Invalidation object if this given key should be invalidated
1234 Returns Invalidation object if this given key should be invalidated
1235 None otherwise. `cache_active = False` means that this cache
1235 None otherwise. `cache_active = False` means that this cache
1236 state is not valid and needs to be invalidated
1236 state is not valid and needs to be invalidated
1237
1237
1238 :param key:
1238 :param key:
1239 """
1239 """
1240
1240
1241 key, _prefix, _org_key = cls._get_key(key)
1241 key, _prefix, _org_key = cls._get_key(key)
1242 inv = cls._get_or_create_key(key, _prefix, _org_key)
1242 inv = cls._get_or_create_key(key, _prefix, _org_key)
1243
1243
1244 if inv and inv.cache_active is False:
1244 if inv and inv.cache_active is False:
1245 return inv
1245 return inv
1246
1246
1247 @classmethod
1247 @classmethod
1248 def set_invalidate(cls, key):
1248 def set_invalidate(cls, key):
1249 """
1249 """
1250 Mark this Cache key for invalidation
1250 Mark this Cache key for invalidation
1251
1251
1252 :param key:
1252 :param key:
1253 """
1253 """
1254
1254
1255 key, _prefix, _org_key = cls._get_key(key)
1255 key, _prefix, _org_key = cls._get_key(key)
1256 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1256 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1257 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1257 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1258 _org_key))
1258 _org_key))
1259 try:
1259 try:
1260 for inv_obj in inv_objs:
1260 for inv_obj in inv_objs:
1261 if inv_obj:
1261 if inv_obj:
1262 inv_obj.cache_active = False
1262 inv_obj.cache_active = False
1263
1263
1264 Session.add(inv_obj)
1264 Session.add(inv_obj)
1265 Session.commit()
1265 Session.commit()
1266 except Exception:
1266 except Exception:
1267 log.error(traceback.format_exc())
1267 log.error(traceback.format_exc())
1268 Session.rollback()
1268 Session.rollback()
1269
1269
1270 @classmethod
1270 @classmethod
1271 def set_valid(cls, key):
1271 def set_valid(cls, key):
1272 """
1272 """
1273 Mark this cache key as active and currently cached
1273 Mark this cache key as active and currently cached
1274
1274
1275 :param key:
1275 :param key:
1276 """
1276 """
1277 inv_obj = cls.get_by_key(key)
1277 inv_obj = cls.get_by_key(key)
1278 inv_obj.cache_active = True
1278 inv_obj.cache_active = True
1279 Session.add(inv_obj)
1279 Session.add(inv_obj)
1280 Session.commit()
1280 Session.commit()
1281
1281
1282 @classmethod
1282 @classmethod
1283 def get_cache_map(cls):
1283 def get_cache_map(cls):
1284
1284
1285 class cachemapdict(dict):
1285 class cachemapdict(dict):
1286
1286
1287 def __init__(self, *args, **kwargs):
1287 def __init__(self, *args, **kwargs):
1288 fixkey = kwargs.get('fixkey')
1288 fixkey = kwargs.get('fixkey')
1289 if fixkey:
1289 if fixkey:
1290 del kwargs['fixkey']
1290 del kwargs['fixkey']
1291 self.fixkey = fixkey
1291 self.fixkey = fixkey
1292 super(cachemapdict, self).__init__(*args, **kwargs)
1292 super(cachemapdict, self).__init__(*args, **kwargs)
1293
1293
1294 def __getattr__(self, name):
1294 def __getattr__(self, name):
1295 key = name
1295 key = name
1296 if self.fixkey:
1296 if self.fixkey:
1297 key, _prefix, _org_key = cls._get_key(key)
1297 key, _prefix, _org_key = cls._get_key(key)
1298 if key in self.__dict__:
1298 if key in self.__dict__:
1299 return self.__dict__[key]
1299 return self.__dict__[key]
1300 else:
1300 else:
1301 return self[key]
1301 return self[key]
1302
1302
1303 def __getitem__(self, key):
1303 def __getitem__(self, key):
1304 if self.fixkey:
1304 if self.fixkey:
1305 key, _prefix, _org_key = cls._get_key(key)
1305 key, _prefix, _org_key = cls._get_key(key)
1306 try:
1306 try:
1307 return super(cachemapdict, self).__getitem__(key)
1307 return super(cachemapdict, self).__getitem__(key)
1308 except KeyError:
1308 except KeyError:
1309 return
1309 return
1310
1310
1311 cache_map = cachemapdict(fixkey=True)
1311 cache_map = cachemapdict(fixkey=True)
1312 for obj in cls.query().all():
1312 for obj in cls.query().all():
1313 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1313 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1314 return cache_map
1314 return cache_map
1315
1315
1316
1316
1317 class ChangesetComment(Base, BaseModel):
1317 class ChangesetComment(Base, BaseModel):
1318 __tablename__ = 'changeset_comments'
1318 __tablename__ = 'changeset_comments'
1319 __table_args__ = (
1319 __table_args__ = (
1320 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1320 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1321 'mysql_charset': 'utf8'},
1321 'mysql_charset': 'utf8'},
1322 )
1322 )
1323 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1323 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1324 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1324 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1325 revision = Column('revision', String(40), nullable=False)
1325 revision = Column('revision', String(40), nullable=True)
1326 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1326 line_no = Column('line_no', Unicode(10), nullable=True)
1327 line_no = Column('line_no', Unicode(10), nullable=True)
1327 f_path = Column('f_path', Unicode(1000), nullable=True)
1328 f_path = Column('f_path', Unicode(1000), nullable=True)
1328 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1329 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1329 text = Column('text', Unicode(25000), nullable=False)
1330 text = Column('text', Unicode(25000), nullable=False)
1330 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1331 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1331
1332
1332 author = relationship('User', lazy='joined')
1333 author = relationship('User', lazy='joined')
1333 repo = relationship('Repository')
1334 repo = relationship('Repository')
1334 status_change = relationship('ChangesetStatus', uselist=False)
1335 status_change = relationship('ChangesetStatus', uselist=False)
1336 pull_request = relationship('PullRequest', lazy='joined')
1335
1337
1336 @classmethod
1338 @classmethod
1337 def get_users(cls, revision):
1339 def get_users(cls, revision):
1338 """
1340 """
1339 Returns user associated with this changesetComment. ie those
1341 Returns user associated with this changesetComment. ie those
1340 who actually commented
1342 who actually commented
1341
1343
1342 :param cls:
1344 :param cls:
1343 :param revision:
1345 :param revision:
1344 """
1346 """
1345 return Session.query(User)\
1347 return Session.query(User)\
1346 .filter(cls.revision == revision)\
1348 .filter(cls.revision == revision)\
1347 .join(ChangesetComment.author).all()
1349 .join(ChangesetComment.author).all()
1348
1350
1349
1351
1350 class ChangesetStatus(Base, BaseModel):
1352 class ChangesetStatus(Base, BaseModel):
1351 __tablename__ = 'changeset_statuses'
1353 __tablename__ = 'changeset_statuses'
1352 __table_args__ = (
1354 __table_args__ = (
1353 UniqueConstraint('repo_id', 'revision', 'version'),
1355 UniqueConstraint('repo_id', 'revision', 'version'),
1354 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1356 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1355 'mysql_charset': 'utf8'}
1357 'mysql_charset': 'utf8'}
1356 )
1358 )
1357
1359
1358 STATUSES = [
1360 STATUSES = [
1359 ('not_reviewed', _("Not Reviewed")), # (no icon) and default
1361 ('not_reviewed', _("Not Reviewed")), # (no icon) and default
1360 ('approved', _("Approved")),
1362 ('approved', _("Approved")),
1361 ('rejected', _("Rejected")),
1363 ('rejected', _("Rejected")),
1362 ('under_review', _("Under Review")),
1364 ('under_review', _("Under Review")),
1363 ]
1365 ]
1364 DEFAULT = STATUSES[0][0]
1366 DEFAULT = STATUSES[0][0]
1365
1367
1366 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1368 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1367 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1369 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1368 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1370 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1369 revision = Column('revision', String(40), nullable=False)
1371 revision = Column('revision', String(40), nullable=False)
1370 status = Column('status', String(128), nullable=False, default=DEFAULT)
1372 status = Column('status', String(128), nullable=False, default=DEFAULT)
1371 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1373 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1372 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1374 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1373 version = Column('version', Integer(), nullable=False, default=0)
1375 version = Column('version', Integer(), nullable=False, default=0)
1374 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1376 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1375
1377
1376 author = relationship('User', lazy='joined')
1378 author = relationship('User', lazy='joined')
1377 repo = relationship('Repository')
1379 repo = relationship('Repository')
1378 comment = relationship('ChangesetComment', lazy='joined')
1380 comment = relationship('ChangesetComment', lazy='joined')
1379 pull_request = relationship('PullRequest', lazy='joined')
1381 pull_request = relationship('PullRequest', lazy='joined')
1380
1382
1381 @classmethod
1383 @classmethod
1382 def get_status_lbl(cls, value):
1384 def get_status_lbl(cls, value):
1383 return dict(cls.STATUSES).get(value)
1385 return dict(cls.STATUSES).get(value)
1384
1386
1385 @property
1387 @property
1386 def status_lbl(self):
1388 def status_lbl(self):
1387 return ChangesetStatus.get_status_lbl(self.status)
1389 return ChangesetStatus.get_status_lbl(self.status)
1388
1390
1389
1391
1390 class PullRequest(Base, BaseModel):
1392 class PullRequest(Base, BaseModel):
1391 __tablename__ = 'pull_requests'
1393 __tablename__ = 'pull_requests'
1392 __table_args__ = (
1394 __table_args__ = (
1393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1395 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1394 'mysql_charset': 'utf8'},
1396 'mysql_charset': 'utf8'},
1395 )
1397 )
1396
1398
1397 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1399 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1398 title = Column('title', Unicode(256), nullable=True)
1400 title = Column('title', Unicode(256), nullable=True)
1399 description = Column('description', Unicode(10240), nullable=True)
1401 description = Column('description', Unicode(10240), nullable=True)
1402 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1403 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1400 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1404 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1401 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1405 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1402 org_ref = Column('org_ref', Unicode(256), nullable=False)
1406 org_ref = Column('org_ref', Unicode(256), nullable=False)
1403 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1407 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1404 other_ref = Column('other_ref', Unicode(256), nullable=False)
1408 other_ref = Column('other_ref', Unicode(256), nullable=False)
1405
1409
1406 @hybrid_property
1410 @hybrid_property
1407 def revisions(self):
1411 def revisions(self):
1408 return self._revisions.split(':')
1412 return self._revisions.split(':')
1409
1413
1410 @revisions.setter
1414 @revisions.setter
1411 def revisions(self, val):
1415 def revisions(self, val):
1412 self._revisions = ':'.join(val)
1416 self._revisions = ':'.join(val)
1413
1417
1418 author = relationship('User', lazy='joined')
1414 reviewers = relationship('PullRequestReviewers')
1419 reviewers = relationship('PullRequestReviewers')
1415 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1420 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1416 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1421 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1417
1422
1418 def __json__(self):
1423 def __json__(self):
1419 return dict(
1424 return dict(
1420 revisions=self.revisions
1425 revisions=self.revisions
1421 )
1426 )
1422
1427
1423
1428
1424 class PullRequestReviewers(Base, BaseModel):
1429 class PullRequestReviewers(Base, BaseModel):
1425 __tablename__ = 'pull_request_reviewers'
1430 __tablename__ = 'pull_request_reviewers'
1426 __table_args__ = (
1431 __table_args__ = (
1427 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1432 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1428 'mysql_charset': 'utf8'},
1433 'mysql_charset': 'utf8'},
1429 )
1434 )
1430
1435
1431 def __init__(self, user=None, pull_request=None):
1436 def __init__(self, user=None, pull_request=None):
1432 self.user = user
1437 self.user = user
1433 self.pull_request = pull_request
1438 self.pull_request = pull_request
1434
1439
1435 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1440 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1436 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1441 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1442 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1438
1443
1439 user = relationship('User')
1444 user = relationship('User')
1440 pull_request = relationship('PullRequest')
1445 pull_request = relationship('PullRequest')
1441
1446
1442
1447
1443 class Notification(Base, BaseModel):
1448 class Notification(Base, BaseModel):
1444 __tablename__ = 'notifications'
1449 __tablename__ = 'notifications'
1445 __table_args__ = (
1450 __table_args__ = (
1446 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1451 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1447 'mysql_charset': 'utf8'},
1452 'mysql_charset': 'utf8'},
1448 )
1453 )
1449
1454
1450 TYPE_CHANGESET_COMMENT = u'cs_comment'
1455 TYPE_CHANGESET_COMMENT = u'cs_comment'
1451 TYPE_MESSAGE = u'message'
1456 TYPE_MESSAGE = u'message'
1452 TYPE_MENTION = u'mention'
1457 TYPE_MENTION = u'mention'
1453 TYPE_REGISTRATION = u'registration'
1458 TYPE_REGISTRATION = u'registration'
1454 TYPE_PULL_REQUEST = u'pull_request'
1459 TYPE_PULL_REQUEST = u'pull_request'
1455
1460
1456 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1461 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1457 subject = Column('subject', Unicode(512), nullable=True)
1462 subject = Column('subject', Unicode(512), nullable=True)
1458 body = Column('body', Unicode(50000), nullable=True)
1463 body = Column('body', Unicode(50000), nullable=True)
1459 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1464 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1460 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1465 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1461 type_ = Column('type', Unicode(256))
1466 type_ = Column('type', Unicode(256))
1462
1467
1463 created_by_user = relationship('User')
1468 created_by_user = relationship('User')
1464 notifications_to_users = relationship('UserNotification', lazy='joined',
1469 notifications_to_users = relationship('UserNotification', lazy='joined',
1465 cascade="all, delete, delete-orphan")
1470 cascade="all, delete, delete-orphan")
1466
1471
1467 @property
1472 @property
1468 def recipients(self):
1473 def recipients(self):
1469 return [x.user for x in UserNotification.query()\
1474 return [x.user for x in UserNotification.query()\
1470 .filter(UserNotification.notification == self)\
1475 .filter(UserNotification.notification == self)\
1471 .order_by(UserNotification.user).all()]
1476 .order_by(UserNotification.user).all()]
1472
1477
1473 @classmethod
1478 @classmethod
1474 def create(cls, created_by, subject, body, recipients, type_=None):
1479 def create(cls, created_by, subject, body, recipients, type_=None):
1475 if type_ is None:
1480 if type_ is None:
1476 type_ = Notification.TYPE_MESSAGE
1481 type_ = Notification.TYPE_MESSAGE
1477
1482
1478 notification = cls()
1483 notification = cls()
1479 notification.created_by_user = created_by
1484 notification.created_by_user = created_by
1480 notification.subject = subject
1485 notification.subject = subject
1481 notification.body = body
1486 notification.body = body
1482 notification.type_ = type_
1487 notification.type_ = type_
1483 notification.created_on = datetime.datetime.now()
1488 notification.created_on = datetime.datetime.now()
1484
1489
1485 for u in recipients:
1490 for u in recipients:
1486 assoc = UserNotification()
1491 assoc = UserNotification()
1487 assoc.notification = notification
1492 assoc.notification = notification
1488 u.notifications.append(assoc)
1493 u.notifications.append(assoc)
1489 Session.add(notification)
1494 Session.add(notification)
1490 return notification
1495 return notification
1491
1496
1492 @property
1497 @property
1493 def description(self):
1498 def description(self):
1494 from rhodecode.model.notification import NotificationModel
1499 from rhodecode.model.notification import NotificationModel
1495 return NotificationModel().make_description(self)
1500 return NotificationModel().make_description(self)
1496
1501
1497
1502
1498 class UserNotification(Base, BaseModel):
1503 class UserNotification(Base, BaseModel):
1499 __tablename__ = 'user_to_notification'
1504 __tablename__ = 'user_to_notification'
1500 __table_args__ = (
1505 __table_args__ = (
1501 UniqueConstraint('user_id', 'notification_id'),
1506 UniqueConstraint('user_id', 'notification_id'),
1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1503 'mysql_charset': 'utf8'}
1508 'mysql_charset': 'utf8'}
1504 )
1509 )
1505 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1510 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1506 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1511 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1507 read = Column('read', Boolean, default=False)
1512 read = Column('read', Boolean, default=False)
1508 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1513 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1509
1514
1510 user = relationship('User', lazy="joined")
1515 user = relationship('User', lazy="joined")
1511 notification = relationship('Notification', lazy="joined",
1516 notification = relationship('Notification', lazy="joined",
1512 order_by=lambda: Notification.created_on.desc(),)
1517 order_by=lambda: Notification.created_on.desc(),)
1513
1518
1514 def mark_as_read(self):
1519 def mark_as_read(self):
1515 self.read = True
1520 self.read = True
1516 Session.add(self)
1521 Session.add(self)
1517
1522
1518
1523
1519 class DbMigrateVersion(Base, BaseModel):
1524 class DbMigrateVersion(Base, BaseModel):
1520 __tablename__ = 'db_migrate_version'
1525 __tablename__ = 'db_migrate_version'
1521 __table_args__ = (
1526 __table_args__ = (
1522 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1523 'mysql_charset': 'utf8'},
1528 'mysql_charset': 'utf8'},
1524 )
1529 )
1525 repository_id = Column('repository_id', String(250), primary_key=True)
1530 repository_id = Column('repository_id', String(250), primary_key=True)
1526 repository_path = Column('repository_path', Text)
1531 repository_path = Column('repository_path', Text)
1527 version = Column('version', Integer)
1532 version = Column('version', Integer)
@@ -1,79 +1,84 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.pull_reuquest
3 rhodecode.model.pull_reuquest
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull request model for RhodeCode
6 pull request model for RhodeCode
7
7
8 :created_on: Jun 6, 2012
8 :created_on: Jun 6, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2012-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 from pylons.i18n.translation import _
27 from pylons.i18n.translation import _
28
28
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.model import BaseModel
30 from rhodecode.model import BaseModel
31 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
31 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
32 from rhodecode.model.notification import NotificationModel
32 from rhodecode.model.notification import NotificationModel
33 from rhodecode.lib.utils2 import safe_unicode
33 from rhodecode.lib.utils2 import safe_unicode
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class PullRequestModel(BaseModel):
38 class PullRequestModel(BaseModel):
39
39
40 def get_all(self, repo):
41 repo = self._get_repo(repo)
42 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
43
40 def create(self, created_by, org_repo, org_ref, other_repo,
44 def create(self, created_by, org_repo, org_ref, other_repo,
41 other_ref, revisions, reviewers, title, description=None):
45 other_ref, revisions, reviewers, title, description=None):
46 created_by_user = self._get_user(created_by)
42
47
43 new = PullRequest()
48 new = PullRequest()
44 new.org_repo = self._get_repo(org_repo)
49 new.org_repo = self._get_repo(org_repo)
45 new.org_ref = org_ref
50 new.org_ref = org_ref
46 new.other_repo = self._get_repo(other_repo)
51 new.other_repo = self._get_repo(other_repo)
47 new.other_ref = other_ref
52 new.other_ref = other_ref
48 new.revisions = revisions
53 new.revisions = revisions
49 new.title = title
54 new.title = title
50 new.description = description
55 new.description = description
51
56 new.author = created_by_user
52 self.sa.add(new)
57 self.sa.add(new)
53
58
54 #members
59 #members
55 for member in reviewers:
60 for member in reviewers:
56 _usr = self._get_user(member)
61 _usr = self._get_user(member)
57 reviewer = PullRequestReviewers(_usr, new)
62 reviewer = PullRequestReviewers(_usr, new)
58 self.sa.add(reviewer)
63 self.sa.add(reviewer)
59
64
60 #notification to reviewers
65 #notification to reviewers
61 notif = NotificationModel()
66 notif = NotificationModel()
62 created_by_user = self._get_user(created_by)
67
63 subject = safe_unicode(
68 subject = safe_unicode(
64 h.link_to(
69 h.link_to(
65 _('%(user)s wants you to review pull request #%(pr_id)s') % \
70 _('%(user)s wants you to review pull request #%(pr_id)s') % \
66 {'user': created_by_user.username,
71 {'user': created_by_user.username,
67 'pr_id': new.pull_request_id},
72 'pr_id': new.pull_request_id},
68 h.url('pullrequest_show', repo_name=other_repo,
73 h.url('pullrequest_show', repo_name=other_repo,
69 pull_request_id=new.pull_request_id,
74 pull_request_id=new.pull_request_id,
70 qualified=True,
75 qualified=True,
71 )
76 )
72 )
77 )
73 )
78 )
74 body = description
79 body = description
75 notif.create(created_by=created_by, subject=subject, body=body,
80 notif.create(created_by=created_by, subject=subject, body=body,
76 recipients=reviewers,
81 recipients=reviewers,
77 type_=Notification.TYPE_PULL_REQUEST,)
82 type_=Notification.TYPE_PULL_REQUEST,)
78
83
79 return new
84 return new
@@ -1,474 +1,476 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 import os
25 import os
26 import time
26 import time
27 import traceback
27 import traceback
28 import logging
28 import logging
29 import cStringIO
29 import cStringIO
30
30
31 from sqlalchemy import func
31 from sqlalchemy import func
32
32
33 from rhodecode.lib.vcs import get_backend
33 from rhodecode.lib.vcs import get_backend
34 from rhodecode.lib.vcs.exceptions import RepositoryError
34 from rhodecode.lib.vcs.exceptions import RepositoryError
35 from rhodecode.lib.vcs.utils.lazy import LazyProperty
35 from rhodecode.lib.vcs.utils.lazy import LazyProperty
36 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.lib.vcs.nodes import FileNode
37
37
38 from rhodecode import BACKENDS
38 from rhodecode import BACKENDS
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.utils2 import safe_str, safe_unicode
40 from rhodecode.lib.utils2 import safe_str, safe_unicode
41 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
41 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
42 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
42 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
43 action_logger, EmptyChangeset, REMOVED_REPO_PAT
43 action_logger, EmptyChangeset, REMOVED_REPO_PAT
44 from rhodecode.model import BaseModel
44 from rhodecode.model import BaseModel
45 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
45 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
46 UserFollowing, UserLog, User, RepoGroup
46 UserFollowing, UserLog, User, RepoGroup, PullRequest
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class UserTemp(object):
51 class UserTemp(object):
52 def __init__(self, user_id):
52 def __init__(self, user_id):
53 self.user_id = user_id
53 self.user_id = user_id
54
54
55 def __repr__(self):
55 def __repr__(self):
56 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
56 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
57
57
58
58
59 class RepoTemp(object):
59 class RepoTemp(object):
60 def __init__(self, repo_id):
60 def __init__(self, repo_id):
61 self.repo_id = repo_id
61 self.repo_id = repo_id
62
62
63 def __repr__(self):
63 def __repr__(self):
64 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
64 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
65
65
66
66
67 class CachedRepoList(object):
67 class CachedRepoList(object):
68
68
69 def __init__(self, db_repo_list, repos_path, order_by=None):
69 def __init__(self, db_repo_list, repos_path, order_by=None):
70 self.db_repo_list = db_repo_list
70 self.db_repo_list = db_repo_list
71 self.repos_path = repos_path
71 self.repos_path = repos_path
72 self.order_by = order_by
72 self.order_by = order_by
73 self.reversed = (order_by or '').startswith('-')
73 self.reversed = (order_by or '').startswith('-')
74
74
75 def __len__(self):
75 def __len__(self):
76 return len(self.db_repo_list)
76 return len(self.db_repo_list)
77
77
78 def __repr__(self):
78 def __repr__(self):
79 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
79 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
80
80
81 def __iter__(self):
81 def __iter__(self):
82 # pre-propagated cache_map to save executing select statements
82 # pre-propagated cache_map to save executing select statements
83 # for each repo
83 # for each repo
84 cache_map = CacheInvalidation.get_cache_map()
84 cache_map = CacheInvalidation.get_cache_map()
85
85
86 for dbr in self.db_repo_list:
86 for dbr in self.db_repo_list:
87 scmr = dbr.scm_instance_cached(cache_map)
87 scmr = dbr.scm_instance_cached(cache_map)
88 # check permission at this level
88 # check permission at this level
89 if not HasRepoPermissionAny(
89 if not HasRepoPermissionAny(
90 'repository.read', 'repository.write', 'repository.admin'
90 'repository.read', 'repository.write', 'repository.admin'
91 )(dbr.repo_name, 'get repo check'):
91 )(dbr.repo_name, 'get repo check'):
92 continue
92 continue
93
93
94 if scmr is None:
94 if scmr is None:
95 log.error(
95 log.error(
96 '%s this repository is present in database but it '
96 '%s this repository is present in database but it '
97 'cannot be created as an scm instance' % dbr.repo_name
97 'cannot be created as an scm instance' % dbr.repo_name
98 )
98 )
99 continue
99 continue
100
100
101 last_change = scmr.last_change
101 last_change = scmr.last_change
102 tip = h.get_changeset_safe(scmr, 'tip')
102 tip = h.get_changeset_safe(scmr, 'tip')
103
103
104 tmp_d = {}
104 tmp_d = {}
105 tmp_d['name'] = dbr.repo_name
105 tmp_d['name'] = dbr.repo_name
106 tmp_d['name_sort'] = tmp_d['name'].lower()
106 tmp_d['name_sort'] = tmp_d['name'].lower()
107 tmp_d['description'] = dbr.description
107 tmp_d['description'] = dbr.description
108 tmp_d['description_sort'] = tmp_d['description']
108 tmp_d['description_sort'] = tmp_d['description']
109 tmp_d['last_change'] = last_change
109 tmp_d['last_change'] = last_change
110 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
110 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
111 tmp_d['tip'] = tip.raw_id
111 tmp_d['tip'] = tip.raw_id
112 tmp_d['tip_sort'] = tip.revision
112 tmp_d['tip_sort'] = tip.revision
113 tmp_d['rev'] = tip.revision
113 tmp_d['rev'] = tip.revision
114 tmp_d['contact'] = dbr.user.full_contact
114 tmp_d['contact'] = dbr.user.full_contact
115 tmp_d['contact_sort'] = tmp_d['contact']
115 tmp_d['contact_sort'] = tmp_d['contact']
116 tmp_d['owner_sort'] = tmp_d['contact']
116 tmp_d['owner_sort'] = tmp_d['contact']
117 tmp_d['repo_archives'] = list(scmr._get_archives())
117 tmp_d['repo_archives'] = list(scmr._get_archives())
118 tmp_d['last_msg'] = tip.message
118 tmp_d['last_msg'] = tip.message
119 tmp_d['author'] = tip.author
119 tmp_d['author'] = tip.author
120 tmp_d['dbrepo'] = dbr.get_dict()
120 tmp_d['dbrepo'] = dbr.get_dict()
121 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
121 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
122 yield tmp_d
122 yield tmp_d
123
123
124
124
125 class GroupList(object):
125 class GroupList(object):
126
126
127 def __init__(self, db_repo_group_list):
127 def __init__(self, db_repo_group_list):
128 self.db_repo_group_list = db_repo_group_list
128 self.db_repo_group_list = db_repo_group_list
129
129
130 def __len__(self):
130 def __len__(self):
131 return len(self.db_repo_group_list)
131 return len(self.db_repo_group_list)
132
132
133 def __repr__(self):
133 def __repr__(self):
134 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
134 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
135
135
136 def __iter__(self):
136 def __iter__(self):
137 for dbgr in self.db_repo_group_list:
137 for dbgr in self.db_repo_group_list:
138 # check permission at this level
138 # check permission at this level
139 if not HasReposGroupPermissionAny(
139 if not HasReposGroupPermissionAny(
140 'group.read', 'group.write', 'group.admin'
140 'group.read', 'group.write', 'group.admin'
141 )(dbgr.group_name, 'get group repo check'):
141 )(dbgr.group_name, 'get group repo check'):
142 continue
142 continue
143
143
144 yield dbgr
144 yield dbgr
145
145
146
146
147 class ScmModel(BaseModel):
147 class ScmModel(BaseModel):
148 """
148 """
149 Generic Scm Model
149 Generic Scm Model
150 """
150 """
151
151
152 def __get_repo(self, instance):
152 def __get_repo(self, instance):
153 cls = Repository
153 cls = Repository
154 if isinstance(instance, cls):
154 if isinstance(instance, cls):
155 return instance
155 return instance
156 elif isinstance(instance, int) or str(instance).isdigit():
156 elif isinstance(instance, int) or str(instance).isdigit():
157 return cls.get(instance)
157 return cls.get(instance)
158 elif isinstance(instance, basestring):
158 elif isinstance(instance, basestring):
159 return cls.get_by_repo_name(instance)
159 return cls.get_by_repo_name(instance)
160 elif instance:
160 elif instance:
161 raise Exception('given object must be int, basestr or Instance'
161 raise Exception('given object must be int, basestr or Instance'
162 ' of %s got %s' % (type(cls), type(instance)))
162 ' of %s got %s' % (type(cls), type(instance)))
163
163
164 @LazyProperty
164 @LazyProperty
165 def repos_path(self):
165 def repos_path(self):
166 """
166 """
167 Get's the repositories root path from database
167 Get's the repositories root path from database
168 """
168 """
169
169
170 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
170 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
171
171
172 return q.ui_value
172 return q.ui_value
173
173
174 def repo_scan(self, repos_path=None):
174 def repo_scan(self, repos_path=None):
175 """
175 """
176 Listing of repositories in given path. This path should not be a
176 Listing of repositories in given path. This path should not be a
177 repository itself. Return a dictionary of repository objects
177 repository itself. Return a dictionary of repository objects
178
178
179 :param repos_path: path to directory containing repositories
179 :param repos_path: path to directory containing repositories
180 """
180 """
181
181
182 if repos_path is None:
182 if repos_path is None:
183 repos_path = self.repos_path
183 repos_path = self.repos_path
184
184
185 log.info('scanning for repositories in %s' % repos_path)
185 log.info('scanning for repositories in %s' % repos_path)
186
186
187 baseui = make_ui('db')
187 baseui = make_ui('db')
188 repos = {}
188 repos = {}
189
189
190 for name, path in get_filesystem_repos(repos_path, recursive=True):
190 for name, path in get_filesystem_repos(repos_path, recursive=True):
191 # skip removed repos
191 # skip removed repos
192 if REMOVED_REPO_PAT.match(name):
192 if REMOVED_REPO_PAT.match(name):
193 continue
193 continue
194
194
195 # name need to be decomposed and put back together using the /
195 # name need to be decomposed and put back together using the /
196 # since this is internal storage separator for rhodecode
196 # since this is internal storage separator for rhodecode
197 name = Repository.url_sep().join(name.split(os.sep))
197 name = Repository.url_sep().join(name.split(os.sep))
198
198
199 try:
199 try:
200 if name in repos:
200 if name in repos:
201 raise RepositoryError('Duplicate repository name %s '
201 raise RepositoryError('Duplicate repository name %s '
202 'found in %s' % (name, path))
202 'found in %s' % (name, path))
203 else:
203 else:
204
204
205 klass = get_backend(path[0])
205 klass = get_backend(path[0])
206
206
207 if path[0] == 'hg' and path[0] in BACKENDS.keys():
207 if path[0] == 'hg' and path[0] in BACKENDS.keys():
208 repos[name] = klass(safe_str(path[1]), baseui=baseui)
208 repos[name] = klass(safe_str(path[1]), baseui=baseui)
209
209
210 if path[0] == 'git' and path[0] in BACKENDS.keys():
210 if path[0] == 'git' and path[0] in BACKENDS.keys():
211 repos[name] = klass(path[1])
211 repos[name] = klass(path[1])
212 except OSError:
212 except OSError:
213 continue
213 continue
214
214
215 return repos
215 return repos
216
216
217 def get_repos(self, all_repos=None, sort_key=None):
217 def get_repos(self, all_repos=None, sort_key=None):
218 """
218 """
219 Get all repos from db and for each repo create it's
219 Get all repos from db and for each repo create it's
220 backend instance and fill that backed with information from database
220 backend instance and fill that backed with information from database
221
221
222 :param all_repos: list of repository names as strings
222 :param all_repos: list of repository names as strings
223 give specific repositories list, good for filtering
223 give specific repositories list, good for filtering
224 """
224 """
225 if all_repos is None:
225 if all_repos is None:
226 all_repos = self.sa.query(Repository)\
226 all_repos = self.sa.query(Repository)\
227 .filter(Repository.group_id == None)\
227 .filter(Repository.group_id == None)\
228 .order_by(func.lower(Repository.repo_name)).all()
228 .order_by(func.lower(Repository.repo_name)).all()
229
229
230 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
230 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
231 order_by=sort_key)
231 order_by=sort_key)
232
232
233 return repo_iter
233 return repo_iter
234
234
235 def get_repos_groups(self, all_groups=None):
235 def get_repos_groups(self, all_groups=None):
236 if all_groups is None:
236 if all_groups is None:
237 all_groups = RepoGroup.query()\
237 all_groups = RepoGroup.query()\
238 .filter(RepoGroup.group_parent_id == None).all()
238 .filter(RepoGroup.group_parent_id == None).all()
239 group_iter = GroupList(all_groups)
239 group_iter = GroupList(all_groups)
240
240
241 return group_iter
241 return group_iter
242
242
243 def mark_for_invalidation(self, repo_name):
243 def mark_for_invalidation(self, repo_name):
244 """
244 """
245 Puts cache invalidation task into db for
245 Puts cache invalidation task into db for
246 further global cache invalidation
246 further global cache invalidation
247
247
248 :param repo_name: this repo that should invalidation take place
248 :param repo_name: this repo that should invalidation take place
249 """
249 """
250 CacheInvalidation.set_invalidate(repo_name)
250 CacheInvalidation.set_invalidate(repo_name)
251
251
252 def toggle_following_repo(self, follow_repo_id, user_id):
252 def toggle_following_repo(self, follow_repo_id, user_id):
253
253
254 f = self.sa.query(UserFollowing)\
254 f = self.sa.query(UserFollowing)\
255 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
255 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
256 .filter(UserFollowing.user_id == user_id).scalar()
256 .filter(UserFollowing.user_id == user_id).scalar()
257
257
258 if f is not None:
258 if f is not None:
259 try:
259 try:
260 self.sa.delete(f)
260 self.sa.delete(f)
261 action_logger(UserTemp(user_id),
261 action_logger(UserTemp(user_id),
262 'stopped_following_repo',
262 'stopped_following_repo',
263 RepoTemp(follow_repo_id))
263 RepoTemp(follow_repo_id))
264 return
264 return
265 except:
265 except:
266 log.error(traceback.format_exc())
266 log.error(traceback.format_exc())
267 raise
267 raise
268
268
269 try:
269 try:
270 f = UserFollowing()
270 f = UserFollowing()
271 f.user_id = user_id
271 f.user_id = user_id
272 f.follows_repo_id = follow_repo_id
272 f.follows_repo_id = follow_repo_id
273 self.sa.add(f)
273 self.sa.add(f)
274
274
275 action_logger(UserTemp(user_id),
275 action_logger(UserTemp(user_id),
276 'started_following_repo',
276 'started_following_repo',
277 RepoTemp(follow_repo_id))
277 RepoTemp(follow_repo_id))
278 except:
278 except:
279 log.error(traceback.format_exc())
279 log.error(traceback.format_exc())
280 raise
280 raise
281
281
282 def toggle_following_user(self, follow_user_id, user_id):
282 def toggle_following_user(self, follow_user_id, user_id):
283 f = self.sa.query(UserFollowing)\
283 f = self.sa.query(UserFollowing)\
284 .filter(UserFollowing.follows_user_id == follow_user_id)\
284 .filter(UserFollowing.follows_user_id == follow_user_id)\
285 .filter(UserFollowing.user_id == user_id).scalar()
285 .filter(UserFollowing.user_id == user_id).scalar()
286
286
287 if f is not None:
287 if f is not None:
288 try:
288 try:
289 self.sa.delete(f)
289 self.sa.delete(f)
290 return
290 return
291 except:
291 except:
292 log.error(traceback.format_exc())
292 log.error(traceback.format_exc())
293 raise
293 raise
294
294
295 try:
295 try:
296 f = UserFollowing()
296 f = UserFollowing()
297 f.user_id = user_id
297 f.user_id = user_id
298 f.follows_user_id = follow_user_id
298 f.follows_user_id = follow_user_id
299 self.sa.add(f)
299 self.sa.add(f)
300 except:
300 except:
301 log.error(traceback.format_exc())
301 log.error(traceback.format_exc())
302 raise
302 raise
303
303
304 def is_following_repo(self, repo_name, user_id, cache=False):
304 def is_following_repo(self, repo_name, user_id, cache=False):
305 r = self.sa.query(Repository)\
305 r = self.sa.query(Repository)\
306 .filter(Repository.repo_name == repo_name).scalar()
306 .filter(Repository.repo_name == repo_name).scalar()
307
307
308 f = self.sa.query(UserFollowing)\
308 f = self.sa.query(UserFollowing)\
309 .filter(UserFollowing.follows_repository == r)\
309 .filter(UserFollowing.follows_repository == r)\
310 .filter(UserFollowing.user_id == user_id).scalar()
310 .filter(UserFollowing.user_id == user_id).scalar()
311
311
312 return f is not None
312 return f is not None
313
313
314 def is_following_user(self, username, user_id, cache=False):
314 def is_following_user(self, username, user_id, cache=False):
315 u = User.get_by_username(username)
315 u = User.get_by_username(username)
316
316
317 f = self.sa.query(UserFollowing)\
317 f = self.sa.query(UserFollowing)\
318 .filter(UserFollowing.follows_user == u)\
318 .filter(UserFollowing.follows_user == u)\
319 .filter(UserFollowing.user_id == user_id).scalar()
319 .filter(UserFollowing.user_id == user_id).scalar()
320
320
321 return f is not None
321 return f is not None
322
322
323 def get_followers(self, repo_id):
323 def get_followers(self, repo):
324 if not isinstance(repo_id, int):
324 repo = self._get_repo(repo)
325 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
326
325
327 return self.sa.query(UserFollowing)\
326 return self.sa.query(UserFollowing)\
328 .filter(UserFollowing.follows_repo_id == repo_id).count()
327 .filter(UserFollowing.follows_repository == repo).count()
329
328
330 def get_forks(self, repo_id):
329 def get_forks(self, repo):
331 if not isinstance(repo_id, int):
330 repo = self._get_repo(repo)
332 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
331 return self.sa.query(Repository)\
332 .filter(Repository.fork == repo).count()
333
333
334 return self.sa.query(Repository)\
334 def get_pull_requests(self, repo):
335 .filter(Repository.fork_id == repo_id).count()
335 repo = self._get_repo(repo)
336 return self.sa.query(PullRequest)\
337 .filter(PullRequest.other_repo == repo).count()
336
338
337 def mark_as_fork(self, repo, fork, user):
339 def mark_as_fork(self, repo, fork, user):
338 repo = self.__get_repo(repo)
340 repo = self.__get_repo(repo)
339 fork = self.__get_repo(fork)
341 fork = self.__get_repo(fork)
340 repo.fork = fork
342 repo.fork = fork
341 self.sa.add(repo)
343 self.sa.add(repo)
342 return repo
344 return repo
343
345
344 def pull_changes(self, repo_name, username):
346 def pull_changes(self, repo_name, username):
345 dbrepo = Repository.get_by_repo_name(repo_name)
347 dbrepo = Repository.get_by_repo_name(repo_name)
346 clone_uri = dbrepo.clone_uri
348 clone_uri = dbrepo.clone_uri
347 if not clone_uri:
349 if not clone_uri:
348 raise Exception("This repository doesn't have a clone uri")
350 raise Exception("This repository doesn't have a clone uri")
349
351
350 repo = dbrepo.scm_instance
352 repo = dbrepo.scm_instance
351 try:
353 try:
352 extras = {
354 extras = {
353 'ip': '',
355 'ip': '',
354 'username': username,
356 'username': username,
355 'action': 'push_remote',
357 'action': 'push_remote',
356 'repository': repo_name,
358 'repository': repo_name,
357 'scm': repo.alias,
359 'scm': repo.alias,
358 }
360 }
359
361
360 # inject ui extra param to log this action via push logger
362 # inject ui extra param to log this action via push logger
361 for k, v in extras.items():
363 for k, v in extras.items():
362 repo._repo.ui.setconfig('rhodecode_extras', k, v)
364 repo._repo.ui.setconfig('rhodecode_extras', k, v)
363 if repo.alias == 'git':
365 if repo.alias == 'git':
364 repo.fetch(clone_uri)
366 repo.fetch(clone_uri)
365 else:
367 else:
366 repo.pull(clone_uri)
368 repo.pull(clone_uri)
367 self.mark_for_invalidation(repo_name)
369 self.mark_for_invalidation(repo_name)
368 except:
370 except:
369 log.error(traceback.format_exc())
371 log.error(traceback.format_exc())
370 raise
372 raise
371
373
372 def commit_change(self, repo, repo_name, cs, user, author, message,
374 def commit_change(self, repo, repo_name, cs, user, author, message,
373 content, f_path):
375 content, f_path):
374
376
375 if repo.alias == 'hg':
377 if repo.alias == 'hg':
376 from rhodecode.lib.vcs.backends.hg import \
378 from rhodecode.lib.vcs.backends.hg import \
377 MercurialInMemoryChangeset as IMC
379 MercurialInMemoryChangeset as IMC
378 elif repo.alias == 'git':
380 elif repo.alias == 'git':
379 from rhodecode.lib.vcs.backends.git import \
381 from rhodecode.lib.vcs.backends.git import \
380 GitInMemoryChangeset as IMC
382 GitInMemoryChangeset as IMC
381
383
382 # decoding here will force that we have proper encoded values
384 # decoding here will force that we have proper encoded values
383 # in any other case this will throw exceptions and deny commit
385 # in any other case this will throw exceptions and deny commit
384 content = safe_str(content)
386 content = safe_str(content)
385 path = safe_str(f_path)
387 path = safe_str(f_path)
386 # message and author needs to be unicode
388 # message and author needs to be unicode
387 # proper backend should then translate that into required type
389 # proper backend should then translate that into required type
388 message = safe_unicode(message)
390 message = safe_unicode(message)
389 author = safe_unicode(author)
391 author = safe_unicode(author)
390 m = IMC(repo)
392 m = IMC(repo)
391 m.change(FileNode(path, content))
393 m.change(FileNode(path, content))
392 tip = m.commit(message=message,
394 tip = m.commit(message=message,
393 author=author,
395 author=author,
394 parents=[cs], branch=cs.branch)
396 parents=[cs], branch=cs.branch)
395
397
396 new_cs = tip.short_id
398 new_cs = tip.short_id
397 action = 'push_local:%s' % new_cs
399 action = 'push_local:%s' % new_cs
398
400
399 action_logger(user, action, repo_name)
401 action_logger(user, action, repo_name)
400
402
401 self.mark_for_invalidation(repo_name)
403 self.mark_for_invalidation(repo_name)
402
404
403 def create_node(self, repo, repo_name, cs, user, author, message, content,
405 def create_node(self, repo, repo_name, cs, user, author, message, content,
404 f_path):
406 f_path):
405 if repo.alias == 'hg':
407 if repo.alias == 'hg':
406 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
408 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
407 elif repo.alias == 'git':
409 elif repo.alias == 'git':
408 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
410 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
409 # decoding here will force that we have proper encoded values
411 # decoding here will force that we have proper encoded values
410 # in any other case this will throw exceptions and deny commit
412 # in any other case this will throw exceptions and deny commit
411
413
412 if isinstance(content, (basestring,)):
414 if isinstance(content, (basestring,)):
413 content = safe_str(content)
415 content = safe_str(content)
414 elif isinstance(content, (file, cStringIO.OutputType,)):
416 elif isinstance(content, (file, cStringIO.OutputType,)):
415 content = content.read()
417 content = content.read()
416 else:
418 else:
417 raise Exception('Content is of unrecognized type %s' % (
419 raise Exception('Content is of unrecognized type %s' % (
418 type(content)
420 type(content)
419 ))
421 ))
420
422
421 message = safe_unicode(message)
423 message = safe_unicode(message)
422 author = safe_unicode(author)
424 author = safe_unicode(author)
423 path = safe_str(f_path)
425 path = safe_str(f_path)
424 m = IMC(repo)
426 m = IMC(repo)
425
427
426 if isinstance(cs, EmptyChangeset):
428 if isinstance(cs, EmptyChangeset):
427 # EmptyChangeset means we we're editing empty repository
429 # EmptyChangeset means we we're editing empty repository
428 parents = None
430 parents = None
429 else:
431 else:
430 parents = [cs]
432 parents = [cs]
431
433
432 m.add(FileNode(path, content=content))
434 m.add(FileNode(path, content=content))
433 tip = m.commit(message=message,
435 tip = m.commit(message=message,
434 author=author,
436 author=author,
435 parents=parents, branch=cs.branch)
437 parents=parents, branch=cs.branch)
436 new_cs = tip.short_id
438 new_cs = tip.short_id
437 action = 'push_local:%s' % new_cs
439 action = 'push_local:%s' % new_cs
438
440
439 action_logger(user, action, repo_name)
441 action_logger(user, action, repo_name)
440
442
441 self.mark_for_invalidation(repo_name)
443 self.mark_for_invalidation(repo_name)
442
444
443 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
445 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
444 """
446 """
445 recursive walk in root dir and return a set of all path in that dir
447 recursive walk in root dir and return a set of all path in that dir
446 based on repository walk function
448 based on repository walk function
447
449
448 :param repo_name: name of repository
450 :param repo_name: name of repository
449 :param revision: revision for which to list nodes
451 :param revision: revision for which to list nodes
450 :param root_path: root path to list
452 :param root_path: root path to list
451 :param flat: return as a list, if False returns a dict with decription
453 :param flat: return as a list, if False returns a dict with decription
452
454
453 """
455 """
454 _files = list()
456 _files = list()
455 _dirs = list()
457 _dirs = list()
456 try:
458 try:
457 _repo = self.__get_repo(repo_name)
459 _repo = self.__get_repo(repo_name)
458 changeset = _repo.scm_instance.get_changeset(revision)
460 changeset = _repo.scm_instance.get_changeset(revision)
459 root_path = root_path.lstrip('/')
461 root_path = root_path.lstrip('/')
460 for topnode, dirs, files in changeset.walk(root_path):
462 for topnode, dirs, files in changeset.walk(root_path):
461 for f in files:
463 for f in files:
462 _files.append(f.path if flat else {"name": f.path,
464 _files.append(f.path if flat else {"name": f.path,
463 "type": "file"})
465 "type": "file"})
464 for d in dirs:
466 for d in dirs:
465 _dirs.append(d.path if flat else {"name": d.path,
467 _dirs.append(d.path if flat else {"name": d.path,
466 "type": "dir"})
468 "type": "dir"})
467 except RepositoryError:
469 except RepositoryError:
468 log.debug(traceback.format_exc())
470 log.debug(traceback.format_exc())
469 raise
471 raise
470
472
471 return _dirs, _files
473 return _dirs, _files
472
474
473 def get_unread_journal(self):
475 def get_unread_journal(self):
474 return self.sa.query(UserLog).count()
476 return self.sa.query(UserLog).count()
@@ -1,338 +1,346 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <!-- HEADER -->
4 <!-- HEADER -->
5 <div id="header">
5 <div id="header">
6 <div id="header-inner" class="title hover">
6 <div id="header-inner" class="title hover">
7 <div id="logo">
7 <div id="logo">
8 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
8 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
9 </div>
9 </div>
10 <!-- MENU -->
10 <!-- MENU -->
11 ${self.page_nav()}
11 ${self.page_nav()}
12 <!-- END MENU -->
12 <!-- END MENU -->
13 ${self.body()}
13 ${self.body()}
14 </div>
14 </div>
15 </div>
15 </div>
16 <!-- END HEADER -->
16 <!-- END HEADER -->
17
17
18 <!-- CONTENT -->
18 <!-- CONTENT -->
19 <div id="content">
19 <div id="content">
20 <div class="flash_msg">
20 <div class="flash_msg">
21 <% messages = h.flash.pop_messages() %>
21 <% messages = h.flash.pop_messages() %>
22 % if messages:
22 % if messages:
23 <ul id="flash-messages">
23 <ul id="flash-messages">
24 % for message in messages:
24 % for message in messages:
25 <li class="${message.category}_msg">${message}</li>
25 <li class="${message.category}_msg">${message}</li>
26 % endfor
26 % endfor
27 </ul>
27 </ul>
28 % endif
28 % endif
29 </div>
29 </div>
30 <div id="main">
30 <div id="main">
31 ${next.main()}
31 ${next.main()}
32 </div>
32 </div>
33 </div>
33 </div>
34 <!-- END CONTENT -->
34 <!-- END CONTENT -->
35
35
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title">
38 <div id="footer-inner" class="title">
39 <div>
39 <div>
40 <p class="footer-link">
40 <p class="footer-link">
41 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
41 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
42 </p>
42 </p>
43 <p class="footer-link-right">
43 <p class="footer-link-right">
44 <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a>
44 <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a>
45 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
45 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
46 </p>
46 </p>
47 </div>
47 </div>
48 </div>
48 </div>
49 </div>
49 </div>
50 <!-- END FOOTER -->
50 <!-- END FOOTER -->
51
51
52 ### MAKO DEFS ###
52 ### MAKO DEFS ###
53 <%def name="page_nav()">
53 <%def name="page_nav()">
54 ${self.menu()}
54 ${self.menu()}
55 </%def>
55 </%def>
56
56
57 <%def name="breadcrumbs()">
57 <%def name="breadcrumbs()">
58 <div class="breadcrumbs">
58 <div class="breadcrumbs">
59 ${self.breadcrumbs_links()}
59 ${self.breadcrumbs_links()}
60 </div>
60 </div>
61 </%def>
61 </%def>
62
62
63 <%def name="usermenu()">
63 <%def name="usermenu()">
64 <div class="user-menu">
64 <div class="user-menu">
65 <div class="container">
65 <div class="container">
66 <div class="gravatar" id="quick_login_link">
66 <div class="gravatar" id="quick_login_link">
67 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
67 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
68 </div>
68 </div>
69 %if c.rhodecode_user.username != 'default' and c.unread_notifications != 0:
69 %if c.rhodecode_user.username != 'default' and c.unread_notifications != 0:
70 <div class="notifications">
70 <div class="notifications">
71 <a id="notification_counter" href="${h.url('notifications')}">${c.unread_notifications}</a>
71 <a id="notification_counter" href="${h.url('notifications')}">${c.unread_notifications}</a>
72 </div>
72 </div>
73 %endif
73 %endif
74 </div>
74 </div>
75 <div id="quick_login" style="display:none">
75 <div id="quick_login" style="display:none">
76 %if c.rhodecode_user.username == 'default':
76 %if c.rhodecode_user.username == 'default':
77 <h4>${_('Login to your account')}</h4>
77 <h4>${_('Login to your account')}</h4>
78 ${h.form(h.url('login_home',came_from=h.url.current()))}
78 ${h.form(h.url('login_home',came_from=h.url.current()))}
79 <div class="form">
79 <div class="form">
80 <div class="fields">
80 <div class="fields">
81 <div class="field">
81 <div class="field">
82 <div class="label">
82 <div class="label">
83 <label for="username">${_('Username')}:</label>
83 <label for="username">${_('Username')}:</label>
84 </div>
84 </div>
85 <div class="input">
85 <div class="input">
86 ${h.text('username',class_='focus',size=40)}
86 ${h.text('username',class_='focus',size=40)}
87 </div>
87 </div>
88
88
89 </div>
89 </div>
90 <div class="field">
90 <div class="field">
91 <div class="label">
91 <div class="label">
92 <label for="password">${_('Password')}:</label>
92 <label for="password">${_('Password')}:</label>
93 </div>
93 </div>
94 <div class="input">
94 <div class="input">
95 ${h.password('password',class_='focus',size=40)}
95 ${h.password('password',class_='focus',size=40)}
96 </div>
96 </div>
97
97
98 </div>
98 </div>
99 <div class="buttons">
99 <div class="buttons">
100 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
100 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
101 <div class="register">
101 <div class="register">
102 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
102 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
103 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
103 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
104 %endif
104 %endif
105 </div>
105 </div>
106 <div class="submit">
106 <div class="submit">
107 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
107 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
108 </div>
108 </div>
109 </div>
109 </div>
110 </div>
110 </div>
111 </div>
111 </div>
112 ${h.end_form()}
112 ${h.end_form()}
113 %else:
113 %else:
114 <div class="links_left">
114 <div class="links_left">
115 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
115 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
116 <div class="email">${c.rhodecode_user.email}</div>
116 <div class="email">${c.rhodecode_user.email}</div>
117 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
117 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
118 <div class="inbox"><a href="${h.url('notifications')}">${_('Inbox')}: ${c.unread_notifications}</a></div>
118 <div class="inbox"><a href="${h.url('notifications')}">${_('Inbox')}: ${c.unread_notifications}</a></div>
119 </div>
119 </div>
120 <div class="links_right">
120 <div class="links_right">
121 <ol class="links">
121 <ol class="links">
122 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
122 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
123 <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li>
123 <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li>
124 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
124 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
125 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
125 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
126 </ol>
126 </ol>
127 </div>
127 </div>
128 %endif
128 %endif
129 </div>
129 </div>
130 </div>
130 </div>
131 </%def>
131 </%def>
132
132
133 <%def name="menu(current=None)">
133 <%def name="menu(current=None)">
134 <%
134 <%
135 def is_current(selected):
135 def is_current(selected):
136 if selected == current:
136 if selected == current:
137 return h.literal('class="current"')
137 return h.literal('class="current"')
138 %>
138 %>
139 %if current not in ['home','admin']:
139 %if current not in ['home','admin']:
140 ##REGULAR MENU
140 ##REGULAR MENU
141 <ul id="quick">
141 <ul id="quick">
142 <!-- repo switcher -->
142 <!-- repo switcher -->
143 <li>
143 <li>
144 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="#">
144 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="#">
145 <span class="icon">
145 <span class="icon">
146 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
146 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
147 </span>
147 </span>
148 <span>&darr;</span>
148 <span>&darr;</span>
149 </a>
149 </a>
150 <ul id="repo_switcher_list" class="repo_switcher">
150 <ul id="repo_switcher_list" class="repo_switcher">
151 <li>
151 <li>
152 <a href="#">${_('loading...')}</a>
152 <a href="#">${_('loading...')}</a>
153 </li>
153 </li>
154 </ul>
154 </ul>
155 </li>
155 </li>
156
156
157 <li ${is_current('summary')}>
157 <li ${is_current('summary')}>
158 <a class="menu_link" title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
158 <a class="menu_link" title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
159 <span class="icon">
159 <span class="icon">
160 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
160 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
161 </span>
161 </span>
162 <span>${_('Summary')}</span>
162 <span>${_('Summary')}</span>
163 </a>
163 </a>
164 </li>
164 </li>
165 <li ${is_current('changelog')}>
165 <li ${is_current('changelog')}>
166 <a class="menu_link" title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
166 <a class="menu_link" title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
167 <span class="icon">
167 <span class="icon">
168 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
168 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
169 </span>
169 </span>
170 <span>${_('Changelog')}</span>
170 <span>${_('Changelog')}</span>
171 </a>
171 </a>
172 </li>
172 </li>
173
173
174 <li ${is_current('switch_to')}>
174 <li ${is_current('switch_to')}>
175 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
175 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
176 <span class="icon">
176 <span class="icon">
177 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
177 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
178 </span>
178 </span>
179 <span>${_('Switch to')}</span>
179 <span>${_('Switch to')}</span>
180 </a>
180 </a>
181 <ul id="switch_to_list" class="switch_to">
181 <ul id="switch_to_list" class="switch_to">
182 <li><a href="#">${_('loading...')}</a></li>
182 <li><a href="#">${_('loading...')}</a></li>
183 </ul>
183 </ul>
184 </li>
184 </li>
185 <li ${is_current('files')}>
185 <li ${is_current('files')}>
186 <a class="menu_link" title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
186 <a class="menu_link" title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
187 <span class="icon">
187 <span class="icon">
188 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
188 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
189 </span>
189 </span>
190 <span>${_('Files')}</span>
190 <span>${_('Files')}</span>
191 </a>
191 </a>
192 </li>
192 </li>
193
193
194 <li ${is_current('options')}>
194 <li ${is_current('options')}>
195 <a class="menu_link" title="${_('Options')}" href="#">
195 <a class="menu_link" title="${_('Options')}" href="#">
196 <span class="icon">
196 <span class="icon">
197 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
197 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
198 </span>
198 </span>
199 <span>${_('Options')}</span>
199 <span>${_('Options')}</span>
200 </a>
200 </a>
201 <ul>
201 <ul>
202 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
202 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
203 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
203 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
204 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
204 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
205 %else:
205 %else:
206 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
206 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
207 %endif
207 %endif
208 %endif
208 %endif
209 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
209 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
210 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
210 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
211
211
212 % if h.HasPermissionAll('hg.admin')('access admin main page'):
212 % if h.HasPermissionAll('hg.admin')('access admin main page'):
213 <li>
213 <li>
214 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
214 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
215 <%def name="admin_menu()">
215 <%def name="admin_menu()">
216 <ul>
216 <ul>
217 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
217 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
218 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
218 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
219 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
219 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
220 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
220 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
221 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
221 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
222 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
222 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
223 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
223 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
224 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
224 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
225 </ul>
225 </ul>
226 </%def>
226 </%def>
227
227
228 ${admin_menu()}
228 ${admin_menu()}
229 </li>
229 </li>
230 % endif
230 % endif
231 </ul>
231 </ul>
232 </li>
232 </li>
233
233
234 <li>
234 <li>
235 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
235 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
236 <span class="icon_short">
236 <span class="icon_short">
237 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
237 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
238 </span>
238 </span>
239 <span id="current_followers_count" class="short">${c.repository_followers}</span>
239 <span id="current_followers_count" class="short">${c.repository_followers}</span>
240 </a>
240 </a>
241 </li>
241 </li>
242 <li>
242 <li>
243 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
243 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
244 <span class="icon_short">
244 <span class="icon_short">
245 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
245 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
246 </span>
246 </span>
247 <span class="short">${c.repository_forks}</span>
247 <span class="short">${c.repository_forks}</span>
248 </a>
248 </a>
249 </li>
249 </li>
250 <li>
251 <a class="menu_link" title="${_('Pull requests')}" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}">
252 <span class="icon_short">
253 <img src="${h.url('/images/icons/arrow_join.png')}" alt="${_('Pull requests')}" />
254 </span>
255 <span class="short">${c.repository_pull_requests}</span>
256 </a>
257 </li>
250 ${usermenu()}
258 ${usermenu()}
251 </ul>
259 </ul>
252 <script type="text/javascript">
260 <script type="text/javascript">
253 YUE.on('repo_switcher','mouseover',function(){
261 YUE.on('repo_switcher','mouseover',function(){
254 function qfilter(){
262 function qfilter(){
255 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
263 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
256 var target = 'q_filter_rs';
264 var target = 'q_filter_rs';
257 var func = function(node){
265 var func = function(node){
258 return node.parentNode;
266 return node.parentNode;
259 }
267 }
260 q_filter(target,nodes,func);
268 q_filter(target,nodes,func);
261 }
269 }
262 var loaded = YUD.hasClass('repo_switcher','loaded');
270 var loaded = YUD.hasClass('repo_switcher','loaded');
263 if(!loaded){
271 if(!loaded){
264 YUD.addClass('repo_switcher','loaded');
272 YUD.addClass('repo_switcher','loaded');
265 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
273 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
266 function(o){qfilter();},
274 function(o){qfilter();},
267 function(o){YUD.removeClass('repo_switcher','loaded');}
275 function(o){YUD.removeClass('repo_switcher','loaded');}
268 ,null);
276 ,null);
269 }
277 }
270 return false;
278 return false;
271 });
279 });
272
280
273 YUE.on('branch_tag_switcher','mouseover',function(){
281 YUE.on('branch_tag_switcher','mouseover',function(){
274 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
282 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
275 if(!loaded){
283 if(!loaded){
276 YUD.addClass('branch_tag_switcher','loaded');
284 YUD.addClass('branch_tag_switcher','loaded');
277 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
285 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
278 function(o){},
286 function(o){},
279 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
287 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
280 ,null);
288 ,null);
281 }
289 }
282 return false;
290 return false;
283 });
291 });
284 </script>
292 </script>
285 %else:
293 %else:
286 ##ROOT MENU
294 ##ROOT MENU
287 <ul id="quick">
295 <ul id="quick">
288 <li>
296 <li>
289 <a class="menu_link" title="${_('Home')}" href="${h.url('home')}">
297 <a class="menu_link" title="${_('Home')}" href="${h.url('home')}">
290 <span class="icon">
298 <span class="icon">
291 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
299 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
292 </span>
300 </span>
293 <span>${_('Home')}</span>
301 <span>${_('Home')}</span>
294 </a>
302 </a>
295 </li>
303 </li>
296 %if c.rhodecode_user.username != 'default':
304 %if c.rhodecode_user.username != 'default':
297 <li>
305 <li>
298 <a class="menu_link" title="${_('Journal')}" href="${h.url('journal')}">
306 <a class="menu_link" title="${_('Journal')}" href="${h.url('journal')}">
299 <span class="icon">
307 <span class="icon">
300 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
308 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
301 </span>
309 </span>
302 <span>${_('Journal')}</span>
310 <span>${_('Journal')}</span>
303 </a>
311 </a>
304 </li>
312 </li>
305 %else:
313 %else:
306 <li>
314 <li>
307 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
315 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
308 <span class="icon">
316 <span class="icon">
309 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
317 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
310 </span>
318 </span>
311 <span>${_('Public journal')}</span>
319 <span>${_('Public journal')}</span>
312 </a>
320 </a>
313 </li>
321 </li>
314 %endif
322 %endif
315 <li>
323 <li>
316 <a class="menu_link" title="${_('Search')}" href="${h.url('search')}">
324 <a class="menu_link" title="${_('Search')}" href="${h.url('search')}">
317 <span class="icon">
325 <span class="icon">
318 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
326 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
319 </span>
327 </span>
320 <span>${_('Search')}</span>
328 <span>${_('Search')}</span>
321 </a>
329 </a>
322 </li>
330 </li>
323
331
324 %if h.HasPermissionAll('hg.admin')('access admin main page'):
332 %if h.HasPermissionAll('hg.admin')('access admin main page'):
325 <li ${is_current('admin')}>
333 <li ${is_current('admin')}>
326 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
334 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
327 <span class="icon">
335 <span class="icon">
328 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
336 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
329 </span>
337 </span>
330 <span>${_('Admin')}</span>
338 <span>${_('Admin')}</span>
331 </a>
339 </a>
332 ${admin_menu()}
340 ${admin_menu()}
333 </li>
341 </li>
334 %endif
342 %endif
335 ${usermenu()}
343 ${usermenu()}
336 </ul>
344 </ul>
337 %endif
345 %endif
338 </%def>
346 </%def>
@@ -1,32 +1,83 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16
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
22
23 pull request ${c.pull_request} overview...
23 <h3>${_('Title')}: ${c.pull_request.title}</h3>
24 <div class="changeset-status-container" style="float:left;padding:0px 20px 20px 20px">
25 %if c.current_changeset_status:
26 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
27 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
28 %endif
29 </div>
30 <div style="padding:4px">
31 <div>${h.fmt_date(c.pull_request.created_on)}</div>
32 </div>
33
34 ##DIFF
35
36 <div class="table">
37 <div id="body" class="diffblock">
38 <div style="white-space:pre-wrap;padding:5px">${h.literal(c.pull_request.description)}</div>
39 </div>
40 <div id="changeset_compare_view_content">
41 ##CS
42 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
43 <%include file="/compare/compare_cs.html" />
44
45 ## FILES
46 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
47 <div class="cs_files">
48 %for fid, change, f, stat in c.files:
49 <div class="cs_${change}">
50 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
51 <div class="changes">${h.fancy_file_stats(stat)}</div>
52 </div>
53 %endfor
54 </div>
55 </div>
56 </div>
57 <script>
58 var _USERS_AC_DATA = ${c.users_array|n};
59 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
60 </script>
61
62 ## diff block
63 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
64 %for fid, change, f, stat in c.files:
65 ${diff_block.diff_block_simple([c.changes[fid]])}
66 %endfor
67
68 ## template for inline comment form
69 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
70 ##${comment.comment_inline_form(c.changeset)}
71
72 ## render comments main comments form and it status
73 ##${comment.comments(h.url('pull_request_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id),
74 ## c.current_changeset_status)}
24
75
25 </div>
76 </div>
26
77
27 <script type="text/javascript">
78 <script type="text/javascript">
28
79
29
80
30 </script>
81 </script>
31
82
32 </%def>
83 </%def>
General Comments 0
You need to be logged in to leave comments. Login now