##// END OF EJS Templates
Fix 'repos group' - it is 'repository group'
Mads Kiilerich -
r3653:4c78a085 beta
parent child Browse files
Show More
@@ -1,989 +1,989 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7 1.6.0 (**2013-XX-XX**)
7 1.6.0 (**2013-XX-XX**)
8 ----------------------
8 ----------------------
9
9
10 :status: in-progress
10 :status: in-progress
11 :branch: beta
11 :branch: beta
12
12
13 news
13 news
14 ++++
14 ++++
15
15
16 fixes
16 fixes
17 +++++
17 +++++
18
18
19 1.5.4 (**2013-03-13**)
19 1.5.4 (**2013-03-13**)
20 ----------------------
20 ----------------------
21
21
22 news
22 news
23 ++++
23 ++++
24
24
25
25
26 fixes
26 fixes
27 +++++
27 +++++
28
28
29 - fixed webtest dependency issues
29 - fixed webtest dependency issues
30 - fixed issues with celery tasks for password reset
30 - fixed issues with celery tasks for password reset
31 - fixed #763 gravatar helper function should fallback into default image
31 - fixed #763 gravatar helper function should fallback into default image
32 if email is empty
32 if email is empty
33 - fixes #762 user global activation flag is also respected for LDAP created
33 - fixes #762 user global activation flag is also respected for LDAP created
34 accounts
34 accounts
35 - use password obfuscate when clonning a remote repo with credentials inside
35 - use password obfuscate when clonning a remote repo with credentials inside
36 - fixed issue with renaming repos group together with changing parents
36 - fixed issue with renaming repository group together with changing parents
37 - disallow cloning from file:/// URIs
37 - disallow cloning from file:/// URIs
38 - handle all cases with multiple IP addresses in proxy headers
38 - handle all cases with multiple IP addresses in proxy headers
39
39
40 1.5.3 (**2013-02-12**)
40 1.5.3 (**2013-02-12**)
41 ----------------------
41 ----------------------
42
42
43 news
43 news
44 ++++
44 ++++
45
45
46 - IP restrictions now also enabled for IPv6
46 - IP restrictions now also enabled for IPv6
47
47
48 fixes
48 fixes
49 +++++
49 +++++
50
50
51 - fixed issues with private checkbox not always working
51 - fixed issues with private checkbox not always working
52 - fixed #746 unicodeDedode errors on feed controllers
52 - fixed #746 unicodeDedode errors on feed controllers
53 - fixes issue #756 cleanup repos didn't properly compose paths of repos to be cleaned up.
53 - fixes issue #756 cleanup repos didn't properly compose paths of repos to be cleaned up.
54 - fixed cache invalidation issues together with vcs_full_cache option
54 - fixed cache invalidation issues together with vcs_full_cache option
55 - repo scan should skip directories with starting with '.'
55 - repo scan should skip directories with starting with '.'
56 - fixes for issue #731, update-repoinfo sometimes failed to update data when changesets
56 - fixes for issue #731, update-repoinfo sometimes failed to update data when changesets
57 were initial commits
57 were initial commits
58 - recursive mode of setting permission skips private repositories
58 - recursive mode of setting permission skips private repositories
59
59
60 1.5.2 (**2013-01-14**)
60 1.5.2 (**2013-01-14**)
61 ----------------------
61 ----------------------
62
62
63 news
63 news
64 ++++
64 ++++
65
65
66 - IP restrictions for users. Each user can get a set of whitelist IP+mask for
66 - IP restrictions for users. Each user can get a set of whitelist IP+mask for
67 extra protection. Useful for buildbots etc.
67 extra protection. Useful for buildbots etc.
68 - added full last changeset info to lightweight dashboard. lightweight dashboard
68 - added full last changeset info to lightweight dashboard. lightweight dashboard
69 is now fully functional replacement of original dashboard.
69 is now fully functional replacement of original dashboard.
70 - implemented certain API calls for non-admin users.
70 - implemented certain API calls for non-admin users.
71 - enabled all Markdown Extra plugins
71 - enabled all Markdown Extra plugins
72 - implemented #725 Pull Request View - Show origin repo URL
72 - implemented #725 Pull Request View - Show origin repo URL
73 - show comments from pull requests into associated changesets
73 - show comments from pull requests into associated changesets
74
74
75 fixes
75 fixes
76 +++++
76 +++++
77
77
78 - update repoinfo script is more failsafe
78 - update repoinfo script is more failsafe
79 - fixed #687 Lazy loaded tooltip bug with simultaneous ajax requests
79 - fixed #687 Lazy loaded tooltip bug with simultaneous ajax requests
80 - fixed #691: Notifications for pull requests: move link to top for better
80 - fixed #691: Notifications for pull requests: move link to top for better
81 readability
81 readability
82 - fixed #699: fix missing fork docs for API
82 - fixed #699: fix missing fork docs for API
83 - fixed #693 Opening changeset from pull request fails
83 - fixed #693 Opening changeset from pull request fails
84 - fixed #710 File view stripping empty lines from beginning and end of file
84 - fixed #710 File view stripping empty lines from beginning and end of file
85 - fixed issues with getting repos by path on windows, caused GIT hooks to fail
85 - fixed issues with getting repos by path on windows, caused GIT hooks to fail
86 - fixed issues with groups paginator on main dashboard
86 - fixed issues with groups paginator on main dashboard
87 - improved fetch/pull command for git repos, now pulling all refs
87 - improved fetch/pull command for git repos, now pulling all refs
88 - fixed issue #719 Journal revision ID tooltip AJAX query path is incorrect
88 - fixed issue #719 Journal revision ID tooltip AJAX query path is incorrect
89 when running in a subdir
89 when running in a subdir
90 - fixed issue #702 API methods without arguments fail when "args":null
90 - fixed issue #702 API methods without arguments fail when "args":null
91 - set the status of changesets initially on pull request. Fixes issues #690 and #587
91 - set the status of changesets initially on pull request. Fixes issues #690 and #587
92
92
93 1.5.1 (**2012-12-13**)
93 1.5.1 (**2012-12-13**)
94 ----------------------
94 ----------------------
95
95
96 news
96 news
97 ++++
97 ++++
98
98
99 - implements #677: Don't allow to close pull requests when they are
99 - implements #677: Don't allow to close pull requests when they are
100 under-review status
100 under-review status
101 - implemented #670 Implementation of Roles in Pull Request
101 - implemented #670 Implementation of Roles in Pull Request
102
102
103 fixes
103 fixes
104 +++++
104 +++++
105
105
106 - default permissions can get duplicated after migration
106 - default permissions can get duplicated after migration
107 - fixed changeset status labels, they now select radio buttons
107 - fixed changeset status labels, they now select radio buttons
108 - #682 translation difficult for multi-line text
108 - #682 translation difficult for multi-line text
109 - #683 fixed difference between messages about not mapped repositories
109 - #683 fixed difference between messages about not mapped repositories
110 - email: fail nicely when no SMTP server has been configured
110 - email: fail nicely when no SMTP server has been configured
111
111
112 1.5.0 (**2012-12-12**)
112 1.5.0 (**2012-12-12**)
113 ----------------------
113 ----------------------
114
114
115 news
115 news
116 ++++
116 ++++
117
117
118 - new rewritten from scratch diff engine. 10x faster in edge cases. Handling
118 - new rewritten from scratch diff engine. 10x faster in edge cases. Handling
119 of file renames, copies, change flags and binary files
119 of file renames, copies, change flags and binary files
120 - added lightweight dashboard option. ref #500. New version of dashboard
120 - added lightweight dashboard option. ref #500. New version of dashboard
121 page that doesn't use any VCS data and is super fast to render. Recommended
121 page that doesn't use any VCS data and is super fast to render. Recommended
122 for large amount of repositories.
122 for large amount of repositories.
123 - implements #648 write Script for updating last modification time for
123 - implements #648 write Script for updating last modification time for
124 lightweight dashboard
124 lightweight dashboard
125 - implemented compare engine for git repositories.
125 - implemented compare engine for git repositories.
126 - LDAP failover, option to specify multiple servers
126 - LDAP failover, option to specify multiple servers
127 - added Errormator and Sentry support for monitoring RhodeCode
127 - added Errormator and Sentry support for monitoring RhodeCode
128 - implemented #628: Pass server URL to rc-extensions hooks
128 - implemented #628: Pass server URL to rc-extensions hooks
129 - new tooltip implementation - added lazy loading of changesets from journal
129 - new tooltip implementation - added lazy loading of changesets from journal
130 pages. This can significantly improve speed of rendering the page
130 pages. This can significantly improve speed of rendering the page
131 - implements #632,added branch/tag/bookmarks info into feeds
131 - implements #632,added branch/tag/bookmarks info into feeds
132 added changeset link to body of message
132 added changeset link to body of message
133 - implemented #638 permissions overview to groups
133 - implemented #638 permissions overview to groups
134 - implements #636, lazy loading of history and authors to speed up source
134 - implements #636, lazy loading of history and authors to speed up source
135 pages rendering
135 pages rendering
136 - implemented #647, option to pass list of default encoding used to
136 - implemented #647, option to pass list of default encoding used to
137 encode to/decode from unicode
137 encode to/decode from unicode
138 - added caching layer into RSS/ATOM feeds.
138 - added caching layer into RSS/ATOM feeds.
139 - basic implementation of cherry picking changesets for pull request, ref #575
139 - basic implementation of cherry picking changesets for pull request, ref #575
140 - implemented #661 Add option to include diff in RSS feed
140 - implemented #661 Add option to include diff in RSS feed
141 - implemented file history page for showing detailed changelog for a given file
141 - implemented file history page for showing detailed changelog for a given file
142 - implemented #663 Admin/permission: specify default repogroup perms
142 - implemented #663 Admin/permission: specify default repogroup perms
143 - implemented #379 defaults settings page for creation of repositories, locking
143 - implemented #379 defaults settings page for creation of repositories, locking
144 statistics, downloads, repository type
144 statistics, downloads, repository type
145 - implemented #210 filtering of admin journal based on Whoosh Query language
145 - implemented #210 filtering of admin journal based on Whoosh Query language
146 - added parents/children links in changeset viewref #650
146 - added parents/children links in changeset viewref #650
147
147
148 fixes
148 fixes
149 +++++
149 +++++
150
150
151 - fixed git version checker
151 - fixed git version checker
152 - #586 patched basic auth handler to fix issues with git behind proxy
152 - #586 patched basic auth handler to fix issues with git behind proxy
153 - #589 search urlgenerator didn't properly escape special characters
153 - #589 search urlgenerator didn't properly escape special characters
154 - fixed issue #614 Include repo name in delete confirmation dialog
154 - fixed issue #614 Include repo name in delete confirmation dialog
155 - fixed #623: Lang meta-tag doesn't work with C#/C++
155 - fixed #623: Lang meta-tag doesn't work with C#/C++
156 - fixes #612 Double quotes to Single quotes result in bad html in diff
156 - fixes #612 Double quotes to Single quotes result in bad html in diff
157 - fixes #630 git statistics do too much work making them slow.
157 - fixes #630 git statistics do too much work making them slow.
158 - fixes #625 Git-Tags are not displayed in Shortlog
158 - fixes #625 Git-Tags are not displayed in Shortlog
159 - fix for issue #602, enforce str when setting mercurial UI object.
159 - fix for issue #602, enforce str when setting mercurial UI object.
160 When this is used together with mercurial internal translation system
160 When this is used together with mercurial internal translation system
161 it can lead to UnicodeDecodeErrors
161 it can lead to UnicodeDecodeErrors
162 - fixes #645 Fix git handler when doing delete remote branch
162 - fixes #645 Fix git handler when doing delete remote branch
163 - implements #649 added two seperate method for author and committer to VCS
163 - implements #649 added two seperate method for author and committer to VCS
164 changeset class switch author for git backed to be the real author not committer
164 changeset class switch author for git backed to be the real author not committer
165 - fix issue #504 RhodeCode is showing different versions of README on
165 - fix issue #504 RhodeCode is showing different versions of README on
166 different summary page loads
166 different summary page loads
167 - implemented #658 Changing username in LDAP-Mode should not be allowed.
167 - implemented #658 Changing username in LDAP-Mode should not be allowed.
168 - fixes #652 switch to generator approach when doing file annotation to prevent
168 - fixes #652 switch to generator approach when doing file annotation to prevent
169 huge memory consumption
169 huge memory consumption
170 - fixes #666 move lockkey path location to cache_dir to ensure this path is
170 - fixes #666 move lockkey path location to cache_dir to ensure this path is
171 always writable for rhodecode server
171 always writable for rhodecode server
172 - many more small fixes and improvements
172 - many more small fixes and improvements
173 - fixed issues with recursive scans on removed repositories that could take
173 - fixed issues with recursive scans on removed repositories that could take
174 long time on instance start
174 long time on instance start
175
175
176 1.4.4 (**2012-10-08**)
176 1.4.4 (**2012-10-08**)
177 ----------------------
177 ----------------------
178
178
179 news
179 news
180 ++++
180 ++++
181
181
182 - obfuscate db password in logs for engine connection string
182 - obfuscate db password in logs for engine connection string
183 - #574 Show pull request status also in shortlog (if any)
183 - #574 Show pull request status also in shortlog (if any)
184 - remember selected tab in my account page
184 - remember selected tab in my account page
185 - Bumped mercurial version to 2.3.2
185 - Bumped mercurial version to 2.3.2
186 - #595 rcextension hook for repository delete
186 - #595 rcextension hook for repository delete
187
187
188 fixes
188 fixes
189 +++++
189 +++++
190
190
191 - Add git version detection to warn users that Git used in system is to
191 - Add git version detection to warn users that Git used in system is to
192 old. Ref #588 - also show git version in system details in settings page
192 old. Ref #588 - also show git version in system details in settings page
193 - fixed files quick filter links
193 - fixed files quick filter links
194 - #590 Add GET flag that controls the way the diff are generated, for pull
194 - #590 Add GET flag that controls the way the diff are generated, for pull
195 requests we want to use non-bundle based diffs, That are far better for
195 requests we want to use non-bundle based diffs, That are far better for
196 doing code reviews. The /compare url still uses bundle compare for full
196 doing code reviews. The /compare url still uses bundle compare for full
197 comparison including the incoming changesets
197 comparison including the incoming changesets
198 - Fixed #585, checks for status of revision where to strict, and made
198 - Fixed #585, checks for status of revision where to strict, and made
199 opening pull request with those revision impossible due to previously set
199 opening pull request with those revision impossible due to previously set
200 status. Checks now are made also for the repository.
200 status. Checks now are made also for the repository.
201 - fixes #591 git backend was causing encoding errors when handling binary
201 - fixes #591 git backend was causing encoding errors when handling binary
202 files - added a test case for VCS lib tests
202 files - added a test case for VCS lib tests
203 - fixed #597 commits in future get negative age.
203 - fixed #597 commits in future get negative age.
204 - fixed #598 API docs methods had wrong members parameter as returned data
204 - fixed #598 API docs methods had wrong members parameter as returned data
205
205
206 1.4.3 (**2012-09-28**)
206 1.4.3 (**2012-09-28**)
207 ----------------------
207 ----------------------
208
208
209 news
209 news
210 ++++
210 ++++
211
211
212 - #558 Added config file to hooks extra data
212 - #558 Added config file to hooks extra data
213 - bumped mercurial version to 2.3.1
213 - bumped mercurial version to 2.3.1
214 - #518 added possibility of specifying multiple patterns for issues
214 - #518 added possibility of specifying multiple patterns for issues
215 - update codemirror to latest version
215 - update codemirror to latest version
216
216
217 fixes
217 fixes
218 +++++
218 +++++
219
219
220 - fixed #570 explicit user group permissions can overwrite owner permissions
220 - fixed #570 explicit user group permissions can overwrite owner permissions
221 - fixed #578 set proper PATH with current Python for Git
221 - fixed #578 set proper PATH with current Python for Git
222 hooks to execute within same Python as RhodeCode
222 hooks to execute within same Python as RhodeCode
223 - fixed issue with Git bare repos that ends with .git in name
223 - fixed issue with Git bare repos that ends with .git in name
224
224
225 1.4.2 (**2012-09-12**)
225 1.4.2 (**2012-09-12**)
226 ----------------------
226 ----------------------
227
227
228 news
228 news
229 ++++
229 ++++
230
230
231 - added option to menu to quick lock/unlock repository for users that have
231 - added option to menu to quick lock/unlock repository for users that have
232 write access to
232 write access to
233 - Implemented permissions for writing to repo
233 - Implemented permissions for writing to repo
234 groups. Now only write access to group allows to create a repostiory
234 groups. Now only write access to group allows to create a repostiory
235 within that group
235 within that group
236 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
236 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
237 - updated translation for zh_CN
237 - updated translation for zh_CN
238
238
239 fixes
239 fixes
240 +++++
240 +++++
241
241
242 - fixed visual permissions check on repos groups inside groups
242 - fixed visual permissions check on repository groups inside groups
243 - fixed issues with non-ascii search terms in search, and indexers
243 - fixed issues with non-ascii search terms in search, and indexers
244 - fixed parsing of page number in GET parameters
244 - fixed parsing of page number in GET parameters
245 - fixed issues with generating pull-request overview for repos with
245 - fixed issues with generating pull-request overview for repos with
246 bookmarks and tags, also preview doesn't loose chosen revision from
246 bookmarks and tags, also preview doesn't loose chosen revision from
247 select dropdown
247 select dropdown
248
248
249 1.4.1 (**2012-09-07**)
249 1.4.1 (**2012-09-07**)
250 ----------------------
250 ----------------------
251
251
252 news
252 news
253 ++++
253 ++++
254
254
255 - always put a comment about code-review status change even if user send
255 - always put a comment about code-review status change even if user send
256 empty data
256 empty data
257 - modified_on column saves repository update and it's going to be used
257 - modified_on column saves repository update and it's going to be used
258 later for light version of main page ref #500
258 later for light version of main page ref #500
259 - pull request notifications send much nicer emails with details about pull
259 - pull request notifications send much nicer emails with details about pull
260 request
260 request
261 - #551 show breadcrumbs in summary view for repositories inside a group
261 - #551 show breadcrumbs in summary view for repositories inside a group
262
262
263 fixes
263 fixes
264 +++++
264 +++++
265
265
266 - fixed migrations of permissions that can lead to inconsistency.
266 - fixed migrations of permissions that can lead to inconsistency.
267 Some users sent feedback that after upgrading from older versions issues
267 Some users sent feedback that after upgrading from older versions issues
268 with updating default permissions occurred. RhodeCode detects that now and
268 with updating default permissions occurred. RhodeCode detects that now and
269 resets default user permission to initial state if there is a need for that.
269 resets default user permission to initial state if there is a need for that.
270 Also forces users to set the default value for new forking permission.
270 Also forces users to set the default value for new forking permission.
271 - #535 improved apache wsgi example configuration in docs
271 - #535 improved apache wsgi example configuration in docs
272 - fixes #550 mercurial repositories comparision failed when origin repo had
272 - fixes #550 mercurial repositories comparision failed when origin repo had
273 additional not-common changesets
273 additional not-common changesets
274 - fixed status of code-review in preview windows of pull request
274 - fixed status of code-review in preview windows of pull request
275 - git forks were not initialized at bare repos
275 - git forks were not initialized at bare repos
276 - fixes #555 fixes issues with comparing non-related repositories
276 - fixes #555 fixes issues with comparing non-related repositories
277 - fixes #557 follower counter always counts up
277 - fixes #557 follower counter always counts up
278 - fixed issue #560 require push ssl checkbox wasn't shown when option was
278 - fixed issue #560 require push ssl checkbox wasn't shown when option was
279 enabled
279 enabled
280 - fixed #559
280 - fixed #559
281 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
281 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
282 if it was a request to url by repository ID
282 if it was a request to url by repository ID
283
283
284 1.4.0 (**2012-09-03**)
284 1.4.0 (**2012-09-03**)
285 ----------------------
285 ----------------------
286
286
287 news
287 news
288 ++++
288 ++++
289
289
290 - new codereview system
290 - new codereview system
291 - email map, allowing users to have multiple email addresses mapped into
291 - email map, allowing users to have multiple email addresses mapped into
292 their accounts
292 their accounts
293 - improved git-hook system. Now all actions for git are logged into journal
293 - improved git-hook system. Now all actions for git are logged into journal
294 including pushed revisions, user and IP address
294 including pushed revisions, user and IP address
295 - changed setup-app into setup-rhodecode and added default options to it.
295 - changed setup-app into setup-rhodecode and added default options to it.
296 - new git repos are created as bare now by default
296 - new git repos are created as bare now by default
297 - #464 added links to groups in permission box
297 - #464 added links to groups in permission box
298 - #465 mentions autocomplete inside comments boxes
298 - #465 mentions autocomplete inside comments boxes
299 - #469 added --update-only option to whoosh to re-index only given list
299 - #469 added --update-only option to whoosh to re-index only given list
300 of repos in index
300 of repos in index
301 - rhodecode-api CLI client
301 - rhodecode-api CLI client
302 - new git http protocol replaced buggy dulwich implementation.
302 - new git http protocol replaced buggy dulwich implementation.
303 Now based on pygrack & gitweb
303 Now based on pygrack & gitweb
304 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
304 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
305 reformated based on user suggestions. Additional rss/atom feeds for user
305 reformated based on user suggestions. Additional rss/atom feeds for user
306 journal
306 journal
307 - various i18n improvements
307 - various i18n improvements
308 - #478 permissions overview for admin in user edit view
308 - #478 permissions overview for admin in user edit view
309 - File view now displays small gravatars off all authors of given file
309 - File view now displays small gravatars off all authors of given file
310 - Implemented landing revisions. Each repository will get landing_rev attribute
310 - Implemented landing revisions. Each repository will get landing_rev attribute
311 that defines 'default' revision/branch for generating readme files
311 that defines 'default' revision/branch for generating readme files
312 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
312 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
313 earliest possible call.
313 earliest possible call.
314 - Import remote svn repositories to mercurial using hgsubversion.
314 - Import remote svn repositories to mercurial using hgsubversion.
315 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
315 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
316 - RhodeCode can use alternative server for generating avatar icons
316 - RhodeCode can use alternative server for generating avatar icons
317 - implemented repositories locking. Pull locks, push unlocks. Also can be done
317 - implemented repositories locking. Pull locks, push unlocks. Also can be done
318 via API calls
318 via API calls
319 - #538 form for permissions can handle multiple users at once
319 - #538 form for permissions can handle multiple users at once
320
320
321 fixes
321 fixes
322 +++++
322 +++++
323
323
324 - improved translations
324 - improved translations
325 - fixes issue #455 Creating an archive generates an exception on Windows
325 - fixes issue #455 Creating an archive generates an exception on Windows
326 - fixes #448 Download ZIP archive keeps file in /tmp open and results
326 - fixes #448 Download ZIP archive keeps file in /tmp open and results
327 in out of disk space
327 in out of disk space
328 - fixes issue #454 Search results under Windows include proceeding
328 - fixes issue #454 Search results under Windows include proceeding
329 backslash
329 backslash
330 - fixed issue #450. Rhodecode no longer will crash when bad revision is
330 - fixed issue #450. Rhodecode no longer will crash when bad revision is
331 present in journal data.
331 present in journal data.
332 - fix for issue #417, git execution was broken on windows for certain
332 - fix for issue #417, git execution was broken on windows for certain
333 commands.
333 commands.
334 - fixed #413. Don't disable .git directory for bare repos on deleting
334 - fixed #413. Don't disable .git directory for bare repos on deleting
335 - fixed issue #459. Changed the way of obtaining logger in reindex task.
335 - fixed issue #459. Changed the way of obtaining logger in reindex task.
336 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
336 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
337 reindexing modified files
337 reindexing modified files
338 - fixed #481 rhodecode emails are sent without Date header
338 - fixed #481 rhodecode emails are sent without Date header
339 - fixed #458 wrong count when no repos are present
339 - fixed #458 wrong count when no repos are present
340 - fixed issue #492 missing `\ No newline at end of file` test at the end of
340 - fixed issue #492 missing `\ No newline at end of file` test at the end of
341 new chunk in html diff
341 new chunk in html diff
342 - full text search now works also for commit messages
342 - full text search now works also for commit messages
343
343
344 1.3.6 (**2012-05-17**)
344 1.3.6 (**2012-05-17**)
345 ----------------------
345 ----------------------
346
346
347 news
347 news
348 ++++
348 ++++
349
349
350 - chinese traditional translation
350 - chinese traditional translation
351 - changed setup-app into setup-rhodecode and added arguments for auto-setup
351 - changed setup-app into setup-rhodecode and added arguments for auto-setup
352 mode that doesn't need user interaction
352 mode that doesn't need user interaction
353
353
354 fixes
354 fixes
355 +++++
355 +++++
356
356
357 - fixed no scm found warning
357 - fixed no scm found warning
358 - fixed __future__ import error on rcextensions
358 - fixed __future__ import error on rcextensions
359 - made simplejson required lib for speedup on JSON encoding
359 - made simplejson required lib for speedup on JSON encoding
360 - fixes #449 bad regex could get more than revisions from parsing history
360 - fixes #449 bad regex could get more than revisions from parsing history
361 - don't clear DB session when CELERY_EAGER is turned ON
361 - don't clear DB session when CELERY_EAGER is turned ON
362
362
363 1.3.5 (**2012-05-10**)
363 1.3.5 (**2012-05-10**)
364 ----------------------
364 ----------------------
365
365
366 news
366 news
367 ++++
367 ++++
368
368
369 - use ext_json for json module
369 - use ext_json for json module
370 - unified annotation view with file source view
370 - unified annotation view with file source view
371 - notification improvements, better inbox + css
371 - notification improvements, better inbox + css
372 - #419 don't strip passwords for login forms, make rhodecode
372 - #419 don't strip passwords for login forms, make rhodecode
373 more compatible with LDAP servers
373 more compatible with LDAP servers
374 - Added HTTP_X_FORWARDED_FOR as another method of extracting
374 - Added HTTP_X_FORWARDED_FOR as another method of extracting
375 IP for pull/push logs. - moved all to base controller
375 IP for pull/push logs. - moved all to base controller
376 - #415: Adding comment to changeset causes reload.
376 - #415: Adding comment to changeset causes reload.
377 Comments are now added via ajax and doesn't reload the page
377 Comments are now added via ajax and doesn't reload the page
378 - #374 LDAP config is discarded when LDAP can't be activated
378 - #374 LDAP config is discarded when LDAP can't be activated
379 - limited push/pull operations are now logged for git in the journal
379 - limited push/pull operations are now logged for git in the journal
380 - bumped mercurial to 2.2.X series
380 - bumped mercurial to 2.2.X series
381 - added support for displaying submodules in file-browser
381 - added support for displaying submodules in file-browser
382 - #421 added bookmarks in changelog view
382 - #421 added bookmarks in changelog view
383
383
384 fixes
384 fixes
385 +++++
385 +++++
386
386
387 - fixed dev-version marker for stable when served from source codes
387 - fixed dev-version marker for stable when served from source codes
388 - fixed missing permission checks on show forks page
388 - fixed missing permission checks on show forks page
389 - #418 cast to unicode fixes in notification objects
389 - #418 cast to unicode fixes in notification objects
390 - #426 fixed mention extracting regex
390 - #426 fixed mention extracting regex
391 - fixed remote-pulling for git remotes remopositories
391 - fixed remote-pulling for git remotes remopositories
392 - fixed #434: Error when accessing files or changesets of a git repository
392 - fixed #434: Error when accessing files or changesets of a git repository
393 with submodules
393 with submodules
394 - fixed issue with empty APIKEYS for users after registration ref. #438
394 - fixed issue with empty APIKEYS for users after registration ref. #438
395 - fixed issue with getting README files from git repositories
395 - fixed issue with getting README files from git repositories
396
396
397 1.3.4 (**2012-03-28**)
397 1.3.4 (**2012-03-28**)
398 ----------------------
398 ----------------------
399
399
400 news
400 news
401 ++++
401 ++++
402
402
403 - Whoosh logging is now controlled by the .ini files logging setup
403 - Whoosh logging is now controlled by the .ini files logging setup
404 - added clone-url into edit form on /settings page
404 - added clone-url into edit form on /settings page
405 - added help text into repo add/edit forms
405 - added help text into repo add/edit forms
406 - created rcextensions module with additional mappings (ref #322) and
406 - created rcextensions module with additional mappings (ref #322) and
407 post push/pull/create repo hooks callbacks
407 post push/pull/create repo hooks callbacks
408 - implemented #377 Users view for his own permissions on account page
408 - implemented #377 Users view for his own permissions on account page
409 - #399 added inheritance of permissions for user group on repos groups
409 - #399 added inheritance of permissions for user group on repository groups
410 - #401 repository group is automatically pre-selected when adding repos
410 - #401 repository group is automatically pre-selected when adding repos
411 inside a repository group
411 inside a repository group
412 - added alternative HTTP 403 response when client failed to authenticate. Helps
412 - added alternative HTTP 403 response when client failed to authenticate. Helps
413 solving issues with Mercurial and LDAP
413 solving issues with Mercurial and LDAP
414 - #402 removed group prefix from repository name when listing repositories
414 - #402 removed group prefix from repository name when listing repositories
415 inside a group
415 inside a group
416 - added gravatars into permission view and permissions autocomplete
416 - added gravatars into permission view and permissions autocomplete
417 - #347 when running multiple RhodeCode instances, properly invalidates cache
417 - #347 when running multiple RhodeCode instances, properly invalidates cache
418 for all registered servers
418 for all registered servers
419
419
420 fixes
420 fixes
421 +++++
421 +++++
422
422
423 - fixed #390 cache invalidation problems on repos inside group
423 - fixed #390 cache invalidation problems on repos inside group
424 - fixed #385 clone by ID url was loosing proxy prefix in URL
424 - fixed #385 clone by ID url was loosing proxy prefix in URL
425 - fixed some unicode problems with waitress
425 - fixed some unicode problems with waitress
426 - fixed issue with escaping < and > in changeset commits
426 - fixed issue with escaping < and > in changeset commits
427 - fixed error occurring during recursive group creation in API
427 - fixed error occurring during recursive group creation in API
428 create_repo function
428 create_repo function
429 - fixed #393 py2.5 fixes for routes url generator
429 - fixed #393 py2.5 fixes for routes url generator
430 - fixed #397 Private repository groups shows up before login
430 - fixed #397 Private repository groups shows up before login
431 - fixed #396 fixed problems with revoking users in nested groups
431 - fixed #396 fixed problems with revoking users in nested groups
432 - fixed mysql unicode issues + specified InnoDB as default engine with
432 - fixed mysql unicode issues + specified InnoDB as default engine with
433 utf8 charset
433 utf8 charset
434 - #406 trim long branch/tag names in changelog to not break UI
434 - #406 trim long branch/tag names in changelog to not break UI
435
435
436 1.3.3 (**2012-03-02**)
436 1.3.3 (**2012-03-02**)
437 ----------------------
437 ----------------------
438
438
439 news
439 news
440 ++++
440 ++++
441
441
442
442
443 fixes
443 fixes
444 +++++
444 +++++
445
445
446 - fixed some python2.5 compatibility issues
446 - fixed some python2.5 compatibility issues
447 - fixed issues with removed repos was accidentally added as groups, after
447 - fixed issues with removed repos was accidentally added as groups, after
448 full rescan of paths
448 full rescan of paths
449 - fixes #376 Cannot edit user (using container auth)
449 - fixes #376 Cannot edit user (using container auth)
450 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
450 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
451 configuration
451 configuration
452 - fixed initial sorting of repos inside repo group
452 - fixed initial sorting of repos inside repo group
453 - fixes issue when user tried to resubmit same permission into user/user_groups
453 - fixes issue when user tried to resubmit same permission into user/user_groups
454 - bumped beaker version that fixes #375 leap error bug
454 - bumped beaker version that fixes #375 leap error bug
455 - fixed raw_changeset for git. It was generated with hg patch headers
455 - fixed raw_changeset for git. It was generated with hg patch headers
456 - fixed vcs issue with last_changeset for filenodes
456 - fixed vcs issue with last_changeset for filenodes
457 - fixed missing commit after hook delete
457 - fixed missing commit after hook delete
458 - fixed #372 issues with git operation detection that caused a security issue
458 - fixed #372 issues with git operation detection that caused a security issue
459 for git repos
459 for git repos
460
460
461 1.3.2 (**2012-02-28**)
461 1.3.2 (**2012-02-28**)
462 ----------------------
462 ----------------------
463
463
464 news
464 news
465 ++++
465 ++++
466
466
467
467
468 fixes
468 fixes
469 +++++
469 +++++
470
470
471 - fixed git protocol issues with repos-groups
471 - fixed git protocol issues with repos-groups
472 - fixed git remote repos validator that prevented from cloning remote git repos
472 - fixed git remote repos validator that prevented from cloning remote git repos
473 - fixes #370 ending slashes fixes for repo and groups
473 - fixes #370 ending slashes fixes for repo and groups
474 - fixes #368 improved git-protocol detection to handle other clients
474 - fixes #368 improved git-protocol detection to handle other clients
475 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
475 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
476 Moved To Root
476 Moved To Root
477 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
477 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
478 - fixed #373 missing cascade drop on user_group_to_perm table
478 - fixed #373 missing cascade drop on user_group_to_perm table
479
479
480 1.3.1 (**2012-02-27**)
480 1.3.1 (**2012-02-27**)
481 ----------------------
481 ----------------------
482
482
483 news
483 news
484 ++++
484 ++++
485
485
486
486
487 fixes
487 fixes
488 +++++
488 +++++
489
489
490 - redirection loop occurs when remember-me wasn't checked during login
490 - redirection loop occurs when remember-me wasn't checked during login
491 - fixes issues with git blob history generation
491 - fixes issues with git blob history generation
492 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
492 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
493
493
494 1.3.0 (**2012-02-26**)
494 1.3.0 (**2012-02-26**)
495 ----------------------
495 ----------------------
496
496
497 news
497 news
498 ++++
498 ++++
499
499
500 - code review, inspired by github code-comments
500 - code review, inspired by github code-comments
501 - #215 rst and markdown README files support
501 - #215 rst and markdown README files support
502 - #252 Container-based and proxy pass-through authentication support
502 - #252 Container-based and proxy pass-through authentication support
503 - #44 branch browser. Filtering of changelog by branches
503 - #44 branch browser. Filtering of changelog by branches
504 - mercurial bookmarks support
504 - mercurial bookmarks support
505 - new hover top menu, optimized to add maximum size for important views
505 - new hover top menu, optimized to add maximum size for important views
506 - configurable clone url template with possibility to specify protocol like
506 - configurable clone url template with possibility to specify protocol like
507 ssh:// or http:// and also manually alter other parts of clone_url.
507 ssh:// or http:// and also manually alter other parts of clone_url.
508 - enabled largefiles extension by default
508 - enabled largefiles extension by default
509 - optimized summary file pages and saved a lot of unused space in them
509 - optimized summary file pages and saved a lot of unused space in them
510 - #239 option to manually mark repository as fork
510 - #239 option to manually mark repository as fork
511 - #320 mapping of commit authors to RhodeCode users
511 - #320 mapping of commit authors to RhodeCode users
512 - #304 hashes are displayed using monospace font
512 - #304 hashes are displayed using monospace font
513 - diff configuration, toggle white lines and context lines
513 - diff configuration, toggle white lines and context lines
514 - #307 configurable diffs, whitespace toggle, increasing context lines
514 - #307 configurable diffs, whitespace toggle, increasing context lines
515 - sorting on branches, tags and bookmarks using YUI datatable
515 - sorting on branches, tags and bookmarks using YUI datatable
516 - improved file filter on files page
516 - improved file filter on files page
517 - implements #330 api method for listing nodes ar particular revision
517 - implements #330 api method for listing nodes ar particular revision
518 - #73 added linking issues in commit messages to chosen issue tracker url
518 - #73 added linking issues in commit messages to chosen issue tracker url
519 based on user defined regular expression
519 based on user defined regular expression
520 - added linking of changesets in commit messages
520 - added linking of changesets in commit messages
521 - new compact changelog with expandable commit messages
521 - new compact changelog with expandable commit messages
522 - firstname and lastname are optional in user creation
522 - firstname and lastname are optional in user creation
523 - #348 added post-create repository hook
523 - #348 added post-create repository hook
524 - #212 global encoding settings is now configurable from .ini files
524 - #212 global encoding settings is now configurable from .ini files
525 - #227 added repository groups permissions
525 - #227 added repository groups permissions
526 - markdown gets codehilite extensions
526 - markdown gets codehilite extensions
527 - new API methods, delete_repositories, grante/revoke permissions for groups
527 - new API methods, delete_repositories, grante/revoke permissions for groups
528 and repos
528 and repos
529
529
530
530
531 fixes
531 fixes
532 +++++
532 +++++
533
533
534 - rewrote dbsession management for atomic operations, and better error handling
534 - rewrote dbsession management for atomic operations, and better error handling
535 - fixed sorting of repo tables
535 - fixed sorting of repo tables
536 - #326 escape of special html entities in diffs
536 - #326 escape of special html entities in diffs
537 - normalized user_name => username in api attributes
537 - normalized user_name => username in api attributes
538 - fixes #298 ldap created users with mixed case emails created conflicts
538 - fixes #298 ldap created users with mixed case emails created conflicts
539 on saving a form
539 on saving a form
540 - fixes issue when owner of a repo couldn't revoke permissions for users
540 - fixes issue when owner of a repo couldn't revoke permissions for users
541 and groups
541 and groups
542 - fixes #271 rare JSON serialization problem with statistics
542 - fixes #271 rare JSON serialization problem with statistics
543 - fixes #337 missing validation check for conflicting names of a group with a
543 - fixes #337 missing validation check for conflicting names of a group with a
544 repository group
544 repository group
545 - #340 fixed session problem for mysql and celery tasks
545 - #340 fixed session problem for mysql and celery tasks
546 - fixed #331 RhodeCode mangles repository names if the a repository group
546 - fixed #331 RhodeCode mangles repository names if the a repository group
547 contains the "full path" to the repositories
547 contains the "full path" to the repositories
548 - #355 RhodeCode doesn't store encrypted LDAP passwords
548 - #355 RhodeCode doesn't store encrypted LDAP passwords
549
549
550 1.2.5 (**2012-01-28**)
550 1.2.5 (**2012-01-28**)
551 ----------------------
551 ----------------------
552
552
553 news
553 news
554 ++++
554 ++++
555
555
556 fixes
556 fixes
557 +++++
557 +++++
558
558
559 - #340 Celery complains about MySQL server gone away, added session cleanup
559 - #340 Celery complains about MySQL server gone away, added session cleanup
560 for celery tasks
560 for celery tasks
561 - #341 "scanning for repositories in None" log message during Rescan was missing
561 - #341 "scanning for repositories in None" log message during Rescan was missing
562 a parameter
562 a parameter
563 - fixed creating archives with subrepos. Some hooks were triggered during that
563 - fixed creating archives with subrepos. Some hooks were triggered during that
564 operation leading to crash.
564 operation leading to crash.
565 - fixed missing email in account page.
565 - fixed missing email in account page.
566 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
566 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
567 forking on windows impossible
567 forking on windows impossible
568
568
569 1.2.4 (**2012-01-19**)
569 1.2.4 (**2012-01-19**)
570 ----------------------
570 ----------------------
571
571
572 news
572 news
573 ++++
573 ++++
574
574
575 - RhodeCode is bundled with mercurial series 2.0.X by default, with
575 - RhodeCode is bundled with mercurial series 2.0.X by default, with
576 full support to largefiles extension. Enabled by default in new installations
576 full support to largefiles extension. Enabled by default in new installations
577 - #329 Ability to Add/Remove Groups to/from a Repository via AP
577 - #329 Ability to Add/Remove Groups to/from a Repository via AP
578 - added requires.txt file with requirements
578 - added requires.txt file with requirements
579
579
580 fixes
580 fixes
581 +++++
581 +++++
582
582
583 - fixes db session issues with celery when emailing admins
583 - fixes db session issues with celery when emailing admins
584 - #331 RhodeCode mangles repository names if the a repository group
584 - #331 RhodeCode mangles repository names if the a repository group
585 contains the "full path" to the repositories
585 contains the "full path" to the repositories
586 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
586 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
587 - DB session cleanup after hg protocol operations, fixes issues with
587 - DB session cleanup after hg protocol operations, fixes issues with
588 `mysql has gone away` errors
588 `mysql has gone away` errors
589 - #333 doc fixes for get_repo api function
589 - #333 doc fixes for get_repo api function
590 - #271 rare JSON serialization problem with statistics enabled
590 - #271 rare JSON serialization problem with statistics enabled
591 - #337 Fixes issues with validation of repository name conflicting with
591 - #337 Fixes issues with validation of repository name conflicting with
592 a group name. A proper message is now displayed.
592 a group name. A proper message is now displayed.
593 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
593 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
594 doesn't work
594 doesn't work
595 - #316 fixes issues with web description in hgrc files
595 - #316 fixes issues with web description in hgrc files
596
596
597 1.2.3 (**2011-11-02**)
597 1.2.3 (**2011-11-02**)
598 ----------------------
598 ----------------------
599
599
600 news
600 news
601 ++++
601 ++++
602
602
603 - added option to manage repos group for non admin users
603 - added option to manage repository group for non admin users
604 - added following API methods for get_users, create_user, get_users_groups,
604 - added following API methods for get_users, create_user, get_users_groups,
605 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
605 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
606 get_repo, create_repo, add_user_to_repo
606 get_repo, create_repo, add_user_to_repo
607 - implements #237 added password confirmation for my account
607 - implements #237 added password confirmation for my account
608 and admin edit user.
608 and admin edit user.
609 - implements #291 email notification for global events are now sent to all
609 - implements #291 email notification for global events are now sent to all
610 administrator users, and global config email.
610 administrator users, and global config email.
611
611
612 fixes
612 fixes
613 +++++
613 +++++
614
614
615 - added option for passing auth method for smtp mailer
615 - added option for passing auth method for smtp mailer
616 - #276 issue with adding a single user with id>10 to usergroups
616 - #276 issue with adding a single user with id>10 to usergroups
617 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
617 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
618 - #288 fixes managing of repos in a group for non admin user
618 - #288 fixes managing of repos in a group for non admin user
619
619
620 1.2.2 (**2011-10-17**)
620 1.2.2 (**2011-10-17**)
621 ----------------------
621 ----------------------
622
622
623 news
623 news
624 ++++
624 ++++
625
625
626 - #226 repo groups are available by path instead of numerical id
626 - #226 repo groups are available by path instead of numerical id
627
627
628 fixes
628 fixes
629 +++++
629 +++++
630
630
631 - #259 Groups with the same name but with different parent group
631 - #259 Groups with the same name but with different parent group
632 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
632 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
633 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
633 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
634 - #265 ldap save fails sometimes on converting attributes to booleans,
634 - #265 ldap save fails sometimes on converting attributes to booleans,
635 added getter and setter into model that will prevent from this on db model level
635 added getter and setter into model that will prevent from this on db model level
636 - fixed problems with timestamps issues #251 and #213
636 - fixed problems with timestamps issues #251 and #213
637 - fixes #266 RhodeCode allows to create repo with the same name and in
637 - fixes #266 RhodeCode allows to create repo with the same name and in
638 the same parent as group
638 the same parent as group
639 - fixes #245 Rescan of the repositories on Windows
639 - fixes #245 Rescan of the repositories on Windows
640 - fixes #248 cannot edit repos inside a group on windows
640 - fixes #248 cannot edit repos inside a group on windows
641 - fixes #219 forking problems on windows
641 - fixes #219 forking problems on windows
642
642
643 1.2.1 (**2011-10-08**)
643 1.2.1 (**2011-10-08**)
644 ----------------------
644 ----------------------
645
645
646 news
646 news
647 ++++
647 ++++
648
648
649
649
650 fixes
650 fixes
651 +++++
651 +++++
652
652
653 - fixed problems with basic auth and push problems
653 - fixed problems with basic auth and push problems
654 - gui fixes
654 - gui fixes
655 - fixed logger
655 - fixed logger
656
656
657 1.2.0 (**2011-10-07**)
657 1.2.0 (**2011-10-07**)
658 ----------------------
658 ----------------------
659
659
660 news
660 news
661 ++++
661 ++++
662
662
663 - implemented #47 repository groups
663 - implemented #47 repository groups
664 - implemented #89 Can setup google analytics code from settings menu
664 - implemented #89 Can setup google analytics code from settings menu
665 - implemented #91 added nicer looking archive urls with more download options
665 - implemented #91 added nicer looking archive urls with more download options
666 like tags, branches
666 like tags, branches
667 - implemented #44 into file browsing, and added follow branch option
667 - implemented #44 into file browsing, and added follow branch option
668 - implemented #84 downloads can be enabled/disabled for each repository
668 - implemented #84 downloads can be enabled/disabled for each repository
669 - anonymous repository can be cloned without having to pass default:default
669 - anonymous repository can be cloned without having to pass default:default
670 into clone url
670 into clone url
671 - fixed #90 whoosh indexer can index chooses repositories passed in command
671 - fixed #90 whoosh indexer can index chooses repositories passed in command
672 line
672 line
673 - extended journal with day aggregates and paging
673 - extended journal with day aggregates and paging
674 - implemented #107 source code lines highlight ranges
674 - implemented #107 source code lines highlight ranges
675 - implemented #93 customizable changelog on combined revision ranges -
675 - implemented #93 customizable changelog on combined revision ranges -
676 equivalent of githubs compare view
676 equivalent of githubs compare view
677 - implemented #108 extended and more powerful LDAP configuration
677 - implemented #108 extended and more powerful LDAP configuration
678 - implemented #56 user groups
678 - implemented #56 user groups
679 - major code rewrites optimized codes for speed and memory usage
679 - major code rewrites optimized codes for speed and memory usage
680 - raw and diff downloads are now in git format
680 - raw and diff downloads are now in git format
681 - setup command checks for write access to given path
681 - setup command checks for write access to given path
682 - fixed many issues with international characters and unicode. It uses utf8
682 - fixed many issues with international characters and unicode. It uses utf8
683 decode with replace to provide less errors even with non utf8 encoded strings
683 decode with replace to provide less errors even with non utf8 encoded strings
684 - #125 added API KEY access to feeds
684 - #125 added API KEY access to feeds
685 - #109 Repository can be created from external Mercurial link (aka. remote
685 - #109 Repository can be created from external Mercurial link (aka. remote
686 repository, and manually updated (via pull) from admin panel
686 repository, and manually updated (via pull) from admin panel
687 - beta git support - push/pull server + basic view for git repos
687 - beta git support - push/pull server + basic view for git repos
688 - added followers page and forks page
688 - added followers page and forks page
689 - server side file creation (with binary file upload interface)
689 - server side file creation (with binary file upload interface)
690 and edition with commits powered by codemirror
690 and edition with commits powered by codemirror
691 - #111 file browser file finder, quick lookup files on whole file tree
691 - #111 file browser file finder, quick lookup files on whole file tree
692 - added quick login sliding menu into main page
692 - added quick login sliding menu into main page
693 - changelog uses lazy loading of affected files details, in some scenarios
693 - changelog uses lazy loading of affected files details, in some scenarios
694 this can improve speed of changelog page dramatically especially for
694 this can improve speed of changelog page dramatically especially for
695 larger repositories.
695 larger repositories.
696 - implements #214 added support for downloading subrepos in download menu.
696 - implements #214 added support for downloading subrepos in download menu.
697 - Added basic API for direct operations on rhodecode via JSON
697 - Added basic API for direct operations on rhodecode via JSON
698 - Implemented advanced hook management
698 - Implemented advanced hook management
699
699
700 fixes
700 fixes
701 +++++
701 +++++
702
702
703 - fixed file browser bug, when switching into given form revision the url was
703 - fixed file browser bug, when switching into given form revision the url was
704 not changing
704 not changing
705 - fixed propagation to error controller on simplehg and simplegit middlewares
705 - fixed propagation to error controller on simplehg and simplegit middlewares
706 - fixed error when trying to make a download on empty repository
706 - fixed error when trying to make a download on empty repository
707 - fixed problem with '[' chars in commit messages in journal
707 - fixed problem with '[' chars in commit messages in journal
708 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
708 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
709 - journal fork fixes
709 - journal fork fixes
710 - removed issue with space inside renamed repository after deletion
710 - removed issue with space inside renamed repository after deletion
711 - fixed strange issue on formencode imports
711 - fixed strange issue on formencode imports
712 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
712 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
713 - #150 fixes for errors on repositories mapped in db but corrupted in
713 - #150 fixes for errors on repositories mapped in db but corrupted in
714 filesystem
714 filesystem
715 - fixed problem with ascendant characters in realm #181
715 - fixed problem with ascendant characters in realm #181
716 - fixed problem with sqlite file based database connection pool
716 - fixed problem with sqlite file based database connection pool
717 - whoosh indexer and code stats share the same dynamic extensions map
717 - whoosh indexer and code stats share the same dynamic extensions map
718 - fixes #188 - relationship delete of repo_to_perm entry on user removal
718 - fixes #188 - relationship delete of repo_to_perm entry on user removal
719 - fixes issue #189 Trending source files shows "show more" when no more exist
719 - fixes issue #189 Trending source files shows "show more" when no more exist
720 - fixes issue #197 Relative paths for pidlocks
720 - fixes issue #197 Relative paths for pidlocks
721 - fixes issue #198 password will require only 3 chars now for login form
721 - fixes issue #198 password will require only 3 chars now for login form
722 - fixes issue #199 wrong redirection for non admin users after creating a repository
722 - fixes issue #199 wrong redirection for non admin users after creating a repository
723 - fixes issues #202, bad db constraint made impossible to attach same group
723 - fixes issues #202, bad db constraint made impossible to attach same group
724 more than one time. Affects only mysql/postgres
724 more than one time. Affects only mysql/postgres
725 - fixes #218 os.kill patch for windows was missing sig param
725 - fixes #218 os.kill patch for windows was missing sig param
726 - improved rendering of dag (they are not trimmed anymore when number of
726 - improved rendering of dag (they are not trimmed anymore when number of
727 heads exceeds 5)
727 heads exceeds 5)
728
728
729 1.1.8 (**2011-04-12**)
729 1.1.8 (**2011-04-12**)
730 ----------------------
730 ----------------------
731
731
732 news
732 news
733 ++++
733 ++++
734
734
735 - improved windows support
735 - improved windows support
736
736
737 fixes
737 fixes
738 +++++
738 +++++
739
739
740 - fixed #140 freeze of python dateutil library, since new version is python2.x
740 - fixed #140 freeze of python dateutil library, since new version is python2.x
741 incompatible
741 incompatible
742 - setup-app will check for write permission in given path
742 - setup-app will check for write permission in given path
743 - cleaned up license info issue #149
743 - cleaned up license info issue #149
744 - fixes for issues #137,#116 and problems with unicode and accented characters.
744 - fixes for issues #137,#116 and problems with unicode and accented characters.
745 - fixes crashes on gravatar, when passed in email as unicode
745 - fixes crashes on gravatar, when passed in email as unicode
746 - fixed tooltip flickering problems
746 - fixed tooltip flickering problems
747 - fixed came_from redirection on windows
747 - fixed came_from redirection on windows
748 - fixed logging modules, and sql formatters
748 - fixed logging modules, and sql formatters
749 - windows fixes for os.kill issue #133
749 - windows fixes for os.kill issue #133
750 - fixes path splitting for windows issues #148
750 - fixes path splitting for windows issues #148
751 - fixed issue #143 wrong import on migration to 1.1.X
751 - fixed issue #143 wrong import on migration to 1.1.X
752 - fixed problems with displaying binary files, thanks to Thomas Waldmann
752 - fixed problems with displaying binary files, thanks to Thomas Waldmann
753 - removed name from archive files since it's breaking ui for long repo names
753 - removed name from archive files since it's breaking ui for long repo names
754 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
754 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
755 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
755 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
756 Thomas Waldmann
756 Thomas Waldmann
757 - fixed issue #166 summary pager was skipping 10 revisions on second page
757 - fixed issue #166 summary pager was skipping 10 revisions on second page
758
758
759
759
760 1.1.7 (**2011-03-23**)
760 1.1.7 (**2011-03-23**)
761 ----------------------
761 ----------------------
762
762
763 news
763 news
764 ++++
764 ++++
765
765
766 fixes
766 fixes
767 +++++
767 +++++
768
768
769 - fixed (again) #136 installation support for FreeBSD
769 - fixed (again) #136 installation support for FreeBSD
770
770
771
771
772 1.1.6 (**2011-03-21**)
772 1.1.6 (**2011-03-21**)
773 ----------------------
773 ----------------------
774
774
775 news
775 news
776 ++++
776 ++++
777
777
778 fixes
778 fixes
779 +++++
779 +++++
780
780
781 - fixed #136 installation support for FreeBSD
781 - fixed #136 installation support for FreeBSD
782 - RhodeCode will check for python version during installation
782 - RhodeCode will check for python version during installation
783
783
784 1.1.5 (**2011-03-17**)
784 1.1.5 (**2011-03-17**)
785 ----------------------
785 ----------------------
786
786
787 news
787 news
788 ++++
788 ++++
789
789
790 - basic windows support, by exchanging pybcrypt into sha256 for windows only
790 - basic windows support, by exchanging pybcrypt into sha256 for windows only
791 highly inspired by idea of mantis406
791 highly inspired by idea of mantis406
792
792
793 fixes
793 fixes
794 +++++
794 +++++
795
795
796 - fixed sorting by author in main page
796 - fixed sorting by author in main page
797 - fixed crashes with diffs on binary files
797 - fixed crashes with diffs on binary files
798 - fixed #131 problem with boolean values for LDAP
798 - fixed #131 problem with boolean values for LDAP
799 - fixed #122 mysql problems thanks to striker69
799 - fixed #122 mysql problems thanks to striker69
800 - fixed problem with errors on calling raw/raw_files/annotate functions
800 - fixed problem with errors on calling raw/raw_files/annotate functions
801 with unknown revisions
801 with unknown revisions
802 - fixed returned rawfiles attachment names with international character
802 - fixed returned rawfiles attachment names with international character
803 - cleaned out docs, big thanks to Jason Harris
803 - cleaned out docs, big thanks to Jason Harris
804
804
805 1.1.4 (**2011-02-19**)
805 1.1.4 (**2011-02-19**)
806 ----------------------
806 ----------------------
807
807
808 news
808 news
809 ++++
809 ++++
810
810
811 fixes
811 fixes
812 +++++
812 +++++
813
813
814 - fixed formencode import problem on settings page, that caused server crash
814 - fixed formencode import problem on settings page, that caused server crash
815 when that page was accessed as first after server start
815 when that page was accessed as first after server start
816 - journal fixes
816 - journal fixes
817 - fixed option to access repository just by entering http://server/<repo_name>
817 - fixed option to access repository just by entering http://server/<repo_name>
818
818
819 1.1.3 (**2011-02-16**)
819 1.1.3 (**2011-02-16**)
820 ----------------------
820 ----------------------
821
821
822 news
822 news
823 ++++
823 ++++
824
824
825 - implemented #102 allowing the '.' character in username
825 - implemented #102 allowing the '.' character in username
826 - added option to access repository just by entering http://server/<repo_name>
826 - added option to access repository just by entering http://server/<repo_name>
827 - celery task ignores result for better performance
827 - celery task ignores result for better performance
828
828
829 fixes
829 fixes
830 +++++
830 +++++
831
831
832 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
832 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
833 apollo13 and Johan Walles
833 apollo13 and Johan Walles
834 - small fixes in journal
834 - small fixes in journal
835 - fixed problems with getting setting for celery from .ini files
835 - fixed problems with getting setting for celery from .ini files
836 - registration, password reset and login boxes share the same title as main
836 - registration, password reset and login boxes share the same title as main
837 application now
837 application now
838 - fixed #113: to high permissions to fork repository
838 - fixed #113: to high permissions to fork repository
839 - fixed problem with '[' chars in commit messages in journal
839 - fixed problem with '[' chars in commit messages in journal
840 - removed issue with space inside renamed repository after deletion
840 - removed issue with space inside renamed repository after deletion
841 - db transaction fixes when filesystem repository creation failed
841 - db transaction fixes when filesystem repository creation failed
842 - fixed #106 relation issues on databases different than sqlite
842 - fixed #106 relation issues on databases different than sqlite
843 - fixed static files paths links to use of url() method
843 - fixed static files paths links to use of url() method
844
844
845 1.1.2 (**2011-01-12**)
845 1.1.2 (**2011-01-12**)
846 ----------------------
846 ----------------------
847
847
848 news
848 news
849 ++++
849 ++++
850
850
851
851
852 fixes
852 fixes
853 +++++
853 +++++
854
854
855 - fixes #98 protection against float division of percentage stats
855 - fixes #98 protection against float division of percentage stats
856 - fixed graph bug
856 - fixed graph bug
857 - forced webhelpers version since it was making troubles during installation
857 - forced webhelpers version since it was making troubles during installation
858
858
859 1.1.1 (**2011-01-06**)
859 1.1.1 (**2011-01-06**)
860 ----------------------
860 ----------------------
861
861
862 news
862 news
863 ++++
863 ++++
864
864
865 - added force https option into ini files for easier https usage (no need to
865 - added force https option into ini files for easier https usage (no need to
866 set server headers with this options)
866 set server headers with this options)
867 - small css updates
867 - small css updates
868
868
869 fixes
869 fixes
870 +++++
870 +++++
871
871
872 - fixed #96 redirect loop on files view on repositories without changesets
872 - fixed #96 redirect loop on files view on repositories without changesets
873 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
873 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
874 and server crashed with errors
874 and server crashed with errors
875 - fixed large tooltips problems on main page
875 - fixed large tooltips problems on main page
876 - fixed #92 whoosh indexer is more error proof
876 - fixed #92 whoosh indexer is more error proof
877
877
878 1.1.0 (**2010-12-18**)
878 1.1.0 (**2010-12-18**)
879 ----------------------
879 ----------------------
880
880
881 news
881 news
882 ++++
882 ++++
883
883
884 - rewrite of internals for vcs >=0.1.10
884 - rewrite of internals for vcs >=0.1.10
885 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
885 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
886 with older clients
886 with older clients
887 - anonymous access, authentication via ldap
887 - anonymous access, authentication via ldap
888 - performance upgrade for cached repos list - each repository has its own
888 - performance upgrade for cached repos list - each repository has its own
889 cache that's invalidated when needed.
889 cache that's invalidated when needed.
890 - performance upgrades on repositories with large amount of commits (20K+)
890 - performance upgrades on repositories with large amount of commits (20K+)
891 - main page quick filter for filtering repositories
891 - main page quick filter for filtering repositories
892 - user dashboards with ability to follow chosen repositories actions
892 - user dashboards with ability to follow chosen repositories actions
893 - sends email to admin on new user registration
893 - sends email to admin on new user registration
894 - added cache/statistics reset options into repository settings
894 - added cache/statistics reset options into repository settings
895 - more detailed action logger (based on hooks) with pushed changesets lists
895 - more detailed action logger (based on hooks) with pushed changesets lists
896 and options to disable those hooks from admin panel
896 and options to disable those hooks from admin panel
897 - introduced new enhanced changelog for merges that shows more accurate results
897 - introduced new enhanced changelog for merges that shows more accurate results
898 - new improved and faster code stats (based on pygments lexers mapping tables,
898 - new improved and faster code stats (based on pygments lexers mapping tables,
899 showing up to 10 trending sources for each repository. Additionally stats
899 showing up to 10 trending sources for each repository. Additionally stats
900 can be disabled in repository settings.
900 can be disabled in repository settings.
901 - gui optimizations, fixed application width to 1024px
901 - gui optimizations, fixed application width to 1024px
902 - added cut off (for large files/changesets) limit into config files
902 - added cut off (for large files/changesets) limit into config files
903 - whoosh, celeryd, upgrade moved to paster command
903 - whoosh, celeryd, upgrade moved to paster command
904 - other than sqlite database backends can be used
904 - other than sqlite database backends can be used
905
905
906 fixes
906 fixes
907 +++++
907 +++++
908
908
909 - fixes #61 forked repo was showing only after cache expired
909 - fixes #61 forked repo was showing only after cache expired
910 - fixes #76 no confirmation on user deletes
910 - fixes #76 no confirmation on user deletes
911 - fixes #66 Name field misspelled
911 - fixes #66 Name field misspelled
912 - fixes #72 block user removal when he owns repositories
912 - fixes #72 block user removal when he owns repositories
913 - fixes #69 added password confirmation fields
913 - fixes #69 added password confirmation fields
914 - fixes #87 RhodeCode crashes occasionally on updating repository owner
914 - fixes #87 RhodeCode crashes occasionally on updating repository owner
915 - fixes #82 broken annotations on files with more than 1 blank line at the end
915 - fixes #82 broken annotations on files with more than 1 blank line at the end
916 - a lot of fixes and tweaks for file browser
916 - a lot of fixes and tweaks for file browser
917 - fixed detached session issues
917 - fixed detached session issues
918 - fixed when user had no repos he would see all repos listed in my account
918 - fixed when user had no repos he would see all repos listed in my account
919 - fixed ui() instance bug when global hgrc settings was loaded for server
919 - fixed ui() instance bug when global hgrc settings was loaded for server
920 instance and all hgrc options were merged with our db ui() object
920 instance and all hgrc options were merged with our db ui() object
921 - numerous small bugfixes
921 - numerous small bugfixes
922
922
923 (special thanks for TkSoh for detailed feedback)
923 (special thanks for TkSoh for detailed feedback)
924
924
925
925
926 1.0.2 (**2010-11-12**)
926 1.0.2 (**2010-11-12**)
927 ----------------------
927 ----------------------
928
928
929 news
929 news
930 ++++
930 ++++
931
931
932 - tested under python2.7
932 - tested under python2.7
933 - bumped sqlalchemy and celery versions
933 - bumped sqlalchemy and celery versions
934
934
935 fixes
935 fixes
936 +++++
936 +++++
937
937
938 - fixed #59 missing graph.js
938 - fixed #59 missing graph.js
939 - fixed repo_size crash when repository had broken symlinks
939 - fixed repo_size crash when repository had broken symlinks
940 - fixed python2.5 crashes.
940 - fixed python2.5 crashes.
941
941
942
942
943 1.0.1 (**2010-11-10**)
943 1.0.1 (**2010-11-10**)
944 ----------------------
944 ----------------------
945
945
946 news
946 news
947 ++++
947 ++++
948
948
949 - small css updated
949 - small css updated
950
950
951 fixes
951 fixes
952 +++++
952 +++++
953
953
954 - fixed #53 python2.5 incompatible enumerate calls
954 - fixed #53 python2.5 incompatible enumerate calls
955 - fixed #52 disable mercurial extension for web
955 - fixed #52 disable mercurial extension for web
956 - fixed #51 deleting repositories don't delete it's dependent objects
956 - fixed #51 deleting repositories don't delete it's dependent objects
957
957
958
958
959 1.0.0 (**2010-11-02**)
959 1.0.0 (**2010-11-02**)
960 ----------------------
960 ----------------------
961
961
962 - security bugfix simplehg wasn't checking for permissions on commands
962 - security bugfix simplehg wasn't checking for permissions on commands
963 other than pull or push.
963 other than pull or push.
964 - fixed doubled messages after push or pull in admin journal
964 - fixed doubled messages after push or pull in admin journal
965 - templating and css corrections, fixed repo switcher on chrome, updated titles
965 - templating and css corrections, fixed repo switcher on chrome, updated titles
966 - admin menu accessible from options menu on repository view
966 - admin menu accessible from options menu on repository view
967 - permissions cached queries
967 - permissions cached queries
968
968
969 1.0.0rc4 (**2010-10-12**)
969 1.0.0rc4 (**2010-10-12**)
970 --------------------------
970 --------------------------
971
971
972 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
972 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
973 - removed cache_manager settings from sqlalchemy meta
973 - removed cache_manager settings from sqlalchemy meta
974 - added sqlalchemy cache settings to ini files
974 - added sqlalchemy cache settings to ini files
975 - validated password length and added second try of failure on paster setup-app
975 - validated password length and added second try of failure on paster setup-app
976 - fixed setup database destroy prompt even when there was no db
976 - fixed setup database destroy prompt even when there was no db
977
977
978
978
979 1.0.0rc3 (**2010-10-11**)
979 1.0.0rc3 (**2010-10-11**)
980 -------------------------
980 -------------------------
981
981
982 - fixed i18n during installation.
982 - fixed i18n during installation.
983
983
984 1.0.0rc2 (**2010-10-11**)
984 1.0.0rc2 (**2010-10-11**)
985 -------------------------
985 -------------------------
986
986
987 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
987 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
988 occure. After vcs is fixed it'll be put back again.
988 occure. After vcs is fixed it'll be put back again.
989 - templating/css rewrites, optimized css.
989 - templating/css rewrites, optimized css.
@@ -1,661 +1,661 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 if match_dict.get('f_path'):
35 if match_dict.get('f_path'):
36 #fix for multiple initial slashes that causes errors
36 #fix for multiple initial slashes that causes errors
37 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
37 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
38
38
39 try:
39 try:
40 by_id = repo_name.split('_')
40 by_id = repo_name.split('_')
41 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
41 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
42 repo_name = Repository.get(by_id[1]).repo_name
42 repo_name = Repository.get(by_id[1]).repo_name
43 match_dict['repo_name'] = repo_name
43 match_dict['repo_name'] = repo_name
44 except Exception:
44 except Exception:
45 pass
45 pass
46
46
47 return is_valid_repo(repo_name, config['base_path'])
47 return is_valid_repo(repo_name, config['base_path'])
48
48
49 def check_group(environ, match_dict):
49 def check_group(environ, match_dict):
50 """
50 """
51 check for valid repository group for proper 404 handling
51 check for valid repository group for proper 404 handling
52
52
53 :param environ:
53 :param environ:
54 :param match_dict:
54 :param match_dict:
55 """
55 """
56 repos_group_name = match_dict.get('group_name')
56 repos_group_name = match_dict.get('group_name')
57 return is_valid_repos_group(repos_group_name, config['base_path'])
57 return is_valid_repos_group(repos_group_name, config['base_path'])
58
58
59 def check_group_skip_path(environ, match_dict):
59 def check_group_skip_path(environ, match_dict):
60 """
60 """
61 check for valid repository group for proper 404 handling, but skips
61 check for valid repository group for proper 404 handling, but skips
62 verification of existing path
62 verification of existing path
63
63
64 :param environ:
64 :param environ:
65 :param match_dict:
65 :param match_dict:
66 """
66 """
67 repos_group_name = match_dict.get('group_name')
67 repos_group_name = match_dict.get('group_name')
68 return is_valid_repos_group(repos_group_name, config['base_path'],
68 return is_valid_repos_group(repos_group_name, config['base_path'],
69 skip_path_check=True)
69 skip_path_check=True)
70
70
71 def check_int(environ, match_dict):
71 def check_int(environ, match_dict):
72 return match_dict.get('id').isdigit()
72 return match_dict.get('id').isdigit()
73
73
74 # The ErrorController route (handles 404/500 error pages); it should
74 # The ErrorController route (handles 404/500 error pages); it should
75 # likely stay at the top, ensuring it can always be resolved
75 # likely stay at the top, ensuring it can always be resolved
76 rmap.connect('/error/{action}', controller='error')
76 rmap.connect('/error/{action}', controller='error')
77 rmap.connect('/error/{action}/{id}', controller='error')
77 rmap.connect('/error/{action}/{id}', controller='error')
78
78
79 #==========================================================================
79 #==========================================================================
80 # CUSTOM ROUTES HERE
80 # CUSTOM ROUTES HERE
81 #==========================================================================
81 #==========================================================================
82
82
83 #MAIN PAGE
83 #MAIN PAGE
84 rmap.connect('home', '/', controller='home', action='index')
84 rmap.connect('home', '/', controller='home', action='index')
85 rmap.connect('repo_switcher', '/repos', controller='home',
85 rmap.connect('repo_switcher', '/repos', controller='home',
86 action='repo_switcher')
86 action='repo_switcher')
87 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
87 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
88 controller='home', action='branch_tag_switcher')
88 controller='home', action='branch_tag_switcher')
89 rmap.connect('bugtracker',
89 rmap.connect('bugtracker',
90 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
90 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
91 _static=True)
91 _static=True)
92 rmap.connect('rst_help',
92 rmap.connect('rst_help',
93 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
93 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
94 _static=True)
94 _static=True)
95 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
95 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
96
96
97 #ADMIN REPOSITORY REST ROUTES
97 #ADMIN REPOSITORY REST ROUTES
98 with rmap.submapper(path_prefix=ADMIN_PREFIX,
98 with rmap.submapper(path_prefix=ADMIN_PREFIX,
99 controller='admin/repos') as m:
99 controller='admin/repos') as m:
100 m.connect("repos", "/repos",
100 m.connect("repos", "/repos",
101 action="create", conditions=dict(method=["POST"]))
101 action="create", conditions=dict(method=["POST"]))
102 m.connect("repos", "/repos",
102 m.connect("repos", "/repos",
103 action="index", conditions=dict(method=["GET"]))
103 action="index", conditions=dict(method=["GET"]))
104 m.connect("formatted_repos", "/repos.{format}",
104 m.connect("formatted_repos", "/repos.{format}",
105 action="index",
105 action="index",
106 conditions=dict(method=["GET"]))
106 conditions=dict(method=["GET"]))
107 m.connect("new_repo", "/repos/new",
107 m.connect("new_repo", "/repos/new",
108 action="new", conditions=dict(method=["GET"]))
108 action="new", conditions=dict(method=["GET"]))
109 #TODO: refactor the name
109 #TODO: refactor the name
110 m.connect("admin_settings_create_repository", "/create_repository",
110 m.connect("admin_settings_create_repository", "/create_repository",
111 action="create_repository", conditions=dict(method=["GET"]))
111 action="create_repository", conditions=dict(method=["GET"]))
112 m.connect("formatted_new_repo", "/repos/new.{format}",
112 m.connect("formatted_new_repo", "/repos/new.{format}",
113 action="new", conditions=dict(method=["GET"]))
113 action="new", conditions=dict(method=["GET"]))
114 m.connect("/repos/{repo_name:.*?}",
114 m.connect("/repos/{repo_name:.*?}",
115 action="update", conditions=dict(method=["PUT"],
115 action="update", conditions=dict(method=["PUT"],
116 function=check_repo))
116 function=check_repo))
117 m.connect("/repos/{repo_name:.*?}",
117 m.connect("/repos/{repo_name:.*?}",
118 action="delete", conditions=dict(method=["DELETE"],
118 action="delete", conditions=dict(method=["DELETE"],
119 function=check_repo))
119 function=check_repo))
120 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
120 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
121 action="edit", conditions=dict(method=["GET"],
121 action="edit", conditions=dict(method=["GET"],
122 function=check_repo))
122 function=check_repo))
123 m.connect("repo", "/repos/{repo_name:.*?}",
123 m.connect("repo", "/repos/{repo_name:.*?}",
124 action="show", conditions=dict(method=["GET"],
124 action="show", conditions=dict(method=["GET"],
125 function=check_repo))
125 function=check_repo))
126 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
126 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
127 action="show", conditions=dict(method=["GET"],
127 action="show", conditions=dict(method=["GET"],
128 function=check_repo))
128 function=check_repo))
129 #add repo perm member
129 #add repo perm member
130 m.connect('set_repo_perm_member', "/set_repo_perm_member/{repo_name:.*?}",
130 m.connect('set_repo_perm_member', "/set_repo_perm_member/{repo_name:.*?}",
131 action="set_repo_perm_member",
131 action="set_repo_perm_member",
132 conditions=dict(method=["POST"], function=check_repo))
132 conditions=dict(method=["POST"], function=check_repo))
133
133
134 #ajax delete repo perm user
134 #ajax delete repo perm user
135 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
135 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
136 action="delete_perm_user",
136 action="delete_perm_user",
137 conditions=dict(method=["DELETE"], function=check_repo))
137 conditions=dict(method=["DELETE"], function=check_repo))
138
138
139 #ajax delete repo perm users_group
139 #ajax delete repo perm users_group
140 m.connect('delete_repo_users_group',
140 m.connect('delete_repo_users_group',
141 "/repos_delete_users_group/{repo_name:.*?}",
141 "/repos_delete_users_group/{repo_name:.*?}",
142 action="delete_perm_users_group",
142 action="delete_perm_users_group",
143 conditions=dict(method=["DELETE"], function=check_repo))
143 conditions=dict(method=["DELETE"], function=check_repo))
144
144
145 #settings actions
145 #settings actions
146 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
146 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
147 action="repo_stats", conditions=dict(method=["DELETE"],
147 action="repo_stats", conditions=dict(method=["DELETE"],
148 function=check_repo))
148 function=check_repo))
149 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
149 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
150 action="repo_cache", conditions=dict(method=["DELETE"],
150 action="repo_cache", conditions=dict(method=["DELETE"],
151 function=check_repo))
151 function=check_repo))
152 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
152 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
153 action="repo_public_journal", conditions=dict(method=["PUT"],
153 action="repo_public_journal", conditions=dict(method=["PUT"],
154 function=check_repo))
154 function=check_repo))
155 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
155 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
156 action="repo_pull", conditions=dict(method=["PUT"],
156 action="repo_pull", conditions=dict(method=["PUT"],
157 function=check_repo))
157 function=check_repo))
158 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
158 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
159 action="repo_as_fork", conditions=dict(method=["PUT"],
159 action="repo_as_fork", conditions=dict(method=["PUT"],
160 function=check_repo))
160 function=check_repo))
161 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
161 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
162 action="repo_locking", conditions=dict(method=["PUT"],
162 action="repo_locking", conditions=dict(method=["PUT"],
163 function=check_repo))
163 function=check_repo))
164 m.connect('toggle_locking', "/locking_toggle/{repo_name:.*?}",
164 m.connect('toggle_locking', "/locking_toggle/{repo_name:.*?}",
165 action="toggle_locking", conditions=dict(method=["GET"],
165 action="toggle_locking", conditions=dict(method=["GET"],
166 function=check_repo))
166 function=check_repo))
167
167
168 #repo fields
168 #repo fields
169 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
169 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
170 action="create_repo_field", conditions=dict(method=["PUT"],
170 action="create_repo_field", conditions=dict(method=["PUT"],
171 function=check_repo))
171 function=check_repo))
172
172
173 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
173 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
174 action="delete_repo_field", conditions=dict(method=["DELETE"],
174 action="delete_repo_field", conditions=dict(method=["DELETE"],
175 function=check_repo))
175 function=check_repo))
176
176
177 with rmap.submapper(path_prefix=ADMIN_PREFIX,
177 with rmap.submapper(path_prefix=ADMIN_PREFIX,
178 controller='admin/repos_groups') as m:
178 controller='admin/repos_groups') as m:
179 m.connect("repos_groups", "/repos_groups",
179 m.connect("repos_groups", "/repos_groups",
180 action="create", conditions=dict(method=["POST"]))
180 action="create", conditions=dict(method=["POST"]))
181 m.connect("repos_groups", "/repos_groups",
181 m.connect("repos_groups", "/repos_groups",
182 action="index", conditions=dict(method=["GET"]))
182 action="index", conditions=dict(method=["GET"]))
183 m.connect("formatted_repos_groups", "/repos_groups.{format}",
183 m.connect("formatted_repos_groups", "/repos_groups.{format}",
184 action="index", conditions=dict(method=["GET"]))
184 action="index", conditions=dict(method=["GET"]))
185 m.connect("new_repos_group", "/repos_groups/new",
185 m.connect("new_repos_group", "/repos_groups/new",
186 action="new", conditions=dict(method=["GET"]))
186 action="new", conditions=dict(method=["GET"]))
187 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
187 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
188 action="new", conditions=dict(method=["GET"]))
188 action="new", conditions=dict(method=["GET"]))
189 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
189 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
190 action="update", conditions=dict(method=["PUT"],
190 action="update", conditions=dict(method=["PUT"],
191 function=check_group))
191 function=check_group))
192 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
192 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
193 action="delete", conditions=dict(method=["DELETE"],
193 action="delete", conditions=dict(method=["DELETE"],
194 function=check_group_skip_path))
194 function=check_group_skip_path))
195 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
195 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
196 action="edit", conditions=dict(method=["GET"],
196 action="edit", conditions=dict(method=["GET"],
197 function=check_group))
197 function=check_group))
198 m.connect("formatted_edit_repos_group",
198 m.connect("formatted_edit_repos_group",
199 "/repos_groups/{group_name:.*?}.{format}/edit",
199 "/repos_groups/{group_name:.*?}.{format}/edit",
200 action="edit", conditions=dict(method=["GET"],
200 action="edit", conditions=dict(method=["GET"],
201 function=check_group))
201 function=check_group))
202 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
202 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
203 action="show", conditions=dict(method=["GET"],
203 action="show", conditions=dict(method=["GET"],
204 function=check_group))
204 function=check_group))
205 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
205 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
206 action="show", conditions=dict(method=["GET"],
206 action="show", conditions=dict(method=["GET"],
207 function=check_group))
207 function=check_group))
208 # ajax delete repos group perm user
208 # ajax delete repository group perm user
209 m.connect('delete_repos_group_user_perm',
209 m.connect('delete_repos_group_user_perm',
210 "/delete_repos_group_user_perm/{group_name:.*?}",
210 "/delete_repos_group_user_perm/{group_name:.*?}",
211 action="delete_repos_group_user_perm",
211 action="delete_repos_group_user_perm",
212 conditions=dict(method=["DELETE"], function=check_group))
212 conditions=dict(method=["DELETE"], function=check_group))
213
213
214 # ajax delete repos group perm users_group
214 # ajax delete repository group perm users_group
215 m.connect('delete_repos_group_users_group_perm',
215 m.connect('delete_repos_group_users_group_perm',
216 "/delete_repos_group_users_group_perm/{group_name:.*?}",
216 "/delete_repos_group_users_group_perm/{group_name:.*?}",
217 action="delete_repos_group_users_group_perm",
217 action="delete_repos_group_users_group_perm",
218 conditions=dict(method=["DELETE"], function=check_group))
218 conditions=dict(method=["DELETE"], function=check_group))
219
219
220 #ADMIN USER REST ROUTES
220 #ADMIN USER REST ROUTES
221 with rmap.submapper(path_prefix=ADMIN_PREFIX,
221 with rmap.submapper(path_prefix=ADMIN_PREFIX,
222 controller='admin/users') as m:
222 controller='admin/users') as m:
223 m.connect("users", "/users",
223 m.connect("users", "/users",
224 action="create", conditions=dict(method=["POST"]))
224 action="create", conditions=dict(method=["POST"]))
225 m.connect("users", "/users",
225 m.connect("users", "/users",
226 action="index", conditions=dict(method=["GET"]))
226 action="index", conditions=dict(method=["GET"]))
227 m.connect("formatted_users", "/users.{format}",
227 m.connect("formatted_users", "/users.{format}",
228 action="index", conditions=dict(method=["GET"]))
228 action="index", conditions=dict(method=["GET"]))
229 m.connect("new_user", "/users/new",
229 m.connect("new_user", "/users/new",
230 action="new", conditions=dict(method=["GET"]))
230 action="new", conditions=dict(method=["GET"]))
231 m.connect("formatted_new_user", "/users/new.{format}",
231 m.connect("formatted_new_user", "/users/new.{format}",
232 action="new", conditions=dict(method=["GET"]))
232 action="new", conditions=dict(method=["GET"]))
233 m.connect("update_user", "/users/{id}",
233 m.connect("update_user", "/users/{id}",
234 action="update", conditions=dict(method=["PUT"]))
234 action="update", conditions=dict(method=["PUT"]))
235 m.connect("delete_user", "/users/{id}",
235 m.connect("delete_user", "/users/{id}",
236 action="delete", conditions=dict(method=["DELETE"]))
236 action="delete", conditions=dict(method=["DELETE"]))
237 m.connect("edit_user", "/users/{id}/edit",
237 m.connect("edit_user", "/users/{id}/edit",
238 action="edit", conditions=dict(method=["GET"]))
238 action="edit", conditions=dict(method=["GET"]))
239 m.connect("formatted_edit_user",
239 m.connect("formatted_edit_user",
240 "/users/{id}.{format}/edit",
240 "/users/{id}.{format}/edit",
241 action="edit", conditions=dict(method=["GET"]))
241 action="edit", conditions=dict(method=["GET"]))
242 m.connect("user", "/users/{id}",
242 m.connect("user", "/users/{id}",
243 action="show", conditions=dict(method=["GET"]))
243 action="show", conditions=dict(method=["GET"]))
244 m.connect("formatted_user", "/users/{id}.{format}",
244 m.connect("formatted_user", "/users/{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("user_perm", "/users_perm/{id}",
248 m.connect("user_perm", "/users_perm/{id}",
249 action="update_perm", conditions=dict(method=["PUT"]))
249 action="update_perm", conditions=dict(method=["PUT"]))
250 m.connect("user_emails", "/users_emails/{id}",
250 m.connect("user_emails", "/users_emails/{id}",
251 action="add_email", conditions=dict(method=["PUT"]))
251 action="add_email", conditions=dict(method=["PUT"]))
252 m.connect("user_emails_delete", "/users_emails/{id}",
252 m.connect("user_emails_delete", "/users_emails/{id}",
253 action="delete_email", conditions=dict(method=["DELETE"]))
253 action="delete_email", conditions=dict(method=["DELETE"]))
254 m.connect("user_ips", "/users_ips/{id}",
254 m.connect("user_ips", "/users_ips/{id}",
255 action="add_ip", conditions=dict(method=["PUT"]))
255 action="add_ip", conditions=dict(method=["PUT"]))
256 m.connect("user_ips_delete", "/users_ips/{id}",
256 m.connect("user_ips_delete", "/users_ips/{id}",
257 action="delete_ip", conditions=dict(method=["DELETE"]))
257 action="delete_ip", conditions=dict(method=["DELETE"]))
258
258
259 #ADMIN USER GROUPS REST ROUTES
259 #ADMIN USER GROUPS REST ROUTES
260 with rmap.submapper(path_prefix=ADMIN_PREFIX,
260 with rmap.submapper(path_prefix=ADMIN_PREFIX,
261 controller='admin/users_groups') as m:
261 controller='admin/users_groups') as m:
262 m.connect("users_groups", "/users_groups",
262 m.connect("users_groups", "/users_groups",
263 action="create", conditions=dict(method=["POST"]))
263 action="create", conditions=dict(method=["POST"]))
264 m.connect("users_groups", "/users_groups",
264 m.connect("users_groups", "/users_groups",
265 action="index", conditions=dict(method=["GET"]))
265 action="index", conditions=dict(method=["GET"]))
266 m.connect("formatted_users_groups", "/users_groups.{format}",
266 m.connect("formatted_users_groups", "/users_groups.{format}",
267 action="index", conditions=dict(method=["GET"]))
267 action="index", conditions=dict(method=["GET"]))
268 m.connect("new_users_group", "/users_groups/new",
268 m.connect("new_users_group", "/users_groups/new",
269 action="new", conditions=dict(method=["GET"]))
269 action="new", conditions=dict(method=["GET"]))
270 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
270 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
271 action="new", conditions=dict(method=["GET"]))
271 action="new", conditions=dict(method=["GET"]))
272 m.connect("update_users_group", "/users_groups/{id}",
272 m.connect("update_users_group", "/users_groups/{id}",
273 action="update", conditions=dict(method=["PUT"]))
273 action="update", conditions=dict(method=["PUT"]))
274 m.connect("delete_users_group", "/users_groups/{id}",
274 m.connect("delete_users_group", "/users_groups/{id}",
275 action="delete", conditions=dict(method=["DELETE"]))
275 action="delete", conditions=dict(method=["DELETE"]))
276 m.connect("edit_users_group", "/users_groups/{id}/edit",
276 m.connect("edit_users_group", "/users_groups/{id}/edit",
277 action="edit", conditions=dict(method=["GET"]))
277 action="edit", conditions=dict(method=["GET"]))
278 m.connect("formatted_edit_users_group",
278 m.connect("formatted_edit_users_group",
279 "/users_groups/{id}.{format}/edit",
279 "/users_groups/{id}.{format}/edit",
280 action="edit", conditions=dict(method=["GET"]))
280 action="edit", conditions=dict(method=["GET"]))
281 m.connect("users_group", "/users_groups/{id}",
281 m.connect("users_group", "/users_groups/{id}",
282 action="show", conditions=dict(method=["GET"]))
282 action="show", conditions=dict(method=["GET"]))
283 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
283 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
284 action="show", conditions=dict(method=["GET"]))
284 action="show", conditions=dict(method=["GET"]))
285
285
286 #EXTRAS USER ROUTES
286 #EXTRAS USER ROUTES
287 m.connect("users_group_perm", "/users_groups_perm/{id}",
287 m.connect("users_group_perm", "/users_groups_perm/{id}",
288 action="update_perm", conditions=dict(method=["PUT"]))
288 action="update_perm", conditions=dict(method=["PUT"]))
289
289
290 #ADMIN GROUP REST ROUTES
290 #ADMIN GROUP REST ROUTES
291 rmap.resource('group', 'groups',
291 rmap.resource('group', 'groups',
292 controller='admin/groups', path_prefix=ADMIN_PREFIX)
292 controller='admin/groups', path_prefix=ADMIN_PREFIX)
293
293
294 #ADMIN PERMISSIONS REST ROUTES
294 #ADMIN PERMISSIONS REST ROUTES
295 rmap.resource('permission', 'permissions',
295 rmap.resource('permission', 'permissions',
296 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
296 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
297
297
298 #ADMIN DEFAULTS REST ROUTES
298 #ADMIN DEFAULTS REST ROUTES
299 rmap.resource('default', 'defaults',
299 rmap.resource('default', 'defaults',
300 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
300 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
301
301
302 ##ADMIN LDAP SETTINGS
302 ##ADMIN LDAP SETTINGS
303 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
303 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
304 controller='admin/ldap_settings', action='ldap_settings',
304 controller='admin/ldap_settings', action='ldap_settings',
305 conditions=dict(method=["POST"]))
305 conditions=dict(method=["POST"]))
306
306
307 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
307 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
308 controller='admin/ldap_settings')
308 controller='admin/ldap_settings')
309
309
310 #ADMIN SETTINGS REST ROUTES
310 #ADMIN SETTINGS REST ROUTES
311 with rmap.submapper(path_prefix=ADMIN_PREFIX,
311 with rmap.submapper(path_prefix=ADMIN_PREFIX,
312 controller='admin/settings') as m:
312 controller='admin/settings') as m:
313 m.connect("admin_settings", "/settings",
313 m.connect("admin_settings", "/settings",
314 action="create", conditions=dict(method=["POST"]))
314 action="create", conditions=dict(method=["POST"]))
315 m.connect("admin_settings", "/settings",
315 m.connect("admin_settings", "/settings",
316 action="index", conditions=dict(method=["GET"]))
316 action="index", conditions=dict(method=["GET"]))
317 m.connect("formatted_admin_settings", "/settings.{format}",
317 m.connect("formatted_admin_settings", "/settings.{format}",
318 action="index", conditions=dict(method=["GET"]))
318 action="index", conditions=dict(method=["GET"]))
319 m.connect("admin_new_setting", "/settings/new",
319 m.connect("admin_new_setting", "/settings/new",
320 action="new", conditions=dict(method=["GET"]))
320 action="new", conditions=dict(method=["GET"]))
321 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
321 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
322 action="new", conditions=dict(method=["GET"]))
322 action="new", conditions=dict(method=["GET"]))
323 m.connect("/settings/{setting_id}",
323 m.connect("/settings/{setting_id}",
324 action="update", conditions=dict(method=["PUT"]))
324 action="update", conditions=dict(method=["PUT"]))
325 m.connect("/settings/{setting_id}",
325 m.connect("/settings/{setting_id}",
326 action="delete", conditions=dict(method=["DELETE"]))
326 action="delete", conditions=dict(method=["DELETE"]))
327 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
327 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
328 action="edit", conditions=dict(method=["GET"]))
328 action="edit", conditions=dict(method=["GET"]))
329 m.connect("formatted_admin_edit_setting",
329 m.connect("formatted_admin_edit_setting",
330 "/settings/{setting_id}.{format}/edit",
330 "/settings/{setting_id}.{format}/edit",
331 action="edit", conditions=dict(method=["GET"]))
331 action="edit", conditions=dict(method=["GET"]))
332 m.connect("admin_setting", "/settings/{setting_id}",
332 m.connect("admin_setting", "/settings/{setting_id}",
333 action="show", conditions=dict(method=["GET"]))
333 action="show", conditions=dict(method=["GET"]))
334 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
334 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
335 action="show", conditions=dict(method=["GET"]))
335 action="show", conditions=dict(method=["GET"]))
336 m.connect("admin_settings_my_account", "/my_account",
336 m.connect("admin_settings_my_account", "/my_account",
337 action="my_account", conditions=dict(method=["GET"]))
337 action="my_account", conditions=dict(method=["GET"]))
338 m.connect("admin_settings_my_account_update", "/my_account_update",
338 m.connect("admin_settings_my_account_update", "/my_account_update",
339 action="my_account_update", conditions=dict(method=["PUT"]))
339 action="my_account_update", conditions=dict(method=["PUT"]))
340 m.connect("admin_settings_my_repos", "/my_account/repos",
340 m.connect("admin_settings_my_repos", "/my_account/repos",
341 action="my_account_my_repos", conditions=dict(method=["GET"]))
341 action="my_account_my_repos", conditions=dict(method=["GET"]))
342 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
342 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
343 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
343 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
344
344
345 #NOTIFICATION REST ROUTES
345 #NOTIFICATION REST ROUTES
346 with rmap.submapper(path_prefix=ADMIN_PREFIX,
346 with rmap.submapper(path_prefix=ADMIN_PREFIX,
347 controller='admin/notifications') as m:
347 controller='admin/notifications') as m:
348 m.connect("notifications", "/notifications",
348 m.connect("notifications", "/notifications",
349 action="create", conditions=dict(method=["POST"]))
349 action="create", conditions=dict(method=["POST"]))
350 m.connect("notifications", "/notifications",
350 m.connect("notifications", "/notifications",
351 action="index", conditions=dict(method=["GET"]))
351 action="index", conditions=dict(method=["GET"]))
352 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
352 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
353 action="mark_all_read", conditions=dict(method=["GET"]))
353 action="mark_all_read", conditions=dict(method=["GET"]))
354 m.connect("formatted_notifications", "/notifications.{format}",
354 m.connect("formatted_notifications", "/notifications.{format}",
355 action="index", conditions=dict(method=["GET"]))
355 action="index", conditions=dict(method=["GET"]))
356 m.connect("new_notification", "/notifications/new",
356 m.connect("new_notification", "/notifications/new",
357 action="new", conditions=dict(method=["GET"]))
357 action="new", conditions=dict(method=["GET"]))
358 m.connect("formatted_new_notification", "/notifications/new.{format}",
358 m.connect("formatted_new_notification", "/notifications/new.{format}",
359 action="new", conditions=dict(method=["GET"]))
359 action="new", conditions=dict(method=["GET"]))
360 m.connect("/notification/{notification_id}",
360 m.connect("/notification/{notification_id}",
361 action="update", conditions=dict(method=["PUT"]))
361 action="update", conditions=dict(method=["PUT"]))
362 m.connect("/notification/{notification_id}",
362 m.connect("/notification/{notification_id}",
363 action="delete", conditions=dict(method=["DELETE"]))
363 action="delete", conditions=dict(method=["DELETE"]))
364 m.connect("edit_notification", "/notification/{notification_id}/edit",
364 m.connect("edit_notification", "/notification/{notification_id}/edit",
365 action="edit", conditions=dict(method=["GET"]))
365 action="edit", conditions=dict(method=["GET"]))
366 m.connect("formatted_edit_notification",
366 m.connect("formatted_edit_notification",
367 "/notification/{notification_id}.{format}/edit",
367 "/notification/{notification_id}.{format}/edit",
368 action="edit", conditions=dict(method=["GET"]))
368 action="edit", conditions=dict(method=["GET"]))
369 m.connect("notification", "/notification/{notification_id}",
369 m.connect("notification", "/notification/{notification_id}",
370 action="show", conditions=dict(method=["GET"]))
370 action="show", conditions=dict(method=["GET"]))
371 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
371 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
372 action="show", conditions=dict(method=["GET"]))
372 action="show", conditions=dict(method=["GET"]))
373
373
374 #ADMIN MAIN PAGES
374 #ADMIN MAIN PAGES
375 with rmap.submapper(path_prefix=ADMIN_PREFIX,
375 with rmap.submapper(path_prefix=ADMIN_PREFIX,
376 controller='admin/admin') as m:
376 controller='admin/admin') as m:
377 m.connect('admin_home', '', action='index')
377 m.connect('admin_home', '', action='index')
378 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
378 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
379 action='add_repo')
379 action='add_repo')
380
380
381 #==========================================================================
381 #==========================================================================
382 # API V2
382 # API V2
383 #==========================================================================
383 #==========================================================================
384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
385 controller='api/api') as m:
385 controller='api/api') as m:
386 m.connect('api', '/api')
386 m.connect('api', '/api')
387
387
388 #USER JOURNAL
388 #USER JOURNAL
389 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
389 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
390 controller='journal', action='index')
390 controller='journal', action='index')
391 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
391 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
392 controller='journal', action='journal_rss')
392 controller='journal', action='journal_rss')
393 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
393 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
394 controller='journal', action='journal_atom')
394 controller='journal', action='journal_atom')
395
395
396 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
396 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
397 controller='journal', action="public_journal")
397 controller='journal', action="public_journal")
398
398
399 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
399 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
400 controller='journal', action="public_journal_rss")
400 controller='journal', action="public_journal_rss")
401
401
402 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
402 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
403 controller='journal', action="public_journal_rss")
403 controller='journal', action="public_journal_rss")
404
404
405 rmap.connect('public_journal_atom',
405 rmap.connect('public_journal_atom',
406 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
406 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
407 action="public_journal_atom")
407 action="public_journal_atom")
408
408
409 rmap.connect('public_journal_atom_old',
409 rmap.connect('public_journal_atom_old',
410 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
410 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
411 action="public_journal_atom")
411 action="public_journal_atom")
412
412
413 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
413 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
414 controller='journal', action='toggle_following',
414 controller='journal', action='toggle_following',
415 conditions=dict(method=["POST"]))
415 conditions=dict(method=["POST"]))
416
416
417 #SEARCH
417 #SEARCH
418 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
418 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
419 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
419 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
420 controller='search',
420 controller='search',
421 conditions=dict(function=check_repo))
421 conditions=dict(function=check_repo))
422 rmap.connect('search_repo', '/{repo_name:.*?}/search',
422 rmap.connect('search_repo', '/{repo_name:.*?}/search',
423 controller='search',
423 controller='search',
424 conditions=dict(function=check_repo),
424 conditions=dict(function=check_repo),
425 )
425 )
426
426
427 #LOGIN/LOGOUT/REGISTER/SIGN IN
427 #LOGIN/LOGOUT/REGISTER/SIGN IN
428 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
428 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
429 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
429 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
430 action='logout')
430 action='logout')
431
431
432 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
432 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
433 action='register')
433 action='register')
434
434
435 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
435 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
436 controller='login', action='password_reset')
436 controller='login', action='password_reset')
437
437
438 rmap.connect('reset_password_confirmation',
438 rmap.connect('reset_password_confirmation',
439 '%s/password_reset_confirmation' % ADMIN_PREFIX,
439 '%s/password_reset_confirmation' % ADMIN_PREFIX,
440 controller='login', action='password_reset_confirmation')
440 controller='login', action='password_reset_confirmation')
441
441
442 #FEEDS
442 #FEEDS
443 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
443 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
444 controller='feed', action='rss',
444 controller='feed', action='rss',
445 conditions=dict(function=check_repo))
445 conditions=dict(function=check_repo))
446
446
447 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
447 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
448 controller='feed', action='atom',
448 controller='feed', action='atom',
449 conditions=dict(function=check_repo))
449 conditions=dict(function=check_repo))
450
450
451 #==========================================================================
451 #==========================================================================
452 # REPOSITORY ROUTES
452 # REPOSITORY ROUTES
453 #==========================================================================
453 #==========================================================================
454 rmap.connect('summary_home', '/{repo_name:.*?}',
454 rmap.connect('summary_home', '/{repo_name:.*?}',
455 controller='summary',
455 controller='summary',
456 conditions=dict(function=check_repo))
456 conditions=dict(function=check_repo))
457
457
458 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
458 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
459 controller='summary', action='repo_size',
459 controller='summary', action='repo_size',
460 conditions=dict(function=check_repo))
460 conditions=dict(function=check_repo))
461
461
462 rmap.connect('repos_group_home', '/{group_name:.*}',
462 rmap.connect('repos_group_home', '/{group_name:.*}',
463 controller='admin/repos_groups', action="show_by_name",
463 controller='admin/repos_groups', action="show_by_name",
464 conditions=dict(function=check_group))
464 conditions=dict(function=check_group))
465
465
466 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
466 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
467 controller='changeset', revision='tip',
467 controller='changeset', revision='tip',
468 conditions=dict(function=check_repo))
468 conditions=dict(function=check_repo))
469
469
470 # no longer user, but kept for routes to work
470 # no longer user, but kept for routes to work
471 rmap.connect("_edit_repo", "/{repo_name:.*?}/edit",
471 rmap.connect("_edit_repo", "/{repo_name:.*?}/edit",
472 controller='admin/repos', action="edit",
472 controller='admin/repos', action="edit",
473 conditions=dict(method=["GET"], function=check_repo)
473 conditions=dict(method=["GET"], function=check_repo)
474 )
474 )
475
475
476 rmap.connect("edit_repo", "/{repo_name:.*?}/settings",
476 rmap.connect("edit_repo", "/{repo_name:.*?}/settings",
477 controller='admin/repos', action="edit",
477 controller='admin/repos', action="edit",
478 conditions=dict(method=["GET"], function=check_repo)
478 conditions=dict(method=["GET"], function=check_repo)
479 )
479 )
480
480
481 #still working url for backward compat.
481 #still working url for backward compat.
482 rmap.connect('raw_changeset_home_depraced',
482 rmap.connect('raw_changeset_home_depraced',
483 '/{repo_name:.*?}/raw-changeset/{revision}',
483 '/{repo_name:.*?}/raw-changeset/{revision}',
484 controller='changeset', action='changeset_raw',
484 controller='changeset', action='changeset_raw',
485 revision='tip', conditions=dict(function=check_repo))
485 revision='tip', conditions=dict(function=check_repo))
486
486
487 ## new URLs
487 ## new URLs
488 rmap.connect('changeset_raw_home',
488 rmap.connect('changeset_raw_home',
489 '/{repo_name:.*?}/changeset-diff/{revision}',
489 '/{repo_name:.*?}/changeset-diff/{revision}',
490 controller='changeset', action='changeset_raw',
490 controller='changeset', action='changeset_raw',
491 revision='tip', conditions=dict(function=check_repo))
491 revision='tip', conditions=dict(function=check_repo))
492
492
493 rmap.connect('changeset_patch_home',
493 rmap.connect('changeset_patch_home',
494 '/{repo_name:.*?}/changeset-patch/{revision}',
494 '/{repo_name:.*?}/changeset-patch/{revision}',
495 controller='changeset', action='changeset_patch',
495 controller='changeset', action='changeset_patch',
496 revision='tip', conditions=dict(function=check_repo))
496 revision='tip', conditions=dict(function=check_repo))
497
497
498 rmap.connect('changeset_download_home',
498 rmap.connect('changeset_download_home',
499 '/{repo_name:.*?}/changeset-download/{revision}',
499 '/{repo_name:.*?}/changeset-download/{revision}',
500 controller='changeset', action='changeset_download',
500 controller='changeset', action='changeset_download',
501 revision='tip', conditions=dict(function=check_repo))
501 revision='tip', conditions=dict(function=check_repo))
502
502
503 rmap.connect('changeset_comment',
503 rmap.connect('changeset_comment',
504 '/{repo_name:.*?}/changeset/{revision}/comment',
504 '/{repo_name:.*?}/changeset/{revision}/comment',
505 controller='changeset', revision='tip', action='comment',
505 controller='changeset', revision='tip', action='comment',
506 conditions=dict(function=check_repo))
506 conditions=dict(function=check_repo))
507
507
508 rmap.connect('changeset_comment_delete',
508 rmap.connect('changeset_comment_delete',
509 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
509 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
510 controller='changeset', action='delete_comment',
510 controller='changeset', action='delete_comment',
511 conditions=dict(function=check_repo, method=["DELETE"]))
511 conditions=dict(function=check_repo, method=["DELETE"]))
512
512
513 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
513 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
514 controller='changeset', action='changeset_info')
514 controller='changeset', action='changeset_info')
515
515
516 rmap.connect('compare_url',
516 rmap.connect('compare_url',
517 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
517 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
518 controller='compare', action='index',
518 controller='compare', action='index',
519 conditions=dict(function=check_repo),
519 conditions=dict(function=check_repo),
520 requirements=dict(
520 requirements=dict(
521 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
521 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
522 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
522 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
523 )
523 )
524
524
525 rmap.connect('pullrequest_home',
525 rmap.connect('pullrequest_home',
526 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
526 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
527 action='index', conditions=dict(function=check_repo,
527 action='index', conditions=dict(function=check_repo,
528 method=["GET"]))
528 method=["GET"]))
529
529
530 rmap.connect('pullrequest',
530 rmap.connect('pullrequest',
531 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
531 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
532 action='create', conditions=dict(function=check_repo,
532 action='create', conditions=dict(function=check_repo,
533 method=["POST"]))
533 method=["POST"]))
534
534
535 rmap.connect('pullrequest_show',
535 rmap.connect('pullrequest_show',
536 '/{repo_name:.*?}/pull-request/{pull_request_id}',
536 '/{repo_name:.*?}/pull-request/{pull_request_id}',
537 controller='pullrequests',
537 controller='pullrequests',
538 action='show', conditions=dict(function=check_repo,
538 action='show', conditions=dict(function=check_repo,
539 method=["GET"]))
539 method=["GET"]))
540 rmap.connect('pullrequest_update',
540 rmap.connect('pullrequest_update',
541 '/{repo_name:.*?}/pull-request/{pull_request_id}',
541 '/{repo_name:.*?}/pull-request/{pull_request_id}',
542 controller='pullrequests',
542 controller='pullrequests',
543 action='update', conditions=dict(function=check_repo,
543 action='update', conditions=dict(function=check_repo,
544 method=["PUT"]))
544 method=["PUT"]))
545 rmap.connect('pullrequest_delete',
545 rmap.connect('pullrequest_delete',
546 '/{repo_name:.*?}/pull-request/{pull_request_id}',
546 '/{repo_name:.*?}/pull-request/{pull_request_id}',
547 controller='pullrequests',
547 controller='pullrequests',
548 action='delete', conditions=dict(function=check_repo,
548 action='delete', conditions=dict(function=check_repo,
549 method=["DELETE"]))
549 method=["DELETE"]))
550
550
551 rmap.connect('pullrequest_show_all',
551 rmap.connect('pullrequest_show_all',
552 '/{repo_name:.*?}/pull-request',
552 '/{repo_name:.*?}/pull-request',
553 controller='pullrequests',
553 controller='pullrequests',
554 action='show_all', conditions=dict(function=check_repo,
554 action='show_all', conditions=dict(function=check_repo,
555 method=["GET"]))
555 method=["GET"]))
556
556
557 rmap.connect('pullrequest_comment',
557 rmap.connect('pullrequest_comment',
558 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
558 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
559 controller='pullrequests',
559 controller='pullrequests',
560 action='comment', conditions=dict(function=check_repo,
560 action='comment', conditions=dict(function=check_repo,
561 method=["POST"]))
561 method=["POST"]))
562
562
563 rmap.connect('pullrequest_comment_delete',
563 rmap.connect('pullrequest_comment_delete',
564 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
564 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
565 controller='pullrequests', action='delete_comment',
565 controller='pullrequests', action='delete_comment',
566 conditions=dict(function=check_repo, method=["DELETE"]))
566 conditions=dict(function=check_repo, method=["DELETE"]))
567
567
568 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
568 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
569 controller='summary', conditions=dict(function=check_repo))
569 controller='summary', conditions=dict(function=check_repo))
570
570
571 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
571 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
572 controller='shortlog', conditions=dict(function=check_repo))
572 controller='shortlog', conditions=dict(function=check_repo))
573
573
574 rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
574 rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
575 controller='shortlog', f_path=None,
575 controller='shortlog', f_path=None,
576 conditions=dict(function=check_repo))
576 conditions=dict(function=check_repo))
577
577
578 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
578 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
579 controller='branches', conditions=dict(function=check_repo))
579 controller='branches', conditions=dict(function=check_repo))
580
580
581 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
581 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
582 controller='tags', conditions=dict(function=check_repo))
582 controller='tags', conditions=dict(function=check_repo))
583
583
584 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
584 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
585 controller='bookmarks', conditions=dict(function=check_repo))
585 controller='bookmarks', conditions=dict(function=check_repo))
586
586
587 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
587 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
588 controller='changelog', conditions=dict(function=check_repo))
588 controller='changelog', conditions=dict(function=check_repo))
589
589
590 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
590 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
591 controller='changelog', action='changelog_details',
591 controller='changelog', action='changelog_details',
592 conditions=dict(function=check_repo))
592 conditions=dict(function=check_repo))
593
593
594 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
594 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
595 controller='files', revision='tip', f_path='',
595 controller='files', revision='tip', f_path='',
596 conditions=dict(function=check_repo))
596 conditions=dict(function=check_repo))
597
597
598 rmap.connect('files_home_nopath', '/{repo_name:.*?}/files/{revision}',
598 rmap.connect('files_home_nopath', '/{repo_name:.*?}/files/{revision}',
599 controller='files', revision='tip', f_path='',
599 controller='files', revision='tip', f_path='',
600 conditions=dict(function=check_repo))
600 conditions=dict(function=check_repo))
601
601
602 rmap.connect('files_history_home',
602 rmap.connect('files_history_home',
603 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
603 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
604 controller='files', action='history', revision='tip', f_path='',
604 controller='files', action='history', revision='tip', f_path='',
605 conditions=dict(function=check_repo))
605 conditions=dict(function=check_repo))
606
606
607 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
607 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
608 controller='files', action='diff', revision='tip', f_path='',
608 controller='files', action='diff', revision='tip', f_path='',
609 conditions=dict(function=check_repo))
609 conditions=dict(function=check_repo))
610
610
611 rmap.connect('files_rawfile_home',
611 rmap.connect('files_rawfile_home',
612 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
612 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
613 controller='files', action='rawfile', revision='tip',
613 controller='files', action='rawfile', revision='tip',
614 f_path='', conditions=dict(function=check_repo))
614 f_path='', conditions=dict(function=check_repo))
615
615
616 rmap.connect('files_raw_home',
616 rmap.connect('files_raw_home',
617 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
617 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
618 controller='files', action='raw', revision='tip', f_path='',
618 controller='files', action='raw', revision='tip', f_path='',
619 conditions=dict(function=check_repo))
619 conditions=dict(function=check_repo))
620
620
621 rmap.connect('files_annotate_home',
621 rmap.connect('files_annotate_home',
622 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
622 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
623 controller='files', action='index', revision='tip',
623 controller='files', action='index', revision='tip',
624 f_path='', annotate=True, conditions=dict(function=check_repo))
624 f_path='', annotate=True, conditions=dict(function=check_repo))
625
625
626 rmap.connect('files_edit_home',
626 rmap.connect('files_edit_home',
627 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
627 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
628 controller='files', action='edit', revision='tip',
628 controller='files', action='edit', revision='tip',
629 f_path='', conditions=dict(function=check_repo))
629 f_path='', conditions=dict(function=check_repo))
630
630
631 rmap.connect('files_add_home',
631 rmap.connect('files_add_home',
632 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
632 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
633 controller='files', action='add', revision='tip',
633 controller='files', action='add', revision='tip',
634 f_path='', conditions=dict(function=check_repo))
634 f_path='', conditions=dict(function=check_repo))
635
635
636 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
636 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
637 controller='files', action='archivefile',
637 controller='files', action='archivefile',
638 conditions=dict(function=check_repo))
638 conditions=dict(function=check_repo))
639
639
640 rmap.connect('files_nodelist_home',
640 rmap.connect('files_nodelist_home',
641 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
641 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
642 controller='files', action='nodelist',
642 controller='files', action='nodelist',
643 conditions=dict(function=check_repo))
643 conditions=dict(function=check_repo))
644
644
645 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
645 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
646 controller='forks', action='fork_create',
646 controller='forks', action='fork_create',
647 conditions=dict(function=check_repo, method=["POST"]))
647 conditions=dict(function=check_repo, method=["POST"]))
648
648
649 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
649 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
650 controller='forks', action='fork',
650 controller='forks', action='fork',
651 conditions=dict(function=check_repo))
651 conditions=dict(function=check_repo))
652
652
653 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
653 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
654 controller='forks', action='forks',
654 controller='forks', action='forks',
655 conditions=dict(function=check_repo))
655 conditions=dict(function=check_repo))
656
656
657 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
657 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
658 controller='followers', action='followers',
658 controller='followers', action='followers',
659 conditions=dict(function=check_repo))
659 conditions=dict(function=check_repo))
660
660
661 return rmap
661 return rmap
@@ -1,392 +1,392 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos_groups
3 rhodecode.controllers.admin.repos_groups
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository groups controller for RhodeCode
6 Repository groups controller for RhodeCode
7
7
8 :created_on: Mar 23, 2010
8 :created_on: Mar 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29
29
30 from formencode import htmlfill
30 from formencode import htmlfill
31
31
32 from pylons import request, tmpl_context as c, url
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import abort, redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from sqlalchemy.exc import IntegrityError
36 from sqlalchemy.exc import IntegrityError
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
41 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
43 HasPermissionAll
43 HasPermissionAll
44 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.base import BaseController, render
45 from rhodecode.model.db import RepoGroup, Repository
45 from rhodecode.model.db import RepoGroup, Repository
46 from rhodecode.model.repos_group import ReposGroupModel
46 from rhodecode.model.repos_group import ReposGroupModel
47 from rhodecode.model.forms import ReposGroupForm
47 from rhodecode.model.forms import ReposGroupForm
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from webob.exc import HTTPInternalServerError, HTTPNotFound
50 from webob.exc import HTTPInternalServerError, HTTPNotFound
51 from rhodecode.lib.utils2 import str2bool, safe_int
51 from rhodecode.lib.utils2 import str2bool, safe_int
52 from sqlalchemy.sql.expression import func
52 from sqlalchemy.sql.expression import func
53 from rhodecode.model.scm import GroupList
53 from rhodecode.model.scm import GroupList
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class ReposGroupsController(BaseController):
58 class ReposGroupsController(BaseController):
59 """REST Controller styled on the Atom Publishing Protocol"""
59 """REST Controller styled on the Atom Publishing Protocol"""
60 # To properly map this controller, ensure your config/routing.py
60 # To properly map this controller, ensure your config/routing.py
61 # file has a resource setup:
61 # file has a resource setup:
62 # map.resource('repos_group', 'repos_groups')
62 # map.resource('repos_group', 'repos_groups')
63
63
64 @LoginRequired()
64 @LoginRequired()
65 def __before__(self):
65 def __before__(self):
66 super(ReposGroupsController, self).__before__()
66 super(ReposGroupsController, self).__before__()
67
67
68 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
68 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
69 if HasPermissionAll('hg.admin')('group edit'):
69 if HasPermissionAll('hg.admin')('group edit'):
70 #we're global admin, we're ok and we can create TOP level groups
70 #we're global admin, we're ok and we can create TOP level groups
71 allow_empty_group = True
71 allow_empty_group = True
72
72
73 #override the choices for this form, we need to filter choices
73 #override the choices for this form, we need to filter choices
74 #and display only those we have ADMIN right
74 #and display only those we have ADMIN right
75 groups_with_admin_rights = GroupList(RepoGroup.query().all(),
75 groups_with_admin_rights = GroupList(RepoGroup.query().all(),
76 perm_set=['group.admin'])
76 perm_set=['group.admin'])
77 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
77 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
78 show_empty_group=allow_empty_group)
78 show_empty_group=allow_empty_group)
79 # exclude filtered ids
79 # exclude filtered ids
80 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
80 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
81 c.repo_groups)
81 c.repo_groups)
82 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
82 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
83 repo_model = RepoModel()
83 repo_model = RepoModel()
84 c.users_array = repo_model.get_users_js()
84 c.users_array = repo_model.get_users_js()
85 c.users_groups_array = repo_model.get_users_groups_js()
85 c.users_groups_array = repo_model.get_users_groups_js()
86
86
87 def __load_data(self, group_id):
87 def __load_data(self, group_id):
88 """
88 """
89 Load defaults settings for edit, and update
89 Load defaults settings for edit, and update
90
90
91 :param group_id:
91 :param group_id:
92 """
92 """
93 repo_group = RepoGroup.get_or_404(group_id)
93 repo_group = RepoGroup.get_or_404(group_id)
94 data = repo_group.get_dict()
94 data = repo_group.get_dict()
95 data['group_name'] = repo_group.name
95 data['group_name'] = repo_group.name
96
96
97 # fill repository users
97 # fill repository users
98 for p in repo_group.repo_group_to_perm:
98 for p in repo_group.repo_group_to_perm:
99 data.update({'u_perm_%s' % p.user.username:
99 data.update({'u_perm_%s' % p.user.username:
100 p.permission.permission_name})
100 p.permission.permission_name})
101
101
102 # fill repository groups
102 # fill repository groups
103 for p in repo_group.users_group_to_perm:
103 for p in repo_group.users_group_to_perm:
104 data.update({'g_perm_%s' % p.users_group.users_group_name:
104 data.update({'g_perm_%s' % p.users_group.users_group_name:
105 p.permission.permission_name})
105 p.permission.permission_name})
106
106
107 return data
107 return data
108
108
109 def _revoke_perms_on_yourself(self, form_result):
109 def _revoke_perms_on_yourself(self, form_result):
110 _up = filter(lambda u: c.rhodecode_user.username == u[0],
110 _up = filter(lambda u: c.rhodecode_user.username == u[0],
111 form_result['perms_updates'])
111 form_result['perms_updates'])
112 _new = filter(lambda u: c.rhodecode_user.username == u[0],
112 _new = filter(lambda u: c.rhodecode_user.username == u[0],
113 form_result['perms_new'])
113 form_result['perms_new'])
114 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
114 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
115 return True
115 return True
116 return False
116 return False
117
117
118 def index(self, format='html'):
118 def index(self, format='html'):
119 """GET /repos_groups: All items in the collection"""
119 """GET /repos_groups: All items in the collection"""
120 # url('repos_groups')
120 # url('repos_groups')
121 group_iter = GroupList(RepoGroup.query().all(), perm_set=['group.admin'])
121 group_iter = GroupList(RepoGroup.query().all(), perm_set=['group.admin'])
122 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
122 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
123 c.groups = sorted(group_iter, key=sk)
123 c.groups = sorted(group_iter, key=sk)
124 return render('admin/repos_groups/repos_groups_show.html')
124 return render('admin/repos_groups/repos_groups_show.html')
125
125
126 def create(self):
126 def create(self):
127 """POST /repos_groups: Create a new item"""
127 """POST /repos_groups: Create a new item"""
128 # url('repos_groups')
128 # url('repos_groups')
129
129
130 self.__load_defaults()
130 self.__load_defaults()
131
131
132 # permissions for can create group based on parent_id are checked
132 # permissions for can create group based on parent_id are checked
133 # here in the Form
133 # here in the Form
134 repos_group_form = ReposGroupForm(available_groups=
134 repos_group_form = ReposGroupForm(available_groups=
135 map(lambda k: unicode(k[0]), c.repo_groups))()
135 map(lambda k: unicode(k[0]), c.repo_groups))()
136 try:
136 try:
137 form_result = repos_group_form.to_python(dict(request.POST))
137 form_result = repos_group_form.to_python(dict(request.POST))
138 ReposGroupModel().create(
138 ReposGroupModel().create(
139 group_name=form_result['group_name'],
139 group_name=form_result['group_name'],
140 group_description=form_result['group_description'],
140 group_description=form_result['group_description'],
141 parent=form_result['group_parent_id'],
141 parent=form_result['group_parent_id'],
142 owner=self.rhodecode_user.user_id
142 owner=self.rhodecode_user.user_id
143 )
143 )
144 Session().commit()
144 Session().commit()
145 h.flash(_('Created repos group %s') \
145 h.flash(_('Created repository group %s') \
146 % form_result['group_name'], category='success')
146 % form_result['group_name'], category='success')
147 #TODO: in futureaction_logger(, '', '', '', self.sa)
147 #TODO: in futureaction_logger(, '', '', '', self.sa)
148 except formencode.Invalid, errors:
148 except formencode.Invalid, errors:
149 return htmlfill.render(
149 return htmlfill.render(
150 render('admin/repos_groups/repos_groups_add.html'),
150 render('admin/repos_groups/repos_groups_add.html'),
151 defaults=errors.value,
151 defaults=errors.value,
152 errors=errors.error_dict or {},
152 errors=errors.error_dict or {},
153 prefix_error=False,
153 prefix_error=False,
154 encoding="UTF-8")
154 encoding="UTF-8")
155 except Exception:
155 except Exception:
156 log.error(traceback.format_exc())
156 log.error(traceback.format_exc())
157 h.flash(_('Error occurred during creation of repos group %s') \
157 h.flash(_('Error occurred during creation of repository group %s') \
158 % request.POST.get('group_name'), category='error')
158 % request.POST.get('group_name'), category='error')
159 parent_group_id = form_result['group_parent_id']
159 parent_group_id = form_result['group_parent_id']
160 #TODO: maybe we should get back to the main view, not the admin one
160 #TODO: maybe we should get back to the main view, not the admin one
161 return redirect(url('repos_groups', parent_group=parent_group_id))
161 return redirect(url('repos_groups', parent_group=parent_group_id))
162
162
163 def new(self, format='html'):
163 def new(self, format='html'):
164 """GET /repos_groups/new: Form to create a new item"""
164 """GET /repos_groups/new: Form to create a new item"""
165 # url('new_repos_group')
165 # url('new_repos_group')
166 if HasPermissionAll('hg.admin')('group create'):
166 if HasPermissionAll('hg.admin')('group create'):
167 #we're global admin, we're ok and we can create TOP level groups
167 #we're global admin, we're ok and we can create TOP level groups
168 pass
168 pass
169 else:
169 else:
170 # we pass in parent group into creation form, thus we know
170 # we pass in parent group into creation form, thus we know
171 # what would be the group, we can check perms here !
171 # what would be the group, we can check perms here !
172 group_id = safe_int(request.GET.get('parent_group'))
172 group_id = safe_int(request.GET.get('parent_group'))
173 group = RepoGroup.get(group_id) if group_id else None
173 group = RepoGroup.get(group_id) if group_id else None
174 group_name = group.group_name if group else None
174 group_name = group.group_name if group else None
175 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
175 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
176 pass
176 pass
177 else:
177 else:
178 return abort(403)
178 return abort(403)
179
179
180 self.__load_defaults()
180 self.__load_defaults()
181 return render('admin/repos_groups/repos_groups_add.html')
181 return render('admin/repos_groups/repos_groups_add.html')
182
182
183 @HasReposGroupPermissionAnyDecorator('group.admin')
183 @HasReposGroupPermissionAnyDecorator('group.admin')
184 def update(self, group_name):
184 def update(self, group_name):
185 """PUT /repos_groups/group_name: Update an existing item"""
185 """PUT /repos_groups/group_name: Update an existing item"""
186 # Forms posted to this method should contain a hidden field:
186 # Forms posted to this method should contain a hidden field:
187 # <input type="hidden" name="_method" value="PUT" />
187 # <input type="hidden" name="_method" value="PUT" />
188 # Or using helpers:
188 # Or using helpers:
189 # h.form(url('repos_group', group_name=GROUP_NAME),
189 # h.form(url('repos_group', group_name=GROUP_NAME),
190 # method='put')
190 # method='put')
191 # url('repos_group', group_name=GROUP_NAME)
191 # url('repos_group', group_name=GROUP_NAME)
192
192
193 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
193 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
194 if HasPermissionAll('hg.admin')('group edit'):
194 if HasPermissionAll('hg.admin')('group edit'):
195 #we're global admin, we're ok and we can create TOP level groups
195 #we're global admin, we're ok and we can create TOP level groups
196 allow_empty_group = True
196 allow_empty_group = True
197 elif not c.repos_group.parent_group:
197 elif not c.repos_group.parent_group:
198 allow_empty_group = True
198 allow_empty_group = True
199 else:
199 else:
200 allow_empty_group = False
200 allow_empty_group = False
201 self.__load_defaults(allow_empty_group=allow_empty_group,
201 self.__load_defaults(allow_empty_group=allow_empty_group,
202 exclude_group_ids=[c.repos_group.group_id])
202 exclude_group_ids=[c.repos_group.group_id])
203
203
204 repos_group_form = ReposGroupForm(
204 repos_group_form = ReposGroupForm(
205 edit=True,
205 edit=True,
206 old_data=c.repos_group.get_dict(),
206 old_data=c.repos_group.get_dict(),
207 available_groups=c.repo_groups_choices,
207 available_groups=c.repo_groups_choices,
208 can_create_in_root=allow_empty_group,
208 can_create_in_root=allow_empty_group,
209 )()
209 )()
210 try:
210 try:
211 form_result = repos_group_form.to_python(dict(request.POST))
211 form_result = repos_group_form.to_python(dict(request.POST))
212 if not c.rhodecode_user.is_admin:
212 if not c.rhodecode_user.is_admin:
213 if self._revoke_perms_on_yourself(form_result):
213 if self._revoke_perms_on_yourself(form_result):
214 msg = _('Cannot revoke permission for yourself as admin')
214 msg = _('Cannot revoke permission for yourself as admin')
215 h.flash(msg, category='warning')
215 h.flash(msg, category='warning')
216 raise Exception('revoke admin permission on self')
216 raise Exception('revoke admin permission on self')
217
217
218 new_gr = ReposGroupModel().update(group_name, form_result)
218 new_gr = ReposGroupModel().update(group_name, form_result)
219 Session().commit()
219 Session().commit()
220 h.flash(_('Updated repos group %s') \
220 h.flash(_('Updated repository group %s') \
221 % form_result['group_name'], category='success')
221 % form_result['group_name'], category='success')
222 # we now have new name !
222 # we now have new name !
223 group_name = new_gr.group_name
223 group_name = new_gr.group_name
224 #TODO: in future action_logger(, '', '', '', self.sa)
224 #TODO: in future action_logger(, '', '', '', self.sa)
225 except formencode.Invalid, errors:
225 except formencode.Invalid, errors:
226
226
227 return htmlfill.render(
227 return htmlfill.render(
228 render('admin/repos_groups/repos_groups_edit.html'),
228 render('admin/repos_groups/repos_groups_edit.html'),
229 defaults=errors.value,
229 defaults=errors.value,
230 errors=errors.error_dict or {},
230 errors=errors.error_dict or {},
231 prefix_error=False,
231 prefix_error=False,
232 encoding="UTF-8")
232 encoding="UTF-8")
233 except Exception:
233 except Exception:
234 log.error(traceback.format_exc())
234 log.error(traceback.format_exc())
235 h.flash(_('Error occurred during update of repos group %s') \
235 h.flash(_('Error occurred during update of repository group %s') \
236 % request.POST.get('group_name'), category='error')
236 % request.POST.get('group_name'), category='error')
237
237
238 return redirect(url('edit_repos_group', group_name=group_name))
238 return redirect(url('edit_repos_group', group_name=group_name))
239
239
240 @HasReposGroupPermissionAnyDecorator('group.admin')
240 @HasReposGroupPermissionAnyDecorator('group.admin')
241 def delete(self, group_name):
241 def delete(self, group_name):
242 """DELETE /repos_groups/group_name: Delete an existing item"""
242 """DELETE /repos_groups/group_name: Delete an existing item"""
243 # Forms posted to this method should contain a hidden field:
243 # Forms posted to this method should contain a hidden field:
244 # <input type="hidden" name="_method" value="DELETE" />
244 # <input type="hidden" name="_method" value="DELETE" />
245 # Or using helpers:
245 # Or using helpers:
246 # h.form(url('repos_group', group_name=GROUP_NAME),
246 # h.form(url('repos_group', group_name=GROUP_NAME),
247 # method='delete')
247 # method='delete')
248 # url('repos_group', group_name=GROUP_NAME)
248 # url('repos_group', group_name=GROUP_NAME)
249
249
250 gr = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
250 gr = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
251 repos = gr.repositories.all()
251 repos = gr.repositories.all()
252 if repos:
252 if repos:
253 h.flash(_('This group contains %s repositores and cannot be '
253 h.flash(_('This group contains %s repositores and cannot be '
254 'deleted') % len(repos), category='warning')
254 'deleted') % len(repos), category='warning')
255 return redirect(url('repos_groups'))
255 return redirect(url('repos_groups'))
256
256
257 children = gr.children.all()
257 children = gr.children.all()
258 if children:
258 if children:
259 h.flash(_('This group contains %s subgroups and cannot be deleted'
259 h.flash(_('This group contains %s subgroups and cannot be deleted'
260 % (len(children))), category='warning')
260 % (len(children))), category='warning')
261 return redirect(url('repos_groups'))
261 return redirect(url('repos_groups'))
262
262
263 try:
263 try:
264 ReposGroupModel().delete(group_name)
264 ReposGroupModel().delete(group_name)
265 Session().commit()
265 Session().commit()
266 h.flash(_('Removed repos group %s') % group_name,
266 h.flash(_('Removed repository group %s') % group_name,
267 category='success')
267 category='success')
268 #TODO: in future action_logger(, '', '', '', self.sa)
268 #TODO: in future action_logger(, '', '', '', self.sa)
269 except Exception:
269 except Exception:
270 log.error(traceback.format_exc())
270 log.error(traceback.format_exc())
271 h.flash(_('Error occurred during deletion of repos '
271 h.flash(_('Error occurred during deletion of repos '
272 'group %s') % group_name, category='error')
272 'group %s') % group_name, category='error')
273
273
274 return redirect(url('repos_groups'))
274 return redirect(url('repos_groups'))
275
275
276 @HasReposGroupPermissionAnyDecorator('group.admin')
276 @HasReposGroupPermissionAnyDecorator('group.admin')
277 def delete_repos_group_user_perm(self, group_name):
277 def delete_repos_group_user_perm(self, group_name):
278 """
278 """
279 DELETE an existing repository group permission user
279 DELETE an existing repository group permission user
280
280
281 :param group_name:
281 :param group_name:
282 """
282 """
283 try:
283 try:
284 if not c.rhodecode_user.is_admin:
284 if not c.rhodecode_user.is_admin:
285 if c.rhodecode_user.user_id == safe_int(request.POST['user_id']):
285 if c.rhodecode_user.user_id == safe_int(request.POST['user_id']):
286 msg = _('Cannot revoke permission for yourself as admin')
286 msg = _('Cannot revoke permission for yourself as admin')
287 h.flash(msg, category='warning')
287 h.flash(msg, category='warning')
288 raise Exception('revoke admin permission on self')
288 raise Exception('revoke admin permission on self')
289 recursive = str2bool(request.POST.get('recursive', False))
289 recursive = str2bool(request.POST.get('recursive', False))
290 ReposGroupModel().delete_permission(
290 ReposGroupModel().delete_permission(
291 repos_group=group_name, obj=request.POST['user_id'],
291 repos_group=group_name, obj=request.POST['user_id'],
292 obj_type='user', recursive=recursive
292 obj_type='user', recursive=recursive
293 )
293 )
294 Session().commit()
294 Session().commit()
295 except Exception:
295 except Exception:
296 log.error(traceback.format_exc())
296 log.error(traceback.format_exc())
297 h.flash(_('An error occurred during deletion of group user'),
297 h.flash(_('An error occurred during deletion of group user'),
298 category='error')
298 category='error')
299 raise HTTPInternalServerError()
299 raise HTTPInternalServerError()
300
300
301 @HasReposGroupPermissionAnyDecorator('group.admin')
301 @HasReposGroupPermissionAnyDecorator('group.admin')
302 def delete_repos_group_users_group_perm(self, group_name):
302 def delete_repos_group_users_group_perm(self, group_name):
303 """
303 """
304 DELETE an existing repository group permission user group
304 DELETE an existing repository group permission user group
305
305
306 :param group_name:
306 :param group_name:
307 """
307 """
308
308
309 try:
309 try:
310 recursive = str2bool(request.POST.get('recursive', False))
310 recursive = str2bool(request.POST.get('recursive', False))
311 ReposGroupModel().delete_permission(
311 ReposGroupModel().delete_permission(
312 repos_group=group_name, obj=request.POST['users_group_id'],
312 repos_group=group_name, obj=request.POST['users_group_id'],
313 obj_type='users_group', recursive=recursive
313 obj_type='users_group', recursive=recursive
314 )
314 )
315 Session().commit()
315 Session().commit()
316 except Exception:
316 except Exception:
317 log.error(traceback.format_exc())
317 log.error(traceback.format_exc())
318 h.flash(_('An error occurred during deletion of group'
318 h.flash(_('An error occurred during deletion of group'
319 ' user groups'),
319 ' user groups'),
320 category='error')
320 category='error')
321 raise HTTPInternalServerError()
321 raise HTTPInternalServerError()
322
322
323 def show_by_name(self, group_name):
323 def show_by_name(self, group_name):
324 """
324 """
325 This is a proxy that does a lookup group_name -> id, and shows
325 This is a proxy that does a lookup group_name -> id, and shows
326 the group by id view instead
326 the group by id view instead
327 """
327 """
328 group_name = group_name.rstrip('/')
328 group_name = group_name.rstrip('/')
329 id_ = RepoGroup.get_by_group_name(group_name)
329 id_ = RepoGroup.get_by_group_name(group_name)
330 if id_:
330 if id_:
331 return self.show(id_.group_id)
331 return self.show(id_.group_id)
332 raise HTTPNotFound
332 raise HTTPNotFound
333
333
334 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
334 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
335 'group.admin')
335 'group.admin')
336 def show(self, group_name, format='html'):
336 def show(self, group_name, format='html'):
337 """GET /repos_groups/group_name: Show a specific item"""
337 """GET /repos_groups/group_name: Show a specific item"""
338 # url('repos_group', group_name=GROUP_NAME)
338 # url('repos_group', group_name=GROUP_NAME)
339
339
340 c.group = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
340 c.group = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
341 c.group_repos = c.group.repositories.all()
341 c.group_repos = c.group.repositories.all()
342
342
343 #overwrite our cached list with current filter
343 #overwrite our cached list with current filter
344 gr_filter = c.group_repos
344 gr_filter = c.group_repos
345 c.repo_cnt = 0
345 c.repo_cnt = 0
346
346
347 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
347 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
348 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
348 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
349 c.groups = self.scm_model.get_repos_groups(groups)
349 c.groups = self.scm_model.get_repos_groups(groups)
350
350
351 if not c.visual.lightweight_dashboard:
351 if not c.visual.lightweight_dashboard:
352 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
352 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
353 ## lightweight version of dashboard
353 ## lightweight version of dashboard
354 else:
354 else:
355 c.repos_list = Repository.query()\
355 c.repos_list = Repository.query()\
356 .filter(Repository.group_id == c.group.group_id)\
356 .filter(Repository.group_id == c.group.group_id)\
357 .order_by(func.lower(Repository.repo_name))\
357 .order_by(func.lower(Repository.repo_name))\
358 .all()
358 .all()
359
359
360 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
360 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
361 admin=False)
361 admin=False)
362 #json used to render the grid
362 #json used to render the grid
363 c.data = json.dumps(repos_data)
363 c.data = json.dumps(repos_data)
364
364
365 return render('admin/repos_groups/repos_groups.html')
365 return render('admin/repos_groups/repos_groups.html')
366
366
367 @HasReposGroupPermissionAnyDecorator('group.admin')
367 @HasReposGroupPermissionAnyDecorator('group.admin')
368 def edit(self, group_name, format='html'):
368 def edit(self, group_name, format='html'):
369 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
369 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
370 # url('edit_repos_group', group_name=GROUP_NAME)
370 # url('edit_repos_group', group_name=GROUP_NAME)
371
371
372 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
372 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
373 #we can only allow moving empty group if it's already a top-level
373 #we can only allow moving empty group if it's already a top-level
374 #group, ie has no parents, or we're admin
374 #group, ie has no parents, or we're admin
375 if HasPermissionAll('hg.admin')('group edit'):
375 if HasPermissionAll('hg.admin')('group edit'):
376 #we're global admin, we're ok and we can create TOP level groups
376 #we're global admin, we're ok and we can create TOP level groups
377 allow_empty_group = True
377 allow_empty_group = True
378 elif not c.repos_group.parent_group:
378 elif not c.repos_group.parent_group:
379 allow_empty_group = True
379 allow_empty_group = True
380 else:
380 else:
381 allow_empty_group = False
381 allow_empty_group = False
382
382
383 self.__load_defaults(allow_empty_group=allow_empty_group,
383 self.__load_defaults(allow_empty_group=allow_empty_group,
384 exclude_group_ids=[c.repos_group.group_id])
384 exclude_group_ids=[c.repos_group.group_id])
385 defaults = self.__load_data(c.repos_group.group_id)
385 defaults = self.__load_data(c.repos_group.group_id)
386
386
387 return htmlfill.render(
387 return htmlfill.render(
388 render('admin/repos_groups/repos_groups_edit.html'),
388 render('admin/repos_groups/repos_groups_edit.html'),
389 defaults=defaults,
389 defaults=defaults,
390 encoding="UTF-8",
390 encoding="UTF-8",
391 force_defaults=False
391 force_defaults=False
392 )
392 )
@@ -1,792 +1,792 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 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 re
27 import re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 import decorator
35 import decorator
36 import warnings
36 import warnings
37 from os.path import abspath
37 from os.path import abspath
38 from os.path import dirname as dn, join as jn
38 from os.path import dirname as dn, join as jn
39
39
40 from paste.script.command import Command, BadCommand
40 from paste.script.command import Command, BadCommand
41
41
42 from mercurial import ui, config
42 from mercurial import ui, config
43
43
44 from webhelpers.text import collapse, remove_formatting, strip_tags
44 from webhelpers.text import collapse, remove_formatting, strip_tags
45
45
46 from rhodecode.lib.vcs import get_backend
46 from rhodecode.lib.vcs import get_backend
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
49 from rhodecode.lib.vcs.utils.helpers import get_scm
49 from rhodecode.lib.vcs.utils.helpers import get_scm
50 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.exceptions import VCSError
51
51
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model import meta
54 from rhodecode.model import meta
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
57 from rhodecode.model.meta import Session
57 from rhodecode.model.meta import Session
58 from rhodecode.model.repos_group import ReposGroupModel
58 from rhodecode.model.repos_group import ReposGroupModel
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 from rhodecode.lib.vcs.utils.fakemod import create_module
60 from rhodecode.lib.vcs.utils.fakemod import create_module
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
65
65
66
66
67 def recursive_replace(str_, replace=' '):
67 def recursive_replace(str_, replace=' '):
68 """
68 """
69 Recursive replace of given sign to just one instance
69 Recursive replace of given sign to just one instance
70
70
71 :param str_: given string
71 :param str_: given string
72 :param replace: char to find and replace multiple instances
72 :param replace: char to find and replace multiple instances
73
73
74 Examples::
74 Examples::
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
76 'Mighty-Mighty-Bo-sstones'
76 'Mighty-Mighty-Bo-sstones'
77 """
77 """
78
78
79 if str_.find(replace * 2) == -1:
79 if str_.find(replace * 2) == -1:
80 return str_
80 return str_
81 else:
81 else:
82 str_ = str_.replace(replace * 2, replace)
82 str_ = str_.replace(replace * 2, replace)
83 return recursive_replace(str_, replace)
83 return recursive_replace(str_, replace)
84
84
85
85
86 def repo_name_slug(value):
86 def repo_name_slug(value):
87 """
87 """
88 Return slug of name of repository
88 Return slug of name of repository
89 This function is called on each creation/modification
89 This function is called on each creation/modification
90 of repository to prevent bad names in repo
90 of repository to prevent bad names in repo
91 """
91 """
92
92
93 slug = remove_formatting(value)
93 slug = remove_formatting(value)
94 slug = strip_tags(slug)
94 slug = strip_tags(slug)
95
95
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
97 slug = slug.replace(c, '-')
97 slug = slug.replace(c, '-')
98 slug = recursive_replace(slug, '-')
98 slug = recursive_replace(slug, '-')
99 slug = collapse(slug, '-')
99 slug = collapse(slug, '-')
100 return slug
100 return slug
101
101
102
102
103 def get_repo_slug(request):
103 def get_repo_slug(request):
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 if _repo:
105 if _repo:
106 _repo = _repo.rstrip('/')
106 _repo = _repo.rstrip('/')
107 return _repo
107 return _repo
108
108
109
109
110 def get_repos_group_slug(request):
110 def get_repos_group_slug(request):
111 _group = request.environ['pylons.routes_dict'].get('group_name')
111 _group = request.environ['pylons.routes_dict'].get('group_name')
112 if _group:
112 if _group:
113 _group = _group.rstrip('/')
113 _group = _group.rstrip('/')
114 return _group
114 return _group
115
115
116
116
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 """
118 """
119 Action logger for various actions made by users
119 Action logger for various actions made by users
120
120
121 :param user: user that made this action, can be a unique username string or
121 :param user: user that made this action, can be a unique username string or
122 object containing user_id attribute
122 object containing user_id attribute
123 :param action: action to log, should be on of predefined unique actions for
123 :param action: action to log, should be on of predefined unique actions for
124 easy translations
124 easy translations
125 :param repo: string name of repository or object containing repo_id,
125 :param repo: string name of repository or object containing repo_id,
126 that action was made on
126 that action was made on
127 :param ipaddr: optional ip address from what the action was made
127 :param ipaddr: optional ip address from what the action was made
128 :param sa: optional sqlalchemy session
128 :param sa: optional sqlalchemy session
129
129
130 """
130 """
131
131
132 if not sa:
132 if not sa:
133 sa = meta.Session()
133 sa = meta.Session()
134
134
135 try:
135 try:
136 if hasattr(user, 'user_id'):
136 if hasattr(user, 'user_id'):
137 user_obj = User.get(user.user_id)
137 user_obj = User.get(user.user_id)
138 elif isinstance(user, basestring):
138 elif isinstance(user, basestring):
139 user_obj = User.get_by_username(user)
139 user_obj = User.get_by_username(user)
140 else:
140 else:
141 raise Exception('You have to provide a user object or a username')
141 raise Exception('You have to provide a user object or a username')
142
142
143 if hasattr(repo, 'repo_id'):
143 if hasattr(repo, 'repo_id'):
144 repo_obj = Repository.get(repo.repo_id)
144 repo_obj = Repository.get(repo.repo_id)
145 repo_name = repo_obj.repo_name
145 repo_name = repo_obj.repo_name
146 elif isinstance(repo, basestring):
146 elif isinstance(repo, basestring):
147 repo_name = repo.lstrip('/')
147 repo_name = repo.lstrip('/')
148 repo_obj = Repository.get_by_repo_name(repo_name)
148 repo_obj = Repository.get_by_repo_name(repo_name)
149 else:
149 else:
150 repo_obj = None
150 repo_obj = None
151 repo_name = ''
151 repo_name = ''
152
152
153 user_log = UserLog()
153 user_log = UserLog()
154 user_log.user_id = user_obj.user_id
154 user_log.user_id = user_obj.user_id
155 user_log.username = user_obj.username
155 user_log.username = user_obj.username
156 user_log.action = safe_unicode(action)
156 user_log.action = safe_unicode(action)
157
157
158 user_log.repository = repo_obj
158 user_log.repository = repo_obj
159 user_log.repository_name = repo_name
159 user_log.repository_name = repo_name
160
160
161 user_log.action_date = datetime.datetime.now()
161 user_log.action_date = datetime.datetime.now()
162 user_log.user_ip = ipaddr
162 user_log.user_ip = ipaddr
163 sa.add(user_log)
163 sa.add(user_log)
164
164
165 log.info('Logging action:%s on %s by user:%s ip:%s' %
165 log.info('Logging action:%s on %s by user:%s ip:%s' %
166 (action, safe_unicode(repo), user_obj, ipaddr))
166 (action, safe_unicode(repo), user_obj, ipaddr))
167 if commit:
167 if commit:
168 sa.commit()
168 sa.commit()
169 except Exception:
169 except Exception:
170 log.error(traceback.format_exc())
170 log.error(traceback.format_exc())
171 raise
171 raise
172
172
173
173
174 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
174 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
175 """
175 """
176 Scans given path for repos and return (name,(type,path)) tuple
176 Scans given path for repos and return (name,(type,path)) tuple
177
177
178 :param path: path to scan for repositories
178 :param path: path to scan for repositories
179 :param recursive: recursive search and return names with subdirs in front
179 :param recursive: recursive search and return names with subdirs in front
180 """
180 """
181
181
182 # remove ending slash for better results
182 # remove ending slash for better results
183 path = path.rstrip(os.sep)
183 path = path.rstrip(os.sep)
184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
185
185
186 def _get_repos(p):
186 def _get_repos(p):
187 if not os.access(p, os.W_OK):
187 if not os.access(p, os.W_OK):
188 log.warn('ignoring repo path without write access: %s', p)
188 log.warn('ignoring repo path without write access: %s', p)
189 return
189 return
190 for dirpath in os.listdir(p):
190 for dirpath in os.listdir(p):
191 if os.path.isfile(os.path.join(p, dirpath)):
191 if os.path.isfile(os.path.join(p, dirpath)):
192 continue
192 continue
193 cur_path = os.path.join(p, dirpath)
193 cur_path = os.path.join(p, dirpath)
194
194
195 # skip removed repos
195 # skip removed repos
196 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
196 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
197 continue
197 continue
198
198
199 #skip .<somethin> dirs
199 #skip .<somethin> dirs
200 if dirpath.startswith('.'):
200 if dirpath.startswith('.'):
201 continue
201 continue
202
202
203 try:
203 try:
204 scm_info = get_scm(cur_path)
204 scm_info = get_scm(cur_path)
205 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
205 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
206 except VCSError:
206 except VCSError:
207 if not recursive:
207 if not recursive:
208 continue
208 continue
209 #check if this dir containts other repos for recursive scan
209 #check if this dir containts other repos for recursive scan
210 rec_path = os.path.join(p, dirpath)
210 rec_path = os.path.join(p, dirpath)
211 if os.path.isdir(rec_path):
211 if os.path.isdir(rec_path):
212 for inner_scm in _get_repos(rec_path):
212 for inner_scm in _get_repos(rec_path):
213 yield inner_scm
213 yield inner_scm
214
214
215 return _get_repos(path)
215 return _get_repos(path)
216
216
217
217
218 def is_valid_repo(repo_name, base_path, scm=None):
218 def is_valid_repo(repo_name, base_path, scm=None):
219 """
219 """
220 Returns True if given path is a valid repository False otherwise.
220 Returns True if given path is a valid repository False otherwise.
221 If scm param is given also compare if given scm is the same as expected
221 If scm param is given also compare if given scm is the same as expected
222 from scm parameter
222 from scm parameter
223
223
224 :param repo_name:
224 :param repo_name:
225 :param base_path:
225 :param base_path:
226 :param scm:
226 :param scm:
227
227
228 :return True: if given path is a valid repository
228 :return True: if given path is a valid repository
229 """
229 """
230 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
230 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
231
231
232 try:
232 try:
233 scm_ = get_scm(full_path)
233 scm_ = get_scm(full_path)
234 if scm:
234 if scm:
235 return scm_[0] == scm
235 return scm_[0] == scm
236 return True
236 return True
237 except VCSError:
237 except VCSError:
238 return False
238 return False
239
239
240
240
241 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
241 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
242 """
242 """
243 Returns True if given path is a repos group False otherwise
243 Returns True if given path is a repository group False otherwise
244
244
245 :param repo_name:
245 :param repo_name:
246 :param base_path:
246 :param base_path:
247 """
247 """
248 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
248 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
249
249
250 # check if it's not a repo
250 # check if it's not a repo
251 if is_valid_repo(repos_group_name, base_path):
251 if is_valid_repo(repos_group_name, base_path):
252 return False
252 return False
253
253
254 try:
254 try:
255 # we need to check bare git repos at higher level
255 # we need to check bare git repos at higher level
256 # since we might match branches/hooks/info/objects or possible
256 # since we might match branches/hooks/info/objects or possible
257 # other things inside bare git repo
257 # other things inside bare git repo
258 get_scm(os.path.dirname(full_path))
258 get_scm(os.path.dirname(full_path))
259 return False
259 return False
260 except VCSError:
260 except VCSError:
261 pass
261 pass
262
262
263 # check if it's a valid path
263 # check if it's a valid path
264 if skip_path_check or os.path.isdir(full_path):
264 if skip_path_check or os.path.isdir(full_path):
265 return True
265 return True
266
266
267 return False
267 return False
268
268
269
269
270 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
270 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
271 while True:
271 while True:
272 ok = raw_input(prompt)
272 ok = raw_input(prompt)
273 if ok in ('y', 'ye', 'yes'):
273 if ok in ('y', 'ye', 'yes'):
274 return True
274 return True
275 if ok in ('n', 'no', 'nop', 'nope'):
275 if ok in ('n', 'no', 'nop', 'nope'):
276 return False
276 return False
277 retries = retries - 1
277 retries = retries - 1
278 if retries < 0:
278 if retries < 0:
279 raise IOError
279 raise IOError
280 print complaint
280 print complaint
281
281
282 #propagated from mercurial documentation
282 #propagated from mercurial documentation
283 ui_sections = ['alias', 'auth',
283 ui_sections = ['alias', 'auth',
284 'decode/encode', 'defaults',
284 'decode/encode', 'defaults',
285 'diff', 'email',
285 'diff', 'email',
286 'extensions', 'format',
286 'extensions', 'format',
287 'merge-patterns', 'merge-tools',
287 'merge-patterns', 'merge-tools',
288 'hooks', 'http_proxy',
288 'hooks', 'http_proxy',
289 'smtp', 'patch',
289 'smtp', 'patch',
290 'paths', 'profiling',
290 'paths', 'profiling',
291 'server', 'trusted',
291 'server', 'trusted',
292 'ui', 'web', ]
292 'ui', 'web', ]
293
293
294
294
295 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
295 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
296 """
296 """
297 A function that will read python rc files or database
297 A function that will read python rc files or database
298 and make an mercurial ui object from read options
298 and make an mercurial ui object from read options
299
299
300 :param path: path to mercurial config file
300 :param path: path to mercurial config file
301 :param checkpaths: check the path
301 :param checkpaths: check the path
302 :param read_from: read from 'file' or 'db'
302 :param read_from: read from 'file' or 'db'
303 """
303 """
304
304
305 baseui = ui.ui()
305 baseui = ui.ui()
306
306
307 # clean the baseui object
307 # clean the baseui object
308 baseui._ocfg = config.config()
308 baseui._ocfg = config.config()
309 baseui._ucfg = config.config()
309 baseui._ucfg = config.config()
310 baseui._tcfg = config.config()
310 baseui._tcfg = config.config()
311
311
312 if read_from == 'file':
312 if read_from == 'file':
313 if not os.path.isfile(path):
313 if not os.path.isfile(path):
314 log.debug('hgrc file is not present at %s, skipping...' % path)
314 log.debug('hgrc file is not present at %s, skipping...' % path)
315 return False
315 return False
316 log.debug('reading hgrc from %s' % path)
316 log.debug('reading hgrc from %s' % path)
317 cfg = config.config()
317 cfg = config.config()
318 cfg.read(path)
318 cfg.read(path)
319 for section in ui_sections:
319 for section in ui_sections:
320 for k, v in cfg.items(section):
320 for k, v in cfg.items(section):
321 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
321 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
322 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
322 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
323
323
324 elif read_from == 'db':
324 elif read_from == 'db':
325 sa = meta.Session()
325 sa = meta.Session()
326 ret = sa.query(RhodeCodeUi)\
326 ret = sa.query(RhodeCodeUi)\
327 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
327 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
328 .all()
328 .all()
329
329
330 hg_ui = ret
330 hg_ui = ret
331 for ui_ in hg_ui:
331 for ui_ in hg_ui:
332 if ui_.ui_active:
332 if ui_.ui_active:
333 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
333 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
334 ui_.ui_key, ui_.ui_value)
334 ui_.ui_key, ui_.ui_value)
335 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
335 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
336 safe_str(ui_.ui_value))
336 safe_str(ui_.ui_value))
337 if ui_.ui_key == 'push_ssl':
337 if ui_.ui_key == 'push_ssl':
338 # force set push_ssl requirement to False, rhodecode
338 # force set push_ssl requirement to False, rhodecode
339 # handles that
339 # handles that
340 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
340 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
341 False)
341 False)
342 if clear_session:
342 if clear_session:
343 meta.Session.remove()
343 meta.Session.remove()
344 return baseui
344 return baseui
345
345
346
346
347 def set_rhodecode_config(config):
347 def set_rhodecode_config(config):
348 """
348 """
349 Updates pylons config with new settings from database
349 Updates pylons config with new settings from database
350
350
351 :param config:
351 :param config:
352 """
352 """
353 hgsettings = RhodeCodeSetting.get_app_settings()
353 hgsettings = RhodeCodeSetting.get_app_settings()
354
354
355 for k, v in hgsettings.items():
355 for k, v in hgsettings.items():
356 config[k] = v
356 config[k] = v
357
357
358
358
359 def invalidate_cache(cache_key, *args):
359 def invalidate_cache(cache_key, *args):
360 """
360 """
361 Puts cache invalidation task into db for
361 Puts cache invalidation task into db for
362 further global cache invalidation
362 further global cache invalidation
363 """
363 """
364
364
365 from rhodecode.model.scm import ScmModel
365 from rhodecode.model.scm import ScmModel
366
366
367 if cache_key.startswith('get_repo_cached_'):
367 if cache_key.startswith('get_repo_cached_'):
368 name = cache_key.split('get_repo_cached_')[-1]
368 name = cache_key.split('get_repo_cached_')[-1]
369 ScmModel().mark_for_invalidation(name)
369 ScmModel().mark_for_invalidation(name)
370
370
371
371
372 def map_groups(path):
372 def map_groups(path):
373 """
373 """
374 Given a full path to a repository, create all nested groups that this
374 Given a full path to a repository, create all nested groups that this
375 repo is inside. This function creates parent-child relationships between
375 repo is inside. This function creates parent-child relationships between
376 groups and creates default perms for all new groups.
376 groups and creates default perms for all new groups.
377
377
378 :param paths: full path to repository
378 :param paths: full path to repository
379 """
379 """
380 sa = meta.Session()
380 sa = meta.Session()
381 groups = path.split(Repository.url_sep())
381 groups = path.split(Repository.url_sep())
382 parent = None
382 parent = None
383 group = None
383 group = None
384
384
385 # last element is repo in nested groups structure
385 # last element is repo in nested groups structure
386 groups = groups[:-1]
386 groups = groups[:-1]
387 rgm = ReposGroupModel(sa)
387 rgm = ReposGroupModel(sa)
388 for lvl, group_name in enumerate(groups):
388 for lvl, group_name in enumerate(groups):
389 group_name = '/'.join(groups[:lvl] + [group_name])
389 group_name = '/'.join(groups[:lvl] + [group_name])
390 group = RepoGroup.get_by_group_name(group_name)
390 group = RepoGroup.get_by_group_name(group_name)
391 desc = '%s group' % group_name
391 desc = '%s group' % group_name
392
392
393 # skip folders that are now removed repos
393 # skip folders that are now removed repos
394 if REMOVED_REPO_PAT.match(group_name):
394 if REMOVED_REPO_PAT.match(group_name):
395 break
395 break
396
396
397 if group is None:
397 if group is None:
398 log.debug('creating group level: %s group_name: %s' % (lvl,
398 log.debug('creating group level: %s group_name: %s' % (lvl,
399 group_name))
399 group_name))
400 group = RepoGroup(group_name, parent)
400 group = RepoGroup(group_name, parent)
401 group.group_description = desc
401 group.group_description = desc
402 sa.add(group)
402 sa.add(group)
403 rgm._create_default_perms(group)
403 rgm._create_default_perms(group)
404 sa.flush()
404 sa.flush()
405 parent = group
405 parent = group
406 return group
406 return group
407
407
408
408
409 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
409 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
410 install_git_hook=False):
410 install_git_hook=False):
411 """
411 """
412 maps all repos given in initial_repo_list, non existing repositories
412 maps all repos given in initial_repo_list, non existing repositories
413 are created, if remove_obsolete is True it also check for db entries
413 are created, if remove_obsolete is True it also check for db entries
414 that are not in initial_repo_list and removes them.
414 that are not in initial_repo_list and removes them.
415
415
416 :param initial_repo_list: list of repositories found by scanning methods
416 :param initial_repo_list: list of repositories found by scanning methods
417 :param remove_obsolete: check for obsolete entries in database
417 :param remove_obsolete: check for obsolete entries in database
418 :param install_git_hook: if this is True, also check and install githook
418 :param install_git_hook: if this is True, also check and install githook
419 for a repo if missing
419 for a repo if missing
420 """
420 """
421 from rhodecode.model.repo import RepoModel
421 from rhodecode.model.repo import RepoModel
422 from rhodecode.model.scm import ScmModel
422 from rhodecode.model.scm import ScmModel
423 sa = meta.Session()
423 sa = meta.Session()
424 rm = RepoModel()
424 rm = RepoModel()
425 user = sa.query(User).filter(User.admin == True).first()
425 user = sa.query(User).filter(User.admin == True).first()
426 if user is None:
426 if user is None:
427 raise Exception('Missing administrative account!')
427 raise Exception('Missing administrative account!')
428 added = []
428 added = []
429
429
430 ##creation defaults
430 ##creation defaults
431 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
431 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
432 enable_statistics = defs.get('repo_enable_statistics')
432 enable_statistics = defs.get('repo_enable_statistics')
433 enable_locking = defs.get('repo_enable_locking')
433 enable_locking = defs.get('repo_enable_locking')
434 enable_downloads = defs.get('repo_enable_downloads')
434 enable_downloads = defs.get('repo_enable_downloads')
435 private = defs.get('repo_private')
435 private = defs.get('repo_private')
436
436
437 for name, repo in initial_repo_list.items():
437 for name, repo in initial_repo_list.items():
438 group = map_groups(name)
438 group = map_groups(name)
439 db_repo = rm.get_by_repo_name(name)
439 db_repo = rm.get_by_repo_name(name)
440 # found repo that is on filesystem not in RhodeCode database
440 # found repo that is on filesystem not in RhodeCode database
441 if not db_repo:
441 if not db_repo:
442 log.info('repository %s not found, creating now' % name)
442 log.info('repository %s not found, creating now' % name)
443 added.append(name)
443 added.append(name)
444 desc = (repo.description
444 desc = (repo.description
445 if repo.description != 'unknown'
445 if repo.description != 'unknown'
446 else '%s repository' % name)
446 else '%s repository' % name)
447
447
448 new_repo = rm.create_repo(
448 new_repo = rm.create_repo(
449 repo_name=name,
449 repo_name=name,
450 repo_type=repo.alias,
450 repo_type=repo.alias,
451 description=desc,
451 description=desc,
452 repos_group=getattr(group, 'group_id', None),
452 repos_group=getattr(group, 'group_id', None),
453 owner=user,
453 owner=user,
454 just_db=True,
454 just_db=True,
455 enable_locking=enable_locking,
455 enable_locking=enable_locking,
456 enable_downloads=enable_downloads,
456 enable_downloads=enable_downloads,
457 enable_statistics=enable_statistics,
457 enable_statistics=enable_statistics,
458 private=private
458 private=private
459 )
459 )
460 # we added that repo just now, and make sure it has githook
460 # we added that repo just now, and make sure it has githook
461 # installed
461 # installed
462 if new_repo.repo_type == 'git':
462 if new_repo.repo_type == 'git':
463 ScmModel().install_git_hook(new_repo.scm_instance)
463 ScmModel().install_git_hook(new_repo.scm_instance)
464 new_repo.update_changeset_cache()
464 new_repo.update_changeset_cache()
465 elif install_git_hook:
465 elif install_git_hook:
466 if db_repo.repo_type == 'git':
466 if db_repo.repo_type == 'git':
467 ScmModel().install_git_hook(db_repo.scm_instance)
467 ScmModel().install_git_hook(db_repo.scm_instance)
468 # during starting install all cache keys for all repositories in the
468 # during starting install all cache keys for all repositories in the
469 # system, this will register all repos and multiple instances
469 # system, this will register all repos and multiple instances
470 cache_key = CacheInvalidation._get_cache_key(name)
470 cache_key = CacheInvalidation._get_cache_key(name)
471 log.debug("Creating invalidation cache key for %s: %s", name, cache_key)
471 log.debug("Creating invalidation cache key for %s: %s", name, cache_key)
472 CacheInvalidation.invalidate(name)
472 CacheInvalidation.invalidate(name)
473
473
474 sa.commit()
474 sa.commit()
475 removed = []
475 removed = []
476 if remove_obsolete:
476 if remove_obsolete:
477 # remove from database those repositories that are not in the filesystem
477 # remove from database those repositories that are not in the filesystem
478 for repo in sa.query(Repository).all():
478 for repo in sa.query(Repository).all():
479 if repo.repo_name not in initial_repo_list.keys():
479 if repo.repo_name not in initial_repo_list.keys():
480 log.debug("Removing non-existing repository found in db `%s`" %
480 log.debug("Removing non-existing repository found in db `%s`" %
481 repo.repo_name)
481 repo.repo_name)
482 try:
482 try:
483 sa.delete(repo)
483 sa.delete(repo)
484 sa.commit()
484 sa.commit()
485 removed.append(repo.repo_name)
485 removed.append(repo.repo_name)
486 except Exception:
486 except Exception:
487 #don't hold further removals on error
487 #don't hold further removals on error
488 log.error(traceback.format_exc())
488 log.error(traceback.format_exc())
489 sa.rollback()
489 sa.rollback()
490 return added, removed
490 return added, removed
491
491
492
492
493 # set cache regions for beaker so celery can utilise it
493 # set cache regions for beaker so celery can utilise it
494 def add_cache(settings):
494 def add_cache(settings):
495 cache_settings = {'regions': None}
495 cache_settings = {'regions': None}
496 for key in settings.keys():
496 for key in settings.keys():
497 for prefix in ['beaker.cache.', 'cache.']:
497 for prefix in ['beaker.cache.', 'cache.']:
498 if key.startswith(prefix):
498 if key.startswith(prefix):
499 name = key.split(prefix)[1].strip()
499 name = key.split(prefix)[1].strip()
500 cache_settings[name] = settings[key].strip()
500 cache_settings[name] = settings[key].strip()
501 if cache_settings['regions']:
501 if cache_settings['regions']:
502 for region in cache_settings['regions'].split(','):
502 for region in cache_settings['regions'].split(','):
503 region = region.strip()
503 region = region.strip()
504 region_settings = {}
504 region_settings = {}
505 for key, value in cache_settings.items():
505 for key, value in cache_settings.items():
506 if key.startswith(region):
506 if key.startswith(region):
507 region_settings[key.split('.')[1]] = value
507 region_settings[key.split('.')[1]] = value
508 region_settings['expire'] = int(region_settings.get('expire',
508 region_settings['expire'] = int(region_settings.get('expire',
509 60))
509 60))
510 region_settings.setdefault('lock_dir',
510 region_settings.setdefault('lock_dir',
511 cache_settings.get('lock_dir'))
511 cache_settings.get('lock_dir'))
512 region_settings.setdefault('data_dir',
512 region_settings.setdefault('data_dir',
513 cache_settings.get('data_dir'))
513 cache_settings.get('data_dir'))
514
514
515 if 'type' not in region_settings:
515 if 'type' not in region_settings:
516 region_settings['type'] = cache_settings.get('type',
516 region_settings['type'] = cache_settings.get('type',
517 'memory')
517 'memory')
518 beaker.cache.cache_regions[region] = region_settings
518 beaker.cache.cache_regions[region] = region_settings
519
519
520
520
521 def load_rcextensions(root_path):
521 def load_rcextensions(root_path):
522 import rhodecode
522 import rhodecode
523 from rhodecode.config import conf
523 from rhodecode.config import conf
524
524
525 path = os.path.join(root_path, 'rcextensions', '__init__.py')
525 path = os.path.join(root_path, 'rcextensions', '__init__.py')
526 if os.path.isfile(path):
526 if os.path.isfile(path):
527 rcext = create_module('rc', path)
527 rcext = create_module('rc', path)
528 EXT = rhodecode.EXTENSIONS = rcext
528 EXT = rhodecode.EXTENSIONS = rcext
529 log.debug('Found rcextensions now loading %s...' % rcext)
529 log.debug('Found rcextensions now loading %s...' % rcext)
530
530
531 # Additional mappings that are not present in the pygments lexers
531 # Additional mappings that are not present in the pygments lexers
532 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
532 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
533
533
534 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
534 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
535
535
536 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
536 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
537 log.debug('settings custom INDEX_EXTENSIONS')
537 log.debug('settings custom INDEX_EXTENSIONS')
538 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
538 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
539
539
540 #ADDITIONAL MAPPINGS
540 #ADDITIONAL MAPPINGS
541 log.debug('adding extra into INDEX_EXTENSIONS')
541 log.debug('adding extra into INDEX_EXTENSIONS')
542 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
542 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
543
543
544 # auto check if the module is not missing any data, set to default if is
544 # auto check if the module is not missing any data, set to default if is
545 # this will help autoupdate new feature of rcext module
545 # this will help autoupdate new feature of rcext module
546 from rhodecode.config import rcextensions
546 from rhodecode.config import rcextensions
547 for k in dir(rcextensions):
547 for k in dir(rcextensions):
548 if not k.startswith('_') and not hasattr(EXT, k):
548 if not k.startswith('_') and not hasattr(EXT, k):
549 setattr(EXT, k, getattr(rcextensions, k))
549 setattr(EXT, k, getattr(rcextensions, k))
550
550
551
551
552 def get_custom_lexer(extension):
552 def get_custom_lexer(extension):
553 """
553 """
554 returns a custom lexer if it's defined in rcextensions module, or None
554 returns a custom lexer if it's defined in rcextensions module, or None
555 if there's no custom lexer defined
555 if there's no custom lexer defined
556 """
556 """
557 import rhodecode
557 import rhodecode
558 from pygments import lexers
558 from pygments import lexers
559 #check if we didn't define this extension as other lexer
559 #check if we didn't define this extension as other lexer
560 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
560 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
561 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
561 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
562 return lexers.get_lexer_by_name(_lexer_name)
562 return lexers.get_lexer_by_name(_lexer_name)
563
563
564
564
565 #==============================================================================
565 #==============================================================================
566 # TEST FUNCTIONS AND CREATORS
566 # TEST FUNCTIONS AND CREATORS
567 #==============================================================================
567 #==============================================================================
568 def create_test_index(repo_location, config, full_index):
568 def create_test_index(repo_location, config, full_index):
569 """
569 """
570 Makes default test index
570 Makes default test index
571
571
572 :param config: test config
572 :param config: test config
573 :param full_index:
573 :param full_index:
574 """
574 """
575
575
576 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
576 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
577 from rhodecode.lib.pidlock import DaemonLock, LockHeld
577 from rhodecode.lib.pidlock import DaemonLock, LockHeld
578
578
579 repo_location = repo_location
579 repo_location = repo_location
580
580
581 index_location = os.path.join(config['app_conf']['index_dir'])
581 index_location = os.path.join(config['app_conf']['index_dir'])
582 if not os.path.exists(index_location):
582 if not os.path.exists(index_location):
583 os.makedirs(index_location)
583 os.makedirs(index_location)
584
584
585 try:
585 try:
586 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
586 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
587 WhooshIndexingDaemon(index_location=index_location,
587 WhooshIndexingDaemon(index_location=index_location,
588 repo_location=repo_location)\
588 repo_location=repo_location)\
589 .run(full_index=full_index)
589 .run(full_index=full_index)
590 l.release()
590 l.release()
591 except LockHeld:
591 except LockHeld:
592 pass
592 pass
593
593
594
594
595 def create_test_env(repos_test_path, config):
595 def create_test_env(repos_test_path, config):
596 """
596 """
597 Makes a fresh database and
597 Makes a fresh database and
598 install test repository into tmp dir
598 install test repository into tmp dir
599 """
599 """
600 from rhodecode.lib.db_manage import DbManage
600 from rhodecode.lib.db_manage import DbManage
601 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
601 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
602
602
603 # PART ONE create db
603 # PART ONE create db
604 dbconf = config['sqlalchemy.db1.url']
604 dbconf = config['sqlalchemy.db1.url']
605 log.debug('making test db %s' % dbconf)
605 log.debug('making test db %s' % dbconf)
606
606
607 # create test dir if it doesn't exist
607 # create test dir if it doesn't exist
608 if not os.path.isdir(repos_test_path):
608 if not os.path.isdir(repos_test_path):
609 log.debug('Creating testdir %s' % repos_test_path)
609 log.debug('Creating testdir %s' % repos_test_path)
610 os.makedirs(repos_test_path)
610 os.makedirs(repos_test_path)
611
611
612 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
612 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
613 tests=True)
613 tests=True)
614 dbmanage.create_tables(override=True)
614 dbmanage.create_tables(override=True)
615 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
615 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
616 dbmanage.create_default_user()
616 dbmanage.create_default_user()
617 dbmanage.admin_prompt()
617 dbmanage.admin_prompt()
618 dbmanage.create_permissions()
618 dbmanage.create_permissions()
619 dbmanage.populate_default_permissions()
619 dbmanage.populate_default_permissions()
620 Session().commit()
620 Session().commit()
621 # PART TWO make test repo
621 # PART TWO make test repo
622 log.debug('making test vcs repositories')
622 log.debug('making test vcs repositories')
623
623
624 idx_path = config['app_conf']['index_dir']
624 idx_path = config['app_conf']['index_dir']
625 data_path = config['app_conf']['cache_dir']
625 data_path = config['app_conf']['cache_dir']
626
626
627 #clean index and data
627 #clean index and data
628 if idx_path and os.path.exists(idx_path):
628 if idx_path and os.path.exists(idx_path):
629 log.debug('remove %s' % idx_path)
629 log.debug('remove %s' % idx_path)
630 shutil.rmtree(idx_path)
630 shutil.rmtree(idx_path)
631
631
632 if data_path and os.path.exists(data_path):
632 if data_path and os.path.exists(data_path):
633 log.debug('remove %s' % data_path)
633 log.debug('remove %s' % data_path)
634 shutil.rmtree(data_path)
634 shutil.rmtree(data_path)
635
635
636 #CREATE DEFAULT TEST REPOS
636 #CREATE DEFAULT TEST REPOS
637 cur_dir = dn(dn(abspath(__file__)))
637 cur_dir = dn(dn(abspath(__file__)))
638 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
638 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
639 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
639 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
640 tar.close()
640 tar.close()
641
641
642 cur_dir = dn(dn(abspath(__file__)))
642 cur_dir = dn(dn(abspath(__file__)))
643 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
643 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
644 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
644 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
645 tar.close()
645 tar.close()
646
646
647 #LOAD VCS test stuff
647 #LOAD VCS test stuff
648 from rhodecode.tests.vcs import setup_package
648 from rhodecode.tests.vcs import setup_package
649 setup_package()
649 setup_package()
650
650
651
651
652 #==============================================================================
652 #==============================================================================
653 # PASTER COMMANDS
653 # PASTER COMMANDS
654 #==============================================================================
654 #==============================================================================
655 class BasePasterCommand(Command):
655 class BasePasterCommand(Command):
656 """
656 """
657 Abstract Base Class for paster commands.
657 Abstract Base Class for paster commands.
658
658
659 The celery commands are somewhat aggressive about loading
659 The celery commands are somewhat aggressive about loading
660 celery.conf, and since our module sets the `CELERY_LOADER`
660 celery.conf, and since our module sets the `CELERY_LOADER`
661 environment variable to our loader, we have to bootstrap a bit and
661 environment variable to our loader, we have to bootstrap a bit and
662 make sure we've had a chance to load the pylons config off of the
662 make sure we've had a chance to load the pylons config off of the
663 command line, otherwise everything fails.
663 command line, otherwise everything fails.
664 """
664 """
665 min_args = 1
665 min_args = 1
666 min_args_error = "Please provide a paster config file as an argument."
666 min_args_error = "Please provide a paster config file as an argument."
667 takes_config_file = 1
667 takes_config_file = 1
668 requires_config_file = True
668 requires_config_file = True
669
669
670 def notify_msg(self, msg, log=False):
670 def notify_msg(self, msg, log=False):
671 """Make a notification to user, additionally if logger is passed
671 """Make a notification to user, additionally if logger is passed
672 it logs this action using given logger
672 it logs this action using given logger
673
673
674 :param msg: message that will be printed to user
674 :param msg: message that will be printed to user
675 :param log: logging instance, to use to additionally log this message
675 :param log: logging instance, to use to additionally log this message
676
676
677 """
677 """
678 if log and isinstance(log, logging):
678 if log and isinstance(log, logging):
679 log(msg)
679 log(msg)
680
680
681 def run(self, args):
681 def run(self, args):
682 """
682 """
683 Overrides Command.run
683 Overrides Command.run
684
684
685 Checks for a config file argument and loads it.
685 Checks for a config file argument and loads it.
686 """
686 """
687 if len(args) < self.min_args:
687 if len(args) < self.min_args:
688 raise BadCommand(
688 raise BadCommand(
689 self.min_args_error % {'min_args': self.min_args,
689 self.min_args_error % {'min_args': self.min_args,
690 'actual_args': len(args)})
690 'actual_args': len(args)})
691
691
692 # Decrement because we're going to lob off the first argument.
692 # Decrement because we're going to lob off the first argument.
693 # @@ This is hacky
693 # @@ This is hacky
694 self.min_args -= 1
694 self.min_args -= 1
695 self.bootstrap_config(args[0])
695 self.bootstrap_config(args[0])
696 self.update_parser()
696 self.update_parser()
697 return super(BasePasterCommand, self).run(args[1:])
697 return super(BasePasterCommand, self).run(args[1:])
698
698
699 def update_parser(self):
699 def update_parser(self):
700 """
700 """
701 Abstract method. Allows for the class's parser to be updated
701 Abstract method. Allows for the class's parser to be updated
702 before the superclass's `run` method is called. Necessary to
702 before the superclass's `run` method is called. Necessary to
703 allow options/arguments to be passed through to the underlying
703 allow options/arguments to be passed through to the underlying
704 celery command.
704 celery command.
705 """
705 """
706 raise NotImplementedError("Abstract Method.")
706 raise NotImplementedError("Abstract Method.")
707
707
708 def bootstrap_config(self, conf):
708 def bootstrap_config(self, conf):
709 """
709 """
710 Loads the pylons configuration.
710 Loads the pylons configuration.
711 """
711 """
712 from pylons import config as pylonsconfig
712 from pylons import config as pylonsconfig
713
713
714 self.path_to_ini_file = os.path.realpath(conf)
714 self.path_to_ini_file = os.path.realpath(conf)
715 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
715 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
716 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
716 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
717
717
718 def _init_session(self):
718 def _init_session(self):
719 """
719 """
720 Inits SqlAlchemy Session
720 Inits SqlAlchemy Session
721 """
721 """
722 logging.config.fileConfig(self.path_to_ini_file)
722 logging.config.fileConfig(self.path_to_ini_file)
723 from pylons import config
723 from pylons import config
724 from rhodecode.model import init_model
724 from rhodecode.model import init_model
725 from rhodecode.lib.utils2 import engine_from_config
725 from rhodecode.lib.utils2 import engine_from_config
726
726
727 #get to remove repos !!
727 #get to remove repos !!
728 add_cache(config)
728 add_cache(config)
729 engine = engine_from_config(config, 'sqlalchemy.db1.')
729 engine = engine_from_config(config, 'sqlalchemy.db1.')
730 init_model(engine)
730 init_model(engine)
731
731
732
732
733 def check_git_version():
733 def check_git_version():
734 """
734 """
735 Checks what version of git is installed in system, and issues a warning
735 Checks what version of git is installed in system, and issues a warning
736 if it's too old for RhodeCode to properly work.
736 if it's too old for RhodeCode to properly work.
737 """
737 """
738 from rhodecode import BACKENDS
738 from rhodecode import BACKENDS
739 from rhodecode.lib.vcs.backends.git.repository import GitRepository
739 from rhodecode.lib.vcs.backends.git.repository import GitRepository
740 from distutils.version import StrictVersion
740 from distutils.version import StrictVersion
741
741
742 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
742 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
743 _safe=True)
743 _safe=True)
744
744
745 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
745 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
746 if len(ver.split('.')) > 3:
746 if len(ver.split('.')) > 3:
747 #StrictVersion needs to be only 3 element type
747 #StrictVersion needs to be only 3 element type
748 ver = '.'.join(ver.split('.')[:3])
748 ver = '.'.join(ver.split('.')[:3])
749 try:
749 try:
750 _ver = StrictVersion(ver)
750 _ver = StrictVersion(ver)
751 except Exception:
751 except Exception:
752 _ver = StrictVersion('0.0.0')
752 _ver = StrictVersion('0.0.0')
753 stderr = traceback.format_exc()
753 stderr = traceback.format_exc()
754
754
755 req_ver = '1.7.4'
755 req_ver = '1.7.4'
756 to_old_git = False
756 to_old_git = False
757 if _ver < StrictVersion(req_ver):
757 if _ver < StrictVersion(req_ver):
758 to_old_git = True
758 to_old_git = True
759
759
760 if 'git' in BACKENDS:
760 if 'git' in BACKENDS:
761 log.debug('GIT version detected: %s' % stdout)
761 log.debug('GIT version detected: %s' % stdout)
762 if stderr:
762 if stderr:
763 log.warning('Unable to detect git version, org error was: %r' % stderr)
763 log.warning('Unable to detect git version, org error was: %r' % stderr)
764 elif to_old_git:
764 elif to_old_git:
765 log.warning('RhodeCode detected git version %s, which is too old '
765 log.warning('RhodeCode detected git version %s, which is too old '
766 'for the system to function properly. Make sure '
766 'for the system to function properly. Make sure '
767 'its version is at least %s' % (ver, req_ver))
767 'its version is at least %s' % (ver, req_ver))
768 return _ver
768 return _ver
769
769
770
770
771 @decorator.decorator
771 @decorator.decorator
772 def jsonify(func, *args, **kwargs):
772 def jsonify(func, *args, **kwargs):
773 """Action decorator that formats output for JSON
773 """Action decorator that formats output for JSON
774
774
775 Given a function that will return content, this decorator will turn
775 Given a function that will return content, this decorator will turn
776 the result into JSON, with a content-type of 'application/json' and
776 the result into JSON, with a content-type of 'application/json' and
777 output it.
777 output it.
778
778
779 """
779 """
780 from pylons.decorators.util import get_pylons
780 from pylons.decorators.util import get_pylons
781 from rhodecode.lib.ext_json import json
781 from rhodecode.lib.ext_json import json
782 pylons = get_pylons(args)
782 pylons = get_pylons(args)
783 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
783 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
784 data = func(*args, **kwargs)
784 data = func(*args, **kwargs)
785 if isinstance(data, (list, tuple)):
785 if isinstance(data, (list, tuple)):
786 msg = "JSON responses with Array envelopes are susceptible to " \
786 msg = "JSON responses with Array envelopes are susceptible to " \
787 "cross-site data leak attacks, see " \
787 "cross-site data leak attacks, see " \
788 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
788 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
789 warnings.warn(msg, Warning, 2)
789 warnings.warn(msg, Warning, 2)
790 log.warning(msg)
790 log.warning(msg)
791 log.debug("Returning JSON wrapped action output")
791 log.debug("Returning JSON wrapped action output")
792 return json.dumps(data, encoding='utf-8')
792 return json.dumps(data, encoding='utf-8')
@@ -1,436 +1,436 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user_group
3 rhodecode.model.user_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 repo group model for RhodeCode
6 repo group model for RhodeCode
7
7
8 :created_on: Jan 25, 2011
8 :created_on: Jan 25, 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 os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30 import datetime
30 import datetime
31
31
32 from rhodecode.lib.utils2 import LazyProperty
32 from rhodecode.lib.utils2 import LazyProperty
33
33
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
35 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
36 User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository
36 User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class ReposGroupModel(BaseModel):
41 class ReposGroupModel(BaseModel):
42
42
43 cls = RepoGroup
43 cls = RepoGroup
44
44
45 def __get_users_group(self, users_group):
45 def __get_users_group(self, users_group):
46 return self._get_instance(UserGroup, users_group,
46 return self._get_instance(UserGroup, users_group,
47 callback=UserGroup.get_by_group_name)
47 callback=UserGroup.get_by_group_name)
48
48
49 def _get_repos_group(self, repos_group):
49 def _get_repos_group(self, repos_group):
50 return self._get_instance(RepoGroup, repos_group,
50 return self._get_instance(RepoGroup, repos_group,
51 callback=RepoGroup.get_by_group_name)
51 callback=RepoGroup.get_by_group_name)
52
52
53 @LazyProperty
53 @LazyProperty
54 def repos_path(self):
54 def repos_path(self):
55 """
55 """
56 Get's the repositories root path from database
56 Get's the repositories root path from database
57 """
57 """
58
58
59 q = RhodeCodeUi.get_by_key('/')
59 q = RhodeCodeUi.get_by_key('/')
60 return q.ui_value
60 return q.ui_value
61
61
62 def _create_default_perms(self, new_group):
62 def _create_default_perms(self, new_group):
63 # create default permission
63 # create default permission
64 repo_group_to_perm = UserRepoGroupToPerm()
64 repo_group_to_perm = UserRepoGroupToPerm()
65 default_perm = 'group.read'
65 default_perm = 'group.read'
66 for p in User.get_by_username('default').user_perms:
66 for p in User.get_by_username('default').user_perms:
67 if p.permission.permission_name.startswith('group.'):
67 if p.permission.permission_name.startswith('group.'):
68 default_perm = p.permission.permission_name
68 default_perm = p.permission.permission_name
69 break
69 break
70
70
71 repo_group_to_perm.permission_id = self.sa.query(Permission)\
71 repo_group_to_perm.permission_id = self.sa.query(Permission)\
72 .filter(Permission.permission_name == default_perm)\
72 .filter(Permission.permission_name == default_perm)\
73 .one().permission_id
73 .one().permission_id
74
74
75 repo_group_to_perm.group = new_group
75 repo_group_to_perm.group = new_group
76 repo_group_to_perm.user_id = User.get_by_username('default').user_id
76 repo_group_to_perm.user_id = User.get_by_username('default').user_id
77
77
78 self.sa.add(repo_group_to_perm)
78 self.sa.add(repo_group_to_perm)
79
79
80 def __create_group(self, group_name):
80 def __create_group(self, group_name):
81 """
81 """
82 makes repository group on filesystem
82 makes repository group on filesystem
83
83
84 :param repo_name:
84 :param repo_name:
85 :param parent_id:
85 :param parent_id:
86 """
86 """
87
87
88 create_path = os.path.join(self.repos_path, group_name)
88 create_path = os.path.join(self.repos_path, group_name)
89 log.debug('creating new group in %s' % create_path)
89 log.debug('creating new group in %s' % create_path)
90
90
91 if os.path.isdir(create_path):
91 if os.path.isdir(create_path):
92 raise Exception('That directory already exists !')
92 raise Exception('That directory already exists !')
93
93
94 os.makedirs(create_path)
94 os.makedirs(create_path)
95
95
96 def __rename_group(self, old, new):
96 def __rename_group(self, old, new):
97 """
97 """
98 Renames a group on filesystem
98 Renames a group on filesystem
99
99
100 :param group_name:
100 :param group_name:
101 """
101 """
102
102
103 if old == new:
103 if old == new:
104 log.debug('skipping group rename')
104 log.debug('skipping group rename')
105 return
105 return
106
106
107 log.debug('renaming repos group from %s to %s' % (old, new))
107 log.debug('renaming repository group from %s to %s' % (old, new))
108
108
109 old_path = os.path.join(self.repos_path, old)
109 old_path = os.path.join(self.repos_path, old)
110 new_path = os.path.join(self.repos_path, new)
110 new_path = os.path.join(self.repos_path, new)
111
111
112 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
112 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
113
113
114 if os.path.isdir(new_path):
114 if os.path.isdir(new_path):
115 raise Exception('Was trying to rename to already '
115 raise Exception('Was trying to rename to already '
116 'existing dir %s' % new_path)
116 'existing dir %s' % new_path)
117 shutil.move(old_path, new_path)
117 shutil.move(old_path, new_path)
118
118
119 def __delete_group(self, group, force_delete=False):
119 def __delete_group(self, group, force_delete=False):
120 """
120 """
121 Deletes a group from a filesystem
121 Deletes a group from a filesystem
122
122
123 :param group: instance of group from database
123 :param group: instance of group from database
124 :param force_delete: use shutil rmtree to remove all objects
124 :param force_delete: use shutil rmtree to remove all objects
125 """
125 """
126 paths = group.full_path.split(RepoGroup.url_sep())
126 paths = group.full_path.split(RepoGroup.url_sep())
127 paths = os.sep.join(paths)
127 paths = os.sep.join(paths)
128
128
129 rm_path = os.path.join(self.repos_path, paths)
129 rm_path = os.path.join(self.repos_path, paths)
130 log.info("Removing group %s" % (rm_path))
130 log.info("Removing group %s" % (rm_path))
131 # delete only if that path really exists
131 # delete only if that path really exists
132 if os.path.isdir(rm_path):
132 if os.path.isdir(rm_path):
133 if force_delete:
133 if force_delete:
134 shutil.rmtree(rm_path)
134 shutil.rmtree(rm_path)
135 else:
135 else:
136 #archive that group`
136 #archive that group`
137 _now = datetime.datetime.now()
137 _now = datetime.datetime.now()
138 _ms = str(_now.microsecond).rjust(6, '0')
138 _ms = str(_now.microsecond).rjust(6, '0')
139 _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
139 _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
140 group.name)
140 group.name)
141 shutil.move(rm_path, os.path.join(self.repos_path, _d))
141 shutil.move(rm_path, os.path.join(self.repos_path, _d))
142
142
143 def create(self, group_name, group_description, owner, parent=None, just_db=False):
143 def create(self, group_name, group_description, owner, parent=None, just_db=False):
144 try:
144 try:
145 new_repos_group = RepoGroup()
145 new_repos_group = RepoGroup()
146 new_repos_group.group_description = group_description or group_name
146 new_repos_group.group_description = group_description or group_name
147 new_repos_group.parent_group = self._get_repos_group(parent)
147 new_repos_group.parent_group = self._get_repos_group(parent)
148 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
148 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
149
149
150 self.sa.add(new_repos_group)
150 self.sa.add(new_repos_group)
151 self._create_default_perms(new_repos_group)
151 self._create_default_perms(new_repos_group)
152
152
153 #create an ADMIN permission for owner, later owner should go into
153 #create an ADMIN permission for owner, later owner should go into
154 #the owner field of groups
154 #the owner field of groups
155 self.grant_user_permission(repos_group=new_repos_group,
155 self.grant_user_permission(repos_group=new_repos_group,
156 user=owner, perm='group.admin')
156 user=owner, perm='group.admin')
157
157
158 if not just_db:
158 if not just_db:
159 # we need to flush here, in order to check if database won't
159 # we need to flush here, in order to check if database won't
160 # throw any exceptions, create filesystem dirs at the very end
160 # throw any exceptions, create filesystem dirs at the very end
161 self.sa.flush()
161 self.sa.flush()
162 self.__create_group(new_repos_group.group_name)
162 self.__create_group(new_repos_group.group_name)
163
163
164 return new_repos_group
164 return new_repos_group
165 except Exception:
165 except Exception:
166 log.error(traceback.format_exc())
166 log.error(traceback.format_exc())
167 raise
167 raise
168
168
169 def _update_permissions(self, repos_group, perms_new=None,
169 def _update_permissions(self, repos_group, perms_new=None,
170 perms_updates=None, recursive=False):
170 perms_updates=None, recursive=False):
171 from rhodecode.model.repo import RepoModel
171 from rhodecode.model.repo import RepoModel
172 if not perms_new:
172 if not perms_new:
173 perms_new = []
173 perms_new = []
174 if not perms_updates:
174 if not perms_updates:
175 perms_updates = []
175 perms_updates = []
176
176
177 def _set_perm_user(obj, user, perm):
177 def _set_perm_user(obj, user, perm):
178 if isinstance(obj, RepoGroup):
178 if isinstance(obj, RepoGroup):
179 ReposGroupModel().grant_user_permission(
179 ReposGroupModel().grant_user_permission(
180 repos_group=obj, user=user, perm=perm
180 repos_group=obj, user=user, perm=perm
181 )
181 )
182 elif isinstance(obj, Repository):
182 elif isinstance(obj, Repository):
183 #we do this ONLY IF repository is non-private
183 #we do this ONLY IF repository is non-private
184 if obj.private:
184 if obj.private:
185 return
185 return
186
186
187 # we set group permission but we have to switch to repo
187 # we set group permission but we have to switch to repo
188 # permission
188 # permission
189 perm = perm.replace('group.', 'repository.')
189 perm = perm.replace('group.', 'repository.')
190 RepoModel().grant_user_permission(
190 RepoModel().grant_user_permission(
191 repo=obj, user=user, perm=perm
191 repo=obj, user=user, perm=perm
192 )
192 )
193
193
194 def _set_perm_group(obj, users_group, perm):
194 def _set_perm_group(obj, users_group, perm):
195 if isinstance(obj, RepoGroup):
195 if isinstance(obj, RepoGroup):
196 ReposGroupModel().grant_users_group_permission(
196 ReposGroupModel().grant_users_group_permission(
197 repos_group=obj, group_name=users_group, perm=perm
197 repos_group=obj, group_name=users_group, perm=perm
198 )
198 )
199 elif isinstance(obj, Repository):
199 elif isinstance(obj, Repository):
200 # we set group permission but we have to switch to repo
200 # we set group permission but we have to switch to repo
201 # permission
201 # permission
202 perm = perm.replace('group.', 'repository.')
202 perm = perm.replace('group.', 'repository.')
203 RepoModel().grant_users_group_permission(
203 RepoModel().grant_users_group_permission(
204 repo=obj, group_name=users_group, perm=perm
204 repo=obj, group_name=users_group, perm=perm
205 )
205 )
206 updates = []
206 updates = []
207 log.debug('Now updating permissions for %s in recursive mode:%s'
207 log.debug('Now updating permissions for %s in recursive mode:%s'
208 % (repos_group, recursive))
208 % (repos_group, recursive))
209
209
210 for obj in repos_group.recursive_groups_and_repos():
210 for obj in repos_group.recursive_groups_and_repos():
211 #obj is an instance of a group or repositories in that group
211 #obj is an instance of a group or repositories in that group
212 if not recursive:
212 if not recursive:
213 obj = repos_group
213 obj = repos_group
214
214
215 # update permissions
215 # update permissions
216 for member, perm, member_type in perms_updates:
216 for member, perm, member_type in perms_updates:
217 ## set for user
217 ## set for user
218 if member_type == 'user':
218 if member_type == 'user':
219 # this updates also current one if found
219 # this updates also current one if found
220 _set_perm_user(obj, user=member, perm=perm)
220 _set_perm_user(obj, user=member, perm=perm)
221 ## set for user group
221 ## set for user group
222 else:
222 else:
223 _set_perm_group(obj, users_group=member, perm=perm)
223 _set_perm_group(obj, users_group=member, perm=perm)
224 # set new permissions
224 # set new permissions
225 for member, perm, member_type in perms_new:
225 for member, perm, member_type in perms_new:
226 if member_type == 'user':
226 if member_type == 'user':
227 _set_perm_user(obj, user=member, perm=perm)
227 _set_perm_user(obj, user=member, perm=perm)
228 else:
228 else:
229 _set_perm_group(obj, users_group=member, perm=perm)
229 _set_perm_group(obj, users_group=member, perm=perm)
230 updates.append(obj)
230 updates.append(obj)
231 #if it's not recursive call
231 #if it's not recursive call
232 # break the loop and don't proceed with other changes
232 # break the loop and don't proceed with other changes
233 if not recursive:
233 if not recursive:
234 break
234 break
235 return updates
235 return updates
236
236
237 def update(self, repos_group, form_data):
237 def update(self, repos_group, form_data):
238
238
239 try:
239 try:
240 repos_group = self._get_repos_group(repos_group)
240 repos_group = self._get_repos_group(repos_group)
241 recursive = form_data['recursive']
241 recursive = form_data['recursive']
242 # iterate over all members(if in recursive mode) of this groups and
242 # iterate over all members(if in recursive mode) of this groups and
243 # set the permissions !
243 # set the permissions !
244 # this can be potentially heavy operation
244 # this can be potentially heavy operation
245 self._update_permissions(repos_group, form_data['perms_new'],
245 self._update_permissions(repos_group, form_data['perms_new'],
246 form_data['perms_updates'], recursive)
246 form_data['perms_updates'], recursive)
247
247
248 old_path = repos_group.full_path
248 old_path = repos_group.full_path
249
249
250 # change properties
250 # change properties
251 repos_group.group_description = form_data['group_description']
251 repos_group.group_description = form_data['group_description']
252 repos_group.group_parent_id = form_data['group_parent_id']
252 repos_group.group_parent_id = form_data['group_parent_id']
253 repos_group.enable_locking = form_data['enable_locking']
253 repos_group.enable_locking = form_data['enable_locking']
254
254
255 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
255 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
256 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
256 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
257 new_path = repos_group.full_path
257 new_path = repos_group.full_path
258 self.sa.add(repos_group)
258 self.sa.add(repos_group)
259
259
260 # iterate over all members of this groups and do fixes
260 # iterate over all members of this groups and do fixes
261 # set locking if given
261 # set locking if given
262 # if obj is a repoGroup also fix the name of the group according
262 # if obj is a repoGroup also fix the name of the group according
263 # to the parent
263 # to the parent
264 # if obj is a Repo fix it's name
264 # if obj is a Repo fix it's name
265 # this can be potentially heavy operation
265 # this can be potentially heavy operation
266 for obj in repos_group.recursive_groups_and_repos():
266 for obj in repos_group.recursive_groups_and_repos():
267 #set the value from it's parent
267 #set the value from it's parent
268 obj.enable_locking = repos_group.enable_locking
268 obj.enable_locking = repos_group.enable_locking
269 if isinstance(obj, RepoGroup):
269 if isinstance(obj, RepoGroup):
270 new_name = obj.get_new_name(obj.name)
270 new_name = obj.get_new_name(obj.name)
271 log.debug('Fixing group %s to new name %s' \
271 log.debug('Fixing group %s to new name %s' \
272 % (obj.group_name, new_name))
272 % (obj.group_name, new_name))
273 obj.group_name = new_name
273 obj.group_name = new_name
274 elif isinstance(obj, Repository):
274 elif isinstance(obj, Repository):
275 # we need to get all repositories from this new group and
275 # we need to get all repositories from this new group and
276 # rename them accordingly to new group path
276 # rename them accordingly to new group path
277 new_name = obj.get_new_name(obj.just_name)
277 new_name = obj.get_new_name(obj.just_name)
278 log.debug('Fixing repo %s to new name %s' \
278 log.debug('Fixing repo %s to new name %s' \
279 % (obj.repo_name, new_name))
279 % (obj.repo_name, new_name))
280 obj.repo_name = new_name
280 obj.repo_name = new_name
281 self.sa.add(obj)
281 self.sa.add(obj)
282
282
283 self.__rename_group(old_path, new_path)
283 self.__rename_group(old_path, new_path)
284
284
285 return repos_group
285 return repos_group
286 except Exception:
286 except Exception:
287 log.error(traceback.format_exc())
287 log.error(traceback.format_exc())
288 raise
288 raise
289
289
290 def delete(self, repos_group, force_delete=False):
290 def delete(self, repos_group, force_delete=False):
291 repos_group = self._get_repos_group(repos_group)
291 repos_group = self._get_repos_group(repos_group)
292 try:
292 try:
293 self.sa.delete(repos_group)
293 self.sa.delete(repos_group)
294 self.__delete_group(repos_group, force_delete)
294 self.__delete_group(repos_group, force_delete)
295 except Exception:
295 except Exception:
296 log.error('Error removing repos_group %s' % repos_group)
296 log.error('Error removing repos_group %s' % repos_group)
297 raise
297 raise
298
298
299 def delete_permission(self, repos_group, obj, obj_type, recursive):
299 def delete_permission(self, repos_group, obj, obj_type, recursive):
300 """
300 """
301 Revokes permission for repos_group for given obj(user or users_group),
301 Revokes permission for repos_group for given obj(user or users_group),
302 obj_type can be user or user group
302 obj_type can be user or user group
303
303
304 :param repos_group:
304 :param repos_group:
305 :param obj: user or user group id
305 :param obj: user or user group id
306 :param obj_type: user or user group type
306 :param obj_type: user or user group type
307 :param recursive: recurse to all children of group
307 :param recursive: recurse to all children of group
308 """
308 """
309 from rhodecode.model.repo import RepoModel
309 from rhodecode.model.repo import RepoModel
310 repos_group = self._get_repos_group(repos_group)
310 repos_group = self._get_repos_group(repos_group)
311
311
312 for el in repos_group.recursive_groups_and_repos():
312 for el in repos_group.recursive_groups_and_repos():
313 if not recursive:
313 if not recursive:
314 # if we don't recurse set the permission on only the top level
314 # if we don't recurse set the permission on only the top level
315 # object
315 # object
316 el = repos_group
316 el = repos_group
317
317
318 if isinstance(el, RepoGroup):
318 if isinstance(el, RepoGroup):
319 if obj_type == 'user':
319 if obj_type == 'user':
320 ReposGroupModel().revoke_user_permission(el, user=obj)
320 ReposGroupModel().revoke_user_permission(el, user=obj)
321 elif obj_type == 'users_group':
321 elif obj_type == 'users_group':
322 ReposGroupModel().revoke_users_group_permission(el, group_name=obj)
322 ReposGroupModel().revoke_users_group_permission(el, group_name=obj)
323 else:
323 else:
324 raise Exception('undefined object type %s' % obj_type)
324 raise Exception('undefined object type %s' % obj_type)
325 elif isinstance(el, Repository):
325 elif isinstance(el, Repository):
326 if obj_type == 'user':
326 if obj_type == 'user':
327 RepoModel().revoke_user_permission(el, user=obj)
327 RepoModel().revoke_user_permission(el, user=obj)
328 elif obj_type == 'users_group':
328 elif obj_type == 'users_group':
329 RepoModel().revoke_users_group_permission(el, group_name=obj)
329 RepoModel().revoke_users_group_permission(el, group_name=obj)
330 else:
330 else:
331 raise Exception('undefined object type %s' % obj_type)
331 raise Exception('undefined object type %s' % obj_type)
332
332
333 #if it's not recursive call
333 #if it's not recursive call
334 # break the loop and don't proceed with other changes
334 # break the loop and don't proceed with other changes
335 if not recursive:
335 if not recursive:
336 break
336 break
337
337
338 def grant_user_permission(self, repos_group, user, perm):
338 def grant_user_permission(self, repos_group, user, perm):
339 """
339 """
340 Grant permission for user on given repository group, or update
340 Grant permission for user on given repository group, or update
341 existing one if found
341 existing one if found
342
342
343 :param repos_group: Instance of ReposGroup, repositories_group_id,
343 :param repos_group: Instance of ReposGroup, repositories_group_id,
344 or repositories_group name
344 or repositories_group name
345 :param user: Instance of User, user_id or username
345 :param user: Instance of User, user_id or username
346 :param perm: Instance of Permission, or permission_name
346 :param perm: Instance of Permission, or permission_name
347 """
347 """
348
348
349 repos_group = self._get_repos_group(repos_group)
349 repos_group = self._get_repos_group(repos_group)
350 user = self._get_user(user)
350 user = self._get_user(user)
351 permission = self._get_perm(perm)
351 permission = self._get_perm(perm)
352
352
353 # check if we have that permission already
353 # check if we have that permission already
354 obj = self.sa.query(UserRepoGroupToPerm)\
354 obj = self.sa.query(UserRepoGroupToPerm)\
355 .filter(UserRepoGroupToPerm.user == user)\
355 .filter(UserRepoGroupToPerm.user == user)\
356 .filter(UserRepoGroupToPerm.group == repos_group)\
356 .filter(UserRepoGroupToPerm.group == repos_group)\
357 .scalar()
357 .scalar()
358 if obj is None:
358 if obj is None:
359 # create new !
359 # create new !
360 obj = UserRepoGroupToPerm()
360 obj = UserRepoGroupToPerm()
361 obj.group = repos_group
361 obj.group = repos_group
362 obj.user = user
362 obj.user = user
363 obj.permission = permission
363 obj.permission = permission
364 self.sa.add(obj)
364 self.sa.add(obj)
365 log.debug('Granted perm %s to %s on %s' % (perm, user, repos_group))
365 log.debug('Granted perm %s to %s on %s' % (perm, user, repos_group))
366
366
367 def revoke_user_permission(self, repos_group, user):
367 def revoke_user_permission(self, repos_group, user):
368 """
368 """
369 Revoke permission for user on given repository group
369 Revoke permission for user on given repository group
370
370
371 :param repos_group: Instance of ReposGroup, repositories_group_id,
371 :param repos_group: Instance of ReposGroup, repositories_group_id,
372 or repositories_group name
372 or repositories_group name
373 :param user: Instance of User, user_id or username
373 :param user: Instance of User, user_id or username
374 """
374 """
375
375
376 repos_group = self._get_repos_group(repos_group)
376 repos_group = self._get_repos_group(repos_group)
377 user = self._get_user(user)
377 user = self._get_user(user)
378
378
379 obj = self.sa.query(UserRepoGroupToPerm)\
379 obj = self.sa.query(UserRepoGroupToPerm)\
380 .filter(UserRepoGroupToPerm.user == user)\
380 .filter(UserRepoGroupToPerm.user == user)\
381 .filter(UserRepoGroupToPerm.group == repos_group)\
381 .filter(UserRepoGroupToPerm.group == repos_group)\
382 .scalar()
382 .scalar()
383 if obj:
383 if obj:
384 self.sa.delete(obj)
384 self.sa.delete(obj)
385 log.debug('Revoked perm on %s on %s' % (repos_group, user))
385 log.debug('Revoked perm on %s on %s' % (repos_group, user))
386
386
387 def grant_users_group_permission(self, repos_group, group_name, perm):
387 def grant_users_group_permission(self, repos_group, group_name, perm):
388 """
388 """
389 Grant permission for user group on given repository group, or update
389 Grant permission for user group on given repository group, or update
390 existing one if found
390 existing one if found
391
391
392 :param repos_group: Instance of ReposGroup, repositories_group_id,
392 :param repos_group: Instance of ReposGroup, repositories_group_id,
393 or repositories_group name
393 or repositories_group name
394 :param group_name: Instance of UserGroup, users_group_id,
394 :param group_name: Instance of UserGroup, users_group_id,
395 or user group name
395 or user group name
396 :param perm: Instance of Permission, or permission_name
396 :param perm: Instance of Permission, or permission_name
397 """
397 """
398 repos_group = self._get_repos_group(repos_group)
398 repos_group = self._get_repos_group(repos_group)
399 group_name = self.__get_users_group(group_name)
399 group_name = self.__get_users_group(group_name)
400 permission = self._get_perm(perm)
400 permission = self._get_perm(perm)
401
401
402 # check if we have that permission already
402 # check if we have that permission already
403 obj = self.sa.query(UserGroupRepoGroupToPerm)\
403 obj = self.sa.query(UserGroupRepoGroupToPerm)\
404 .filter(UserGroupRepoGroupToPerm.group == repos_group)\
404 .filter(UserGroupRepoGroupToPerm.group == repos_group)\
405 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
405 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
406 .scalar()
406 .scalar()
407
407
408 if obj is None:
408 if obj is None:
409 # create new
409 # create new
410 obj = UserGroupRepoGroupToPerm()
410 obj = UserGroupRepoGroupToPerm()
411
411
412 obj.group = repos_group
412 obj.group = repos_group
413 obj.users_group = group_name
413 obj.users_group = group_name
414 obj.permission = permission
414 obj.permission = permission
415 self.sa.add(obj)
415 self.sa.add(obj)
416 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repos_group))
416 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repos_group))
417
417
418 def revoke_users_group_permission(self, repos_group, group_name):
418 def revoke_users_group_permission(self, repos_group, group_name):
419 """
419 """
420 Revoke permission for user group on given repository group
420 Revoke permission for user group on given repository group
421
421
422 :param repos_group: Instance of ReposGroup, repositories_group_id,
422 :param repos_group: Instance of ReposGroup, repositories_group_id,
423 or repositories_group name
423 or repositories_group name
424 :param group_name: Instance of UserGroup, users_group_id,
424 :param group_name: Instance of UserGroup, users_group_id,
425 or user group name
425 or user group name
426 """
426 """
427 repos_group = self._get_repos_group(repos_group)
427 repos_group = self._get_repos_group(repos_group)
428 group_name = self.__get_users_group(group_name)
428 group_name = self.__get_users_group(group_name)
429
429
430 obj = self.sa.query(UserGroupRepoGroupToPerm)\
430 obj = self.sa.query(UserGroupRepoGroupToPerm)\
431 .filter(UserGroupRepoGroupToPerm.group == repos_group)\
431 .filter(UserGroupRepoGroupToPerm.group == repos_group)\
432 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
432 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
433 .scalar()
433 .scalar()
434 if obj:
434 if obj:
435 self.sa.delete(obj)
435 self.sa.delete(obj)
436 log.debug('Revoked perm to %s on %s' % (repos_group, group_name))
436 log.debug('Revoked perm to %s on %s' % (repos_group, group_name))
@@ -1,763 +1,763 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user
3 rhodecode.model.user
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 users model for RhodeCode
6 users 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
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import itertools
28 import itertools
29 import collections
29 import collections
30 from pylons import url
30 from pylons import url
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.orm import joinedload
34 from sqlalchemy.orm import joinedload
35
35
36 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
36 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
37 from rhodecode.lib.caching_query import FromCache
37 from rhodecode.lib.caching_query import FromCache
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
39 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
40 UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
40 UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
41 Notification, RepoGroup, UserRepoGroupToPerm, UserGroupRepoGroupToPerm, \
41 Notification, RepoGroup, UserRepoGroupToPerm, UserGroupRepoGroupToPerm, \
42 UserEmailMap, UserIpMap
42 UserEmailMap, UserIpMap
43 from rhodecode.lib.exceptions import DefaultUserException, \
43 from rhodecode.lib.exceptions import DefaultUserException, \
44 UserOwnsReposException
44 UserOwnsReposException
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46
46
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50 PERM_WEIGHTS = Permission.PERM_WEIGHTS
50 PERM_WEIGHTS = Permission.PERM_WEIGHTS
51
51
52
52
53 class UserModel(BaseModel):
53 class UserModel(BaseModel):
54 cls = User
54 cls = User
55
55
56 def get(self, user_id, cache=False):
56 def get(self, user_id, cache=False):
57 user = self.sa.query(User)
57 user = self.sa.query(User)
58 if cache:
58 if cache:
59 user = user.options(FromCache("sql_cache_short",
59 user = user.options(FromCache("sql_cache_short",
60 "get_user_%s" % user_id))
60 "get_user_%s" % user_id))
61 return user.get(user_id)
61 return user.get(user_id)
62
62
63 def get_user(self, user):
63 def get_user(self, user):
64 return self._get_user(user)
64 return self._get_user(user)
65
65
66 def get_by_username(self, username, cache=False, case_insensitive=False):
66 def get_by_username(self, username, cache=False, case_insensitive=False):
67
67
68 if case_insensitive:
68 if case_insensitive:
69 user = self.sa.query(User).filter(User.username.ilike(username))
69 user = self.sa.query(User).filter(User.username.ilike(username))
70 else:
70 else:
71 user = self.sa.query(User)\
71 user = self.sa.query(User)\
72 .filter(User.username == username)
72 .filter(User.username == username)
73 if cache:
73 if cache:
74 user = user.options(FromCache("sql_cache_short",
74 user = user.options(FromCache("sql_cache_short",
75 "get_user_%s" % username))
75 "get_user_%s" % username))
76 return user.scalar()
76 return user.scalar()
77
77
78 def get_by_email(self, email, cache=False, case_insensitive=False):
78 def get_by_email(self, email, cache=False, case_insensitive=False):
79 return User.get_by_email(email, case_insensitive, cache)
79 return User.get_by_email(email, case_insensitive, cache)
80
80
81 def get_by_api_key(self, api_key, cache=False):
81 def get_by_api_key(self, api_key, cache=False):
82 return User.get_by_api_key(api_key, cache)
82 return User.get_by_api_key(api_key, cache)
83
83
84 def create(self, form_data):
84 def create(self, form_data):
85 from rhodecode.lib.auth import get_crypt_password
85 from rhodecode.lib.auth import get_crypt_password
86 try:
86 try:
87 new_user = User()
87 new_user = User()
88 for k, v in form_data.items():
88 for k, v in form_data.items():
89 if k == 'password':
89 if k == 'password':
90 v = get_crypt_password(v)
90 v = get_crypt_password(v)
91 if k == 'firstname':
91 if k == 'firstname':
92 k = 'name'
92 k = 'name'
93 setattr(new_user, k, v)
93 setattr(new_user, k, v)
94
94
95 new_user.api_key = generate_api_key(form_data['username'])
95 new_user.api_key = generate_api_key(form_data['username'])
96 self.sa.add(new_user)
96 self.sa.add(new_user)
97 return new_user
97 return new_user
98 except Exception:
98 except Exception:
99 log.error(traceback.format_exc())
99 log.error(traceback.format_exc())
100 raise
100 raise
101
101
102 def create_or_update(self, username, password, email, firstname='',
102 def create_or_update(self, username, password, email, firstname='',
103 lastname='', active=True, admin=False, ldap_dn=None):
103 lastname='', active=True, admin=False, ldap_dn=None):
104 """
104 """
105 Creates a new instance if not found, or updates current one
105 Creates a new instance if not found, or updates current one
106
106
107 :param username:
107 :param username:
108 :param password:
108 :param password:
109 :param email:
109 :param email:
110 :param active:
110 :param active:
111 :param firstname:
111 :param firstname:
112 :param lastname:
112 :param lastname:
113 :param active:
113 :param active:
114 :param admin:
114 :param admin:
115 :param ldap_dn:
115 :param ldap_dn:
116 """
116 """
117
117
118 from rhodecode.lib.auth import get_crypt_password
118 from rhodecode.lib.auth import get_crypt_password
119
119
120 log.debug('Checking for %s account in RhodeCode database' % username)
120 log.debug('Checking for %s account in RhodeCode database' % username)
121 user = User.get_by_username(username, case_insensitive=True)
121 user = User.get_by_username(username, case_insensitive=True)
122 if user is None:
122 if user is None:
123 log.debug('creating new user %s' % username)
123 log.debug('creating new user %s' % username)
124 new_user = User()
124 new_user = User()
125 edit = False
125 edit = False
126 else:
126 else:
127 log.debug('updating user %s' % username)
127 log.debug('updating user %s' % username)
128 new_user = user
128 new_user = user
129 edit = True
129 edit = True
130
130
131 try:
131 try:
132 new_user.username = username
132 new_user.username = username
133 new_user.admin = admin
133 new_user.admin = admin
134 # set password only if creating an user or password is changed
134 # set password only if creating an user or password is changed
135 if not edit or user.password != password:
135 if not edit or user.password != password:
136 new_user.password = get_crypt_password(password)
136 new_user.password = get_crypt_password(password)
137 new_user.api_key = generate_api_key(username)
137 new_user.api_key = generate_api_key(username)
138 new_user.email = email
138 new_user.email = email
139 new_user.active = active
139 new_user.active = active
140 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
140 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
141 new_user.name = firstname
141 new_user.name = firstname
142 new_user.lastname = lastname
142 new_user.lastname = lastname
143 self.sa.add(new_user)
143 self.sa.add(new_user)
144 return new_user
144 return new_user
145 except (DatabaseError,):
145 except (DatabaseError,):
146 log.error(traceback.format_exc())
146 log.error(traceback.format_exc())
147 raise
147 raise
148
148
149 def create_for_container_auth(self, username, attrs):
149 def create_for_container_auth(self, username, attrs):
150 """
150 """
151 Creates the given user if it's not already in the database
151 Creates the given user if it's not already in the database
152
152
153 :param username:
153 :param username:
154 :param attrs:
154 :param attrs:
155 """
155 """
156 if self.get_by_username(username, case_insensitive=True) is None:
156 if self.get_by_username(username, case_insensitive=True) is None:
157
157
158 # autogenerate email for container account without one
158 # autogenerate email for container account without one
159 generate_email = lambda usr: '%s@container_auth.account' % usr
159 generate_email = lambda usr: '%s@container_auth.account' % usr
160
160
161 try:
161 try:
162 new_user = User()
162 new_user = User()
163 new_user.username = username
163 new_user.username = username
164 new_user.password = None
164 new_user.password = None
165 new_user.api_key = generate_api_key(username)
165 new_user.api_key = generate_api_key(username)
166 new_user.email = attrs['email']
166 new_user.email = attrs['email']
167 new_user.active = attrs.get('active', True)
167 new_user.active = attrs.get('active', True)
168 new_user.name = attrs['name'] or generate_email(username)
168 new_user.name = attrs['name'] or generate_email(username)
169 new_user.lastname = attrs['lastname']
169 new_user.lastname = attrs['lastname']
170
170
171 self.sa.add(new_user)
171 self.sa.add(new_user)
172 return new_user
172 return new_user
173 except (DatabaseError,):
173 except (DatabaseError,):
174 log.error(traceback.format_exc())
174 log.error(traceback.format_exc())
175 self.sa.rollback()
175 self.sa.rollback()
176 raise
176 raise
177 log.debug('User %s already exists. Skipping creation of account'
177 log.debug('User %s already exists. Skipping creation of account'
178 ' for container auth.', username)
178 ' for container auth.', username)
179 return None
179 return None
180
180
181 def create_ldap(self, username, password, user_dn, attrs):
181 def create_ldap(self, username, password, user_dn, attrs):
182 """
182 """
183 Checks if user is in database, if not creates this user marked
183 Checks if user is in database, if not creates this user marked
184 as ldap user
184 as ldap user
185
185
186 :param username:
186 :param username:
187 :param password:
187 :param password:
188 :param user_dn:
188 :param user_dn:
189 :param attrs:
189 :param attrs:
190 """
190 """
191 from rhodecode.lib.auth import get_crypt_password
191 from rhodecode.lib.auth import get_crypt_password
192 log.debug('Checking for such ldap account in RhodeCode database')
192 log.debug('Checking for such ldap account in RhodeCode database')
193 if self.get_by_username(username, case_insensitive=True) is None:
193 if self.get_by_username(username, case_insensitive=True) is None:
194
194
195 # autogenerate email for ldap account without one
195 # autogenerate email for ldap account without one
196 generate_email = lambda usr: '%s@ldap.account' % usr
196 generate_email = lambda usr: '%s@ldap.account' % usr
197
197
198 try:
198 try:
199 new_user = User()
199 new_user = User()
200 username = username.lower()
200 username = username.lower()
201 # add ldap account always lowercase
201 # add ldap account always lowercase
202 new_user.username = username
202 new_user.username = username
203 new_user.password = get_crypt_password(password)
203 new_user.password = get_crypt_password(password)
204 new_user.api_key = generate_api_key(username)
204 new_user.api_key = generate_api_key(username)
205 new_user.email = attrs['email'] or generate_email(username)
205 new_user.email = attrs['email'] or generate_email(username)
206 new_user.active = attrs.get('active', True)
206 new_user.active = attrs.get('active', True)
207 new_user.ldap_dn = safe_unicode(user_dn)
207 new_user.ldap_dn = safe_unicode(user_dn)
208 new_user.name = attrs['name']
208 new_user.name = attrs['name']
209 new_user.lastname = attrs['lastname']
209 new_user.lastname = attrs['lastname']
210
210
211 self.sa.add(new_user)
211 self.sa.add(new_user)
212 return new_user
212 return new_user
213 except (DatabaseError,):
213 except (DatabaseError,):
214 log.error(traceback.format_exc())
214 log.error(traceback.format_exc())
215 self.sa.rollback()
215 self.sa.rollback()
216 raise
216 raise
217 log.debug('this %s user exists skipping creation of ldap account',
217 log.debug('this %s user exists skipping creation of ldap account',
218 username)
218 username)
219 return None
219 return None
220
220
221 def create_registration(self, form_data):
221 def create_registration(self, form_data):
222 from rhodecode.model.notification import NotificationModel
222 from rhodecode.model.notification import NotificationModel
223
223
224 try:
224 try:
225 form_data['admin'] = False
225 form_data['admin'] = False
226 new_user = self.create(form_data)
226 new_user = self.create(form_data)
227
227
228 self.sa.add(new_user)
228 self.sa.add(new_user)
229 self.sa.flush()
229 self.sa.flush()
230
230
231 # notification to admins
231 # notification to admins
232 subject = _('new user registration')
232 subject = _('new user registration')
233 body = ('New user registration\n'
233 body = ('New user registration\n'
234 '---------------------\n'
234 '---------------------\n'
235 '- Username: %s\n'
235 '- Username: %s\n'
236 '- Full Name: %s\n'
236 '- Full Name: %s\n'
237 '- Email: %s\n')
237 '- Email: %s\n')
238 body = body % (new_user.username, new_user.full_name,
238 body = body % (new_user.username, new_user.full_name,
239 new_user.email)
239 new_user.email)
240 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
240 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
241 kw = {'registered_user_url': edit_url}
241 kw = {'registered_user_url': edit_url}
242 NotificationModel().create(created_by=new_user, subject=subject,
242 NotificationModel().create(created_by=new_user, subject=subject,
243 body=body, recipients=None,
243 body=body, recipients=None,
244 type_=Notification.TYPE_REGISTRATION,
244 type_=Notification.TYPE_REGISTRATION,
245 email_kwargs=kw)
245 email_kwargs=kw)
246
246
247 except Exception:
247 except Exception:
248 log.error(traceback.format_exc())
248 log.error(traceback.format_exc())
249 raise
249 raise
250
250
251 def update(self, user_id, form_data, skip_attrs=[]):
251 def update(self, user_id, form_data, skip_attrs=[]):
252 from rhodecode.lib.auth import get_crypt_password
252 from rhodecode.lib.auth import get_crypt_password
253 try:
253 try:
254 user = self.get(user_id, cache=False)
254 user = self.get(user_id, cache=False)
255 if user.username == 'default':
255 if user.username == 'default':
256 raise DefaultUserException(
256 raise DefaultUserException(
257 _("You can't Edit this user since it's"
257 _("You can't Edit this user since it's"
258 " crucial for entire application"))
258 " crucial for entire application"))
259
259
260 for k, v in form_data.items():
260 for k, v in form_data.items():
261 if k in skip_attrs:
261 if k in skip_attrs:
262 continue
262 continue
263 if k == 'new_password' and v:
263 if k == 'new_password' and v:
264 user.password = get_crypt_password(v)
264 user.password = get_crypt_password(v)
265 user.api_key = generate_api_key(user.username)
265 user.api_key = generate_api_key(user.username)
266 else:
266 else:
267 if k == 'firstname':
267 if k == 'firstname':
268 k = 'name'
268 k = 'name'
269 setattr(user, k, v)
269 setattr(user, k, v)
270 self.sa.add(user)
270 self.sa.add(user)
271 except Exception:
271 except Exception:
272 log.error(traceback.format_exc())
272 log.error(traceback.format_exc())
273 raise
273 raise
274
274
275 def update_user(self, user, **kwargs):
275 def update_user(self, user, **kwargs):
276 from rhodecode.lib.auth import get_crypt_password
276 from rhodecode.lib.auth import get_crypt_password
277 try:
277 try:
278 user = self._get_user(user)
278 user = self._get_user(user)
279 if user.username == 'default':
279 if user.username == 'default':
280 raise DefaultUserException(
280 raise DefaultUserException(
281 _("You can't Edit this user since it's"
281 _("You can't Edit this user since it's"
282 " crucial for entire application")
282 " crucial for entire application")
283 )
283 )
284
284
285 for k, v in kwargs.items():
285 for k, v in kwargs.items():
286 if k == 'password' and v:
286 if k == 'password' and v:
287 v = get_crypt_password(v)
287 v = get_crypt_password(v)
288 user.api_key = generate_api_key(user.username)
288 user.api_key = generate_api_key(user.username)
289
289
290 setattr(user, k, v)
290 setattr(user, k, v)
291 self.sa.add(user)
291 self.sa.add(user)
292 return user
292 return user
293 except Exception:
293 except Exception:
294 log.error(traceback.format_exc())
294 log.error(traceback.format_exc())
295 raise
295 raise
296
296
297 def delete(self, user):
297 def delete(self, user):
298 user = self._get_user(user)
298 user = self._get_user(user)
299
299
300 try:
300 try:
301 if user.username == 'default':
301 if user.username == 'default':
302 raise DefaultUserException(
302 raise DefaultUserException(
303 _(u"You can't remove this user since it's"
303 _(u"You can't remove this user since it's"
304 " crucial for entire application")
304 " crucial for entire application")
305 )
305 )
306 if user.repositories:
306 if user.repositories:
307 repos = [x.repo_name for x in user.repositories]
307 repos = [x.repo_name for x in user.repositories]
308 raise UserOwnsReposException(
308 raise UserOwnsReposException(
309 _(u'user "%s" still owns %s repositories and cannot be '
309 _(u'user "%s" still owns %s repositories and cannot be '
310 'removed. Switch owners or remove those repositories. %s')
310 'removed. Switch owners or remove those repositories. %s')
311 % (user.username, len(repos), ', '.join(repos))
311 % (user.username, len(repos), ', '.join(repos))
312 )
312 )
313 self.sa.delete(user)
313 self.sa.delete(user)
314 except Exception:
314 except Exception:
315 log.error(traceback.format_exc())
315 log.error(traceback.format_exc())
316 raise
316 raise
317
317
318 def reset_password_link(self, data):
318 def reset_password_link(self, data):
319 from rhodecode.lib.celerylib import tasks, run_task
319 from rhodecode.lib.celerylib import tasks, run_task
320 from rhodecode.model.notification import EmailNotificationModel
320 from rhodecode.model.notification import EmailNotificationModel
321 user_email = data['email']
321 user_email = data['email']
322 try:
322 try:
323 user = User.get_by_email(user_email)
323 user = User.get_by_email(user_email)
324 if user:
324 if user:
325 log.debug('password reset user found %s' % user)
325 log.debug('password reset user found %s' % user)
326 link = url('reset_password_confirmation', key=user.api_key,
326 link = url('reset_password_confirmation', key=user.api_key,
327 qualified=True)
327 qualified=True)
328 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
328 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
329 body = EmailNotificationModel().get_email_tmpl(reg_type,
329 body = EmailNotificationModel().get_email_tmpl(reg_type,
330 **{'user': user.short_contact,
330 **{'user': user.short_contact,
331 'reset_url': link})
331 'reset_url': link})
332 log.debug('sending email')
332 log.debug('sending email')
333 run_task(tasks.send_email, user_email,
333 run_task(tasks.send_email, user_email,
334 _("password reset link"), body, body)
334 _("password reset link"), body, body)
335 log.info('send new password mail to %s' % user_email)
335 log.info('send new password mail to %s' % user_email)
336 else:
336 else:
337 log.debug("password reset email %s not found" % user_email)
337 log.debug("password reset email %s not found" % user_email)
338 except Exception:
338 except Exception:
339 log.error(traceback.format_exc())
339 log.error(traceback.format_exc())
340 return False
340 return False
341
341
342 return True
342 return True
343
343
344 def reset_password(self, data):
344 def reset_password(self, data):
345 from rhodecode.lib.celerylib import tasks, run_task
345 from rhodecode.lib.celerylib import tasks, run_task
346 from rhodecode.lib import auth
346 from rhodecode.lib import auth
347 user_email = data['email']
347 user_email = data['email']
348 try:
348 try:
349 try:
349 try:
350 user = User.get_by_email(user_email)
350 user = User.get_by_email(user_email)
351 new_passwd = auth.PasswordGenerator().gen_password(8,
351 new_passwd = auth.PasswordGenerator().gen_password(8,
352 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
352 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
353 if user:
353 if user:
354 user.password = auth.get_crypt_password(new_passwd)
354 user.password = auth.get_crypt_password(new_passwd)
355 user.api_key = auth.generate_api_key(user.username)
355 user.api_key = auth.generate_api_key(user.username)
356 Session().add(user)
356 Session().add(user)
357 Session().commit()
357 Session().commit()
358 log.info('change password for %s' % user_email)
358 log.info('change password for %s' % user_email)
359 if new_passwd is None:
359 if new_passwd is None:
360 raise Exception('unable to generate new password')
360 raise Exception('unable to generate new password')
361 except Exception:
361 except Exception:
362 log.error(traceback.format_exc())
362 log.error(traceback.format_exc())
363 Session().rollback()
363 Session().rollback()
364
364
365 run_task(tasks.send_email, user_email,
365 run_task(tasks.send_email, user_email,
366 _('Your new password'),
366 _('Your new password'),
367 _('Your new RhodeCode password:%s') % (new_passwd))
367 _('Your new RhodeCode password:%s') % (new_passwd))
368 log.info('send new password mail to %s' % user_email)
368 log.info('send new password mail to %s' % user_email)
369
369
370 except Exception:
370 except Exception:
371 log.error('Failed to update user password')
371 log.error('Failed to update user password')
372 log.error(traceback.format_exc())
372 log.error(traceback.format_exc())
373
373
374 return True
374 return True
375
375
376 def fill_data(self, auth_user, user_id=None, api_key=None):
376 def fill_data(self, auth_user, user_id=None, api_key=None):
377 """
377 """
378 Fetches auth_user by user_id,or api_key if present.
378 Fetches auth_user by user_id,or api_key if present.
379 Fills auth_user attributes with those taken from database.
379 Fills auth_user attributes with those taken from database.
380 Additionally set's is_authenitated if lookup fails
380 Additionally set's is_authenitated if lookup fails
381 present in database
381 present in database
382
382
383 :param auth_user: instance of user to set attributes
383 :param auth_user: instance of user to set attributes
384 :param user_id: user id to fetch by
384 :param user_id: user id to fetch by
385 :param api_key: api key to fetch by
385 :param api_key: api key to fetch by
386 """
386 """
387 if user_id is None and api_key is None:
387 if user_id is None and api_key is None:
388 raise Exception('You need to pass user_id or api_key')
388 raise Exception('You need to pass user_id or api_key')
389
389
390 try:
390 try:
391 if api_key:
391 if api_key:
392 dbuser = self.get_by_api_key(api_key)
392 dbuser = self.get_by_api_key(api_key)
393 else:
393 else:
394 dbuser = self.get(user_id)
394 dbuser = self.get(user_id)
395
395
396 if dbuser is not None and dbuser.active:
396 if dbuser is not None and dbuser.active:
397 log.debug('filling %s data' % dbuser)
397 log.debug('filling %s data' % dbuser)
398 for k, v in dbuser.get_dict().items():
398 for k, v in dbuser.get_dict().items():
399 setattr(auth_user, k, v)
399 setattr(auth_user, k, v)
400 else:
400 else:
401 return False
401 return False
402
402
403 except Exception:
403 except Exception:
404 log.error(traceback.format_exc())
404 log.error(traceback.format_exc())
405 auth_user.is_authenticated = False
405 auth_user.is_authenticated = False
406 return False
406 return False
407
407
408 return True
408 return True
409
409
410 def fill_perms(self, user, explicit=True, algo='higherwin'):
410 def fill_perms(self, user, explicit=True, algo='higherwin'):
411 """
411 """
412 Fills user permission attribute with permissions taken from database
412 Fills user permission attribute with permissions taken from database
413 works for permissions given for repositories, and for permissions that
413 works for permissions given for repositories, and for permissions that
414 are granted to groups
414 are granted to groups
415
415
416 :param user: user instance to fill his perms
416 :param user: user instance to fill his perms
417 :param explicit: In case there are permissions both for user and a group
417 :param explicit: In case there are permissions both for user and a group
418 that user is part of, explicit flag will defiine if user will
418 that user is part of, explicit flag will defiine if user will
419 explicitly override permissions from group, if it's False it will
419 explicitly override permissions from group, if it's False it will
420 make decision based on the algo
420 make decision based on the algo
421 :param algo: algorithm to decide what permission should be choose if
421 :param algo: algorithm to decide what permission should be choose if
422 it's multiple defined, eg user in two different groups. It also
422 it's multiple defined, eg user in two different groups. It also
423 decides if explicit flag is turned off how to specify the permission
423 decides if explicit flag is turned off how to specify the permission
424 for case when user is in a group + have defined separate permission
424 for case when user is in a group + have defined separate permission
425 """
425 """
426 RK = 'repositories'
426 RK = 'repositories'
427 GK = 'repositories_groups'
427 GK = 'repositories_groups'
428 GLOBAL = 'global'
428 GLOBAL = 'global'
429 user.permissions[RK] = {}
429 user.permissions[RK] = {}
430 user.permissions[GK] = {}
430 user.permissions[GK] = {}
431 user.permissions[GLOBAL] = set()
431 user.permissions[GLOBAL] = set()
432
432
433 def _choose_perm(new_perm, cur_perm):
433 def _choose_perm(new_perm, cur_perm):
434 new_perm_val = PERM_WEIGHTS[new_perm]
434 new_perm_val = PERM_WEIGHTS[new_perm]
435 cur_perm_val = PERM_WEIGHTS[cur_perm]
435 cur_perm_val = PERM_WEIGHTS[cur_perm]
436 if algo == 'higherwin':
436 if algo == 'higherwin':
437 if new_perm_val > cur_perm_val:
437 if new_perm_val > cur_perm_val:
438 return new_perm
438 return new_perm
439 return cur_perm
439 return cur_perm
440 elif algo == 'lowerwin':
440 elif algo == 'lowerwin':
441 if new_perm_val < cur_perm_val:
441 if new_perm_val < cur_perm_val:
442 return new_perm
442 return new_perm
443 return cur_perm
443 return cur_perm
444
444
445 #======================================================================
445 #======================================================================
446 # fetch default permissions
446 # fetch default permissions
447 #======================================================================
447 #======================================================================
448 default_user = User.get_by_username('default', cache=True)
448 default_user = User.get_by_username('default', cache=True)
449 default_user_id = default_user.user_id
449 default_user_id = default_user.user_id
450
450
451 default_repo_perms = Permission.get_default_perms(default_user_id)
451 default_repo_perms = Permission.get_default_perms(default_user_id)
452 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
452 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
453
453
454 if user.is_admin:
454 if user.is_admin:
455 #==================================================================
455 #==================================================================
456 # admin user have all default rights for repositories
456 # admin user have all default rights for repositories
457 # and groups set to admin
457 # and groups set to admin
458 #==================================================================
458 #==================================================================
459 user.permissions[GLOBAL].add('hg.admin')
459 user.permissions[GLOBAL].add('hg.admin')
460
460
461 # repositories
461 # repositories
462 for perm in default_repo_perms:
462 for perm in default_repo_perms:
463 r_k = perm.UserRepoToPerm.repository.repo_name
463 r_k = perm.UserRepoToPerm.repository.repo_name
464 p = 'repository.admin'
464 p = 'repository.admin'
465 user.permissions[RK][r_k] = p
465 user.permissions[RK][r_k] = p
466
466
467 # repository groups
467 # repository groups
468 for perm in default_repo_groups_perms:
468 for perm in default_repo_groups_perms:
469 rg_k = perm.UserRepoGroupToPerm.group.group_name
469 rg_k = perm.UserRepoGroupToPerm.group.group_name
470 p = 'group.admin'
470 p = 'group.admin'
471 user.permissions[GK][rg_k] = p
471 user.permissions[GK][rg_k] = p
472 return user
472 return user
473
473
474 #==================================================================
474 #==================================================================
475 # SET DEFAULTS GLOBAL, REPOS, REPOS GROUPS
475 # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS
476 #==================================================================
476 #==================================================================
477 uid = user.user_id
477 uid = user.user_id
478
478
479 # default global permissions taken fron the default user
479 # default global permissions taken fron the default user
480 default_global_perms = self.sa.query(UserToPerm)\
480 default_global_perms = self.sa.query(UserToPerm)\
481 .filter(UserToPerm.user_id == default_user_id)
481 .filter(UserToPerm.user_id == default_user_id)
482
482
483 for perm in default_global_perms:
483 for perm in default_global_perms:
484 user.permissions[GLOBAL].add(perm.permission.permission_name)
484 user.permissions[GLOBAL].add(perm.permission.permission_name)
485
485
486 # defaults for repositories, taken from default user
486 # defaults for repositories, taken from default user
487 for perm in default_repo_perms:
487 for perm in default_repo_perms:
488 r_k = perm.UserRepoToPerm.repository.repo_name
488 r_k = perm.UserRepoToPerm.repository.repo_name
489 if perm.Repository.private and not (perm.Repository.user_id == uid):
489 if perm.Repository.private and not (perm.Repository.user_id == uid):
490 # disable defaults for private repos,
490 # disable defaults for private repos,
491 p = 'repository.none'
491 p = 'repository.none'
492 elif perm.Repository.user_id == uid:
492 elif perm.Repository.user_id == uid:
493 # set admin if owner
493 # set admin if owner
494 p = 'repository.admin'
494 p = 'repository.admin'
495 else:
495 else:
496 p = perm.Permission.permission_name
496 p = perm.Permission.permission_name
497
497
498 user.permissions[RK][r_k] = p
498 user.permissions[RK][r_k] = p
499
499
500 # defaults for repository groups taken from default user permission
500 # defaults for repository groups taken from default user permission
501 # on given group
501 # on given group
502 for perm in default_repo_groups_perms:
502 for perm in default_repo_groups_perms:
503 rg_k = perm.UserRepoGroupToPerm.group.group_name
503 rg_k = perm.UserRepoGroupToPerm.group.group_name
504 p = perm.Permission.permission_name
504 p = perm.Permission.permission_name
505 user.permissions[GK][rg_k] = p
505 user.permissions[GK][rg_k] = p
506
506
507 #======================================================================
507 #======================================================================
508 # !! OVERRIDE GLOBALS !! with user permissions if any found
508 # !! OVERRIDE GLOBALS !! with user permissions if any found
509 #======================================================================
509 #======================================================================
510 # those can be configured from groups or users explicitly
510 # those can be configured from groups or users explicitly
511 _configurable = set(['hg.fork.none', 'hg.fork.repository',
511 _configurable = set(['hg.fork.none', 'hg.fork.repository',
512 'hg.create.none', 'hg.create.repository'])
512 'hg.create.none', 'hg.create.repository'])
513
513
514 # USER GROUPS comes first
514 # USER GROUPS comes first
515 # user group global permissions
515 # user group global permissions
516 user_perms_from_users_groups = self.sa.query(UserGroupToPerm)\
516 user_perms_from_users_groups = self.sa.query(UserGroupToPerm)\
517 .options(joinedload(UserGroupToPerm.permission))\
517 .options(joinedload(UserGroupToPerm.permission))\
518 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
518 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
519 UserGroupMember.users_group_id))\
519 UserGroupMember.users_group_id))\
520 .filter(UserGroupMember.user_id == uid)\
520 .filter(UserGroupMember.user_id == uid)\
521 .order_by(UserGroupToPerm.users_group_id)\
521 .order_by(UserGroupToPerm.users_group_id)\
522 .all()
522 .all()
523 #need to group here by groups since user can be in more than one group
523 #need to group here by groups since user can be in more than one group
524 _grouped = [[x, list(y)] for x, y in
524 _grouped = [[x, list(y)] for x, y in
525 itertools.groupby(user_perms_from_users_groups,
525 itertools.groupby(user_perms_from_users_groups,
526 lambda x:x.users_group)]
526 lambda x:x.users_group)]
527 for gr, perms in _grouped:
527 for gr, perms in _grouped:
528 # since user can be in multiple groups iterate over them and
528 # since user can be in multiple groups iterate over them and
529 # select the lowest permissions first (more explicit)
529 # select the lowest permissions first (more explicit)
530 ##TODO: do this^^
530 ##TODO: do this^^
531 if not gr.inherit_default_permissions:
531 if not gr.inherit_default_permissions:
532 # NEED TO IGNORE all configurable permissions and
532 # NEED TO IGNORE all configurable permissions and
533 # replace them with explicitly set
533 # replace them with explicitly set
534 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
534 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
535 .difference(_configurable)
535 .difference(_configurable)
536 for perm in perms:
536 for perm in perms:
537 user.permissions[GLOBAL].add(perm.permission.permission_name)
537 user.permissions[GLOBAL].add(perm.permission.permission_name)
538
538
539 # user specific global permissions
539 # user specific global permissions
540 user_perms = self.sa.query(UserToPerm)\
540 user_perms = self.sa.query(UserToPerm)\
541 .options(joinedload(UserToPerm.permission))\
541 .options(joinedload(UserToPerm.permission))\
542 .filter(UserToPerm.user_id == uid).all()
542 .filter(UserToPerm.user_id == uid).all()
543
543
544 if not user.inherit_default_permissions:
544 if not user.inherit_default_permissions:
545 # NEED TO IGNORE all configurable permissions and
545 # NEED TO IGNORE all configurable permissions and
546 # replace them with explicitly set
546 # replace them with explicitly set
547 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
547 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
548 .difference(_configurable)
548 .difference(_configurable)
549
549
550 for perm in user_perms:
550 for perm in user_perms:
551 user.permissions[GLOBAL].add(perm.permission.permission_name)
551 user.permissions[GLOBAL].add(perm.permission.permission_name)
552
552
553 #======================================================================
553 #======================================================================
554 # !! PERMISSIONS FOR REPOSITORIES !!
554 # !! PERMISSIONS FOR REPOSITORIES !!
555 #======================================================================
555 #======================================================================
556 #======================================================================
556 #======================================================================
557 # check if user is part of user groups for this repository and
557 # check if user is part of user groups for this repository and
558 # fill in his permission from it. _choose_perm decides of which
558 # fill in his permission from it. _choose_perm decides of which
559 # permission should be selected based on selected method
559 # permission should be selected based on selected method
560 #======================================================================
560 #======================================================================
561
561
562 # user group for repositories permissions
562 # user group for repositories permissions
563 user_repo_perms_from_users_groups = \
563 user_repo_perms_from_users_groups = \
564 self.sa.query(UserGroupRepoToPerm, Permission, Repository,)\
564 self.sa.query(UserGroupRepoToPerm, Permission, Repository,)\
565 .join((Repository, UserGroupRepoToPerm.repository_id ==
565 .join((Repository, UserGroupRepoToPerm.repository_id ==
566 Repository.repo_id))\
566 Repository.repo_id))\
567 .join((Permission, UserGroupRepoToPerm.permission_id ==
567 .join((Permission, UserGroupRepoToPerm.permission_id ==
568 Permission.permission_id))\
568 Permission.permission_id))\
569 .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
569 .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
570 UserGroupMember.users_group_id))\
570 UserGroupMember.users_group_id))\
571 .filter(UserGroupMember.user_id == uid)\
571 .filter(UserGroupMember.user_id == uid)\
572 .all()
572 .all()
573
573
574 multiple_counter = collections.defaultdict(int)
574 multiple_counter = collections.defaultdict(int)
575 for perm in user_repo_perms_from_users_groups:
575 for perm in user_repo_perms_from_users_groups:
576 r_k = perm.UserGroupRepoToPerm.repository.repo_name
576 r_k = perm.UserGroupRepoToPerm.repository.repo_name
577 multiple_counter[r_k] += 1
577 multiple_counter[r_k] += 1
578 p = perm.Permission.permission_name
578 p = perm.Permission.permission_name
579 cur_perm = user.permissions[RK][r_k]
579 cur_perm = user.permissions[RK][r_k]
580
580
581 if perm.Repository.user_id == uid:
581 if perm.Repository.user_id == uid:
582 # set admin if owner
582 # set admin if owner
583 p = 'repository.admin'
583 p = 'repository.admin'
584 else:
584 else:
585 if multiple_counter[r_k] > 1:
585 if multiple_counter[r_k] > 1:
586 p = _choose_perm(p, cur_perm)
586 p = _choose_perm(p, cur_perm)
587 user.permissions[RK][r_k] = p
587 user.permissions[RK][r_k] = p
588
588
589 # user explicit permissions for repositories, overrides any specified
589 # user explicit permissions for repositories, overrides any specified
590 # by the group permission
590 # by the group permission
591 user_repo_perms = \
591 user_repo_perms = \
592 self.sa.query(UserRepoToPerm, Permission, Repository)\
592 self.sa.query(UserRepoToPerm, Permission, Repository)\
593 .join((Repository, UserRepoToPerm.repository_id ==
593 .join((Repository, UserRepoToPerm.repository_id ==
594 Repository.repo_id))\
594 Repository.repo_id))\
595 .join((Permission, UserRepoToPerm.permission_id ==
595 .join((Permission, UserRepoToPerm.permission_id ==
596 Permission.permission_id))\
596 Permission.permission_id))\
597 .filter(UserRepoToPerm.user_id == uid)\
597 .filter(UserRepoToPerm.user_id == uid)\
598 .all()
598 .all()
599
599
600 for perm in user_repo_perms:
600 for perm in user_repo_perms:
601 r_k = perm.UserRepoToPerm.repository.repo_name
601 r_k = perm.UserRepoToPerm.repository.repo_name
602 cur_perm = user.permissions[RK][r_k]
602 cur_perm = user.permissions[RK][r_k]
603 # set admin if owner
603 # set admin if owner
604 if perm.Repository.user_id == uid:
604 if perm.Repository.user_id == uid:
605 p = 'repository.admin'
605 p = 'repository.admin'
606 else:
606 else:
607 p = perm.Permission.permission_name
607 p = perm.Permission.permission_name
608 if not explicit:
608 if not explicit:
609 p = _choose_perm(p, cur_perm)
609 p = _choose_perm(p, cur_perm)
610 user.permissions[RK][r_k] = p
610 user.permissions[RK][r_k] = p
611
611
612 #======================================================================
612 #======================================================================
613 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
613 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
614 #======================================================================
614 #======================================================================
615 #======================================================================
615 #======================================================================
616 # check if user is part of user groups for this repository groups and
616 # check if user is part of user groups for this repository groups and
617 # fill in his permission from it. _choose_perm decides of which
617 # fill in his permission from it. _choose_perm decides of which
618 # permission should be selected based on selected method
618 # permission should be selected based on selected method
619 #======================================================================
619 #======================================================================
620 # user group for repo groups permissions
620 # user group for repo groups permissions
621 user_repo_group_perms_from_users_groups = \
621 user_repo_group_perms_from_users_groups = \
622 self.sa.query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\
622 self.sa.query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\
623 .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
623 .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
624 .join((Permission, UserGroupRepoGroupToPerm.permission_id
624 .join((Permission, UserGroupRepoGroupToPerm.permission_id
625 == Permission.permission_id))\
625 == Permission.permission_id))\
626 .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
626 .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
627 == UserGroupMember.users_group_id))\
627 == UserGroupMember.users_group_id))\
628 .filter(UserGroupMember.user_id == uid)\
628 .filter(UserGroupMember.user_id == uid)\
629 .all()
629 .all()
630
630
631 multiple_counter = collections.defaultdict(int)
631 multiple_counter = collections.defaultdict(int)
632 for perm in user_repo_group_perms_from_users_groups:
632 for perm in user_repo_group_perms_from_users_groups:
633 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
633 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
634 multiple_counter[g_k] += 1
634 multiple_counter[g_k] += 1
635 p = perm.Permission.permission_name
635 p = perm.Permission.permission_name
636 cur_perm = user.permissions[GK][g_k]
636 cur_perm = user.permissions[GK][g_k]
637 if multiple_counter[g_k] > 1:
637 if multiple_counter[g_k] > 1:
638 p = _choose_perm(p, cur_perm)
638 p = _choose_perm(p, cur_perm)
639 user.permissions[GK][g_k] = p
639 user.permissions[GK][g_k] = p
640
640
641 # user explicit permissions for repository groups
641 # user explicit permissions for repository groups
642 user_repo_groups_perms = \
642 user_repo_groups_perms = \
643 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
643 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
644 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
644 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
645 .join((Permission, UserRepoGroupToPerm.permission_id
645 .join((Permission, UserRepoGroupToPerm.permission_id
646 == Permission.permission_id))\
646 == Permission.permission_id))\
647 .filter(UserRepoGroupToPerm.user_id == uid)\
647 .filter(UserRepoGroupToPerm.user_id == uid)\
648 .all()
648 .all()
649
649
650 for perm in user_repo_groups_perms:
650 for perm in user_repo_groups_perms:
651 rg_k = perm.UserRepoGroupToPerm.group.group_name
651 rg_k = perm.UserRepoGroupToPerm.group.group_name
652 p = perm.Permission.permission_name
652 p = perm.Permission.permission_name
653 cur_perm = user.permissions[GK][rg_k]
653 cur_perm = user.permissions[GK][rg_k]
654 if not explicit:
654 if not explicit:
655 p = _choose_perm(p, cur_perm)
655 p = _choose_perm(p, cur_perm)
656 user.permissions[GK][rg_k] = p
656 user.permissions[GK][rg_k] = p
657
657
658 return user
658 return user
659
659
660 def has_perm(self, user, perm):
660 def has_perm(self, user, perm):
661 perm = self._get_perm(perm)
661 perm = self._get_perm(perm)
662 user = self._get_user(user)
662 user = self._get_user(user)
663
663
664 return UserToPerm.query().filter(UserToPerm.user == user)\
664 return UserToPerm.query().filter(UserToPerm.user == user)\
665 .filter(UserToPerm.permission == perm).scalar() is not None
665 .filter(UserToPerm.permission == perm).scalar() is not None
666
666
667 def grant_perm(self, user, perm):
667 def grant_perm(self, user, perm):
668 """
668 """
669 Grant user global permissions
669 Grant user global permissions
670
670
671 :param user:
671 :param user:
672 :param perm:
672 :param perm:
673 """
673 """
674 user = self._get_user(user)
674 user = self._get_user(user)
675 perm = self._get_perm(perm)
675 perm = self._get_perm(perm)
676 # if this permission is already granted skip it
676 # if this permission is already granted skip it
677 _perm = UserToPerm.query()\
677 _perm = UserToPerm.query()\
678 .filter(UserToPerm.user == user)\
678 .filter(UserToPerm.user == user)\
679 .filter(UserToPerm.permission == perm)\
679 .filter(UserToPerm.permission == perm)\
680 .scalar()
680 .scalar()
681 if _perm:
681 if _perm:
682 return
682 return
683 new = UserToPerm()
683 new = UserToPerm()
684 new.user = user
684 new.user = user
685 new.permission = perm
685 new.permission = perm
686 self.sa.add(new)
686 self.sa.add(new)
687
687
688 def revoke_perm(self, user, perm):
688 def revoke_perm(self, user, perm):
689 """
689 """
690 Revoke users global permissions
690 Revoke users global permissions
691
691
692 :param user:
692 :param user:
693 :param perm:
693 :param perm:
694 """
694 """
695 user = self._get_user(user)
695 user = self._get_user(user)
696 perm = self._get_perm(perm)
696 perm = self._get_perm(perm)
697
697
698 obj = UserToPerm.query()\
698 obj = UserToPerm.query()\
699 .filter(UserToPerm.user == user)\
699 .filter(UserToPerm.user == user)\
700 .filter(UserToPerm.permission == perm)\
700 .filter(UserToPerm.permission == perm)\
701 .scalar()
701 .scalar()
702 if obj:
702 if obj:
703 self.sa.delete(obj)
703 self.sa.delete(obj)
704
704
705 def add_extra_email(self, user, email):
705 def add_extra_email(self, user, email):
706 """
706 """
707 Adds email address to UserEmailMap
707 Adds email address to UserEmailMap
708
708
709 :param user:
709 :param user:
710 :param email:
710 :param email:
711 """
711 """
712 from rhodecode.model import forms
712 from rhodecode.model import forms
713 form = forms.UserExtraEmailForm()()
713 form = forms.UserExtraEmailForm()()
714 data = form.to_python(dict(email=email))
714 data = form.to_python(dict(email=email))
715 user = self._get_user(user)
715 user = self._get_user(user)
716
716
717 obj = UserEmailMap()
717 obj = UserEmailMap()
718 obj.user = user
718 obj.user = user
719 obj.email = data['email']
719 obj.email = data['email']
720 self.sa.add(obj)
720 self.sa.add(obj)
721 return obj
721 return obj
722
722
723 def delete_extra_email(self, user, email_id):
723 def delete_extra_email(self, user, email_id):
724 """
724 """
725 Removes email address from UserEmailMap
725 Removes email address from UserEmailMap
726
726
727 :param user:
727 :param user:
728 :param email_id:
728 :param email_id:
729 """
729 """
730 user = self._get_user(user)
730 user = self._get_user(user)
731 obj = UserEmailMap.query().get(email_id)
731 obj = UserEmailMap.query().get(email_id)
732 if obj:
732 if obj:
733 self.sa.delete(obj)
733 self.sa.delete(obj)
734
734
735 def add_extra_ip(self, user, ip):
735 def add_extra_ip(self, user, ip):
736 """
736 """
737 Adds ip address to UserIpMap
737 Adds ip address to UserIpMap
738
738
739 :param user:
739 :param user:
740 :param ip:
740 :param ip:
741 """
741 """
742 from rhodecode.model import forms
742 from rhodecode.model import forms
743 form = forms.UserExtraIpForm()()
743 form = forms.UserExtraIpForm()()
744 data = form.to_python(dict(ip=ip))
744 data = form.to_python(dict(ip=ip))
745 user = self._get_user(user)
745 user = self._get_user(user)
746
746
747 obj = UserIpMap()
747 obj = UserIpMap()
748 obj.user = user
748 obj.user = user
749 obj.ip_addr = data['ip']
749 obj.ip_addr = data['ip']
750 self.sa.add(obj)
750 self.sa.add(obj)
751 return obj
751 return obj
752
752
753 def delete_extra_ip(self, user, ip_id):
753 def delete_extra_ip(self, user, ip_id):
754 """
754 """
755 Removes ip address from UserIpMap
755 Removes ip address from UserIpMap
756
756
757 :param user:
757 :param user:
758 :param ip_id:
758 :param ip_id:
759 """
759 """
760 user = self._get_user(user)
760 user = self._get_user(user)
761 obj = UserIpMap.query().get(ip_id)
761 obj = UserIpMap.query().get(ip_id)
762 if obj:
762 if obj:
763 self.sa.delete(obj)
763 self.sa.delete(obj)
@@ -1,192 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.users_group
3 rhodecode.model.users_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 user group model for RhodeCode
6 user group model for RhodeCode
7
7
8 :created_on: Oct 1, 2011
8 :created_on: Oct 1, 2011
9 :author: nvinot
9 :author: nvinot
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from rhodecode.model import BaseModel
30 from rhodecode.model import BaseModel
31 from rhodecode.model.db import UserGroupMember, UserGroup,\
31 from rhodecode.model.db import UserGroupMember, UserGroup,\
32 UserGroupRepoToPerm, Permission, UserGroupToPerm, User
32 UserGroupRepoToPerm, Permission, UserGroupToPerm, User
33 from rhodecode.lib.exceptions import UserGroupsAssignedException
33 from rhodecode.lib.exceptions import UserGroupsAssignedException
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class UserGroupModel(BaseModel):
38 class UserGroupModel(BaseModel):
39
39
40 cls = UserGroup
40 cls = UserGroup
41
41
42 def __get_users_group(self, users_group):
42 def __get_users_group(self, users_group):
43 return self._get_instance(UserGroup, users_group,
43 return self._get_instance(UserGroup, users_group,
44 callback=UserGroup.get_by_group_name)
44 callback=UserGroup.get_by_group_name)
45
45
46 def get(self, users_group_id, cache=False):
46 def get(self, users_group_id, cache=False):
47 return UserGroup.get(users_group_id)
47 return UserGroup.get(users_group_id)
48
48
49 def get_group(self, users_group):
49 def get_group(self, users_group):
50 return self.__get_users_group(users_group)
50 return self.__get_users_group(users_group)
51
51
52 def get_by_name(self, name, cache=False, case_insensitive=False):
52 def get_by_name(self, name, cache=False, case_insensitive=False):
53 return UserGroup.get_by_group_name(name, cache, case_insensitive)
53 return UserGroup.get_by_group_name(name, cache, case_insensitive)
54
54
55 def create(self, name, active=True):
55 def create(self, name, active=True):
56 try:
56 try:
57 new = UserGroup()
57 new = UserGroup()
58 new.users_group_name = name
58 new.users_group_name = name
59 new.users_group_active = active
59 new.users_group_active = active
60 self.sa.add(new)
60 self.sa.add(new)
61 return new
61 return new
62 except Exception:
62 except Exception:
63 log.error(traceback.format_exc())
63 log.error(traceback.format_exc())
64 raise
64 raise
65
65
66 def update(self, users_group, form_data):
66 def update(self, users_group, form_data):
67
67
68 try:
68 try:
69 users_group = self.__get_users_group(users_group)
69 users_group = self.__get_users_group(users_group)
70
70
71 for k, v in form_data.items():
71 for k, v in form_data.items():
72 if k == 'users_group_members':
72 if k == 'users_group_members':
73 users_group.members = []
73 users_group.members = []
74 self.sa.flush()
74 self.sa.flush()
75 members_list = []
75 members_list = []
76 if v:
76 if v:
77 v = [v] if isinstance(v, basestring) else v
77 v = [v] if isinstance(v, basestring) else v
78 for u_id in set(v):
78 for u_id in set(v):
79 member = UserGroupMember(users_group.users_group_id, u_id)
79 member = UserGroupMember(users_group.users_group_id, u_id)
80 members_list.append(member)
80 members_list.append(member)
81 setattr(users_group, 'members', members_list)
81 setattr(users_group, 'members', members_list)
82 setattr(users_group, k, v)
82 setattr(users_group, k, v)
83
83
84 self.sa.add(users_group)
84 self.sa.add(users_group)
85 except Exception:
85 except Exception:
86 log.error(traceback.format_exc())
86 log.error(traceback.format_exc())
87 raise
87 raise
88
88
89 def delete(self, users_group, force=False):
89 def delete(self, users_group, force=False):
90 """
90 """
91 Deletes repos group, unless force flag is used
91 Deletes repository group, unless force flag is used
92 raises exception if there are members in that group, else deletes
92 raises exception if there are members in that group, else deletes
93 group and users
93 group and users
94
94
95 :param users_group:
95 :param users_group:
96 :param force:
96 :param force:
97 """
97 """
98 try:
98 try:
99 users_group = self.__get_users_group(users_group)
99 users_group = self.__get_users_group(users_group)
100
100
101 # check if this group is not assigned to repo
101 # check if this group is not assigned to repo
102 assigned_groups = UserGroupRepoToPerm.query()\
102 assigned_groups = UserGroupRepoToPerm.query()\
103 .filter(UserGroupRepoToPerm.users_group == users_group).all()
103 .filter(UserGroupRepoToPerm.users_group == users_group).all()
104
104
105 if assigned_groups and not force:
105 if assigned_groups and not force:
106 raise UserGroupsAssignedException('RepoGroup assigned to %s' %
106 raise UserGroupsAssignedException('RepoGroup assigned to %s' %
107 assigned_groups)
107 assigned_groups)
108
108
109 self.sa.delete(users_group)
109 self.sa.delete(users_group)
110 except Exception:
110 except Exception:
111 log.error(traceback.format_exc())
111 log.error(traceback.format_exc())
112 raise
112 raise
113
113
114 def add_user_to_group(self, users_group, user):
114 def add_user_to_group(self, users_group, user):
115 users_group = self.__get_users_group(users_group)
115 users_group = self.__get_users_group(users_group)
116 user = self._get_user(user)
116 user = self._get_user(user)
117
117
118 for m in users_group.members:
118 for m in users_group.members:
119 u = m.user
119 u = m.user
120 if u.user_id == user.user_id:
120 if u.user_id == user.user_id:
121 return True
121 return True
122
122
123 try:
123 try:
124 users_group_member = UserGroupMember()
124 users_group_member = UserGroupMember()
125 users_group_member.user = user
125 users_group_member.user = user
126 users_group_member.users_group = users_group
126 users_group_member.users_group = users_group
127
127
128 users_group.members.append(users_group_member)
128 users_group.members.append(users_group_member)
129 user.group_member.append(users_group_member)
129 user.group_member.append(users_group_member)
130
130
131 self.sa.add(users_group_member)
131 self.sa.add(users_group_member)
132 return users_group_member
132 return users_group_member
133 except Exception:
133 except Exception:
134 log.error(traceback.format_exc())
134 log.error(traceback.format_exc())
135 raise
135 raise
136
136
137 def remove_user_from_group(self, users_group, user):
137 def remove_user_from_group(self, users_group, user):
138 users_group = self.__get_users_group(users_group)
138 users_group = self.__get_users_group(users_group)
139 user = self._get_user(user)
139 user = self._get_user(user)
140
140
141 users_group_member = None
141 users_group_member = None
142 for m in users_group.members:
142 for m in users_group.members:
143 if m.user.user_id == user.user_id:
143 if m.user.user_id == user.user_id:
144 # Found this user's membership row
144 # Found this user's membership row
145 users_group_member = m
145 users_group_member = m
146 break
146 break
147
147
148 if users_group_member:
148 if users_group_member:
149 try:
149 try:
150 self.sa.delete(users_group_member)
150 self.sa.delete(users_group_member)
151 return True
151 return True
152 except Exception:
152 except Exception:
153 log.error(traceback.format_exc())
153 log.error(traceback.format_exc())
154 raise
154 raise
155 else:
155 else:
156 # User isn't in that group
156 # User isn't in that group
157 return False
157 return False
158
158
159 def has_perm(self, users_group, perm):
159 def has_perm(self, users_group, perm):
160 users_group = self.__get_users_group(users_group)
160 users_group = self.__get_users_group(users_group)
161 perm = self._get_perm(perm)
161 perm = self._get_perm(perm)
162
162
163 return UserGroupToPerm.query()\
163 return UserGroupToPerm.query()\
164 .filter(UserGroupToPerm.users_group == users_group)\
164 .filter(UserGroupToPerm.users_group == users_group)\
165 .filter(UserGroupToPerm.permission == perm).scalar() is not None
165 .filter(UserGroupToPerm.permission == perm).scalar() is not None
166
166
167 def grant_perm(self, users_group, perm):
167 def grant_perm(self, users_group, perm):
168 users_group = self.__get_users_group(users_group)
168 users_group = self.__get_users_group(users_group)
169 perm = self._get_perm(perm)
169 perm = self._get_perm(perm)
170
170
171 # if this permission is already granted skip it
171 # if this permission is already granted skip it
172 _perm = UserGroupToPerm.query()\
172 _perm = UserGroupToPerm.query()\
173 .filter(UserGroupToPerm.users_group == users_group)\
173 .filter(UserGroupToPerm.users_group == users_group)\
174 .filter(UserGroupToPerm.permission == perm)\
174 .filter(UserGroupToPerm.permission == perm)\
175 .scalar()
175 .scalar()
176 if _perm:
176 if _perm:
177 return
177 return
178
178
179 new = UserGroupToPerm()
179 new = UserGroupToPerm()
180 new.users_group = users_group
180 new.users_group = users_group
181 new.permission = perm
181 new.permission = perm
182 self.sa.add(new)
182 self.sa.add(new)
183
183
184 def revoke_perm(self, users_group, perm):
184 def revoke_perm(self, users_group, perm):
185 users_group = self.__get_users_group(users_group)
185 users_group = self.__get_users_group(users_group)
186 perm = self._get_perm(perm)
186 perm = self._get_perm(perm)
187
187
188 obj = UserGroupToPerm.query()\
188 obj = UserGroupToPerm.query()\
189 .filter(UserGroupToPerm.users_group == users_group)\
189 .filter(UserGroupToPerm.users_group == users_group)\
190 .filter(UserGroupToPerm.permission == perm).scalar()
190 .filter(UserGroupToPerm.permission == perm).scalar()
191 if obj:
191 if obj:
192 self.sa.delete(obj)
192 self.sa.delete(obj)
@@ -1,65 +1,65 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Add repos group')} &middot; ${c.rhodecode_name}
5 ${_('Add repository group')} &middot; ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 ${h.link_to(_('Repository groups'),h.url('repos_groups'))}
12 &raquo;
12 &raquo;
13 ${_('add new repos group')}
13 ${_('Add new repository group')}
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 ${h.form(url('repos_groups'))}
27 ${h.form(url('repos_groups'))}
28 <div class="form">
28 <div class="form">
29 <!-- fields -->
29 <!-- fields -->
30 <div class="fields">
30 <div class="fields">
31 <div class="field">
31 <div class="field">
32 <div class="label">
32 <div class="label">
33 <label for="group_name">${_('Group name')}:</label>
33 <label for="group_name">${_('Group name')}:</label>
34 </div>
34 </div>
35 <div class="input">
35 <div class="input">
36 ${h.text('group_name',class_='medium')}
36 ${h.text('group_name',class_='medium')}
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label label-textarea">
41 <div class="label label-textarea">
42 <label for="group_description">${_('Description')}:</label>
42 <label for="group_description">${_('Description')}:</label>
43 </div>
43 </div>
44 <div class="textarea text-area editor">
44 <div class="textarea text-area editor">
45 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
46 </div>
46 </div>
47 </div>
47 </div>
48
48
49 <div class="field">
49 <div class="field">
50 <div class="label">
50 <div class="label">
51 <label for="group_parent_id">${_('Group parent')}:</label>
51 <label for="group_parent_id">${_('Group parent')}:</label>
52 </div>
52 </div>
53 <div class="input">
53 <div class="input">
54 ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
54 ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="buttons">
58 <div class="buttons">
59 ${h.submit('save',_('save'),class_="ui-btn large")}
59 ${h.submit('save',_('save'),class_="ui-btn large")}
60 </div>
60 </div>
61 </div>
61 </div>
62 </div>
62 </div>
63 ${h.end_form()}
63 ${h.end_form()}
64 </div>
64 </div>
65 </%def>
65 </%def>
@@ -1,87 +1,87 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit repos group')} ${c.repos_group.name} &middot; ${c.rhodecode_name}
5 ${_('Edit repository group')} ${c.repos_group.name} &middot; ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 ${h.link_to(_('Repository groups'),h.url('repos_groups'))}
12 &raquo;
12 &raquo;
13 ${_('edit repos group')} "${c.repos_group.name}"
13 ${_('Edit repository group')} "${c.repos_group.name}"
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 <ul class="links">
25 <ul class="links">
26 <li>
26 <li>
27 <span>${h.link_to(_(u'Add child group'),h.url('new_repos_group', parent_group=c.repos_group.group_id))}</span>
27 <span>${h.link_to(_(u'Add child group'),h.url('new_repos_group', parent_group=c.repos_group.group_id))}</span>
28 </li>
28 </li>
29 </ul>
29 </ul>
30 </div>
30 </div>
31 <!-- end box / title -->
31 <!-- end box / title -->
32 ${h.form(url('repos_group',group_name=c.repos_group.group_name),method='put')}
32 ${h.form(url('repos_group',group_name=c.repos_group.group_name),method='put')}
33 <div class="form">
33 <div class="form">
34 <!-- fields -->
34 <!-- fields -->
35 <div class="fields">
35 <div class="fields">
36 <div class="field">
36 <div class="field">
37 <div class="label">
37 <div class="label">
38 <label for="group_name">${_('Group name')}:</label>
38 <label for="group_name">${_('Group name')}:</label>
39 </div>
39 </div>
40 <div class="input">
40 <div class="input">
41 ${h.text('group_name',class_='medium')}
41 ${h.text('group_name',class_='medium')}
42 </div>
42 </div>
43 </div>
43 </div>
44
44
45 <div class="field">
45 <div class="field">
46 <div class="label label-textarea">
46 <div class="label label-textarea">
47 <label for="group_description">${_('Description')}:</label>
47 <label for="group_description">${_('Description')}:</label>
48 </div>
48 </div>
49 <div class="textarea text-area editor">
49 <div class="textarea text-area editor">
50 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
50 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
51 </div>
51 </div>
52 </div>
52 </div>
53
53
54 <div class="field">
54 <div class="field">
55 <div class="label">
55 <div class="label">
56 <label for="group_parent_id">${_('Group parent')}:</label>
56 <label for="group_parent_id">${_('Group parent')}:</label>
57 </div>
57 </div>
58 <div class="input">
58 <div class="input">
59 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
59 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
60 </div>
60 </div>
61 </div>
61 </div>
62 <div class="field">
62 <div class="field">
63 <div class="label">
63 <div class="label">
64 <label for="input">${_('Permissions')}:</label>
64 <label for="input">${_('Permissions')}:</label>
65 </div>
65 </div>
66 <div class="input">
66 <div class="input">
67 <%include file="repos_group_edit_perms.html"/>
67 <%include file="repos_group_edit_perms.html"/>
68 </div>
68 </div>
69 </div>
69 </div>
70 <div class="field">
70 <div class="field">
71 <div class="label label-checkbox">
71 <div class="label label-checkbox">
72 <label for="enable_locking">${_('Enable locking')}:</label>
72 <label for="enable_locking">${_('Enable locking')}:</label>
73 </div>
73 </div>
74 <div class="checkboxes">
74 <div class="checkboxes">
75 ${h.checkbox('enable_locking',value="True")}
75 ${h.checkbox('enable_locking',value="True")}
76 <span class="help-block">${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')}</span>
76 <span class="help-block">${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')}</span>
77 </div>
77 </div>
78 </div>
78 </div>
79 <div class="buttons">
79 <div class="buttons">
80 ${h.submit('save',_('Save'),class_="ui-btn large")}
80 ${h.submit('save',_('Save'),class_="ui-btn large")}
81 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
81 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
82 </div>
82 </div>
83 </div>
83 </div>
84 </div>
84 </div>
85 ${h.end_form()}
85 ${h.end_form()}
86 </div>
86 </div>
87 </%def>
87 </%def>
General Comments 0
You need to be logged in to leave comments. Login now