##// END OF EJS Templates
- #683 fixed difference between messages about not mapped repositories
marcink -
r3110:144128ef beta
parent child Browse files
Show More
@@ -1,913 +1,914 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7 1.5.2 (**2012-XX-XX**)
7 1.5.2 (**2012-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.1 (**2012-12-13**)
19 1.5.1 (**2012-12-13**)
20 ----------------------
20 ----------------------
21
21
22 news
22 news
23 ++++
23 ++++
24
24
25 - implements #677: Don't allow to close pull requests when they are
25 - implements #677: Don't allow to close pull requests when they are
26 under-review status
26 under-review status
27 - implemented #670 Implementation of Roles in Pull Request
27 - implemented #670 Implementation of Roles in Pull Request
28
28
29 fixes
29 fixes
30 +++++
30 +++++
31
31
32 - default permissions can get duplicated after migration
32 - default permissions can get duplicated after migration
33 - fixed changeset status labels, they now select radio buttons
33 - fixed changeset status labels, they now select radio buttons
34 - #682 translation difficult for multi-line text
34 - #682 translation difficult for multi-line text
35 - #683 fixed difference between messages about not mapped repositories
35
36
36 1.5.0 (**2012-12-12**)
37 1.5.0 (**2012-12-12**)
37 ----------------------
38 ----------------------
38
39
39 news
40 news
40 ++++
41 ++++
41
42
42 - new rewritten from scratch diff engine. 10x faster in edge cases. Handling
43 - new rewritten from scratch diff engine. 10x faster in edge cases. Handling
43 of file renames, copies, change flags and binary files
44 of file renames, copies, change flags and binary files
44 - added lightweight dashboard option. ref #500. New version of dashboard
45 - added lightweight dashboard option. ref #500. New version of dashboard
45 page that doesn't use any VCS data and is super fast to render. Recommended
46 page that doesn't use any VCS data and is super fast to render. Recommended
46 for large amount of repositories.
47 for large amount of repositories.
47 - implements #648 write Script for updating last modification time for
48 - implements #648 write Script for updating last modification time for
48 lightweight dashboard
49 lightweight dashboard
49 - implemented compare engine for git repositories.
50 - implemented compare engine for git repositories.
50 - LDAP failover, option to specify multiple servers
51 - LDAP failover, option to specify multiple servers
51 - added Errormator and Sentry support for monitoring RhodeCode
52 - added Errormator and Sentry support for monitoring RhodeCode
52 - implemented #628: Pass server URL to rc-extensions hooks
53 - implemented #628: Pass server URL to rc-extensions hooks
53 - new tooltip implementation - added lazy loading of changesets from journal
54 - new tooltip implementation - added lazy loading of changesets from journal
54 pages. This can significantly improve speed of rendering the page
55 pages. This can significantly improve speed of rendering the page
55 - implements #632,added branch/tag/bookmarks info into feeds
56 - implements #632,added branch/tag/bookmarks info into feeds
56 added changeset link to body of message
57 added changeset link to body of message
57 - implemented #638 permissions overview to groups
58 - implemented #638 permissions overview to groups
58 - implements #636, lazy loading of history and authors to speed up source
59 - implements #636, lazy loading of history and authors to speed up source
59 pages rendering
60 pages rendering
60 - implemented #647, option to pass list of default encoding used to
61 - implemented #647, option to pass list of default encoding used to
61 encode to/decode from unicode
62 encode to/decode from unicode
62 - added caching layer into RSS/ATOM feeds.
63 - added caching layer into RSS/ATOM feeds.
63 - basic implementation of cherry picking changesets for pull request, ref #575
64 - basic implementation of cherry picking changesets for pull request, ref #575
64 - implemented #661 Add option to include diff in RSS feed
65 - implemented #661 Add option to include diff in RSS feed
65 - implemented file history page for showing detailed changelog for a given file
66 - implemented file history page for showing detailed changelog for a given file
66 - implemented #663 Admin/permission: specify default repogroup perms
67 - implemented #663 Admin/permission: specify default repogroup perms
67 - implemented #379 defaults settings page for creation of repositories, locking
68 - implemented #379 defaults settings page for creation of repositories, locking
68 statistics, downloads, repository type
69 statistics, downloads, repository type
69 - implemented #210 filtering of admin journal based on Whoosh Query language
70 - implemented #210 filtering of admin journal based on Whoosh Query language
70 - added parents/children links in changeset viewref #650
71 - added parents/children links in changeset viewref #650
71
72
72 fixes
73 fixes
73 +++++
74 +++++
74
75
75 - fixed git version checker
76 - fixed git version checker
76 - #586 patched basic auth handler to fix issues with git behind proxy
77 - #586 patched basic auth handler to fix issues with git behind proxy
77 - #589 search urlgenerator didn't properly escape special characters
78 - #589 search urlgenerator didn't properly escape special characters
78 - fixed issue #614 Include repo name in delete confirmation dialog
79 - fixed issue #614 Include repo name in delete confirmation dialog
79 - fixed #623: Lang meta-tag doesn't work with C#/C++
80 - fixed #623: Lang meta-tag doesn't work with C#/C++
80 - fixes #612 Double quotes to Single quotes result in bad html in diff
81 - fixes #612 Double quotes to Single quotes result in bad html in diff
81 - fixes #630 git statistics do too much work making them slow.
82 - fixes #630 git statistics do too much work making them slow.
82 - fixes #625 Git-Tags are not displayed in Shortlog
83 - fixes #625 Git-Tags are not displayed in Shortlog
83 - fix for issue #602, enforce str when setting mercurial UI object.
84 - fix for issue #602, enforce str when setting mercurial UI object.
84 When this is used together with mercurial internal translation system
85 When this is used together with mercurial internal translation system
85 it can lead to UnicodeDecodeErrors
86 it can lead to UnicodeDecodeErrors
86 - fixes #645 Fix git handler when doing delete remote branch
87 - fixes #645 Fix git handler when doing delete remote branch
87 - implements #649 added two seperate method for author and commiter to VCS
88 - implements #649 added two seperate method for author and commiter to VCS
88 changeset class switch author for git backed to be the real author not commiter
89 changeset class switch author for git backed to be the real author not commiter
89 - fix issue #504 RhodeCode is showing different versions of README on
90 - fix issue #504 RhodeCode is showing different versions of README on
90 different summary page loads
91 different summary page loads
91 - implemented #658 Changing username in LDAP-Mode should not be allowed.
92 - implemented #658 Changing username in LDAP-Mode should not be allowed.
92 - fixes #652 switch to generator approach when doing file annotation to prevent
93 - fixes #652 switch to generator approach when doing file annotation to prevent
93 huge memory consumption
94 huge memory consumption
94 - fixes #666 move lockkey path location to cache_dir to ensure this path is
95 - fixes #666 move lockkey path location to cache_dir to ensure this path is
95 always writable for rhodecode server
96 always writable for rhodecode server
96 - many more small fixes and improvements
97 - many more small fixes and improvements
97 - fixed issues with recursive scans on removed repositories that could take
98 - fixed issues with recursive scans on removed repositories that could take
98 long time on instance start
99 long time on instance start
99
100
100 1.4.4 (**2012-10-08**)
101 1.4.4 (**2012-10-08**)
101 ----------------------
102 ----------------------
102
103
103 news
104 news
104 ++++
105 ++++
105
106
106 - obfuscate db password in logs for engine connection string
107 - obfuscate db password in logs for engine connection string
107 - #574 Show pull request status also in shortlog (if any)
108 - #574 Show pull request status also in shortlog (if any)
108 - remember selected tab in my account page
109 - remember selected tab in my account page
109 - Bumped mercurial version to 2.3.2
110 - Bumped mercurial version to 2.3.2
110 - #595 rcextension hook for repository delete
111 - #595 rcextension hook for repository delete
111
112
112 fixes
113 fixes
113 +++++
114 +++++
114
115
115 - Add git version detection to warn users that Git used in system is to
116 - Add git version detection to warn users that Git used in system is to
116 old. Ref #588 - also show git version in system details in settings page
117 old. Ref #588 - also show git version in system details in settings page
117 - fixed files quick filter links
118 - fixed files quick filter links
118 - #590 Add GET flag that controls the way the diff are generated, for pull
119 - #590 Add GET flag that controls the way the diff are generated, for pull
119 requests we want to use non-bundle based diffs, That are far better for
120 requests we want to use non-bundle based diffs, That are far better for
120 doing code reviews. The /compare url still uses bundle compare for full
121 doing code reviews. The /compare url still uses bundle compare for full
121 comparison including the incoming changesets
122 comparison including the incoming changesets
122 - Fixed #585, checks for status of revision where to strict, and made
123 - Fixed #585, checks for status of revision where to strict, and made
123 opening pull request with those revision impossible due to previously set
124 opening pull request with those revision impossible due to previously set
124 status. Checks now are made also for the repository.
125 status. Checks now are made also for the repository.
125 - fixes #591 git backend was causing encoding errors when handling binary
126 - fixes #591 git backend was causing encoding errors when handling binary
126 files - added a test case for VCS lib tests
127 files - added a test case for VCS lib tests
127 - fixed #597 commits in future get negative age.
128 - fixed #597 commits in future get negative age.
128 - fixed #598 API docs methods had wrong members parameter as returned data
129 - fixed #598 API docs methods had wrong members parameter as returned data
129
130
130 1.4.3 (**2012-09-28**)
131 1.4.3 (**2012-09-28**)
131 ----------------------
132 ----------------------
132
133
133 news
134 news
134 ++++
135 ++++
135
136
136 - #558 Added config file to hooks extra data
137 - #558 Added config file to hooks extra data
137 - bumped mercurial version to 2.3.1
138 - bumped mercurial version to 2.3.1
138 - #518 added possibility of specifying multiple patterns for issues
139 - #518 added possibility of specifying multiple patterns for issues
139 - update codemirror to latest version
140 - update codemirror to latest version
140
141
141 fixes
142 fixes
142 +++++
143 +++++
143
144
144 - fixed #570 explicit users group permissions can overwrite owner permissions
145 - fixed #570 explicit users group permissions can overwrite owner permissions
145 - fixed #578 set proper PATH with current Python for Git
146 - fixed #578 set proper PATH with current Python for Git
146 hooks to execute within same Python as RhodeCode
147 hooks to execute within same Python as RhodeCode
147 - fixed issue with Git bare repos that ends with .git in name
148 - fixed issue with Git bare repos that ends with .git in name
148
149
149 1.4.2 (**2012-09-12**)
150 1.4.2 (**2012-09-12**)
150 ----------------------
151 ----------------------
151
152
152 news
153 news
153 ++++
154 ++++
154
155
155 - added option to menu to quick lock/unlock repository for users that have
156 - added option to menu to quick lock/unlock repository for users that have
156 write access to
157 write access to
157 - Implemented permissions for writing to repo
158 - Implemented permissions for writing to repo
158 groups. Now only write access to group allows to create a repostiory
159 groups. Now only write access to group allows to create a repostiory
159 within that group
160 within that group
160 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
161 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
161 - updated translation for zh_CN
162 - updated translation for zh_CN
162
163
163 fixes
164 fixes
164 +++++
165 +++++
165
166
166 - fixed visual permissions check on repos groups inside groups
167 - fixed visual permissions check on repos groups inside groups
167 - fixed issues with non-ascii search terms in search, and indexers
168 - fixed issues with non-ascii search terms in search, and indexers
168 - fixed parsing of page number in GET parameters
169 - fixed parsing of page number in GET parameters
169 - fixed issues with generating pull-request overview for repos with
170 - fixed issues with generating pull-request overview for repos with
170 bookmarks and tags, also preview doesn't loose chosen revision from
171 bookmarks and tags, also preview doesn't loose chosen revision from
171 select dropdown
172 select dropdown
172
173
173 1.4.1 (**2012-09-07**)
174 1.4.1 (**2012-09-07**)
174 ----------------------
175 ----------------------
175
176
176 news
177 news
177 ++++
178 ++++
178
179
179 - always put a comment about code-review status change even if user send
180 - always put a comment about code-review status change even if user send
180 empty data
181 empty data
181 - modified_on column saves repository update and it's going to be used
182 - modified_on column saves repository update and it's going to be used
182 later for light version of main page ref #500
183 later for light version of main page ref #500
183 - pull request notifications send much nicer emails with details about pull
184 - pull request notifications send much nicer emails with details about pull
184 request
185 request
185 - #551 show breadcrumbs in summary view for repositories inside a group
186 - #551 show breadcrumbs in summary view for repositories inside a group
186
187
187 fixes
188 fixes
188 +++++
189 +++++
189
190
190 - fixed migrations of permissions that can lead to inconsistency.
191 - fixed migrations of permissions that can lead to inconsistency.
191 Some users sent feedback that after upgrading from older versions issues
192 Some users sent feedback that after upgrading from older versions issues
192 with updating default permissions occurred. RhodeCode detects that now and
193 with updating default permissions occurred. RhodeCode detects that now and
193 resets default user permission to initial state if there is a need for that.
194 resets default user permission to initial state if there is a need for that.
194 Also forces users to set the default value for new forking permission.
195 Also forces users to set the default value for new forking permission.
195 - #535 improved apache wsgi example configuration in docs
196 - #535 improved apache wsgi example configuration in docs
196 - fixes #550 mercurial repositories comparision failed when origin repo had
197 - fixes #550 mercurial repositories comparision failed when origin repo had
197 additional not-common changesets
198 additional not-common changesets
198 - fixed status of code-review in preview windows of pull request
199 - fixed status of code-review in preview windows of pull request
199 - git forks were not initialized at bare repos
200 - git forks were not initialized at bare repos
200 - fixes #555 fixes issues with comparing non-related repositories
201 - fixes #555 fixes issues with comparing non-related repositories
201 - fixes #557 follower counter always counts up
202 - fixes #557 follower counter always counts up
202 - fixed issue #560 require push ssl checkbox wasn't shown when option was
203 - fixed issue #560 require push ssl checkbox wasn't shown when option was
203 enabled
204 enabled
204 - fixed #559
205 - fixed #559
205 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
206 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
206 if it was a request to url by repository ID
207 if it was a request to url by repository ID
207
208
208 1.4.0 (**2012-09-03**)
209 1.4.0 (**2012-09-03**)
209 ----------------------
210 ----------------------
210
211
211 news
212 news
212 ++++
213 ++++
213
214
214 - new codereview system
215 - new codereview system
215 - email map, allowing users to have multiple email addresses mapped into
216 - email map, allowing users to have multiple email addresses mapped into
216 their accounts
217 their accounts
217 - improved git-hook system. Now all actions for git are logged into journal
218 - improved git-hook system. Now all actions for git are logged into journal
218 including pushed revisions, user and IP address
219 including pushed revisions, user and IP address
219 - changed setup-app into setup-rhodecode and added default options to it.
220 - changed setup-app into setup-rhodecode and added default options to it.
220 - new git repos are created as bare now by default
221 - new git repos are created as bare now by default
221 - #464 added links to groups in permission box
222 - #464 added links to groups in permission box
222 - #465 mentions autocomplete inside comments boxes
223 - #465 mentions autocomplete inside comments boxes
223 - #469 added --update-only option to whoosh to re-index only given list
224 - #469 added --update-only option to whoosh to re-index only given list
224 of repos in index
225 of repos in index
225 - rhodecode-api CLI client
226 - rhodecode-api CLI client
226 - new git http protocol replaced buggy dulwich implementation.
227 - new git http protocol replaced buggy dulwich implementation.
227 Now based on pygrack & gitweb
228 Now based on pygrack & gitweb
228 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
229 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
229 reformated based on user suggestions. Additional rss/atom feeds for user
230 reformated based on user suggestions. Additional rss/atom feeds for user
230 journal
231 journal
231 - various i18n improvements
232 - various i18n improvements
232 - #478 permissions overview for admin in user edit view
233 - #478 permissions overview for admin in user edit view
233 - File view now displays small gravatars off all authors of given file
234 - File view now displays small gravatars off all authors of given file
234 - Implemented landing revisions. Each repository will get landing_rev attribute
235 - Implemented landing revisions. Each repository will get landing_rev attribute
235 that defines 'default' revision/branch for generating readme files
236 that defines 'default' revision/branch for generating readme files
236 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
237 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
237 earliest possible call.
238 earliest possible call.
238 - Import remote svn repositories to mercurial using hgsubversion.
239 - Import remote svn repositories to mercurial using hgsubversion.
239 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
240 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
240 - RhodeCode can use alternative server for generating avatar icons
241 - RhodeCode can use alternative server for generating avatar icons
241 - implemented repositories locking. Pull locks, push unlocks. Also can be done
242 - implemented repositories locking. Pull locks, push unlocks. Also can be done
242 via API calls
243 via API calls
243 - #538 form for permissions can handle multiple users at once
244 - #538 form for permissions can handle multiple users at once
244
245
245 fixes
246 fixes
246 +++++
247 +++++
247
248
248 - improved translations
249 - improved translations
249 - fixes issue #455 Creating an archive generates an exception on Windows
250 - fixes issue #455 Creating an archive generates an exception on Windows
250 - fixes #448 Download ZIP archive keeps file in /tmp open and results
251 - fixes #448 Download ZIP archive keeps file in /tmp open and results
251 in out of disk space
252 in out of disk space
252 - fixes issue #454 Search results under Windows include proceeding
253 - fixes issue #454 Search results under Windows include proceeding
253 backslash
254 backslash
254 - fixed issue #450. Rhodecode no longer will crash when bad revision is
255 - fixed issue #450. Rhodecode no longer will crash when bad revision is
255 present in journal data.
256 present in journal data.
256 - fix for issue #417, git execution was broken on windows for certain
257 - fix for issue #417, git execution was broken on windows for certain
257 commands.
258 commands.
258 - fixed #413. Don't disable .git directory for bare repos on deleting
259 - fixed #413. Don't disable .git directory for bare repos on deleting
259 - fixed issue #459. Changed the way of obtaining logger in reindex task.
260 - fixed issue #459. Changed the way of obtaining logger in reindex task.
260 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
261 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
261 reindexing modified files
262 reindexing modified files
262 - fixed #481 rhodecode emails are sent without Date header
263 - fixed #481 rhodecode emails are sent without Date header
263 - fixed #458 wrong count when no repos are present
264 - fixed #458 wrong count when no repos are present
264 - fixed issue #492 missing `\ No newline at end of file` test at the end of
265 - fixed issue #492 missing `\ No newline at end of file` test at the end of
265 new chunk in html diff
266 new chunk in html diff
266 - full text search now works also for commit messages
267 - full text search now works also for commit messages
267
268
268 1.3.6 (**2012-05-17**)
269 1.3.6 (**2012-05-17**)
269 ----------------------
270 ----------------------
270
271
271 news
272 news
272 ++++
273 ++++
273
274
274 - chinese traditional translation
275 - chinese traditional translation
275 - changed setup-app into setup-rhodecode and added arguments for auto-setup
276 - changed setup-app into setup-rhodecode and added arguments for auto-setup
276 mode that doesn't need user interaction
277 mode that doesn't need user interaction
277
278
278 fixes
279 fixes
279 +++++
280 +++++
280
281
281 - fixed no scm found warning
282 - fixed no scm found warning
282 - fixed __future__ import error on rcextensions
283 - fixed __future__ import error on rcextensions
283 - made simplejson required lib for speedup on JSON encoding
284 - made simplejson required lib for speedup on JSON encoding
284 - fixes #449 bad regex could get more than revisions from parsing history
285 - fixes #449 bad regex could get more than revisions from parsing history
285 - don't clear DB session when CELERY_EAGER is turned ON
286 - don't clear DB session when CELERY_EAGER is turned ON
286
287
287 1.3.5 (**2012-05-10**)
288 1.3.5 (**2012-05-10**)
288 ----------------------
289 ----------------------
289
290
290 news
291 news
291 ++++
292 ++++
292
293
293 - use ext_json for json module
294 - use ext_json for json module
294 - unified annotation view with file source view
295 - unified annotation view with file source view
295 - notification improvements, better inbox + css
296 - notification improvements, better inbox + css
296 - #419 don't strip passwords for login forms, make rhodecode
297 - #419 don't strip passwords for login forms, make rhodecode
297 more compatible with LDAP servers
298 more compatible with LDAP servers
298 - Added HTTP_X_FORWARDED_FOR as another method of extracting
299 - Added HTTP_X_FORWARDED_FOR as another method of extracting
299 IP for pull/push logs. - moved all to base controller
300 IP for pull/push logs. - moved all to base controller
300 - #415: Adding comment to changeset causes reload.
301 - #415: Adding comment to changeset causes reload.
301 Comments are now added via ajax and doesn't reload the page
302 Comments are now added via ajax and doesn't reload the page
302 - #374 LDAP config is discarded when LDAP can't be activated
303 - #374 LDAP config is discarded when LDAP can't be activated
303 - limited push/pull operations are now logged for git in the journal
304 - limited push/pull operations are now logged for git in the journal
304 - bumped mercurial to 2.2.X series
305 - bumped mercurial to 2.2.X series
305 - added support for displaying submodules in file-browser
306 - added support for displaying submodules in file-browser
306 - #421 added bookmarks in changelog view
307 - #421 added bookmarks in changelog view
307
308
308 fixes
309 fixes
309 +++++
310 +++++
310
311
311 - fixed dev-version marker for stable when served from source codes
312 - fixed dev-version marker for stable when served from source codes
312 - fixed missing permission checks on show forks page
313 - fixed missing permission checks on show forks page
313 - #418 cast to unicode fixes in notification objects
314 - #418 cast to unicode fixes in notification objects
314 - #426 fixed mention extracting regex
315 - #426 fixed mention extracting regex
315 - fixed remote-pulling for git remotes remopositories
316 - fixed remote-pulling for git remotes remopositories
316 - fixed #434: Error when accessing files or changesets of a git repository
317 - fixed #434: Error when accessing files or changesets of a git repository
317 with submodules
318 with submodules
318 - fixed issue with empty APIKEYS for users after registration ref. #438
319 - fixed issue with empty APIKEYS for users after registration ref. #438
319 - fixed issue with getting README files from git repositories
320 - fixed issue with getting README files from git repositories
320
321
321 1.3.4 (**2012-03-28**)
322 1.3.4 (**2012-03-28**)
322 ----------------------
323 ----------------------
323
324
324 news
325 news
325 ++++
326 ++++
326
327
327 - Whoosh logging is now controlled by the .ini files logging setup
328 - Whoosh logging is now controlled by the .ini files logging setup
328 - added clone-url into edit form on /settings page
329 - added clone-url into edit form on /settings page
329 - added help text into repo add/edit forms
330 - added help text into repo add/edit forms
330 - created rcextensions module with additional mappings (ref #322) and
331 - created rcextensions module with additional mappings (ref #322) and
331 post push/pull/create repo hooks callbacks
332 post push/pull/create repo hooks callbacks
332 - implemented #377 Users view for his own permissions on account page
333 - implemented #377 Users view for his own permissions on account page
333 - #399 added inheritance of permissions for users group on repos groups
334 - #399 added inheritance of permissions for users group on repos groups
334 - #401 repository group is automatically pre-selected when adding repos
335 - #401 repository group is automatically pre-selected when adding repos
335 inside a repository group
336 inside a repository group
336 - added alternative HTTP 403 response when client failed to authenticate. Helps
337 - added alternative HTTP 403 response when client failed to authenticate. Helps
337 solving issues with Mercurial and LDAP
338 solving issues with Mercurial and LDAP
338 - #402 removed group prefix from repository name when listing repositories
339 - #402 removed group prefix from repository name when listing repositories
339 inside a group
340 inside a group
340 - added gravatars into permission view and permissions autocomplete
341 - added gravatars into permission view and permissions autocomplete
341 - #347 when running multiple RhodeCode instances, properly invalidates cache
342 - #347 when running multiple RhodeCode instances, properly invalidates cache
342 for all registered servers
343 for all registered servers
343
344
344 fixes
345 fixes
345 +++++
346 +++++
346
347
347 - fixed #390 cache invalidation problems on repos inside group
348 - fixed #390 cache invalidation problems on repos inside group
348 - fixed #385 clone by ID url was loosing proxy prefix in URL
349 - fixed #385 clone by ID url was loosing proxy prefix in URL
349 - fixed some unicode problems with waitress
350 - fixed some unicode problems with waitress
350 - fixed issue with escaping < and > in changeset commits
351 - fixed issue with escaping < and > in changeset commits
351 - fixed error occurring during recursive group creation in API
352 - fixed error occurring during recursive group creation in API
352 create_repo function
353 create_repo function
353 - fixed #393 py2.5 fixes for routes url generator
354 - fixed #393 py2.5 fixes for routes url generator
354 - fixed #397 Private repository groups shows up before login
355 - fixed #397 Private repository groups shows up before login
355 - fixed #396 fixed problems with revoking users in nested groups
356 - fixed #396 fixed problems with revoking users in nested groups
356 - fixed mysql unicode issues + specified InnoDB as default engine with
357 - fixed mysql unicode issues + specified InnoDB as default engine with
357 utf8 charset
358 utf8 charset
358 - #406 trim long branch/tag names in changelog to not break UI
359 - #406 trim long branch/tag names in changelog to not break UI
359
360
360 1.3.3 (**2012-03-02**)
361 1.3.3 (**2012-03-02**)
361 ----------------------
362 ----------------------
362
363
363 news
364 news
364 ++++
365 ++++
365
366
366
367
367 fixes
368 fixes
368 +++++
369 +++++
369
370
370 - fixed some python2.5 compatibility issues
371 - fixed some python2.5 compatibility issues
371 - fixed issues with removed repos was accidentally added as groups, after
372 - fixed issues with removed repos was accidentally added as groups, after
372 full rescan of paths
373 full rescan of paths
373 - fixes #376 Cannot edit user (using container auth)
374 - fixes #376 Cannot edit user (using container auth)
374 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
375 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
375 configuration
376 configuration
376 - fixed initial sorting of repos inside repo group
377 - fixed initial sorting of repos inside repo group
377 - fixes issue when user tried to resubmit same permission into user/user_groups
378 - fixes issue when user tried to resubmit same permission into user/user_groups
378 - bumped beaker version that fixes #375 leap error bug
379 - bumped beaker version that fixes #375 leap error bug
379 - fixed raw_changeset for git. It was generated with hg patch headers
380 - fixed raw_changeset for git. It was generated with hg patch headers
380 - fixed vcs issue with last_changeset for filenodes
381 - fixed vcs issue with last_changeset for filenodes
381 - fixed missing commit after hook delete
382 - fixed missing commit after hook delete
382 - fixed #372 issues with git operation detection that caused a security issue
383 - fixed #372 issues with git operation detection that caused a security issue
383 for git repos
384 for git repos
384
385
385 1.3.2 (**2012-02-28**)
386 1.3.2 (**2012-02-28**)
386 ----------------------
387 ----------------------
387
388
388 news
389 news
389 ++++
390 ++++
390
391
391
392
392 fixes
393 fixes
393 +++++
394 +++++
394
395
395 - fixed git protocol issues with repos-groups
396 - fixed git protocol issues with repos-groups
396 - fixed git remote repos validator that prevented from cloning remote git repos
397 - fixed git remote repos validator that prevented from cloning remote git repos
397 - fixes #370 ending slashes fixes for repo and groups
398 - fixes #370 ending slashes fixes for repo and groups
398 - fixes #368 improved git-protocol detection to handle other clients
399 - fixes #368 improved git-protocol detection to handle other clients
399 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
400 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
400 Moved To Root
401 Moved To Root
401 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
402 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
402 - fixed #373 missing cascade drop on user_group_to_perm table
403 - fixed #373 missing cascade drop on user_group_to_perm table
403
404
404 1.3.1 (**2012-02-27**)
405 1.3.1 (**2012-02-27**)
405 ----------------------
406 ----------------------
406
407
407 news
408 news
408 ++++
409 ++++
409
410
410
411
411 fixes
412 fixes
412 +++++
413 +++++
413
414
414 - redirection loop occurs when remember-me wasn't checked during login
415 - redirection loop occurs when remember-me wasn't checked during login
415 - fixes issues with git blob history generation
416 - fixes issues with git blob history generation
416 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
417 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
417
418
418 1.3.0 (**2012-02-26**)
419 1.3.0 (**2012-02-26**)
419 ----------------------
420 ----------------------
420
421
421 news
422 news
422 ++++
423 ++++
423
424
424 - code review, inspired by github code-comments
425 - code review, inspired by github code-comments
425 - #215 rst and markdown README files support
426 - #215 rst and markdown README files support
426 - #252 Container-based and proxy pass-through authentication support
427 - #252 Container-based and proxy pass-through authentication support
427 - #44 branch browser. Filtering of changelog by branches
428 - #44 branch browser. Filtering of changelog by branches
428 - mercurial bookmarks support
429 - mercurial bookmarks support
429 - new hover top menu, optimized to add maximum size for important views
430 - new hover top menu, optimized to add maximum size for important views
430 - configurable clone url template with possibility to specify protocol like
431 - configurable clone url template with possibility to specify protocol like
431 ssh:// or http:// and also manually alter other parts of clone_url.
432 ssh:// or http:// and also manually alter other parts of clone_url.
432 - enabled largefiles extension by default
433 - enabled largefiles extension by default
433 - optimized summary file pages and saved a lot of unused space in them
434 - optimized summary file pages and saved a lot of unused space in them
434 - #239 option to manually mark repository as fork
435 - #239 option to manually mark repository as fork
435 - #320 mapping of commit authors to RhodeCode users
436 - #320 mapping of commit authors to RhodeCode users
436 - #304 hashes are displayed using monospace font
437 - #304 hashes are displayed using monospace font
437 - diff configuration, toggle white lines and context lines
438 - diff configuration, toggle white lines and context lines
438 - #307 configurable diffs, whitespace toggle, increasing context lines
439 - #307 configurable diffs, whitespace toggle, increasing context lines
439 - sorting on branches, tags and bookmarks using YUI datatable
440 - sorting on branches, tags and bookmarks using YUI datatable
440 - improved file filter on files page
441 - improved file filter on files page
441 - implements #330 api method for listing nodes ar particular revision
442 - implements #330 api method for listing nodes ar particular revision
442 - #73 added linking issues in commit messages to chosen issue tracker url
443 - #73 added linking issues in commit messages to chosen issue tracker url
443 based on user defined regular expression
444 based on user defined regular expression
444 - added linking of changesets in commit messages
445 - added linking of changesets in commit messages
445 - new compact changelog with expandable commit messages
446 - new compact changelog with expandable commit messages
446 - firstname and lastname are optional in user creation
447 - firstname and lastname are optional in user creation
447 - #348 added post-create repository hook
448 - #348 added post-create repository hook
448 - #212 global encoding settings is now configurable from .ini files
449 - #212 global encoding settings is now configurable from .ini files
449 - #227 added repository groups permissions
450 - #227 added repository groups permissions
450 - markdown gets codehilite extensions
451 - markdown gets codehilite extensions
451 - new API methods, delete_repositories, grante/revoke permissions for groups
452 - new API methods, delete_repositories, grante/revoke permissions for groups
452 and repos
453 and repos
453
454
454
455
455 fixes
456 fixes
456 +++++
457 +++++
457
458
458 - rewrote dbsession management for atomic operations, and better error handling
459 - rewrote dbsession management for atomic operations, and better error handling
459 - fixed sorting of repo tables
460 - fixed sorting of repo tables
460 - #326 escape of special html entities in diffs
461 - #326 escape of special html entities in diffs
461 - normalized user_name => username in api attributes
462 - normalized user_name => username in api attributes
462 - fixes #298 ldap created users with mixed case emails created conflicts
463 - fixes #298 ldap created users with mixed case emails created conflicts
463 on saving a form
464 on saving a form
464 - fixes issue when owner of a repo couldn't revoke permissions for users
465 - fixes issue when owner of a repo couldn't revoke permissions for users
465 and groups
466 and groups
466 - fixes #271 rare JSON serialization problem with statistics
467 - fixes #271 rare JSON serialization problem with statistics
467 - fixes #337 missing validation check for conflicting names of a group with a
468 - fixes #337 missing validation check for conflicting names of a group with a
468 repositories group
469 repositories group
469 - #340 fixed session problem for mysql and celery tasks
470 - #340 fixed session problem for mysql and celery tasks
470 - fixed #331 RhodeCode mangles repository names if the a repository group
471 - fixed #331 RhodeCode mangles repository names if the a repository group
471 contains the "full path" to the repositories
472 contains the "full path" to the repositories
472 - #355 RhodeCode doesn't store encrypted LDAP passwords
473 - #355 RhodeCode doesn't store encrypted LDAP passwords
473
474
474 1.2.5 (**2012-01-28**)
475 1.2.5 (**2012-01-28**)
475 ----------------------
476 ----------------------
476
477
477 news
478 news
478 ++++
479 ++++
479
480
480 fixes
481 fixes
481 +++++
482 +++++
482
483
483 - #340 Celery complains about MySQL server gone away, added session cleanup
484 - #340 Celery complains about MySQL server gone away, added session cleanup
484 for celery tasks
485 for celery tasks
485 - #341 "scanning for repositories in None" log message during Rescan was missing
486 - #341 "scanning for repositories in None" log message during Rescan was missing
486 a parameter
487 a parameter
487 - fixed creating archives with subrepos. Some hooks were triggered during that
488 - fixed creating archives with subrepos. Some hooks were triggered during that
488 operation leading to crash.
489 operation leading to crash.
489 - fixed missing email in account page.
490 - fixed missing email in account page.
490 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
491 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
491 forking on windows impossible
492 forking on windows impossible
492
493
493 1.2.4 (**2012-01-19**)
494 1.2.4 (**2012-01-19**)
494 ----------------------
495 ----------------------
495
496
496 news
497 news
497 ++++
498 ++++
498
499
499 - RhodeCode is bundled with mercurial series 2.0.X by default, with
500 - RhodeCode is bundled with mercurial series 2.0.X by default, with
500 full support to largefiles extension. Enabled by default in new installations
501 full support to largefiles extension. Enabled by default in new installations
501 - #329 Ability to Add/Remove Groups to/from a Repository via AP
502 - #329 Ability to Add/Remove Groups to/from a Repository via AP
502 - added requires.txt file with requirements
503 - added requires.txt file with requirements
503
504
504 fixes
505 fixes
505 +++++
506 +++++
506
507
507 - fixes db session issues with celery when emailing admins
508 - fixes db session issues with celery when emailing admins
508 - #331 RhodeCode mangles repository names if the a repository group
509 - #331 RhodeCode mangles repository names if the a repository group
509 contains the "full path" to the repositories
510 contains the "full path" to the repositories
510 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
511 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
511 - DB session cleanup after hg protocol operations, fixes issues with
512 - DB session cleanup after hg protocol operations, fixes issues with
512 `mysql has gone away` errors
513 `mysql has gone away` errors
513 - #333 doc fixes for get_repo api function
514 - #333 doc fixes for get_repo api function
514 - #271 rare JSON serialization problem with statistics enabled
515 - #271 rare JSON serialization problem with statistics enabled
515 - #337 Fixes issues with validation of repository name conflicting with
516 - #337 Fixes issues with validation of repository name conflicting with
516 a group name. A proper message is now displayed.
517 a group name. A proper message is now displayed.
517 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
518 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
518 doesn't work
519 doesn't work
519 - #316 fixes issues with web description in hgrc files
520 - #316 fixes issues with web description in hgrc files
520
521
521 1.2.3 (**2011-11-02**)
522 1.2.3 (**2011-11-02**)
522 ----------------------
523 ----------------------
523
524
524 news
525 news
525 ++++
526 ++++
526
527
527 - added option to manage repos group for non admin users
528 - added option to manage repos group for non admin users
528 - added following API methods for get_users, create_user, get_users_groups,
529 - added following API methods for get_users, create_user, get_users_groups,
529 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
530 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
530 get_repo, create_repo, add_user_to_repo
531 get_repo, create_repo, add_user_to_repo
531 - implements #237 added password confirmation for my account
532 - implements #237 added password confirmation for my account
532 and admin edit user.
533 and admin edit user.
533 - implements #291 email notification for global events are now sent to all
534 - implements #291 email notification for global events are now sent to all
534 administrator users, and global config email.
535 administrator users, and global config email.
535
536
536 fixes
537 fixes
537 +++++
538 +++++
538
539
539 - added option for passing auth method for smtp mailer
540 - added option for passing auth method for smtp mailer
540 - #276 issue with adding a single user with id>10 to usergroups
541 - #276 issue with adding a single user with id>10 to usergroups
541 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
542 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
542 - #288 fixes managing of repos in a group for non admin user
543 - #288 fixes managing of repos in a group for non admin user
543
544
544 1.2.2 (**2011-10-17**)
545 1.2.2 (**2011-10-17**)
545 ----------------------
546 ----------------------
546
547
547 news
548 news
548 ++++
549 ++++
549
550
550 - #226 repo groups are available by path instead of numerical id
551 - #226 repo groups are available by path instead of numerical id
551
552
552 fixes
553 fixes
553 +++++
554 +++++
554
555
555 - #259 Groups with the same name but with different parent group
556 - #259 Groups with the same name but with different parent group
556 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
557 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
557 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
558 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
558 - #265 ldap save fails sometimes on converting attributes to booleans,
559 - #265 ldap save fails sometimes on converting attributes to booleans,
559 added getter and setter into model that will prevent from this on db model level
560 added getter and setter into model that will prevent from this on db model level
560 - fixed problems with timestamps issues #251 and #213
561 - fixed problems with timestamps issues #251 and #213
561 - fixes #266 RhodeCode allows to create repo with the same name and in
562 - fixes #266 RhodeCode allows to create repo with the same name and in
562 the same parent as group
563 the same parent as group
563 - fixes #245 Rescan of the repositories on Windows
564 - fixes #245 Rescan of the repositories on Windows
564 - fixes #248 cannot edit repos inside a group on windows
565 - fixes #248 cannot edit repos inside a group on windows
565 - fixes #219 forking problems on windows
566 - fixes #219 forking problems on windows
566
567
567 1.2.1 (**2011-10-08**)
568 1.2.1 (**2011-10-08**)
568 ----------------------
569 ----------------------
569
570
570 news
571 news
571 ++++
572 ++++
572
573
573
574
574 fixes
575 fixes
575 +++++
576 +++++
576
577
577 - fixed problems with basic auth and push problems
578 - fixed problems with basic auth and push problems
578 - gui fixes
579 - gui fixes
579 - fixed logger
580 - fixed logger
580
581
581 1.2.0 (**2011-10-07**)
582 1.2.0 (**2011-10-07**)
582 ----------------------
583 ----------------------
583
584
584 news
585 news
585 ++++
586 ++++
586
587
587 - implemented #47 repository groups
588 - implemented #47 repository groups
588 - implemented #89 Can setup google analytics code from settings menu
589 - implemented #89 Can setup google analytics code from settings menu
589 - implemented #91 added nicer looking archive urls with more download options
590 - implemented #91 added nicer looking archive urls with more download options
590 like tags, branches
591 like tags, branches
591 - implemented #44 into file browsing, and added follow branch option
592 - implemented #44 into file browsing, and added follow branch option
592 - implemented #84 downloads can be enabled/disabled for each repository
593 - implemented #84 downloads can be enabled/disabled for each repository
593 - anonymous repository can be cloned without having to pass default:default
594 - anonymous repository can be cloned without having to pass default:default
594 into clone url
595 into clone url
595 - fixed #90 whoosh indexer can index chooses repositories passed in command
596 - fixed #90 whoosh indexer can index chooses repositories passed in command
596 line
597 line
597 - extended journal with day aggregates and paging
598 - extended journal with day aggregates and paging
598 - implemented #107 source code lines highlight ranges
599 - implemented #107 source code lines highlight ranges
599 - implemented #93 customizable changelog on combined revision ranges -
600 - implemented #93 customizable changelog on combined revision ranges -
600 equivalent of githubs compare view
601 equivalent of githubs compare view
601 - implemented #108 extended and more powerful LDAP configuration
602 - implemented #108 extended and more powerful LDAP configuration
602 - implemented #56 users groups
603 - implemented #56 users groups
603 - major code rewrites optimized codes for speed and memory usage
604 - major code rewrites optimized codes for speed and memory usage
604 - raw and diff downloads are now in git format
605 - raw and diff downloads are now in git format
605 - setup command checks for write access to given path
606 - setup command checks for write access to given path
606 - fixed many issues with international characters and unicode. It uses utf8
607 - fixed many issues with international characters and unicode. It uses utf8
607 decode with replace to provide less errors even with non utf8 encoded strings
608 decode with replace to provide less errors even with non utf8 encoded strings
608 - #125 added API KEY access to feeds
609 - #125 added API KEY access to feeds
609 - #109 Repository can be created from external Mercurial link (aka. remote
610 - #109 Repository can be created from external Mercurial link (aka. remote
610 repository, and manually updated (via pull) from admin panel
611 repository, and manually updated (via pull) from admin panel
611 - beta git support - push/pull server + basic view for git repos
612 - beta git support - push/pull server + basic view for git repos
612 - added followers page and forks page
613 - added followers page and forks page
613 - server side file creation (with binary file upload interface)
614 - server side file creation (with binary file upload interface)
614 and edition with commits powered by codemirror
615 and edition with commits powered by codemirror
615 - #111 file browser file finder, quick lookup files on whole file tree
616 - #111 file browser file finder, quick lookup files on whole file tree
616 - added quick login sliding menu into main page
617 - added quick login sliding menu into main page
617 - changelog uses lazy loading of affected files details, in some scenarios
618 - changelog uses lazy loading of affected files details, in some scenarios
618 this can improve speed of changelog page dramatically especially for
619 this can improve speed of changelog page dramatically especially for
619 larger repositories.
620 larger repositories.
620 - implements #214 added support for downloading subrepos in download menu.
621 - implements #214 added support for downloading subrepos in download menu.
621 - Added basic API for direct operations on rhodecode via JSON
622 - Added basic API for direct operations on rhodecode via JSON
622 - Implemented advanced hook management
623 - Implemented advanced hook management
623
624
624 fixes
625 fixes
625 +++++
626 +++++
626
627
627 - fixed file browser bug, when switching into given form revision the url was
628 - fixed file browser bug, when switching into given form revision the url was
628 not changing
629 not changing
629 - fixed propagation to error controller on simplehg and simplegit middlewares
630 - fixed propagation to error controller on simplehg and simplegit middlewares
630 - fixed error when trying to make a download on empty repository
631 - fixed error when trying to make a download on empty repository
631 - fixed problem with '[' chars in commit messages in journal
632 - fixed problem with '[' chars in commit messages in journal
632 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
633 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
633 - journal fork fixes
634 - journal fork fixes
634 - removed issue with space inside renamed repository after deletion
635 - removed issue with space inside renamed repository after deletion
635 - fixed strange issue on formencode imports
636 - fixed strange issue on formencode imports
636 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
637 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
637 - #150 fixes for errors on repositories mapped in db but corrupted in
638 - #150 fixes for errors on repositories mapped in db but corrupted in
638 filesystem
639 filesystem
639 - fixed problem with ascendant characters in realm #181
640 - fixed problem with ascendant characters in realm #181
640 - fixed problem with sqlite file based database connection pool
641 - fixed problem with sqlite file based database connection pool
641 - whoosh indexer and code stats share the same dynamic extensions map
642 - whoosh indexer and code stats share the same dynamic extensions map
642 - fixes #188 - relationship delete of repo_to_perm entry on user removal
643 - fixes #188 - relationship delete of repo_to_perm entry on user removal
643 - fixes issue #189 Trending source files shows "show more" when no more exist
644 - fixes issue #189 Trending source files shows "show more" when no more exist
644 - fixes issue #197 Relative paths for pidlocks
645 - fixes issue #197 Relative paths for pidlocks
645 - fixes issue #198 password will require only 3 chars now for login form
646 - fixes issue #198 password will require only 3 chars now for login form
646 - fixes issue #199 wrong redirection for non admin users after creating a repository
647 - fixes issue #199 wrong redirection for non admin users after creating a repository
647 - fixes issues #202, bad db constraint made impossible to attach same group
648 - fixes issues #202, bad db constraint made impossible to attach same group
648 more than one time. Affects only mysql/postgres
649 more than one time. Affects only mysql/postgres
649 - fixes #218 os.kill patch for windows was missing sig param
650 - fixes #218 os.kill patch for windows was missing sig param
650 - improved rendering of dag (they are not trimmed anymore when number of
651 - improved rendering of dag (they are not trimmed anymore when number of
651 heads exceeds 5)
652 heads exceeds 5)
652
653
653 1.1.8 (**2011-04-12**)
654 1.1.8 (**2011-04-12**)
654 ----------------------
655 ----------------------
655
656
656 news
657 news
657 ++++
658 ++++
658
659
659 - improved windows support
660 - improved windows support
660
661
661 fixes
662 fixes
662 +++++
663 +++++
663
664
664 - fixed #140 freeze of python dateutil library, since new version is python2.x
665 - fixed #140 freeze of python dateutil library, since new version is python2.x
665 incompatible
666 incompatible
666 - setup-app will check for write permission in given path
667 - setup-app will check for write permission in given path
667 - cleaned up license info issue #149
668 - cleaned up license info issue #149
668 - fixes for issues #137,#116 and problems with unicode and accented characters.
669 - fixes for issues #137,#116 and problems with unicode and accented characters.
669 - fixes crashes on gravatar, when passed in email as unicode
670 - fixes crashes on gravatar, when passed in email as unicode
670 - fixed tooltip flickering problems
671 - fixed tooltip flickering problems
671 - fixed came_from redirection on windows
672 - fixed came_from redirection on windows
672 - fixed logging modules, and sql formatters
673 - fixed logging modules, and sql formatters
673 - windows fixes for os.kill issue #133
674 - windows fixes for os.kill issue #133
674 - fixes path splitting for windows issues #148
675 - fixes path splitting for windows issues #148
675 - fixed issue #143 wrong import on migration to 1.1.X
676 - fixed issue #143 wrong import on migration to 1.1.X
676 - fixed problems with displaying binary files, thanks to Thomas Waldmann
677 - fixed problems with displaying binary files, thanks to Thomas Waldmann
677 - removed name from archive files since it's breaking ui for long repo names
678 - removed name from archive files since it's breaking ui for long repo names
678 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
679 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
679 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
680 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
680 Thomas Waldmann
681 Thomas Waldmann
681 - fixed issue #166 summary pager was skipping 10 revisions on second page
682 - fixed issue #166 summary pager was skipping 10 revisions on second page
682
683
683
684
684 1.1.7 (**2011-03-23**)
685 1.1.7 (**2011-03-23**)
685 ----------------------
686 ----------------------
686
687
687 news
688 news
688 ++++
689 ++++
689
690
690 fixes
691 fixes
691 +++++
692 +++++
692
693
693 - fixed (again) #136 installation support for FreeBSD
694 - fixed (again) #136 installation support for FreeBSD
694
695
695
696
696 1.1.6 (**2011-03-21**)
697 1.1.6 (**2011-03-21**)
697 ----------------------
698 ----------------------
698
699
699 news
700 news
700 ++++
701 ++++
701
702
702 fixes
703 fixes
703 +++++
704 +++++
704
705
705 - fixed #136 installation support for FreeBSD
706 - fixed #136 installation support for FreeBSD
706 - RhodeCode will check for python version during installation
707 - RhodeCode will check for python version during installation
707
708
708 1.1.5 (**2011-03-17**)
709 1.1.5 (**2011-03-17**)
709 ----------------------
710 ----------------------
710
711
711 news
712 news
712 ++++
713 ++++
713
714
714 - basic windows support, by exchanging pybcrypt into sha256 for windows only
715 - basic windows support, by exchanging pybcrypt into sha256 for windows only
715 highly inspired by idea of mantis406
716 highly inspired by idea of mantis406
716
717
717 fixes
718 fixes
718 +++++
719 +++++
719
720
720 - fixed sorting by author in main page
721 - fixed sorting by author in main page
721 - fixed crashes with diffs on binary files
722 - fixed crashes with diffs on binary files
722 - fixed #131 problem with boolean values for LDAP
723 - fixed #131 problem with boolean values for LDAP
723 - fixed #122 mysql problems thanks to striker69
724 - fixed #122 mysql problems thanks to striker69
724 - fixed problem with errors on calling raw/raw_files/annotate functions
725 - fixed problem with errors on calling raw/raw_files/annotate functions
725 with unknown revisions
726 with unknown revisions
726 - fixed returned rawfiles attachment names with international character
727 - fixed returned rawfiles attachment names with international character
727 - cleaned out docs, big thanks to Jason Harris
728 - cleaned out docs, big thanks to Jason Harris
728
729
729 1.1.4 (**2011-02-19**)
730 1.1.4 (**2011-02-19**)
730 ----------------------
731 ----------------------
731
732
732 news
733 news
733 ++++
734 ++++
734
735
735 fixes
736 fixes
736 +++++
737 +++++
737
738
738 - fixed formencode import problem on settings page, that caused server crash
739 - fixed formencode import problem on settings page, that caused server crash
739 when that page was accessed as first after server start
740 when that page was accessed as first after server start
740 - journal fixes
741 - journal fixes
741 - fixed option to access repository just by entering http://server/<repo_name>
742 - fixed option to access repository just by entering http://server/<repo_name>
742
743
743 1.1.3 (**2011-02-16**)
744 1.1.3 (**2011-02-16**)
744 ----------------------
745 ----------------------
745
746
746 news
747 news
747 ++++
748 ++++
748
749
749 - implemented #102 allowing the '.' character in username
750 - implemented #102 allowing the '.' character in username
750 - added option to access repository just by entering http://server/<repo_name>
751 - added option to access repository just by entering http://server/<repo_name>
751 - celery task ignores result for better performance
752 - celery task ignores result for better performance
752
753
753 fixes
754 fixes
754 +++++
755 +++++
755
756
756 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
757 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
757 apollo13 and Johan Walles
758 apollo13 and Johan Walles
758 - small fixes in journal
759 - small fixes in journal
759 - fixed problems with getting setting for celery from .ini files
760 - fixed problems with getting setting for celery from .ini files
760 - registration, password reset and login boxes share the same title as main
761 - registration, password reset and login boxes share the same title as main
761 application now
762 application now
762 - fixed #113: to high permissions to fork repository
763 - fixed #113: to high permissions to fork repository
763 - fixed problem with '[' chars in commit messages in journal
764 - fixed problem with '[' chars in commit messages in journal
764 - removed issue with space inside renamed repository after deletion
765 - removed issue with space inside renamed repository after deletion
765 - db transaction fixes when filesystem repository creation failed
766 - db transaction fixes when filesystem repository creation failed
766 - fixed #106 relation issues on databases different than sqlite
767 - fixed #106 relation issues on databases different than sqlite
767 - fixed static files paths links to use of url() method
768 - fixed static files paths links to use of url() method
768
769
769 1.1.2 (**2011-01-12**)
770 1.1.2 (**2011-01-12**)
770 ----------------------
771 ----------------------
771
772
772 news
773 news
773 ++++
774 ++++
774
775
775
776
776 fixes
777 fixes
777 +++++
778 +++++
778
779
779 - fixes #98 protection against float division of percentage stats
780 - fixes #98 protection against float division of percentage stats
780 - fixed graph bug
781 - fixed graph bug
781 - forced webhelpers version since it was making troubles during installation
782 - forced webhelpers version since it was making troubles during installation
782
783
783 1.1.1 (**2011-01-06**)
784 1.1.1 (**2011-01-06**)
784 ----------------------
785 ----------------------
785
786
786 news
787 news
787 ++++
788 ++++
788
789
789 - added force https option into ini files for easier https usage (no need to
790 - added force https option into ini files for easier https usage (no need to
790 set server headers with this options)
791 set server headers with this options)
791 - small css updates
792 - small css updates
792
793
793 fixes
794 fixes
794 +++++
795 +++++
795
796
796 - fixed #96 redirect loop on files view on repositories without changesets
797 - fixed #96 redirect loop on files view on repositories without changesets
797 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
798 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
798 and server crashed with errors
799 and server crashed with errors
799 - fixed large tooltips problems on main page
800 - fixed large tooltips problems on main page
800 - fixed #92 whoosh indexer is more error proof
801 - fixed #92 whoosh indexer is more error proof
801
802
802 1.1.0 (**2010-12-18**)
803 1.1.0 (**2010-12-18**)
803 ----------------------
804 ----------------------
804
805
805 news
806 news
806 ++++
807 ++++
807
808
808 - rewrite of internals for vcs >=0.1.10
809 - rewrite of internals for vcs >=0.1.10
809 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
810 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
810 with older clients
811 with older clients
811 - anonymous access, authentication via ldap
812 - anonymous access, authentication via ldap
812 - performance upgrade for cached repos list - each repository has its own
813 - performance upgrade for cached repos list - each repository has its own
813 cache that's invalidated when needed.
814 cache that's invalidated when needed.
814 - performance upgrades on repositories with large amount of commits (20K+)
815 - performance upgrades on repositories with large amount of commits (20K+)
815 - main page quick filter for filtering repositories
816 - main page quick filter for filtering repositories
816 - user dashboards with ability to follow chosen repositories actions
817 - user dashboards with ability to follow chosen repositories actions
817 - sends email to admin on new user registration
818 - sends email to admin on new user registration
818 - added cache/statistics reset options into repository settings
819 - added cache/statistics reset options into repository settings
819 - more detailed action logger (based on hooks) with pushed changesets lists
820 - more detailed action logger (based on hooks) with pushed changesets lists
820 and options to disable those hooks from admin panel
821 and options to disable those hooks from admin panel
821 - introduced new enhanced changelog for merges that shows more accurate results
822 - introduced new enhanced changelog for merges that shows more accurate results
822 - new improved and faster code stats (based on pygments lexers mapping tables,
823 - new improved and faster code stats (based on pygments lexers mapping tables,
823 showing up to 10 trending sources for each repository. Additionally stats
824 showing up to 10 trending sources for each repository. Additionally stats
824 can be disabled in repository settings.
825 can be disabled in repository settings.
825 - gui optimizations, fixed application width to 1024px
826 - gui optimizations, fixed application width to 1024px
826 - added cut off (for large files/changesets) limit into config files
827 - added cut off (for large files/changesets) limit into config files
827 - whoosh, celeryd, upgrade moved to paster command
828 - whoosh, celeryd, upgrade moved to paster command
828 - other than sqlite database backends can be used
829 - other than sqlite database backends can be used
829
830
830 fixes
831 fixes
831 +++++
832 +++++
832
833
833 - fixes #61 forked repo was showing only after cache expired
834 - fixes #61 forked repo was showing only after cache expired
834 - fixes #76 no confirmation on user deletes
835 - fixes #76 no confirmation on user deletes
835 - fixes #66 Name field misspelled
836 - fixes #66 Name field misspelled
836 - fixes #72 block user removal when he owns repositories
837 - fixes #72 block user removal when he owns repositories
837 - fixes #69 added password confirmation fields
838 - fixes #69 added password confirmation fields
838 - fixes #87 RhodeCode crashes occasionally on updating repository owner
839 - fixes #87 RhodeCode crashes occasionally on updating repository owner
839 - fixes #82 broken annotations on files with more than 1 blank line at the end
840 - fixes #82 broken annotations on files with more than 1 blank line at the end
840 - a lot of fixes and tweaks for file browser
841 - a lot of fixes and tweaks for file browser
841 - fixed detached session issues
842 - fixed detached session issues
842 - fixed when user had no repos he would see all repos listed in my account
843 - fixed when user had no repos he would see all repos listed in my account
843 - fixed ui() instance bug when global hgrc settings was loaded for server
844 - fixed ui() instance bug when global hgrc settings was loaded for server
844 instance and all hgrc options were merged with our db ui() object
845 instance and all hgrc options were merged with our db ui() object
845 - numerous small bugfixes
846 - numerous small bugfixes
846
847
847 (special thanks for TkSoh for detailed feedback)
848 (special thanks for TkSoh for detailed feedback)
848
849
849
850
850 1.0.2 (**2010-11-12**)
851 1.0.2 (**2010-11-12**)
851 ----------------------
852 ----------------------
852
853
853 news
854 news
854 ++++
855 ++++
855
856
856 - tested under python2.7
857 - tested under python2.7
857 - bumped sqlalchemy and celery versions
858 - bumped sqlalchemy and celery versions
858
859
859 fixes
860 fixes
860 +++++
861 +++++
861
862
862 - fixed #59 missing graph.js
863 - fixed #59 missing graph.js
863 - fixed repo_size crash when repository had broken symlinks
864 - fixed repo_size crash when repository had broken symlinks
864 - fixed python2.5 crashes.
865 - fixed python2.5 crashes.
865
866
866
867
867 1.0.1 (**2010-11-10**)
868 1.0.1 (**2010-11-10**)
868 ----------------------
869 ----------------------
869
870
870 news
871 news
871 ++++
872 ++++
872
873
873 - small css updated
874 - small css updated
874
875
875 fixes
876 fixes
876 +++++
877 +++++
877
878
878 - fixed #53 python2.5 incompatible enumerate calls
879 - fixed #53 python2.5 incompatible enumerate calls
879 - fixed #52 disable mercurial extension for web
880 - fixed #52 disable mercurial extension for web
880 - fixed #51 deleting repositories don't delete it's dependent objects
881 - fixed #51 deleting repositories don't delete it's dependent objects
881
882
882
883
883 1.0.0 (**2010-11-02**)
884 1.0.0 (**2010-11-02**)
884 ----------------------
885 ----------------------
885
886
886 - security bugfix simplehg wasn't checking for permissions on commands
887 - security bugfix simplehg wasn't checking for permissions on commands
887 other than pull or push.
888 other than pull or push.
888 - fixed doubled messages after push or pull in admin journal
889 - fixed doubled messages after push or pull in admin journal
889 - templating and css corrections, fixed repo switcher on chrome, updated titles
890 - templating and css corrections, fixed repo switcher on chrome, updated titles
890 - admin menu accessible from options menu on repository view
891 - admin menu accessible from options menu on repository view
891 - permissions cached queries
892 - permissions cached queries
892
893
893 1.0.0rc4 (**2010-10-12**)
894 1.0.0rc4 (**2010-10-12**)
894 --------------------------
895 --------------------------
895
896
896 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
897 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
897 - removed cache_manager settings from sqlalchemy meta
898 - removed cache_manager settings from sqlalchemy meta
898 - added sqlalchemy cache settings to ini files
899 - added sqlalchemy cache settings to ini files
899 - validated password length and added second try of failure on paster setup-app
900 - validated password length and added second try of failure on paster setup-app
900 - fixed setup database destroy prompt even when there was no db
901 - fixed setup database destroy prompt even when there was no db
901
902
902
903
903 1.0.0rc3 (**2010-10-11**)
904 1.0.0rc3 (**2010-10-11**)
904 -------------------------
905 -------------------------
905
906
906 - fixed i18n during installation.
907 - fixed i18n during installation.
907
908
908 1.0.0rc2 (**2010-10-11**)
909 1.0.0rc2 (**2010-10-11**)
909 -------------------------
910 -------------------------
910
911
911 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
912 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
912 occure. After vcs is fixed it'll be put back again.
913 occure. After vcs is fixed it'll be put back again.
913 - templating/css rewrites, optimized css. No newline at end of file
914 - templating/css rewrites, optimized css.
@@ -1,520 +1,510 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos
3 rhodecode.controllers.admin.repos
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Repositories controller for RhodeCode
6 Repositories controller for RhodeCode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from webob.exc import HTTPInternalServerError
31 from webob.exc import HTTPInternalServerError
32 from pylons import request, session, tmpl_context as c, url
32 from pylons import request, session, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.exc import IntegrityError
35 from sqlalchemy.exc import IntegrityError
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
43 from rhodecode.lib.helpers import get_token
43 from rhodecode.lib.helpers import get_token
44 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
45 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
45 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
46 RhodeCodeSetting
46 RhodeCodeSetting
47 from rhodecode.model.forms import RepoForm
47 from rhodecode.model.forms import RepoForm
48 from rhodecode.model.scm import ScmModel
48 from rhodecode.model.scm import ScmModel
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from rhodecode.lib.compat import json
50 from rhodecode.lib.compat import json
51 from sqlalchemy.sql.expression import func
51 from sqlalchemy.sql.expression import func
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class ReposController(BaseController):
56 class ReposController(BaseController):
57 """
57 """
58 REST Controller styled on the Atom Publishing Protocol"""
58 REST Controller styled on the Atom Publishing Protocol"""
59 # To properly map this controller, ensure your config/routing.py
59 # To properly map this controller, ensure your config/routing.py
60 # file has a resource setup:
60 # file has a resource setup:
61 # map.resource('repo', 'repos')
61 # map.resource('repo', 'repos')
62
62
63 @LoginRequired()
63 @LoginRequired()
64 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
64 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
65 def __before__(self):
65 def __before__(self):
66 c.admin_user = session.get('admin_user')
66 c.admin_user = session.get('admin_user')
67 c.admin_username = session.get('admin_username')
67 c.admin_username = session.get('admin_username')
68 super(ReposController, self).__before__()
68 super(ReposController, self).__before__()
69
69
70 def __load_defaults(self):
70 def __load_defaults(self):
71 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
71 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
72 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
72 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
73
73
74 repo_model = RepoModel()
74 repo_model = RepoModel()
75 c.users_array = repo_model.get_users_js()
75 c.users_array = repo_model.get_users_js()
76 c.users_groups_array = repo_model.get_users_groups_js()
76 c.users_groups_array = repo_model.get_users_groups_js()
77 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
77 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
78 c.landing_revs_choices = choices
78 c.landing_revs_choices = choices
79
79
80 def __load_data(self, repo_name=None):
80 def __load_data(self, repo_name=None):
81 """
81 """
82 Load defaults settings for edit, and update
82 Load defaults settings for edit, and update
83
83
84 :param repo_name:
84 :param repo_name:
85 """
85 """
86 self.__load_defaults()
86 self.__load_defaults()
87
87
88 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
88 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
89 repo = db_repo.scm_instance
89 repo = db_repo.scm_instance
90
90
91 if c.repo_info is None:
91 if c.repo_info is None:
92 h.flash(_('%s repository is not mapped to db perhaps'
92 h.not_mapped_error(repo_name)
93 ' it was created or renamed from the filesystem'
94 ' please run the application again'
95 ' in order to rescan repositories') % repo_name,
96 category='error')
97
98 return redirect(url('repos'))
93 return redirect(url('repos'))
99
94
100 ##override defaults for exact repo info here git/hg etc
95 ##override defaults for exact repo info here git/hg etc
101 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
96 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
102 c.landing_revs_choices = choices
97 c.landing_revs_choices = choices
103
98
104 c.default_user_id = User.get_by_username('default').user_id
99 c.default_user_id = User.get_by_username('default').user_id
105 c.in_public_journal = UserFollowing.query()\
100 c.in_public_journal = UserFollowing.query()\
106 .filter(UserFollowing.user_id == c.default_user_id)\
101 .filter(UserFollowing.user_id == c.default_user_id)\
107 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
102 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
108
103
109 if c.repo_info.stats:
104 if c.repo_info.stats:
110 # this is on what revision we ended up so we add +1 for count
105 # this is on what revision we ended up so we add +1 for count
111 last_rev = c.repo_info.stats.stat_on_revision + 1
106 last_rev = c.repo_info.stats.stat_on_revision + 1
112 else:
107 else:
113 last_rev = 0
108 last_rev = 0
114 c.stats_revision = last_rev
109 c.stats_revision = last_rev
115
110
116 c.repo_last_rev = repo.count() if repo.revisions else 0
111 c.repo_last_rev = repo.count() if repo.revisions else 0
117
112
118 if last_rev == 0 or c.repo_last_rev == 0:
113 if last_rev == 0 or c.repo_last_rev == 0:
119 c.stats_percentage = 0
114 c.stats_percentage = 0
120 else:
115 else:
121 c.stats_percentage = '%.2f' % ((float((last_rev)) /
116 c.stats_percentage = '%.2f' % ((float((last_rev)) /
122 c.repo_last_rev) * 100)
117 c.repo_last_rev) * 100)
123
118
124 defaults = RepoModel()._get_defaults(repo_name)
119 defaults = RepoModel()._get_defaults(repo_name)
125
120
126 c.repos_list = [('', _('--REMOVE FORK--'))]
121 c.repos_list = [('', _('--REMOVE FORK--'))]
127 c.repos_list += [(x.repo_id, x.repo_name) for x in
122 c.repos_list += [(x.repo_id, x.repo_name) for x in
128 Repository.query().order_by(Repository.repo_name).all()
123 Repository.query().order_by(Repository.repo_name).all()
129 if x.repo_id != c.repo_info.repo_id]
124 if x.repo_id != c.repo_info.repo_id]
130
125
131 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
126 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
132 return defaults
127 return defaults
133
128
134 @HasPermissionAllDecorator('hg.admin')
129 @HasPermissionAllDecorator('hg.admin')
135 def index(self, format='html'):
130 def index(self, format='html'):
136 """GET /repos: All items in the collection"""
131 """GET /repos: All items in the collection"""
137 # url('repos')
132 # url('repos')
138
133
139 c.repos_list = Repository.query()\
134 c.repos_list = Repository.query()\
140 .order_by(func.lower(Repository.repo_name))\
135 .order_by(func.lower(Repository.repo_name))\
141 .all()
136 .all()
142
137
143 repos_data = []
138 repos_data = []
144 total_records = len(c.repos_list)
139 total_records = len(c.repos_list)
145
140
146 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
141 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
147 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
142 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
148
143
149 quick_menu = lambda repo_name: (template.get_def("quick_menu")
144 quick_menu = lambda repo_name: (template.get_def("quick_menu")
150 .render(repo_name, _=_, h=h, c=c))
145 .render(repo_name, _=_, h=h, c=c))
151 repo_lnk = lambda name, rtype, private, fork_of: (
146 repo_lnk = lambda name, rtype, private, fork_of: (
152 template.get_def("repo_name")
147 template.get_def("repo_name")
153 .render(name, rtype, private, fork_of, short_name=False,
148 .render(name, rtype, private, fork_of, short_name=False,
154 admin=True, _=_, h=h, c=c))
149 admin=True, _=_, h=h, c=c))
155
150
156 repo_actions = lambda repo_name: (template.get_def("repo_actions")
151 repo_actions = lambda repo_name: (template.get_def("repo_actions")
157 .render(repo_name, _=_, h=h, c=c))
152 .render(repo_name, _=_, h=h, c=c))
158
153
159 for repo in c.repos_list:
154 for repo in c.repos_list:
160 repos_data.append({
155 repos_data.append({
161 "menu": quick_menu(repo.repo_name),
156 "menu": quick_menu(repo.repo_name),
162 "raw_name": repo.repo_name.lower(),
157 "raw_name": repo.repo_name.lower(),
163 "name": repo_lnk(repo.repo_name, repo.repo_type,
158 "name": repo_lnk(repo.repo_name, repo.repo_type,
164 repo.private, repo.fork),
159 repo.private, repo.fork),
165 "desc": repo.description,
160 "desc": repo.description,
166 "owner": repo.user.username,
161 "owner": repo.user.username,
167 "action": repo_actions(repo.repo_name),
162 "action": repo_actions(repo.repo_name),
168 })
163 })
169
164
170 c.data = json.dumps({
165 c.data = json.dumps({
171 "totalRecords": total_records,
166 "totalRecords": total_records,
172 "startIndex": 0,
167 "startIndex": 0,
173 "sort": "name",
168 "sort": "name",
174 "dir": "asc",
169 "dir": "asc",
175 "records": repos_data
170 "records": repos_data
176 })
171 })
177
172
178 return render('admin/repos/repos.html')
173 return render('admin/repos/repos.html')
179
174
180 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
175 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
181 def create(self):
176 def create(self):
182 """
177 """
183 POST /repos: Create a new item"""
178 POST /repos: Create a new item"""
184 # url('repos')
179 # url('repos')
185
180
186 self.__load_defaults()
181 self.__load_defaults()
187 form_result = {}
182 form_result = {}
188 try:
183 try:
189 form_result = RepoForm(repo_groups=c.repo_groups_choices,
184 form_result = RepoForm(repo_groups=c.repo_groups_choices,
190 landing_revs=c.landing_revs_choices)()\
185 landing_revs=c.landing_revs_choices)()\
191 .to_python(dict(request.POST))
186 .to_python(dict(request.POST))
192 new_repo = RepoModel().create(form_result,
187 new_repo = RepoModel().create(form_result,
193 self.rhodecode_user.user_id)
188 self.rhodecode_user.user_id)
194 if form_result['clone_uri']:
189 if form_result['clone_uri']:
195 h.flash(_('created repository %s from %s') \
190 h.flash(_('created repository %s from %s') \
196 % (form_result['repo_name'], form_result['clone_uri']),
191 % (form_result['repo_name'], form_result['clone_uri']),
197 category='success')
192 category='success')
198 else:
193 else:
199 h.flash(_('created repository %s') % form_result['repo_name'],
194 h.flash(_('created repository %s') % form_result['repo_name'],
200 category='success')
195 category='success')
201
196
202 if request.POST.get('user_created'):
197 if request.POST.get('user_created'):
203 # created by regular non admin user
198 # created by regular non admin user
204 action_logger(self.rhodecode_user, 'user_created_repo',
199 action_logger(self.rhodecode_user, 'user_created_repo',
205 form_result['repo_name_full'], self.ip_addr,
200 form_result['repo_name_full'], self.ip_addr,
206 self.sa)
201 self.sa)
207 else:
202 else:
208 action_logger(self.rhodecode_user, 'admin_created_repo',
203 action_logger(self.rhodecode_user, 'admin_created_repo',
209 form_result['repo_name_full'], self.ip_addr,
204 form_result['repo_name_full'], self.ip_addr,
210 self.sa)
205 self.sa)
211 Session().commit()
206 Session().commit()
212 except formencode.Invalid, errors:
207 except formencode.Invalid, errors:
213
208
214 c.new_repo = errors.value['repo_name']
209 c.new_repo = errors.value['repo_name']
215
210
216 if request.POST.get('user_created'):
211 if request.POST.get('user_created'):
217 r = render('admin/repos/repo_add_create_repository.html')
212 r = render('admin/repos/repo_add_create_repository.html')
218 else:
213 else:
219 r = render('admin/repos/repo_add.html')
214 r = render('admin/repos/repo_add.html')
220
215
221 return htmlfill.render(
216 return htmlfill.render(
222 r,
217 r,
223 defaults=errors.value,
218 defaults=errors.value,
224 errors=errors.error_dict or {},
219 errors=errors.error_dict or {},
225 prefix_error=False,
220 prefix_error=False,
226 encoding="UTF-8")
221 encoding="UTF-8")
227
222
228 except Exception:
223 except Exception:
229 log.error(traceback.format_exc())
224 log.error(traceback.format_exc())
230 msg = _('error occurred during creation of repository %s') \
225 msg = _('error occurred during creation of repository %s') \
231 % form_result.get('repo_name')
226 % form_result.get('repo_name')
232 h.flash(msg, category='error')
227 h.flash(msg, category='error')
233 return redirect(url('repos'))
228 return redirect(url('repos'))
234 #redirect to our new repo !
229 #redirect to our new repo !
235 return redirect(url('summary_home', repo_name=new_repo.repo_name))
230 return redirect(url('summary_home', repo_name=new_repo.repo_name))
236
231
237 @HasPermissionAllDecorator('hg.admin')
232 @HasPermissionAllDecorator('hg.admin')
238 def new(self, format='html'):
233 def new(self, format='html'):
239 """GET /repos/new: Form to create a new item"""
234 """GET /repos/new: Form to create a new item"""
240 new_repo = request.GET.get('repo', '')
235 new_repo = request.GET.get('repo', '')
241 c.new_repo = repo_name_slug(new_repo)
236 c.new_repo = repo_name_slug(new_repo)
242 self.__load_defaults()
237 self.__load_defaults()
243 ## apply the defaults from defaults page
238 ## apply the defaults from defaults page
244 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
239 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
245 return htmlfill.render(
240 return htmlfill.render(
246 render('admin/repos/repo_add.html'),
241 render('admin/repos/repo_add.html'),
247 defaults=defaults,
242 defaults=defaults,
248 errors={},
243 errors={},
249 prefix_error=False,
244 prefix_error=False,
250 encoding="UTF-8"
245 encoding="UTF-8"
251 )
246 )
252
247
253 @HasPermissionAllDecorator('hg.admin')
248 @HasPermissionAllDecorator('hg.admin')
254 def update(self, repo_name):
249 def update(self, repo_name):
255 """
250 """
256 PUT /repos/repo_name: Update an existing item"""
251 PUT /repos/repo_name: Update an existing item"""
257 # Forms posted to this method should contain a hidden field:
252 # Forms posted to this method should contain a hidden field:
258 # <input type="hidden" name="_method" value="PUT" />
253 # <input type="hidden" name="_method" value="PUT" />
259 # Or using helpers:
254 # Or using helpers:
260 # h.form(url('repo', repo_name=ID),
255 # h.form(url('repo', repo_name=ID),
261 # method='put')
256 # method='put')
262 # url('repo', repo_name=ID)
257 # url('repo', repo_name=ID)
263 self.__load_defaults()
258 self.__load_defaults()
264 repo_model = RepoModel()
259 repo_model = RepoModel()
265 changed_name = repo_name
260 changed_name = repo_name
266 #override the choices with extracted revisions !
261 #override the choices with extracted revisions !
267 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
262 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
268 c.landing_revs_choices = choices
263 c.landing_revs_choices = choices
269
264
270 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
265 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
271 repo_groups=c.repo_groups_choices,
266 repo_groups=c.repo_groups_choices,
272 landing_revs=c.landing_revs_choices)()
267 landing_revs=c.landing_revs_choices)()
273 try:
268 try:
274 form_result = _form.to_python(dict(request.POST))
269 form_result = _form.to_python(dict(request.POST))
275 repo = repo_model.update(repo_name, **form_result)
270 repo = repo_model.update(repo_name, **form_result)
276 invalidate_cache('get_repo_cached_%s' % repo_name)
271 invalidate_cache('get_repo_cached_%s' % repo_name)
277 h.flash(_('Repository %s updated successfully') % repo_name,
272 h.flash(_('Repository %s updated successfully') % repo_name,
278 category='success')
273 category='success')
279 changed_name = repo.repo_name
274 changed_name = repo.repo_name
280 action_logger(self.rhodecode_user, 'admin_updated_repo',
275 action_logger(self.rhodecode_user, 'admin_updated_repo',
281 changed_name, self.ip_addr, self.sa)
276 changed_name, self.ip_addr, self.sa)
282 Session().commit()
277 Session().commit()
283 except formencode.Invalid, errors:
278 except formencode.Invalid, errors:
284 defaults = self.__load_data(repo_name)
279 defaults = self.__load_data(repo_name)
285 defaults.update(errors.value)
280 defaults.update(errors.value)
286 return htmlfill.render(
281 return htmlfill.render(
287 render('admin/repos/repo_edit.html'),
282 render('admin/repos/repo_edit.html'),
288 defaults=defaults,
283 defaults=defaults,
289 errors=errors.error_dict or {},
284 errors=errors.error_dict or {},
290 prefix_error=False,
285 prefix_error=False,
291 encoding="UTF-8")
286 encoding="UTF-8")
292
287
293 except Exception:
288 except Exception:
294 log.error(traceback.format_exc())
289 log.error(traceback.format_exc())
295 h.flash(_('error occurred during update of repository %s') \
290 h.flash(_('error occurred during update of repository %s') \
296 % repo_name, category='error')
291 % repo_name, category='error')
297 return redirect(url('edit_repo', repo_name=changed_name))
292 return redirect(url('edit_repo', repo_name=changed_name))
298
293
299 @HasPermissionAllDecorator('hg.admin')
294 @HasPermissionAllDecorator('hg.admin')
300 def delete(self, repo_name):
295 def delete(self, repo_name):
301 """
296 """
302 DELETE /repos/repo_name: Delete an existing item"""
297 DELETE /repos/repo_name: Delete an existing item"""
303 # Forms posted to this method should contain a hidden field:
298 # Forms posted to this method should contain a hidden field:
304 # <input type="hidden" name="_method" value="DELETE" />
299 # <input type="hidden" name="_method" value="DELETE" />
305 # Or using helpers:
300 # Or using helpers:
306 # h.form(url('repo', repo_name=ID),
301 # h.form(url('repo', repo_name=ID),
307 # method='delete')
302 # method='delete')
308 # url('repo', repo_name=ID)
303 # url('repo', repo_name=ID)
309
304
310 repo_model = RepoModel()
305 repo_model = RepoModel()
311 repo = repo_model.get_by_repo_name(repo_name)
306 repo = repo_model.get_by_repo_name(repo_name)
312 if not repo:
307 if not repo:
313 h.flash(_('%s repository is not mapped to db perhaps'
308 h.not_mapped_error(repo_name)
314 ' it was moved or renamed from the filesystem'
315 ' please run the application again'
316 ' in order to rescan repositories') % repo_name,
317 category='error')
318
319 return redirect(url('repos'))
309 return redirect(url('repos'))
320 try:
310 try:
321 action_logger(self.rhodecode_user, 'admin_deleted_repo',
311 action_logger(self.rhodecode_user, 'admin_deleted_repo',
322 repo_name, self.ip_addr, self.sa)
312 repo_name, self.ip_addr, self.sa)
323 repo_model.delete(repo)
313 repo_model.delete(repo)
324 invalidate_cache('get_repo_cached_%s' % repo_name)
314 invalidate_cache('get_repo_cached_%s' % repo_name)
325 h.flash(_('deleted repository %s') % repo_name, category='success')
315 h.flash(_('deleted repository %s') % repo_name, category='success')
326 Session().commit()
316 Session().commit()
327 except IntegrityError, e:
317 except IntegrityError, e:
328 if e.message.find('repositories_fork_id_fkey') != -1:
318 if e.message.find('repositories_fork_id_fkey') != -1:
329 log.error(traceback.format_exc())
319 log.error(traceback.format_exc())
330 h.flash(_('Cannot delete %s it still contains attached '
320 h.flash(_('Cannot delete %s it still contains attached '
331 'forks') % repo_name,
321 'forks') % repo_name,
332 category='warning')
322 category='warning')
333 else:
323 else:
334 log.error(traceback.format_exc())
324 log.error(traceback.format_exc())
335 h.flash(_('An error occurred during '
325 h.flash(_('An error occurred during '
336 'deletion of %s') % repo_name,
326 'deletion of %s') % repo_name,
337 category='error')
327 category='error')
338
328
339 except Exception, e:
329 except Exception, e:
340 log.error(traceback.format_exc())
330 log.error(traceback.format_exc())
341 h.flash(_('An error occurred during deletion of %s') % repo_name,
331 h.flash(_('An error occurred during deletion of %s') % repo_name,
342 category='error')
332 category='error')
343
333
344 return redirect(url('repos'))
334 return redirect(url('repos'))
345
335
346 @HasRepoPermissionAllDecorator('repository.admin')
336 @HasRepoPermissionAllDecorator('repository.admin')
347 def delete_perm_user(self, repo_name):
337 def delete_perm_user(self, repo_name):
348 """
338 """
349 DELETE an existing repository permission user
339 DELETE an existing repository permission user
350
340
351 :param repo_name:
341 :param repo_name:
352 """
342 """
353 try:
343 try:
354 RepoModel().revoke_user_permission(repo=repo_name,
344 RepoModel().revoke_user_permission(repo=repo_name,
355 user=request.POST['user_id'])
345 user=request.POST['user_id'])
356 Session().commit()
346 Session().commit()
357 except Exception:
347 except Exception:
358 log.error(traceback.format_exc())
348 log.error(traceback.format_exc())
359 h.flash(_('An error occurred during deletion of repository user'),
349 h.flash(_('An error occurred during deletion of repository user'),
360 category='error')
350 category='error')
361 raise HTTPInternalServerError()
351 raise HTTPInternalServerError()
362
352
363 @HasRepoPermissionAllDecorator('repository.admin')
353 @HasRepoPermissionAllDecorator('repository.admin')
364 def delete_perm_users_group(self, repo_name):
354 def delete_perm_users_group(self, repo_name):
365 """
355 """
366 DELETE an existing repository permission users group
356 DELETE an existing repository permission users group
367
357
368 :param repo_name:
358 :param repo_name:
369 """
359 """
370
360
371 try:
361 try:
372 RepoModel().revoke_users_group_permission(
362 RepoModel().revoke_users_group_permission(
373 repo=repo_name, group_name=request.POST['users_group_id']
363 repo=repo_name, group_name=request.POST['users_group_id']
374 )
364 )
375 Session().commit()
365 Session().commit()
376 except Exception:
366 except Exception:
377 log.error(traceback.format_exc())
367 log.error(traceback.format_exc())
378 h.flash(_('An error occurred during deletion of repository'
368 h.flash(_('An error occurred during deletion of repository'
379 ' users groups'),
369 ' users groups'),
380 category='error')
370 category='error')
381 raise HTTPInternalServerError()
371 raise HTTPInternalServerError()
382
372
383 @HasPermissionAllDecorator('hg.admin')
373 @HasPermissionAllDecorator('hg.admin')
384 def repo_stats(self, repo_name):
374 def repo_stats(self, repo_name):
385 """
375 """
386 DELETE an existing repository statistics
376 DELETE an existing repository statistics
387
377
388 :param repo_name:
378 :param repo_name:
389 """
379 """
390
380
391 try:
381 try:
392 RepoModel().delete_stats(repo_name)
382 RepoModel().delete_stats(repo_name)
393 Session().commit()
383 Session().commit()
394 except Exception, e:
384 except Exception, e:
395 log.error(traceback.format_exc())
385 log.error(traceback.format_exc())
396 h.flash(_('An error occurred during deletion of repository stats'),
386 h.flash(_('An error occurred during deletion of repository stats'),
397 category='error')
387 category='error')
398 return redirect(url('edit_repo', repo_name=repo_name))
388 return redirect(url('edit_repo', repo_name=repo_name))
399
389
400 @HasPermissionAllDecorator('hg.admin')
390 @HasPermissionAllDecorator('hg.admin')
401 def repo_cache(self, repo_name):
391 def repo_cache(self, repo_name):
402 """
392 """
403 INVALIDATE existing repository cache
393 INVALIDATE existing repository cache
404
394
405 :param repo_name:
395 :param repo_name:
406 """
396 """
407
397
408 try:
398 try:
409 ScmModel().mark_for_invalidation(repo_name)
399 ScmModel().mark_for_invalidation(repo_name)
410 Session().commit()
400 Session().commit()
411 except Exception, e:
401 except Exception, e:
412 log.error(traceback.format_exc())
402 log.error(traceback.format_exc())
413 h.flash(_('An error occurred during cache invalidation'),
403 h.flash(_('An error occurred during cache invalidation'),
414 category='error')
404 category='error')
415 return redirect(url('edit_repo', repo_name=repo_name))
405 return redirect(url('edit_repo', repo_name=repo_name))
416
406
417 @HasPermissionAllDecorator('hg.admin')
407 @HasPermissionAllDecorator('hg.admin')
418 def repo_locking(self, repo_name):
408 def repo_locking(self, repo_name):
419 """
409 """
420 Unlock repository when it is locked !
410 Unlock repository when it is locked !
421
411
422 :param repo_name:
412 :param repo_name:
423 """
413 """
424
414
425 try:
415 try:
426 repo = Repository.get_by_repo_name(repo_name)
416 repo = Repository.get_by_repo_name(repo_name)
427 if request.POST.get('set_lock'):
417 if request.POST.get('set_lock'):
428 Repository.lock(repo, c.rhodecode_user.user_id)
418 Repository.lock(repo, c.rhodecode_user.user_id)
429 elif request.POST.get('set_unlock'):
419 elif request.POST.get('set_unlock'):
430 Repository.unlock(repo)
420 Repository.unlock(repo)
431 except Exception, e:
421 except Exception, e:
432 log.error(traceback.format_exc())
422 log.error(traceback.format_exc())
433 h.flash(_('An error occurred during unlocking'),
423 h.flash(_('An error occurred during unlocking'),
434 category='error')
424 category='error')
435 return redirect(url('edit_repo', repo_name=repo_name))
425 return redirect(url('edit_repo', repo_name=repo_name))
436
426
437 @HasPermissionAllDecorator('hg.admin')
427 @HasPermissionAllDecorator('hg.admin')
438 def repo_public_journal(self, repo_name):
428 def repo_public_journal(self, repo_name):
439 """
429 """
440 Set's this repository to be visible in public journal,
430 Set's this repository to be visible in public journal,
441 in other words assing default user to follow this repo
431 in other words assing default user to follow this repo
442
432
443 :param repo_name:
433 :param repo_name:
444 """
434 """
445
435
446 cur_token = request.POST.get('auth_token')
436 cur_token = request.POST.get('auth_token')
447 token = get_token()
437 token = get_token()
448 if cur_token == token:
438 if cur_token == token:
449 try:
439 try:
450 repo_id = Repository.get_by_repo_name(repo_name).repo_id
440 repo_id = Repository.get_by_repo_name(repo_name).repo_id
451 user_id = User.get_by_username('default').user_id
441 user_id = User.get_by_username('default').user_id
452 self.scm_model.toggle_following_repo(repo_id, user_id)
442 self.scm_model.toggle_following_repo(repo_id, user_id)
453 h.flash(_('Updated repository visibility in public journal'),
443 h.flash(_('Updated repository visibility in public journal'),
454 category='success')
444 category='success')
455 Session().commit()
445 Session().commit()
456 except:
446 except:
457 h.flash(_('An error occurred during setting this'
447 h.flash(_('An error occurred during setting this'
458 ' repository in public journal'),
448 ' repository in public journal'),
459 category='error')
449 category='error')
460
450
461 else:
451 else:
462 h.flash(_('Token mismatch'), category='error')
452 h.flash(_('Token mismatch'), category='error')
463 return redirect(url('edit_repo', repo_name=repo_name))
453 return redirect(url('edit_repo', repo_name=repo_name))
464
454
465 @HasPermissionAllDecorator('hg.admin')
455 @HasPermissionAllDecorator('hg.admin')
466 def repo_pull(self, repo_name):
456 def repo_pull(self, repo_name):
467 """
457 """
468 Runs task to update given repository with remote changes,
458 Runs task to update given repository with remote changes,
469 ie. make pull on remote location
459 ie. make pull on remote location
470
460
471 :param repo_name:
461 :param repo_name:
472 """
462 """
473 try:
463 try:
474 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
464 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
475 h.flash(_('Pulled from remote location'), category='success')
465 h.flash(_('Pulled from remote location'), category='success')
476 except Exception, e:
466 except Exception, e:
477 h.flash(_('An error occurred during pull from remote location'),
467 h.flash(_('An error occurred during pull from remote location'),
478 category='error')
468 category='error')
479
469
480 return redirect(url('edit_repo', repo_name=repo_name))
470 return redirect(url('edit_repo', repo_name=repo_name))
481
471
482 @HasPermissionAllDecorator('hg.admin')
472 @HasPermissionAllDecorator('hg.admin')
483 def repo_as_fork(self, repo_name):
473 def repo_as_fork(self, repo_name):
484 """
474 """
485 Mark given repository as a fork of another
475 Mark given repository as a fork of another
486
476
487 :param repo_name:
477 :param repo_name:
488 """
478 """
489 try:
479 try:
490 fork_id = request.POST.get('id_fork_of')
480 fork_id = request.POST.get('id_fork_of')
491 repo = ScmModel().mark_as_fork(repo_name, fork_id,
481 repo = ScmModel().mark_as_fork(repo_name, fork_id,
492 self.rhodecode_user.username)
482 self.rhodecode_user.username)
493 fork = repo.fork.repo_name if repo.fork else _('Nothing')
483 fork = repo.fork.repo_name if repo.fork else _('Nothing')
494 Session().commit()
484 Session().commit()
495 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
485 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
496 category='success')
486 category='success')
497 except Exception, e:
487 except Exception, e:
498 log.error(traceback.format_exc())
488 log.error(traceback.format_exc())
499 h.flash(_('An error occurred during this operation'),
489 h.flash(_('An error occurred during this operation'),
500 category='error')
490 category='error')
501
491
502 return redirect(url('edit_repo', repo_name=repo_name))
492 return redirect(url('edit_repo', repo_name=repo_name))
503
493
504 @HasPermissionAllDecorator('hg.admin')
494 @HasPermissionAllDecorator('hg.admin')
505 def show(self, repo_name, format='html'):
495 def show(self, repo_name, format='html'):
506 """GET /repos/repo_name: Show a specific item"""
496 """GET /repos/repo_name: Show a specific item"""
507 # url('repo', repo_name=ID)
497 # url('repo', repo_name=ID)
508
498
509 @HasPermissionAllDecorator('hg.admin')
499 @HasPermissionAllDecorator('hg.admin')
510 def edit(self, repo_name, format='html'):
500 def edit(self, repo_name, format='html'):
511 """GET /repos/repo_name/edit: Form to edit an existing item"""
501 """GET /repos/repo_name/edit: Form to edit an existing item"""
512 # url('edit_repo', repo_name=ID)
502 # url('edit_repo', repo_name=ID)
513 defaults = self.__load_data(repo_name)
503 defaults = self.__load_data(repo_name)
514
504
515 return htmlfill.render(
505 return htmlfill.render(
516 render('admin/repos/repo_edit.html'),
506 render('admin/repos/repo_edit.html'),
517 defaults=defaults,
507 defaults=defaults,
518 encoding="UTF-8",
508 encoding="UTF-8",
519 force_defaults=False
509 force_defaults=False
520 )
510 )
@@ -1,185 +1,175 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.forks
3 rhodecode.controllers.forks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 forks controller for rhodecode
6 forks controller for rhodecode
7
7
8 :created_on: Apr 23, 2011
8 :created_on: Apr 23, 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 import logging
25 import logging
26 import formencode
26 import formencode
27 import traceback
27 import traceback
28 from formencode import htmlfill
28 from formencode import htmlfill
29
29
30 from pylons import tmpl_context as c, request, url
30 from pylons import tmpl_context as c, request, url
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode.lib.helpers as h
34 import rhodecode.lib.helpers as h
35
35
36 from rhodecode.lib.helpers import Page
36 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
38 NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
38 NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
39 HasPermissionAnyDecorator
39 HasPermissionAnyDecorator
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.forms import RepoForkForm
43 from rhodecode.model.forms import RepoForkForm
44 from rhodecode.model.scm import ScmModel
44 from rhodecode.model.scm import ScmModel
45 from rhodecode.lib.utils2 import safe_int
45 from rhodecode.lib.utils2 import safe_int
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class ForksController(BaseRepoController):
50 class ForksController(BaseRepoController):
51
51
52 @LoginRequired()
52 @LoginRequired()
53 def __before__(self):
53 def __before__(self):
54 super(ForksController, self).__before__()
54 super(ForksController, self).__before__()
55
55
56 def __load_defaults(self):
56 def __load_defaults(self):
57 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
57 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
59 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
59 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
60 c.landing_revs_choices = choices
60 c.landing_revs_choices = choices
61
61
62 def __load_data(self, repo_name=None):
62 def __load_data(self, repo_name=None):
63 """
63 """
64 Load defaults settings for edit, and update
64 Load defaults settings for edit, and update
65
65
66 :param repo_name:
66 :param repo_name:
67 """
67 """
68 self.__load_defaults()
68 self.__load_defaults()
69
69
70 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
70 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
71 repo = db_repo.scm_instance
71 repo = db_repo.scm_instance
72
72
73 if c.repo_info is None:
73 if c.repo_info is None:
74 h.flash(_('%s repository is not mapped to db perhaps'
74 h.not_mapped_error(repo_name)
75 ' it was created or renamed from the filesystem'
76 ' please run the application again'
77 ' in order to rescan repositories') % repo_name,
78 category='error')
79
80 return redirect(url('repos'))
75 return redirect(url('repos'))
81
76
82 c.default_user_id = User.get_by_username('default').user_id
77 c.default_user_id = User.get_by_username('default').user_id
83 c.in_public_journal = UserFollowing.query()\
78 c.in_public_journal = UserFollowing.query()\
84 .filter(UserFollowing.user_id == c.default_user_id)\
79 .filter(UserFollowing.user_id == c.default_user_id)\
85 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
80 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
86
81
87 if c.repo_info.stats:
82 if c.repo_info.stats:
88 last_rev = c.repo_info.stats.stat_on_revision+1
83 last_rev = c.repo_info.stats.stat_on_revision+1
89 else:
84 else:
90 last_rev = 0
85 last_rev = 0
91 c.stats_revision = last_rev
86 c.stats_revision = last_rev
92
87
93 c.repo_last_rev = repo.count() if repo.revisions else 0
88 c.repo_last_rev = repo.count() if repo.revisions else 0
94
89
95 if last_rev == 0 or c.repo_last_rev == 0:
90 if last_rev == 0 or c.repo_last_rev == 0:
96 c.stats_percentage = 0
91 c.stats_percentage = 0
97 else:
92 else:
98 c.stats_percentage = '%.2f' % ((float((last_rev)) /
93 c.stats_percentage = '%.2f' % ((float((last_rev)) /
99 c.repo_last_rev) * 100)
94 c.repo_last_rev) * 100)
100
95
101 defaults = RepoModel()._get_defaults(repo_name)
96 defaults = RepoModel()._get_defaults(repo_name)
102 # add suffix to fork
97 # add suffix to fork
103 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
98 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
104 return defaults
99 return defaults
105
100
106 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
101 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
107 'repository.admin')
102 'repository.admin')
108 def forks(self, repo_name):
103 def forks(self, repo_name):
109 p = safe_int(request.params.get('page', 1), 1)
104 p = safe_int(request.params.get('page', 1), 1)
110 repo_id = c.rhodecode_db_repo.repo_id
105 repo_id = c.rhodecode_db_repo.repo_id
111 d = []
106 d = []
112 for r in Repository.get_repo_forks(repo_id):
107 for r in Repository.get_repo_forks(repo_id):
113 if not HasRepoPermissionAny(
108 if not HasRepoPermissionAny(
114 'repository.read', 'repository.write', 'repository.admin'
109 'repository.read', 'repository.write', 'repository.admin'
115 )(r.repo_name, 'get forks check'):
110 )(r.repo_name, 'get forks check'):
116 continue
111 continue
117 d.append(r)
112 d.append(r)
118 c.forks_pager = Page(d, page=p, items_per_page=20)
113 c.forks_pager = Page(d, page=p, items_per_page=20)
119
114
120 c.forks_data = render('/forks/forks_data.html')
115 c.forks_data = render('/forks/forks_data.html')
121
116
122 if request.environ.get('HTTP_X_PARTIAL_XHR'):
117 if request.environ.get('HTTP_X_PARTIAL_XHR'):
123 return c.forks_data
118 return c.forks_data
124
119
125 return render('/forks/forks.html')
120 return render('/forks/forks.html')
126
121
127 @NotAnonymous()
122 @NotAnonymous()
128 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
123 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
129 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
124 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
130 'repository.admin')
125 'repository.admin')
131 def fork(self, repo_name):
126 def fork(self, repo_name):
132 c.repo_info = Repository.get_by_repo_name(repo_name)
127 c.repo_info = Repository.get_by_repo_name(repo_name)
133 if not c.repo_info:
128 if not c.repo_info:
134 h.flash(_('%s repository is not mapped to db perhaps'
129 h.not_mapped_error(repo_name)
135 ' it was created or renamed from the file system'
136 ' please run the application again'
137 ' in order to rescan repositories') % repo_name,
138 category='error')
139
140 return redirect(url('home'))
130 return redirect(url('home'))
141
131
142 defaults = self.__load_data(repo_name)
132 defaults = self.__load_data(repo_name)
143
133
144 return htmlfill.render(
134 return htmlfill.render(
145 render('forks/fork.html'),
135 render('forks/fork.html'),
146 defaults=defaults,
136 defaults=defaults,
147 encoding="UTF-8",
137 encoding="UTF-8",
148 force_defaults=False
138 force_defaults=False
149 )
139 )
150
140
151 @NotAnonymous()
141 @NotAnonymous()
152 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
142 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
153 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
143 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
154 'repository.admin')
144 'repository.admin')
155 def fork_create(self, repo_name):
145 def fork_create(self, repo_name):
156 self.__load_defaults()
146 self.__load_defaults()
157 c.repo_info = Repository.get_by_repo_name(repo_name)
147 c.repo_info = Repository.get_by_repo_name(repo_name)
158 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
148 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
159 repo_groups=c.repo_groups_choices,
149 repo_groups=c.repo_groups_choices,
160 landing_revs=c.landing_revs_choices)()
150 landing_revs=c.landing_revs_choices)()
161 form_result = {}
151 form_result = {}
162 try:
152 try:
163 form_result = _form.to_python(dict(request.POST))
153 form_result = _form.to_python(dict(request.POST))
164
154
165 # create fork is done sometimes async on celery, db transaction
155 # create fork is done sometimes async on celery, db transaction
166 # management is handled there.
156 # management is handled there.
167 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
157 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
168 h.flash(_('forked %s repository as %s') \
158 h.flash(_('forked %s repository as %s') \
169 % (repo_name, form_result['repo_name']),
159 % (repo_name, form_result['repo_name']),
170 category='success')
160 category='success')
171 except formencode.Invalid, errors:
161 except formencode.Invalid, errors:
172 c.new_repo = errors.value['repo_name']
162 c.new_repo = errors.value['repo_name']
173
163
174 return htmlfill.render(
164 return htmlfill.render(
175 render('forks/fork.html'),
165 render('forks/fork.html'),
176 defaults=errors.value,
166 defaults=errors.value,
177 errors=errors.error_dict or {},
167 errors=errors.error_dict or {},
178 prefix_error=False,
168 prefix_error=False,
179 encoding="UTF-8")
169 encoding="UTF-8")
180 except Exception:
170 except Exception:
181 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
182 h.flash(_('An error occurred during repository forking %s') %
172 h.flash(_('An error occurred during repository forking %s') %
183 repo_name, category='error')
173 repo_name, category='error')
184
174
185 return redirect(url('home'))
175 return redirect(url('home'))
@@ -1,206 +1,196 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.settings
3 rhodecode.controllers.settings
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Settings controller for rhodecode
6 Settings controller for rhodecode
7
7
8 :created_on: Jun 30, 2010
8 :created_on: Jun 30, 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 tmpl_context as c, request, url
32 from pylons import tmpl_context as c, request, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37
37
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator,\
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator,\
39 HasRepoPermissionAnyDecorator
39 HasRepoPermissionAnyDecorator
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.utils import invalidate_cache, action_logger
41 from rhodecode.lib.utils import invalidate_cache, action_logger
42
42
43 from rhodecode.model.forms import RepoSettingsForm
43 from rhodecode.model.forms import RepoSettingsForm
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.db import RepoGroup, Repository
45 from rhodecode.model.db import RepoGroup, Repository
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.scm import ScmModel
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class SettingsController(BaseRepoController):
52 class SettingsController(BaseRepoController):
53
53
54 @LoginRequired()
54 @LoginRequired()
55 def __before__(self):
55 def __before__(self):
56 super(SettingsController, self).__before__()
56 super(SettingsController, self).__before__()
57
57
58 def __load_defaults(self):
58 def __load_defaults(self):
59 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
59 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
60 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
60 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
61
61
62 repo_model = RepoModel()
62 repo_model = RepoModel()
63 c.users_array = repo_model.get_users_js()
63 c.users_array = repo_model.get_users_js()
64 c.users_groups_array = repo_model.get_users_groups_js()
64 c.users_groups_array = repo_model.get_users_groups_js()
65 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
65 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
66 c.landing_revs_choices = choices
66 c.landing_revs_choices = choices
67
67
68 def __load_data(self, repo_name=None):
68 def __load_data(self, repo_name=None):
69 """
69 """
70 Load defaults settings for edit, and update
70 Load defaults settings for edit, and update
71
71
72 :param repo_name:
72 :param repo_name:
73 """
73 """
74 self.__load_defaults()
74 self.__load_defaults()
75
75
76 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
76 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
77 repo = db_repo.scm_instance
77 repo = db_repo.scm_instance
78
78
79 if c.repo_info is None:
79 if c.repo_info is None:
80 h.flash(_('%s repository is not mapped to db perhaps'
80 h.not_mapped_error(repo_name)
81 ' it was created or renamed from the filesystem'
82 ' please run the application again'
83 ' in order to rescan repositories') % repo_name,
84 category='error')
85
86 return redirect(url('home'))
81 return redirect(url('home'))
87
82
88 ##override defaults for exact repo info here git/hg etc
83 ##override defaults for exact repo info here git/hg etc
89 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
84 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
90 c.landing_revs_choices = choices
85 c.landing_revs_choices = choices
91
86
92 defaults = RepoModel()._get_defaults(repo_name)
87 defaults = RepoModel()._get_defaults(repo_name)
93
88
94 return defaults
89 return defaults
95
90
96 @HasRepoPermissionAllDecorator('repository.admin')
91 @HasRepoPermissionAllDecorator('repository.admin')
97 def index(self, repo_name):
92 def index(self, repo_name):
98 defaults = self.__load_data(repo_name)
93 defaults = self.__load_data(repo_name)
99
94
100 return htmlfill.render(
95 return htmlfill.render(
101 render('settings/repo_settings.html'),
96 render('settings/repo_settings.html'),
102 defaults=defaults,
97 defaults=defaults,
103 encoding="UTF-8",
98 encoding="UTF-8",
104 force_defaults=False
99 force_defaults=False
105 )
100 )
106
101
107 @HasRepoPermissionAllDecorator('repository.admin')
102 @HasRepoPermissionAllDecorator('repository.admin')
108 def update(self, repo_name):
103 def update(self, repo_name):
109 self.__load_defaults()
104 self.__load_defaults()
110 repo_model = RepoModel()
105 repo_model = RepoModel()
111 changed_name = repo_name
106 changed_name = repo_name
112 #override the choices with extracted revisions !
107 #override the choices with extracted revisions !
113 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
108 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
114 c.landing_revs_choices = choices
109 c.landing_revs_choices = choices
115
110
116 _form = RepoSettingsForm(edit=True,
111 _form = RepoSettingsForm(edit=True,
117 old_data={'repo_name': repo_name},
112 old_data={'repo_name': repo_name},
118 repo_groups=c.repo_groups_choices,
113 repo_groups=c.repo_groups_choices,
119 landing_revs=c.landing_revs_choices)()
114 landing_revs=c.landing_revs_choices)()
120 try:
115 try:
121 form_result = _form.to_python(dict(request.POST))
116 form_result = _form.to_python(dict(request.POST))
122 repo_model.update(repo_name, **form_result)
117 repo_model.update(repo_name, **form_result)
123 invalidate_cache('get_repo_cached_%s' % repo_name)
118 invalidate_cache('get_repo_cached_%s' % repo_name)
124 h.flash(_('Repository %s updated successfully') % repo_name,
119 h.flash(_('Repository %s updated successfully') % repo_name,
125 category='success')
120 category='success')
126 changed_name = form_result['repo_name_full']
121 changed_name = form_result['repo_name_full']
127 action_logger(self.rhodecode_user, 'user_updated_repo',
122 action_logger(self.rhodecode_user, 'user_updated_repo',
128 changed_name, self.ip_addr, self.sa)
123 changed_name, self.ip_addr, self.sa)
129 Session().commit()
124 Session().commit()
130 except formencode.Invalid, errors:
125 except formencode.Invalid, errors:
131 defaults = self.__load_data(repo_name)
126 defaults = self.__load_data(repo_name)
132 defaults.update(errors.value)
127 defaults.update(errors.value)
133 return htmlfill.render(
128 return htmlfill.render(
134 render('settings/repo_settings.html'),
129 render('settings/repo_settings.html'),
135 defaults=errors.value,
130 defaults=errors.value,
136 errors=errors.error_dict or {},
131 errors=errors.error_dict or {},
137 prefix_error=False,
132 prefix_error=False,
138 encoding="UTF-8")
133 encoding="UTF-8")
139
134
140 except Exception:
135 except Exception:
141 log.error(traceback.format_exc())
136 log.error(traceback.format_exc())
142 h.flash(_('error occurred during update of repository %s') \
137 h.flash(_('error occurred during update of repository %s') \
143 % repo_name, category='error')
138 % repo_name, category='error')
144
139
145 return redirect(url('repo_settings_home', repo_name=changed_name))
140 return redirect(url('repo_settings_home', repo_name=changed_name))
146
141
147 @HasRepoPermissionAllDecorator('repository.admin')
142 @HasRepoPermissionAllDecorator('repository.admin')
148 def delete(self, repo_name):
143 def delete(self, repo_name):
149 """DELETE /repos/repo_name: Delete an existing item"""
144 """DELETE /repos/repo_name: Delete an existing item"""
150 # Forms posted to this method should contain a hidden field:
145 # Forms posted to this method should contain a hidden field:
151 # <input type="hidden" name="_method" value="DELETE" />
146 # <input type="hidden" name="_method" value="DELETE" />
152 # Or using helpers:
147 # Or using helpers:
153 # h.form(url('repo_settings_delete', repo_name=ID),
148 # h.form(url('repo_settings_delete', repo_name=ID),
154 # method='delete')
149 # method='delete')
155 # url('repo_settings_delete', repo_name=ID)
150 # url('repo_settings_delete', repo_name=ID)
156
151
157 repo_model = RepoModel()
152 repo_model = RepoModel()
158 repo = repo_model.get_by_repo_name(repo_name)
153 repo = repo_model.get_by_repo_name(repo_name)
159 if not repo:
154 if not repo:
160 h.flash(_('%s repository is not mapped to db perhaps'
155 h.not_mapped_error(repo_name)
161 ' it was moved or renamed from the filesystem'
162 ' please run the application again'
163 ' in order to rescan repositories') % repo_name,
164 category='error')
165
166 return redirect(url('home'))
156 return redirect(url('home'))
167 try:
157 try:
168 action_logger(self.rhodecode_user, 'user_deleted_repo',
158 action_logger(self.rhodecode_user, 'user_deleted_repo',
169 repo_name, self.ip_addr, self.sa)
159 repo_name, self.ip_addr, self.sa)
170 repo_model.delete(repo)
160 repo_model.delete(repo)
171 invalidate_cache('get_repo_cached_%s' % repo_name)
161 invalidate_cache('get_repo_cached_%s' % repo_name)
172 h.flash(_('deleted repository %s') % repo_name, category='success')
162 h.flash(_('deleted repository %s') % repo_name, category='success')
173 Session().commit()
163 Session().commit()
174 except Exception:
164 except Exception:
175 log.error(traceback.format_exc())
165 log.error(traceback.format_exc())
176 h.flash(_('An error occurred during deletion of %s') % repo_name,
166 h.flash(_('An error occurred during deletion of %s') % repo_name,
177 category='error')
167 category='error')
178
168
179 return redirect(url('admin_settings_my_account', anchor='my'))
169 return redirect(url('admin_settings_my_account', anchor='my'))
180
170
181 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
171 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
182 def toggle_locking(self, repo_name):
172 def toggle_locking(self, repo_name):
183 """
173 """
184 Toggle locking of repository by simple GET call to url
174 Toggle locking of repository by simple GET call to url
185
175
186 :param repo_name:
176 :param repo_name:
187 """
177 """
188
178
189 try:
179 try:
190 repo = Repository.get_by_repo_name(repo_name)
180 repo = Repository.get_by_repo_name(repo_name)
191
181
192 if repo.enable_locking:
182 if repo.enable_locking:
193 if repo.locked[0]:
183 if repo.locked[0]:
194 Repository.unlock(repo)
184 Repository.unlock(repo)
195 action = _('unlocked')
185 action = _('unlocked')
196 else:
186 else:
197 Repository.lock(repo, c.rhodecode_user.user_id)
187 Repository.lock(repo, c.rhodecode_user.user_id)
198 action = _('locked')
188 action = _('locked')
199
189
200 h.flash(_('Repository has been %s') % action,
190 h.flash(_('Repository has been %s') % action,
201 category='success')
191 category='success')
202 except Exception, e:
192 except Exception, e:
203 log.error(traceback.format_exc())
193 log.error(traceback.format_exc())
204 h.flash(_('An error occurred during unlocking'),
194 h.flash(_('An error occurred during unlocking'),
205 category='error')
195 category='error')
206 return redirect(url('summary_home', repo_name=repo_name))
196 return redirect(url('summary_home', repo_name=repo_name))
@@ -1,1159 +1,1166 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14 import textwrap
14 import textwrap
15
15
16 from datetime import datetime
16 from datetime import datetime
17 from pygments.formatters.html import HtmlFormatter
17 from pygments.formatters.html import HtmlFormatter
18 from pygments import highlight as code_highlight
18 from pygments import highlight as code_highlight
19 from pylons import url, request, config
19 from pylons import url, request, config
20 from pylons.i18n.translation import _, ungettext
20 from pylons.i18n.translation import _, ungettext
21 from hashlib import md5
21 from hashlib import md5
22
22
23 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html import literal, HTML, escape
24 from webhelpers.html.tools import *
24 from webhelpers.html.tools import *
25 from webhelpers.html.builder import make_tag
25 from webhelpers.html.builder import make_tag
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
29 submit, text, password, textarea, title, ul, xml_declaration, radio
29 submit, text, password, textarea, title, ul, xml_declaration, radio
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
32 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.number import format_byte_size, format_bit_size
33 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib import Flash as _Flash
34 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.pylonslib.secure_form import secure_form
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
37 replace_whitespace, urlify, truncate, wrap_paragraphs
37 replace_whitespace, urlify, truncate, wrap_paragraphs
38 from webhelpers.date import time_ago_in_words
38 from webhelpers.date import time_ago_in_words
39 from webhelpers.paginate import Page
39 from webhelpers.paginate import Page
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
42
42
43 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.annotate import annotate_highlight
44 from rhodecode.lib.utils import repo_name_slug
44 from rhodecode.lib.utils import repo_name_slug
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
47 from rhodecode.lib.markup_renderer import MarkupRenderer
47 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
48 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
49 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
49 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
50 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
50 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
51 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.db import URL_SEP, Permission
52 from rhodecode.model.db import URL_SEP, Permission
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 html_escape_table = {
57 html_escape_table = {
58 "&": "&amp;",
58 "&": "&amp;",
59 '"': "&quot;",
59 '"': "&quot;",
60 "'": "&apos;",
60 "'": "&apos;",
61 ">": "&gt;",
61 ">": "&gt;",
62 "<": "&lt;",
62 "<": "&lt;",
63 }
63 }
64
64
65
65
66 def html_escape(text):
66 def html_escape(text):
67 """Produce entities within text."""
67 """Produce entities within text."""
68 return "".join(html_escape_table.get(c, c) for c in text)
68 return "".join(html_escape_table.get(c, c) for c in text)
69
69
70
70
71 def shorter(text, size=20):
71 def shorter(text, size=20):
72 postfix = '...'
72 postfix = '...'
73 if len(text) > size:
73 if len(text) > size:
74 return text[:size - len(postfix)] + postfix
74 return text[:size - len(postfix)] + postfix
75 return text
75 return text
76
76
77
77
78 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
78 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
79 """
79 """
80 Reset button
80 Reset button
81 """
81 """
82 _set_input_attrs(attrs, type, name, value)
82 _set_input_attrs(attrs, type, name, value)
83 _set_id_attr(attrs, id, name)
83 _set_id_attr(attrs, id, name)
84 convert_boolean_attrs(attrs, ["disabled"])
84 convert_boolean_attrs(attrs, ["disabled"])
85 return HTML.input(**attrs)
85 return HTML.input(**attrs)
86
86
87 reset = _reset
87 reset = _reset
88 safeid = _make_safe_id_component
88 safeid = _make_safe_id_component
89
89
90
90
91 def FID(raw_id, path):
91 def FID(raw_id, path):
92 """
92 """
93 Creates a uniqe ID for filenode based on it's hash of path and revision
93 Creates a uniqe ID for filenode based on it's hash of path and revision
94 it's safe to use in urls
94 it's safe to use in urls
95
95
96 :param raw_id:
96 :param raw_id:
97 :param path:
97 :param path:
98 """
98 """
99
99
100 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
100 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
101
101
102
102
103 def get_token():
103 def get_token():
104 """Return the current authentication token, creating one if one doesn't
104 """Return the current authentication token, creating one if one doesn't
105 already exist.
105 already exist.
106 """
106 """
107 token_key = "_authentication_token"
107 token_key = "_authentication_token"
108 from pylons import session
108 from pylons import session
109 if not token_key in session:
109 if not token_key in session:
110 try:
110 try:
111 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
111 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
112 except AttributeError: # Python < 2.4
112 except AttributeError: # Python < 2.4
113 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
113 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
114 session[token_key] = token
114 session[token_key] = token
115 if hasattr(session, 'save'):
115 if hasattr(session, 'save'):
116 session.save()
116 session.save()
117 return session[token_key]
117 return session[token_key]
118
118
119
119
120 class _GetError(object):
120 class _GetError(object):
121 """Get error from form_errors, and represent it as span wrapped error
121 """Get error from form_errors, and represent it as span wrapped error
122 message
122 message
123
123
124 :param field_name: field to fetch errors for
124 :param field_name: field to fetch errors for
125 :param form_errors: form errors dict
125 :param form_errors: form errors dict
126 """
126 """
127
127
128 def __call__(self, field_name, form_errors):
128 def __call__(self, field_name, form_errors):
129 tmpl = """<span class="error_msg">%s</span>"""
129 tmpl = """<span class="error_msg">%s</span>"""
130 if form_errors and field_name in form_errors:
130 if form_errors and field_name in form_errors:
131 return literal(tmpl % form_errors.get(field_name))
131 return literal(tmpl % form_errors.get(field_name))
132
132
133 get_error = _GetError()
133 get_error = _GetError()
134
134
135
135
136 class _ToolTip(object):
136 class _ToolTip(object):
137
137
138 def __call__(self, tooltip_title, trim_at=50):
138 def __call__(self, tooltip_title, trim_at=50):
139 """
139 """
140 Special function just to wrap our text into nice formatted
140 Special function just to wrap our text into nice formatted
141 autowrapped text
141 autowrapped text
142
142
143 :param tooltip_title:
143 :param tooltip_title:
144 """
144 """
145 tooltip_title = escape(tooltip_title)
145 tooltip_title = escape(tooltip_title)
146 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
146 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
147 return tooltip_title
147 return tooltip_title
148 tooltip = _ToolTip()
148 tooltip = _ToolTip()
149
149
150
150
151 class _FilesBreadCrumbs(object):
151 class _FilesBreadCrumbs(object):
152
152
153 def __call__(self, repo_name, rev, paths):
153 def __call__(self, repo_name, rev, paths):
154 if isinstance(paths, str):
154 if isinstance(paths, str):
155 paths = safe_unicode(paths)
155 paths = safe_unicode(paths)
156 url_l = [link_to(repo_name, url('files_home',
156 url_l = [link_to(repo_name, url('files_home',
157 repo_name=repo_name,
157 repo_name=repo_name,
158 revision=rev, f_path=''),
158 revision=rev, f_path=''),
159 class_='ypjax-link')]
159 class_='ypjax-link')]
160 paths_l = paths.split('/')
160 paths_l = paths.split('/')
161 for cnt, p in enumerate(paths_l):
161 for cnt, p in enumerate(paths_l):
162 if p != '':
162 if p != '':
163 url_l.append(link_to(p,
163 url_l.append(link_to(p,
164 url('files_home',
164 url('files_home',
165 repo_name=repo_name,
165 repo_name=repo_name,
166 revision=rev,
166 revision=rev,
167 f_path='/'.join(paths_l[:cnt + 1])
167 f_path='/'.join(paths_l[:cnt + 1])
168 ),
168 ),
169 class_='ypjax-link'
169 class_='ypjax-link'
170 )
170 )
171 )
171 )
172
172
173 return literal('/'.join(url_l))
173 return literal('/'.join(url_l))
174
174
175 files_breadcrumbs = _FilesBreadCrumbs()
175 files_breadcrumbs = _FilesBreadCrumbs()
176
176
177
177
178 class CodeHtmlFormatter(HtmlFormatter):
178 class CodeHtmlFormatter(HtmlFormatter):
179 """
179 """
180 My code Html Formatter for source codes
180 My code Html Formatter for source codes
181 """
181 """
182
182
183 def wrap(self, source, outfile):
183 def wrap(self, source, outfile):
184 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
184 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
185
185
186 def _wrap_code(self, source):
186 def _wrap_code(self, source):
187 for cnt, it in enumerate(source):
187 for cnt, it in enumerate(source):
188 i, t = it
188 i, t = it
189 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
189 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
190 yield i, t
190 yield i, t
191
191
192 def _wrap_tablelinenos(self, inner):
192 def _wrap_tablelinenos(self, inner):
193 dummyoutfile = StringIO.StringIO()
193 dummyoutfile = StringIO.StringIO()
194 lncount = 0
194 lncount = 0
195 for t, line in inner:
195 for t, line in inner:
196 if t:
196 if t:
197 lncount += 1
197 lncount += 1
198 dummyoutfile.write(line)
198 dummyoutfile.write(line)
199
199
200 fl = self.linenostart
200 fl = self.linenostart
201 mw = len(str(lncount + fl - 1))
201 mw = len(str(lncount + fl - 1))
202 sp = self.linenospecial
202 sp = self.linenospecial
203 st = self.linenostep
203 st = self.linenostep
204 la = self.lineanchors
204 la = self.lineanchors
205 aln = self.anchorlinenos
205 aln = self.anchorlinenos
206 nocls = self.noclasses
206 nocls = self.noclasses
207 if sp:
207 if sp:
208 lines = []
208 lines = []
209
209
210 for i in range(fl, fl + lncount):
210 for i in range(fl, fl + lncount):
211 if i % st == 0:
211 if i % st == 0:
212 if i % sp == 0:
212 if i % sp == 0:
213 if aln:
213 if aln:
214 lines.append('<a href="#%s%d" class="special">%*d</a>' %
214 lines.append('<a href="#%s%d" class="special">%*d</a>' %
215 (la, i, mw, i))
215 (la, i, mw, i))
216 else:
216 else:
217 lines.append('<span class="special">%*d</span>' % (mw, i))
217 lines.append('<span class="special">%*d</span>' % (mw, i))
218 else:
218 else:
219 if aln:
219 if aln:
220 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
220 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
221 else:
221 else:
222 lines.append('%*d' % (mw, i))
222 lines.append('%*d' % (mw, i))
223 else:
223 else:
224 lines.append('')
224 lines.append('')
225 ls = '\n'.join(lines)
225 ls = '\n'.join(lines)
226 else:
226 else:
227 lines = []
227 lines = []
228 for i in range(fl, fl + lncount):
228 for i in range(fl, fl + lncount):
229 if i % st == 0:
229 if i % st == 0:
230 if aln:
230 if aln:
231 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
231 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
232 else:
232 else:
233 lines.append('%*d' % (mw, i))
233 lines.append('%*d' % (mw, i))
234 else:
234 else:
235 lines.append('')
235 lines.append('')
236 ls = '\n'.join(lines)
236 ls = '\n'.join(lines)
237
237
238 # in case you wonder about the seemingly redundant <div> here: since the
238 # in case you wonder about the seemingly redundant <div> here: since the
239 # content in the other cell also is wrapped in a div, some browsers in
239 # content in the other cell also is wrapped in a div, some browsers in
240 # some configurations seem to mess up the formatting...
240 # some configurations seem to mess up the formatting...
241 if nocls:
241 if nocls:
242 yield 0, ('<table class="%stable">' % self.cssclass +
242 yield 0, ('<table class="%stable">' % self.cssclass +
243 '<tr><td><div class="linenodiv" '
243 '<tr><td><div class="linenodiv" '
244 'style="background-color: #f0f0f0; padding-right: 10px">'
244 'style="background-color: #f0f0f0; padding-right: 10px">'
245 '<pre style="line-height: 125%">' +
245 '<pre style="line-height: 125%">' +
246 ls + '</pre></div></td><td id="hlcode" class="code">')
246 ls + '</pre></div></td><td id="hlcode" class="code">')
247 else:
247 else:
248 yield 0, ('<table class="%stable">' % self.cssclass +
248 yield 0, ('<table class="%stable">' % self.cssclass +
249 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
249 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
250 ls + '</pre></div></td><td id="hlcode" class="code">')
250 ls + '</pre></div></td><td id="hlcode" class="code">')
251 yield 0, dummyoutfile.getvalue()
251 yield 0, dummyoutfile.getvalue()
252 yield 0, '</td></tr></table>'
252 yield 0, '</td></tr></table>'
253
253
254
254
255 def pygmentize(filenode, **kwargs):
255 def pygmentize(filenode, **kwargs):
256 """pygmentize function using pygments
256 """pygmentize function using pygments
257
257
258 :param filenode:
258 :param filenode:
259 """
259 """
260
260
261 return literal(code_highlight(filenode.content,
261 return literal(code_highlight(filenode.content,
262 filenode.lexer, CodeHtmlFormatter(**kwargs)))
262 filenode.lexer, CodeHtmlFormatter(**kwargs)))
263
263
264
264
265 def pygmentize_annotation(repo_name, filenode, **kwargs):
265 def pygmentize_annotation(repo_name, filenode, **kwargs):
266 """
266 """
267 pygmentize function for annotation
267 pygmentize function for annotation
268
268
269 :param filenode:
269 :param filenode:
270 """
270 """
271
271
272 color_dict = {}
272 color_dict = {}
273
273
274 def gen_color(n=10000):
274 def gen_color(n=10000):
275 """generator for getting n of evenly distributed colors using
275 """generator for getting n of evenly distributed colors using
276 hsv color and golden ratio. It always return same order of colors
276 hsv color and golden ratio. It always return same order of colors
277
277
278 :returns: RGB tuple
278 :returns: RGB tuple
279 """
279 """
280
280
281 def hsv_to_rgb(h, s, v):
281 def hsv_to_rgb(h, s, v):
282 if s == 0.0:
282 if s == 0.0:
283 return v, v, v
283 return v, v, v
284 i = int(h * 6.0) # XXX assume int() truncates!
284 i = int(h * 6.0) # XXX assume int() truncates!
285 f = (h * 6.0) - i
285 f = (h * 6.0) - i
286 p = v * (1.0 - s)
286 p = v * (1.0 - s)
287 q = v * (1.0 - s * f)
287 q = v * (1.0 - s * f)
288 t = v * (1.0 - s * (1.0 - f))
288 t = v * (1.0 - s * (1.0 - f))
289 i = i % 6
289 i = i % 6
290 if i == 0:
290 if i == 0:
291 return v, t, p
291 return v, t, p
292 if i == 1:
292 if i == 1:
293 return q, v, p
293 return q, v, p
294 if i == 2:
294 if i == 2:
295 return p, v, t
295 return p, v, t
296 if i == 3:
296 if i == 3:
297 return p, q, v
297 return p, q, v
298 if i == 4:
298 if i == 4:
299 return t, p, v
299 return t, p, v
300 if i == 5:
300 if i == 5:
301 return v, p, q
301 return v, p, q
302
302
303 golden_ratio = 0.618033988749895
303 golden_ratio = 0.618033988749895
304 h = 0.22717784590367374
304 h = 0.22717784590367374
305
305
306 for _ in xrange(n):
306 for _ in xrange(n):
307 h += golden_ratio
307 h += golden_ratio
308 h %= 1
308 h %= 1
309 HSV_tuple = [h, 0.95, 0.95]
309 HSV_tuple = [h, 0.95, 0.95]
310 RGB_tuple = hsv_to_rgb(*HSV_tuple)
310 RGB_tuple = hsv_to_rgb(*HSV_tuple)
311 yield map(lambda x: str(int(x * 256)), RGB_tuple)
311 yield map(lambda x: str(int(x * 256)), RGB_tuple)
312
312
313 cgenerator = gen_color()
313 cgenerator = gen_color()
314
314
315 def get_color_string(cs):
315 def get_color_string(cs):
316 if cs in color_dict:
316 if cs in color_dict:
317 col = color_dict[cs]
317 col = color_dict[cs]
318 else:
318 else:
319 col = color_dict[cs] = cgenerator.next()
319 col = color_dict[cs] = cgenerator.next()
320 return "color: rgb(%s)! important;" % (', '.join(col))
320 return "color: rgb(%s)! important;" % (', '.join(col))
321
321
322 def url_func(repo_name):
322 def url_func(repo_name):
323
323
324 def _url_func(changeset):
324 def _url_func(changeset):
325 author = changeset.author
325 author = changeset.author
326 date = changeset.date
326 date = changeset.date
327 message = tooltip(changeset.message)
327 message = tooltip(changeset.message)
328
328
329 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
329 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
330 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
330 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
331 "</b> %s<br/></div>")
331 "</b> %s<br/></div>")
332
332
333 tooltip_html = tooltip_html % (author, date, message)
333 tooltip_html = tooltip_html % (author, date, message)
334 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
334 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
335 short_id(changeset.raw_id))
335 short_id(changeset.raw_id))
336 uri = link_to(
336 uri = link_to(
337 lnk_format,
337 lnk_format,
338 url('changeset_home', repo_name=repo_name,
338 url('changeset_home', repo_name=repo_name,
339 revision=changeset.raw_id),
339 revision=changeset.raw_id),
340 style=get_color_string(changeset.raw_id),
340 style=get_color_string(changeset.raw_id),
341 class_='tooltip',
341 class_='tooltip',
342 title=tooltip_html
342 title=tooltip_html
343 )
343 )
344
344
345 uri += '\n'
345 uri += '\n'
346 return uri
346 return uri
347 return _url_func
347 return _url_func
348
348
349 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
349 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
350
350
351
351
352 def is_following_repo(repo_name, user_id):
352 def is_following_repo(repo_name, user_id):
353 from rhodecode.model.scm import ScmModel
353 from rhodecode.model.scm import ScmModel
354 return ScmModel().is_following_repo(repo_name, user_id)
354 return ScmModel().is_following_repo(repo_name, user_id)
355
355
356 flash = _Flash()
356 flash = _Flash()
357
357
358 #==============================================================================
358 #==============================================================================
359 # SCM FILTERS available via h.
359 # SCM FILTERS available via h.
360 #==============================================================================
360 #==============================================================================
361 from rhodecode.lib.vcs.utils import author_name, author_email
361 from rhodecode.lib.vcs.utils import author_name, author_email
362 from rhodecode.lib.utils2 import credentials_filter, age as _age
362 from rhodecode.lib.utils2 import credentials_filter, age as _age
363 from rhodecode.model.db import User, ChangesetStatus
363 from rhodecode.model.db import User, ChangesetStatus
364
364
365 age = lambda x: _age(x)
365 age = lambda x: _age(x)
366 capitalize = lambda x: x.capitalize()
366 capitalize = lambda x: x.capitalize()
367 email = author_email
367 email = author_email
368 short_id = lambda x: x[:12]
368 short_id = lambda x: x[:12]
369 hide_credentials = lambda x: ''.join(credentials_filter(x))
369 hide_credentials = lambda x: ''.join(credentials_filter(x))
370
370
371
371
372 def fmt_date(date):
372 def fmt_date(date):
373 if date:
373 if date:
374 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
374 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
375 return date.strftime(_fmt).decode('utf8')
375 return date.strftime(_fmt).decode('utf8')
376
376
377 return ""
377 return ""
378
378
379
379
380 def is_git(repository):
380 def is_git(repository):
381 if hasattr(repository, 'alias'):
381 if hasattr(repository, 'alias'):
382 _type = repository.alias
382 _type = repository.alias
383 elif hasattr(repository, 'repo_type'):
383 elif hasattr(repository, 'repo_type'):
384 _type = repository.repo_type
384 _type = repository.repo_type
385 else:
385 else:
386 _type = repository
386 _type = repository
387 return _type == 'git'
387 return _type == 'git'
388
388
389
389
390 def is_hg(repository):
390 def is_hg(repository):
391 if hasattr(repository, 'alias'):
391 if hasattr(repository, 'alias'):
392 _type = repository.alias
392 _type = repository.alias
393 elif hasattr(repository, 'repo_type'):
393 elif hasattr(repository, 'repo_type'):
394 _type = repository.repo_type
394 _type = repository.repo_type
395 else:
395 else:
396 _type = repository
396 _type = repository
397 return _type == 'hg'
397 return _type == 'hg'
398
398
399
399
400 def email_or_none(author):
400 def email_or_none(author):
401 # extract email from the commit string
401 # extract email from the commit string
402 _email = email(author)
402 _email = email(author)
403 if _email != '':
403 if _email != '':
404 # check it against RhodeCode database, and use the MAIN email for this
404 # check it against RhodeCode database, and use the MAIN email for this
405 # user
405 # user
406 user = User.get_by_email(_email, case_insensitive=True, cache=True)
406 user = User.get_by_email(_email, case_insensitive=True, cache=True)
407 if user is not None:
407 if user is not None:
408 return user.email
408 return user.email
409 return _email
409 return _email
410
410
411 # See if it contains a username we can get an email from
411 # See if it contains a username we can get an email from
412 user = User.get_by_username(author_name(author), case_insensitive=True,
412 user = User.get_by_username(author_name(author), case_insensitive=True,
413 cache=True)
413 cache=True)
414 if user is not None:
414 if user is not None:
415 return user.email
415 return user.email
416
416
417 # No valid email, not a valid user in the system, none!
417 # No valid email, not a valid user in the system, none!
418 return None
418 return None
419
419
420
420
421 def person(author, show_attr="username_and_name"):
421 def person(author, show_attr="username_and_name"):
422 # attr to return from fetched user
422 # attr to return from fetched user
423 person_getter = lambda usr: getattr(usr, show_attr)
423 person_getter = lambda usr: getattr(usr, show_attr)
424
424
425 # Valid email in the attribute passed, see if they're in the system
425 # Valid email in the attribute passed, see if they're in the system
426 _email = email(author)
426 _email = email(author)
427 if _email != '':
427 if _email != '':
428 user = User.get_by_email(_email, case_insensitive=True, cache=True)
428 user = User.get_by_email(_email, case_insensitive=True, cache=True)
429 if user is not None:
429 if user is not None:
430 return person_getter(user)
430 return person_getter(user)
431 return _email
431 return _email
432
432
433 # Maybe it's a username?
433 # Maybe it's a username?
434 _author = author_name(author)
434 _author = author_name(author)
435 user = User.get_by_username(_author, case_insensitive=True,
435 user = User.get_by_username(_author, case_insensitive=True,
436 cache=True)
436 cache=True)
437 if user is not None:
437 if user is not None:
438 return person_getter(user)
438 return person_getter(user)
439
439
440 # Still nothing? Just pass back the author name then
440 # Still nothing? Just pass back the author name then
441 return _author
441 return _author
442
442
443
443
444 def person_by_id(id_, show_attr="username_and_name"):
444 def person_by_id(id_, show_attr="username_and_name"):
445 # attr to return from fetched user
445 # attr to return from fetched user
446 person_getter = lambda usr: getattr(usr, show_attr)
446 person_getter = lambda usr: getattr(usr, show_attr)
447
447
448 #maybe it's an ID ?
448 #maybe it's an ID ?
449 if str(id_).isdigit() or isinstance(id_, int):
449 if str(id_).isdigit() or isinstance(id_, int):
450 id_ = int(id_)
450 id_ = int(id_)
451 user = User.get(id_)
451 user = User.get(id_)
452 if user is not None:
452 if user is not None:
453 return person_getter(user)
453 return person_getter(user)
454 return id_
454 return id_
455
455
456
456
457 def desc_stylize(value):
457 def desc_stylize(value):
458 """
458 """
459 converts tags from value into html equivalent
459 converts tags from value into html equivalent
460
460
461 :param value:
461 :param value:
462 """
462 """
463 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
463 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
464 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
465 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
465 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
466 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
466 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
467 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
467 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
468 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
468 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
469 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
469 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
470 '<div class="metatag" tag="lang">\\2</div>', value)
470 '<div class="metatag" tag="lang">\\2</div>', value)
471 value = re.sub(r'\[([a-z]+)\]',
471 value = re.sub(r'\[([a-z]+)\]',
472 '<div class="metatag" tag="\\1">\\1</div>', value)
472 '<div class="metatag" tag="\\1">\\1</div>', value)
473
473
474 return value
474 return value
475
475
476
476
477 def bool2icon(value):
477 def bool2icon(value):
478 """Returns True/False values represented as small html image of true/false
478 """Returns True/False values represented as small html image of true/false
479 icons
479 icons
480
480
481 :param value: bool value
481 :param value: bool value
482 """
482 """
483
483
484 if value is True:
484 if value is True:
485 return HTML.tag('img', src=url("/images/icons/accept.png"),
485 return HTML.tag('img', src=url("/images/icons/accept.png"),
486 alt=_('True'))
486 alt=_('True'))
487
487
488 if value is False:
488 if value is False:
489 return HTML.tag('img', src=url("/images/icons/cancel.png"),
489 return HTML.tag('img', src=url("/images/icons/cancel.png"),
490 alt=_('False'))
490 alt=_('False'))
491
491
492 return value
492 return value
493
493
494
494
495 def action_parser(user_log, feed=False, parse_cs=False):
495 def action_parser(user_log, feed=False, parse_cs=False):
496 """
496 """
497 This helper will action_map the specified string action into translated
497 This helper will action_map the specified string action into translated
498 fancy names with icons and links
498 fancy names with icons and links
499
499
500 :param user_log: user log instance
500 :param user_log: user log instance
501 :param feed: use output for feeds (no html and fancy icons)
501 :param feed: use output for feeds (no html and fancy icons)
502 :param parse_cs: parse Changesets into VCS instances
502 :param parse_cs: parse Changesets into VCS instances
503 """
503 """
504
504
505 action = user_log.action
505 action = user_log.action
506 action_params = ' '
506 action_params = ' '
507
507
508 x = action.split(':')
508 x = action.split(':')
509
509
510 if len(x) > 1:
510 if len(x) > 1:
511 action, action_params = x
511 action, action_params = x
512
512
513 def get_cs_links():
513 def get_cs_links():
514 revs_limit = 3 # display this amount always
514 revs_limit = 3 # display this amount always
515 revs_top_limit = 50 # show upto this amount of changesets hidden
515 revs_top_limit = 50 # show upto this amount of changesets hidden
516 revs_ids = action_params.split(',')
516 revs_ids = action_params.split(',')
517 deleted = user_log.repository is None
517 deleted = user_log.repository is None
518 if deleted:
518 if deleted:
519 return ','.join(revs_ids)
519 return ','.join(revs_ids)
520
520
521 repo_name = user_log.repository.repo_name
521 repo_name = user_log.repository.repo_name
522
522
523 def lnk(rev, repo_name):
523 def lnk(rev, repo_name):
524 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
524 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
525 lazy_cs = True
525 lazy_cs = True
526 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
526 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
527 lazy_cs = False
527 lazy_cs = False
528 lbl = '?'
528 lbl = '?'
529 if rev.op == 'delete_branch':
529 if rev.op == 'delete_branch':
530 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
530 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
531 title = ''
531 title = ''
532 elif rev.op == 'tag':
532 elif rev.op == 'tag':
533 lbl = '%s' % _('Created tag: %s') % rev.ref_name
533 lbl = '%s' % _('Created tag: %s') % rev.ref_name
534 title = ''
534 title = ''
535 _url = '#'
535 _url = '#'
536
536
537 else:
537 else:
538 lbl = '%s' % (rev.short_id[:8])
538 lbl = '%s' % (rev.short_id[:8])
539 _url = url('changeset_home', repo_name=repo_name,
539 _url = url('changeset_home', repo_name=repo_name,
540 revision=rev.raw_id)
540 revision=rev.raw_id)
541 title = tooltip(rev.message)
541 title = tooltip(rev.message)
542 else:
542 else:
543 ## changeset cannot be found/striped/removed etc.
543 ## changeset cannot be found/striped/removed etc.
544 lbl = ('%s' % rev)[:12]
544 lbl = ('%s' % rev)[:12]
545 _url = '#'
545 _url = '#'
546 title = _('Changeset not found')
546 title = _('Changeset not found')
547 if parse_cs:
547 if parse_cs:
548 return link_to(lbl, _url, title=title, class_='tooltip')
548 return link_to(lbl, _url, title=title, class_='tooltip')
549 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
549 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
550 class_='lazy-cs' if lazy_cs else '')
550 class_='lazy-cs' if lazy_cs else '')
551
551
552 revs = []
552 revs = []
553 if len(filter(lambda v: v != '', revs_ids)) > 0:
553 if len(filter(lambda v: v != '', revs_ids)) > 0:
554 repo = None
554 repo = None
555 for rev in revs_ids[:revs_top_limit]:
555 for rev in revs_ids[:revs_top_limit]:
556 _op = _name = None
556 _op = _name = None
557 if len(rev.split('=>')) == 2:
557 if len(rev.split('=>')) == 2:
558 _op, _name = rev.split('=>')
558 _op, _name = rev.split('=>')
559
559
560 # we want parsed changesets, or new log store format is bad
560 # we want parsed changesets, or new log store format is bad
561 if parse_cs:
561 if parse_cs:
562 try:
562 try:
563 if repo is None:
563 if repo is None:
564 repo = user_log.repository.scm_instance
564 repo = user_log.repository.scm_instance
565 _rev = repo.get_changeset(rev)
565 _rev = repo.get_changeset(rev)
566 revs.append(_rev)
566 revs.append(_rev)
567 except ChangesetDoesNotExistError:
567 except ChangesetDoesNotExistError:
568 log.error('cannot find revision %s in this repo' % rev)
568 log.error('cannot find revision %s in this repo' % rev)
569 revs.append(rev)
569 revs.append(rev)
570 continue
570 continue
571 else:
571 else:
572 _rev = AttributeDict({
572 _rev = AttributeDict({
573 'short_id': rev[:12],
573 'short_id': rev[:12],
574 'raw_id': rev,
574 'raw_id': rev,
575 'message': '',
575 'message': '',
576 'op': _op,
576 'op': _op,
577 'ref_name': _name
577 'ref_name': _name
578 })
578 })
579 revs.append(_rev)
579 revs.append(_rev)
580 cs_links = []
580 cs_links = []
581 cs_links.append(" " + ', '.join(
581 cs_links.append(" " + ', '.join(
582 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
582 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
583 )
583 )
584 )
584 )
585
585
586 compare_view = (
586 compare_view = (
587 ' <div class="compare_view tooltip" title="%s">'
587 ' <div class="compare_view tooltip" title="%s">'
588 '<a href="%s">%s</a> </div>' % (
588 '<a href="%s">%s</a> </div>' % (
589 _('Show all combined changesets %s->%s') % (
589 _('Show all combined changesets %s->%s') % (
590 revs_ids[0][:12], revs_ids[-1][:12]
590 revs_ids[0][:12], revs_ids[-1][:12]
591 ),
591 ),
592 url('changeset_home', repo_name=repo_name,
592 url('changeset_home', repo_name=repo_name,
593 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
593 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
594 ),
594 ),
595 _('compare view')
595 _('compare view')
596 )
596 )
597 )
597 )
598
598
599 # if we have exactly one more than normally displayed
599 # if we have exactly one more than normally displayed
600 # just display it, takes less space than displaying
600 # just display it, takes less space than displaying
601 # "and 1 more revisions"
601 # "and 1 more revisions"
602 if len(revs_ids) == revs_limit + 1:
602 if len(revs_ids) == revs_limit + 1:
603 rev = revs[revs_limit]
603 rev = revs[revs_limit]
604 cs_links.append(", " + lnk(rev, repo_name))
604 cs_links.append(", " + lnk(rev, repo_name))
605
605
606 # hidden-by-default ones
606 # hidden-by-default ones
607 if len(revs_ids) > revs_limit + 1:
607 if len(revs_ids) > revs_limit + 1:
608 uniq_id = revs_ids[0]
608 uniq_id = revs_ids[0]
609 html_tmpl = (
609 html_tmpl = (
610 '<span> %s <a class="show_more" id="_%s" '
610 '<span> %s <a class="show_more" id="_%s" '
611 'href="#more">%s</a> %s</span>'
611 'href="#more">%s</a> %s</span>'
612 )
612 )
613 if not feed:
613 if not feed:
614 cs_links.append(html_tmpl % (
614 cs_links.append(html_tmpl % (
615 _('and'),
615 _('and'),
616 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
616 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
617 _('revisions')
617 _('revisions')
618 )
618 )
619 )
619 )
620
620
621 if not feed:
621 if not feed:
622 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
622 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
623 else:
623 else:
624 html_tmpl = '<span id="%s"> %s </span>'
624 html_tmpl = '<span id="%s"> %s </span>'
625
625
626 morelinks = ', '.join(
626 morelinks = ', '.join(
627 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
627 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
628 )
628 )
629
629
630 if len(revs_ids) > revs_top_limit:
630 if len(revs_ids) > revs_top_limit:
631 morelinks += ', ...'
631 morelinks += ', ...'
632
632
633 cs_links.append(html_tmpl % (uniq_id, morelinks))
633 cs_links.append(html_tmpl % (uniq_id, morelinks))
634 if len(revs) > 1:
634 if len(revs) > 1:
635 cs_links.append(compare_view)
635 cs_links.append(compare_view)
636 return ''.join(cs_links)
636 return ''.join(cs_links)
637
637
638 def get_fork_name():
638 def get_fork_name():
639 repo_name = action_params
639 repo_name = action_params
640 _url = url('summary_home', repo_name=repo_name)
640 _url = url('summary_home', repo_name=repo_name)
641 return _('fork name %s') % link_to(action_params, _url)
641 return _('fork name %s') % link_to(action_params, _url)
642
642
643 def get_user_name():
643 def get_user_name():
644 user_name = action_params
644 user_name = action_params
645 return user_name
645 return user_name
646
646
647 def get_users_group():
647 def get_users_group():
648 group_name = action_params
648 group_name = action_params
649 return group_name
649 return group_name
650
650
651 def get_pull_request():
651 def get_pull_request():
652 pull_request_id = action_params
652 pull_request_id = action_params
653 deleted = user_log.repository is None
653 deleted = user_log.repository is None
654 if deleted:
654 if deleted:
655 repo_name = user_log.repository_name
655 repo_name = user_log.repository_name
656 else:
656 else:
657 repo_name = user_log.repository.repo_name
657 repo_name = user_log.repository.repo_name
658 return link_to(_('Pull request #%s') % pull_request_id,
658 return link_to(_('Pull request #%s') % pull_request_id,
659 url('pullrequest_show', repo_name=repo_name,
659 url('pullrequest_show', repo_name=repo_name,
660 pull_request_id=pull_request_id))
660 pull_request_id=pull_request_id))
661
661
662 # action : translated str, callback(extractor), icon
662 # action : translated str, callback(extractor), icon
663 action_map = {
663 action_map = {
664 'user_deleted_repo': (_('[deleted] repository'),
664 'user_deleted_repo': (_('[deleted] repository'),
665 None, 'database_delete.png'),
665 None, 'database_delete.png'),
666 'user_created_repo': (_('[created] repository'),
666 'user_created_repo': (_('[created] repository'),
667 None, 'database_add.png'),
667 None, 'database_add.png'),
668 'user_created_fork': (_('[created] repository as fork'),
668 'user_created_fork': (_('[created] repository as fork'),
669 None, 'arrow_divide.png'),
669 None, 'arrow_divide.png'),
670 'user_forked_repo': (_('[forked] repository'),
670 'user_forked_repo': (_('[forked] repository'),
671 get_fork_name, 'arrow_divide.png'),
671 get_fork_name, 'arrow_divide.png'),
672 'user_updated_repo': (_('[updated] repository'),
672 'user_updated_repo': (_('[updated] repository'),
673 None, 'database_edit.png'),
673 None, 'database_edit.png'),
674 'admin_deleted_repo': (_('[delete] repository'),
674 'admin_deleted_repo': (_('[delete] repository'),
675 None, 'database_delete.png'),
675 None, 'database_delete.png'),
676 'admin_created_repo': (_('[created] repository'),
676 'admin_created_repo': (_('[created] repository'),
677 None, 'database_add.png'),
677 None, 'database_add.png'),
678 'admin_forked_repo': (_('[forked] repository'),
678 'admin_forked_repo': (_('[forked] repository'),
679 None, 'arrow_divide.png'),
679 None, 'arrow_divide.png'),
680 'admin_updated_repo': (_('[updated] repository'),
680 'admin_updated_repo': (_('[updated] repository'),
681 None, 'database_edit.png'),
681 None, 'database_edit.png'),
682 'admin_created_user': (_('[created] user'),
682 'admin_created_user': (_('[created] user'),
683 get_user_name, 'user_add.png'),
683 get_user_name, 'user_add.png'),
684 'admin_updated_user': (_('[updated] user'),
684 'admin_updated_user': (_('[updated] user'),
685 get_user_name, 'user_edit.png'),
685 get_user_name, 'user_edit.png'),
686 'admin_created_users_group': (_('[created] users group'),
686 'admin_created_users_group': (_('[created] users group'),
687 get_users_group, 'group_add.png'),
687 get_users_group, 'group_add.png'),
688 'admin_updated_users_group': (_('[updated] users group'),
688 'admin_updated_users_group': (_('[updated] users group'),
689 get_users_group, 'group_edit.png'),
689 get_users_group, 'group_edit.png'),
690 'user_commented_revision': (_('[commented] on revision in repository'),
690 'user_commented_revision': (_('[commented] on revision in repository'),
691 get_cs_links, 'comment_add.png'),
691 get_cs_links, 'comment_add.png'),
692 'user_commented_pull_request': (_('[commented] on pull request for'),
692 'user_commented_pull_request': (_('[commented] on pull request for'),
693 get_pull_request, 'comment_add.png'),
693 get_pull_request, 'comment_add.png'),
694 'user_closed_pull_request': (_('[closed] pull request for'),
694 'user_closed_pull_request': (_('[closed] pull request for'),
695 get_pull_request, 'tick.png'),
695 get_pull_request, 'tick.png'),
696 'push': (_('[pushed] into'),
696 'push': (_('[pushed] into'),
697 get_cs_links, 'script_add.png'),
697 get_cs_links, 'script_add.png'),
698 'push_local': (_('[committed via RhodeCode] into repository'),
698 'push_local': (_('[committed via RhodeCode] into repository'),
699 get_cs_links, 'script_edit.png'),
699 get_cs_links, 'script_edit.png'),
700 'push_remote': (_('[pulled from remote] into repository'),
700 'push_remote': (_('[pulled from remote] into repository'),
701 get_cs_links, 'connect.png'),
701 get_cs_links, 'connect.png'),
702 'pull': (_('[pulled] from'),
702 'pull': (_('[pulled] from'),
703 None, 'down_16.png'),
703 None, 'down_16.png'),
704 'started_following_repo': (_('[started following] repository'),
704 'started_following_repo': (_('[started following] repository'),
705 None, 'heart_add.png'),
705 None, 'heart_add.png'),
706 'stopped_following_repo': (_('[stopped following] repository'),
706 'stopped_following_repo': (_('[stopped following] repository'),
707 None, 'heart_delete.png'),
707 None, 'heart_delete.png'),
708 }
708 }
709
709
710 action_str = action_map.get(action, action)
710 action_str = action_map.get(action, action)
711 if feed:
711 if feed:
712 action = action_str[0].replace('[', '').replace(']', '')
712 action = action_str[0].replace('[', '').replace(']', '')
713 else:
713 else:
714 action = action_str[0]\
714 action = action_str[0]\
715 .replace('[', '<span class="journal_highlight">')\
715 .replace('[', '<span class="journal_highlight">')\
716 .replace(']', '</span>')
716 .replace(']', '</span>')
717
717
718 action_params_func = lambda: ""
718 action_params_func = lambda: ""
719
719
720 if callable(action_str[1]):
720 if callable(action_str[1]):
721 action_params_func = action_str[1]
721 action_params_func = action_str[1]
722
722
723 def action_parser_icon():
723 def action_parser_icon():
724 action = user_log.action
724 action = user_log.action
725 action_params = None
725 action_params = None
726 x = action.split(':')
726 x = action.split(':')
727
727
728 if len(x) > 1:
728 if len(x) > 1:
729 action, action_params = x
729 action, action_params = x
730
730
731 tmpl = """<img src="%s%s" alt="%s"/>"""
731 tmpl = """<img src="%s%s" alt="%s"/>"""
732 ico = action_map.get(action, ['', '', ''])[2]
732 ico = action_map.get(action, ['', '', ''])[2]
733 return literal(tmpl % ((url('/images/icons/')), ico, action))
733 return literal(tmpl % ((url('/images/icons/')), ico, action))
734
734
735 # returned callbacks we need to call to get
735 # returned callbacks we need to call to get
736 return [lambda: literal(action), action_params_func, action_parser_icon]
736 return [lambda: literal(action), action_params_func, action_parser_icon]
737
737
738
738
739
739
740 #==============================================================================
740 #==============================================================================
741 # PERMS
741 # PERMS
742 #==============================================================================
742 #==============================================================================
743 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
743 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
744 HasRepoPermissionAny, HasRepoPermissionAll
744 HasRepoPermissionAny, HasRepoPermissionAll
745
745
746
746
747 #==============================================================================
747 #==============================================================================
748 # GRAVATAR URL
748 # GRAVATAR URL
749 #==============================================================================
749 #==============================================================================
750
750
751 def gravatar_url(email_address, size=30):
751 def gravatar_url(email_address, size=30):
752 from pylons import url # doh, we need to re-import url to mock it later
752 from pylons import url # doh, we need to re-import url to mock it later
753
753
754 if (not str2bool(config['app_conf'].get('use_gravatar')) or
754 if (not str2bool(config['app_conf'].get('use_gravatar')) or
755 not email_address or email_address == 'anonymous@rhodecode.org'):
755 not email_address or email_address == 'anonymous@rhodecode.org'):
756 f = lambda a, l: min(l, key=lambda x: abs(x - a))
756 f = lambda a, l: min(l, key=lambda x: abs(x - a))
757 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
757 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
758
758
759 if(str2bool(config['app_conf'].get('use_gravatar')) and
759 if(str2bool(config['app_conf'].get('use_gravatar')) and
760 config['app_conf'].get('alternative_gravatar_url')):
760 config['app_conf'].get('alternative_gravatar_url')):
761 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
761 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
762 parsed_url = urlparse.urlparse(url.current(qualified=True))
762 parsed_url = urlparse.urlparse(url.current(qualified=True))
763 tmpl = tmpl.replace('{email}', email_address)\
763 tmpl = tmpl.replace('{email}', email_address)\
764 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
764 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
765 .replace('{netloc}', parsed_url.netloc)\
765 .replace('{netloc}', parsed_url.netloc)\
766 .replace('{scheme}', parsed_url.scheme)\
766 .replace('{scheme}', parsed_url.scheme)\
767 .replace('{size}', str(size))
767 .replace('{size}', str(size))
768 return tmpl
768 return tmpl
769
769
770 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
770 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
771 default = 'identicon'
771 default = 'identicon'
772 baseurl_nossl = "http://www.gravatar.com/avatar/"
772 baseurl_nossl = "http://www.gravatar.com/avatar/"
773 baseurl_ssl = "https://secure.gravatar.com/avatar/"
773 baseurl_ssl = "https://secure.gravatar.com/avatar/"
774 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
774 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
775
775
776 if isinstance(email_address, unicode):
776 if isinstance(email_address, unicode):
777 #hashlib crashes on unicode items
777 #hashlib crashes on unicode items
778 email_address = safe_str(email_address)
778 email_address = safe_str(email_address)
779 # construct the url
779 # construct the url
780 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
780 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
781 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
781 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
782
782
783 return gravatar_url
783 return gravatar_url
784
784
785
785
786 #==============================================================================
786 #==============================================================================
787 # REPO PAGER, PAGER FOR REPOSITORY
787 # REPO PAGER, PAGER FOR REPOSITORY
788 #==============================================================================
788 #==============================================================================
789 class RepoPage(Page):
789 class RepoPage(Page):
790
790
791 def __init__(self, collection, page=1, items_per_page=20,
791 def __init__(self, collection, page=1, items_per_page=20,
792 item_count=None, url=None, **kwargs):
792 item_count=None, url=None, **kwargs):
793
793
794 """Create a "RepoPage" instance. special pager for paging
794 """Create a "RepoPage" instance. special pager for paging
795 repository
795 repository
796 """
796 """
797 self._url_generator = url
797 self._url_generator = url
798
798
799 # Safe the kwargs class-wide so they can be used in the pager() method
799 # Safe the kwargs class-wide so they can be used in the pager() method
800 self.kwargs = kwargs
800 self.kwargs = kwargs
801
801
802 # Save a reference to the collection
802 # Save a reference to the collection
803 self.original_collection = collection
803 self.original_collection = collection
804
804
805 self.collection = collection
805 self.collection = collection
806
806
807 # The self.page is the number of the current page.
807 # The self.page is the number of the current page.
808 # The first page has the number 1!
808 # The first page has the number 1!
809 try:
809 try:
810 self.page = int(page) # make it int() if we get it as a string
810 self.page = int(page) # make it int() if we get it as a string
811 except (ValueError, TypeError):
811 except (ValueError, TypeError):
812 self.page = 1
812 self.page = 1
813
813
814 self.items_per_page = items_per_page
814 self.items_per_page = items_per_page
815
815
816 # Unless the user tells us how many items the collections has
816 # Unless the user tells us how many items the collections has
817 # we calculate that ourselves.
817 # we calculate that ourselves.
818 if item_count is not None:
818 if item_count is not None:
819 self.item_count = item_count
819 self.item_count = item_count
820 else:
820 else:
821 self.item_count = len(self.collection)
821 self.item_count = len(self.collection)
822
822
823 # Compute the number of the first and last available page
823 # Compute the number of the first and last available page
824 if self.item_count > 0:
824 if self.item_count > 0:
825 self.first_page = 1
825 self.first_page = 1
826 self.page_count = int(math.ceil(float(self.item_count) /
826 self.page_count = int(math.ceil(float(self.item_count) /
827 self.items_per_page))
827 self.items_per_page))
828 self.last_page = self.first_page + self.page_count - 1
828 self.last_page = self.first_page + self.page_count - 1
829
829
830 # Make sure that the requested page number is the range of
830 # Make sure that the requested page number is the range of
831 # valid pages
831 # valid pages
832 if self.page > self.last_page:
832 if self.page > self.last_page:
833 self.page = self.last_page
833 self.page = self.last_page
834 elif self.page < self.first_page:
834 elif self.page < self.first_page:
835 self.page = self.first_page
835 self.page = self.first_page
836
836
837 # Note: the number of items on this page can be less than
837 # Note: the number of items on this page can be less than
838 # items_per_page if the last page is not full
838 # items_per_page if the last page is not full
839 self.first_item = max(0, (self.item_count) - (self.page *
839 self.first_item = max(0, (self.item_count) - (self.page *
840 items_per_page))
840 items_per_page))
841 self.last_item = ((self.item_count - 1) - items_per_page *
841 self.last_item = ((self.item_count - 1) - items_per_page *
842 (self.page - 1))
842 (self.page - 1))
843
843
844 self.items = list(self.collection[self.first_item:self.last_item + 1])
844 self.items = list(self.collection[self.first_item:self.last_item + 1])
845
845
846 # Links to previous and next page
846 # Links to previous and next page
847 if self.page > self.first_page:
847 if self.page > self.first_page:
848 self.previous_page = self.page - 1
848 self.previous_page = self.page - 1
849 else:
849 else:
850 self.previous_page = None
850 self.previous_page = None
851
851
852 if self.page < self.last_page:
852 if self.page < self.last_page:
853 self.next_page = self.page + 1
853 self.next_page = self.page + 1
854 else:
854 else:
855 self.next_page = None
855 self.next_page = None
856
856
857 # No items available
857 # No items available
858 else:
858 else:
859 self.first_page = None
859 self.first_page = None
860 self.page_count = 0
860 self.page_count = 0
861 self.last_page = None
861 self.last_page = None
862 self.first_item = None
862 self.first_item = None
863 self.last_item = None
863 self.last_item = None
864 self.previous_page = None
864 self.previous_page = None
865 self.next_page = None
865 self.next_page = None
866 self.items = []
866 self.items = []
867
867
868 # This is a subclass of the 'list' type. Initialise the list now.
868 # This is a subclass of the 'list' type. Initialise the list now.
869 list.__init__(self, reversed(self.items))
869 list.__init__(self, reversed(self.items))
870
870
871
871
872 def changed_tooltip(nodes):
872 def changed_tooltip(nodes):
873 """
873 """
874 Generates a html string for changed nodes in changeset page.
874 Generates a html string for changed nodes in changeset page.
875 It limits the output to 30 entries
875 It limits the output to 30 entries
876
876
877 :param nodes: LazyNodesGenerator
877 :param nodes: LazyNodesGenerator
878 """
878 """
879 if nodes:
879 if nodes:
880 pref = ': <br/> '
880 pref = ': <br/> '
881 suf = ''
881 suf = ''
882 if len(nodes) > 30:
882 if len(nodes) > 30:
883 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
883 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
884 return literal(pref + '<br/> '.join([safe_unicode(x.path)
884 return literal(pref + '<br/> '.join([safe_unicode(x.path)
885 for x in nodes[:30]]) + suf)
885 for x in nodes[:30]]) + suf)
886 else:
886 else:
887 return ': ' + _('No Files')
887 return ': ' + _('No Files')
888
888
889
889
890 def repo_link(groups_and_repos, last_url=None):
890 def repo_link(groups_and_repos, last_url=None):
891 """
891 """
892 Makes a breadcrumbs link to repo within a group
892 Makes a breadcrumbs link to repo within a group
893 joins &raquo; on each group to create a fancy link
893 joins &raquo; on each group to create a fancy link
894
894
895 ex::
895 ex::
896 group >> subgroup >> repo
896 group >> subgroup >> repo
897
897
898 :param groups_and_repos:
898 :param groups_and_repos:
899 :param last_url:
899 :param last_url:
900 """
900 """
901 groups, repo_name = groups_and_repos
901 groups, repo_name = groups_and_repos
902 last_link = link_to(repo_name, last_url) if last_url else repo_name
902 last_link = link_to(repo_name, last_url) if last_url else repo_name
903
903
904 if not groups:
904 if not groups:
905 if last_url:
905 if last_url:
906 return last_link
906 return last_link
907 return repo_name
907 return repo_name
908 else:
908 else:
909 def make_link(group):
909 def make_link(group):
910 return link_to(group.name,
910 return link_to(group.name,
911 url('repos_group_home', group_name=group.group_name))
911 url('repos_group_home', group_name=group.group_name))
912 return literal(' &raquo; '.join(map(make_link, groups) + [last_link]))
912 return literal(' &raquo; '.join(map(make_link, groups) + [last_link]))
913
913
914
914
915 def fancy_file_stats(stats):
915 def fancy_file_stats(stats):
916 """
916 """
917 Displays a fancy two colored bar for number of added/deleted
917 Displays a fancy two colored bar for number of added/deleted
918 lines of code on file
918 lines of code on file
919
919
920 :param stats: two element list of added/deleted lines of code
920 :param stats: two element list of added/deleted lines of code
921 """
921 """
922 def cgen(l_type, a_v, d_v):
922 def cgen(l_type, a_v, d_v):
923 mapping = {'tr': 'top-right-rounded-corner-mid',
923 mapping = {'tr': 'top-right-rounded-corner-mid',
924 'tl': 'top-left-rounded-corner-mid',
924 'tl': 'top-left-rounded-corner-mid',
925 'br': 'bottom-right-rounded-corner-mid',
925 'br': 'bottom-right-rounded-corner-mid',
926 'bl': 'bottom-left-rounded-corner-mid'}
926 'bl': 'bottom-left-rounded-corner-mid'}
927 map_getter = lambda x: mapping[x]
927 map_getter = lambda x: mapping[x]
928
928
929 if l_type == 'a' and d_v:
929 if l_type == 'a' and d_v:
930 #case when added and deleted are present
930 #case when added and deleted are present
931 return ' '.join(map(map_getter, ['tl', 'bl']))
931 return ' '.join(map(map_getter, ['tl', 'bl']))
932
932
933 if l_type == 'a' and not d_v:
933 if l_type == 'a' and not d_v:
934 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
934 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
935
935
936 if l_type == 'd' and a_v:
936 if l_type == 'd' and a_v:
937 return ' '.join(map(map_getter, ['tr', 'br']))
937 return ' '.join(map(map_getter, ['tr', 'br']))
938
938
939 if l_type == 'd' and not a_v:
939 if l_type == 'd' and not a_v:
940 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
940 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
941
941
942 a, d = stats[0], stats[1]
942 a, d = stats[0], stats[1]
943 width = 100
943 width = 100
944
944
945 if a == 'b':
945 if a == 'b':
946 #binary mode
946 #binary mode
947 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
947 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
948 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
948 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
949 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
949 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
950
950
951 t = stats[0] + stats[1]
951 t = stats[0] + stats[1]
952 unit = float(width) / (t or 1)
952 unit = float(width) / (t or 1)
953
953
954 # needs > 9% of width to be visible or 0 to be hidden
954 # needs > 9% of width to be visible or 0 to be hidden
955 a_p = max(9, unit * a) if a > 0 else 0
955 a_p = max(9, unit * a) if a > 0 else 0
956 d_p = max(9, unit * d) if d > 0 else 0
956 d_p = max(9, unit * d) if d > 0 else 0
957 p_sum = a_p + d_p
957 p_sum = a_p + d_p
958
958
959 if p_sum > width:
959 if p_sum > width:
960 #adjust the percentage to be == 100% since we adjusted to 9
960 #adjust the percentage to be == 100% since we adjusted to 9
961 if a_p > d_p:
961 if a_p > d_p:
962 a_p = a_p - (p_sum - width)
962 a_p = a_p - (p_sum - width)
963 else:
963 else:
964 d_p = d_p - (p_sum - width)
964 d_p = d_p - (p_sum - width)
965
965
966 a_v = a if a > 0 else ''
966 a_v = a if a > 0 else ''
967 d_v = d if d > 0 else ''
967 d_v = d if d > 0 else ''
968
968
969 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
969 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
970 cgen('a', a_v, d_v), a_p, a_v
970 cgen('a', a_v, d_v), a_p, a_v
971 )
971 )
972 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
972 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
973 cgen('d', a_v, d_v), d_p, d_v
973 cgen('d', a_v, d_v), d_p, d_v
974 )
974 )
975 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
975 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
976
976
977
977
978 def urlify_text(text_):
978 def urlify_text(text_):
979
979
980 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
980 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
981 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
981 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
982
982
983 def url_func(match_obj):
983 def url_func(match_obj):
984 url_full = match_obj.groups()[0]
984 url_full = match_obj.groups()[0]
985 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
985 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
986
986
987 return literal(url_pat.sub(url_func, text_))
987 return literal(url_pat.sub(url_func, text_))
988
988
989
989
990 def urlify_changesets(text_, repository):
990 def urlify_changesets(text_, repository):
991 """
991 """
992 Extract revision ids from changeset and make link from them
992 Extract revision ids from changeset and make link from them
993
993
994 :param text_:
994 :param text_:
995 :param repository:
995 :param repository:
996 """
996 """
997
997
998 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
998 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
999
999
1000 def url_func(match_obj):
1000 def url_func(match_obj):
1001 rev = match_obj.groups()[0]
1001 rev = match_obj.groups()[0]
1002 pref = ''
1002 pref = ''
1003 if match_obj.group().startswith(' '):
1003 if match_obj.group().startswith(' '):
1004 pref = ' '
1004 pref = ' '
1005 tmpl = (
1005 tmpl = (
1006 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1006 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1007 '%(rev)s'
1007 '%(rev)s'
1008 '</a>'
1008 '</a>'
1009 )
1009 )
1010 return tmpl % {
1010 return tmpl % {
1011 'pref': pref,
1011 'pref': pref,
1012 'cls': 'revision-link',
1012 'cls': 'revision-link',
1013 'url': url('changeset_home', repo_name=repository, revision=rev),
1013 'url': url('changeset_home', repo_name=repository, revision=rev),
1014 'rev': rev,
1014 'rev': rev,
1015 }
1015 }
1016
1016
1017 newtext = URL_PAT.sub(url_func, text_)
1017 newtext = URL_PAT.sub(url_func, text_)
1018
1018
1019 return newtext
1019 return newtext
1020
1020
1021
1021
1022 def urlify_commit(text_, repository=None, link_=None):
1022 def urlify_commit(text_, repository=None, link_=None):
1023 """
1023 """
1024 Parses given text message and makes proper links.
1024 Parses given text message and makes proper links.
1025 issues are linked to given issue-server, and rest is a changeset link
1025 issues are linked to given issue-server, and rest is a changeset link
1026 if link_ is given, in other case it's a plain text
1026 if link_ is given, in other case it's a plain text
1027
1027
1028 :param text_:
1028 :param text_:
1029 :param repository:
1029 :param repository:
1030 :param link_: changeset link
1030 :param link_: changeset link
1031 """
1031 """
1032 import traceback
1032 import traceback
1033
1033
1034 def escaper(string):
1034 def escaper(string):
1035 return string.replace('<', '&lt;').replace('>', '&gt;')
1035 return string.replace('<', '&lt;').replace('>', '&gt;')
1036
1036
1037 def linkify_others(t, l):
1037 def linkify_others(t, l):
1038 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1038 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1039 links = []
1039 links = []
1040 for e in urls.split(t):
1040 for e in urls.split(t):
1041 if not urls.match(e):
1041 if not urls.match(e):
1042 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1042 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1043 else:
1043 else:
1044 links.append(e)
1044 links.append(e)
1045
1045
1046 return ''.join(links)
1046 return ''.join(links)
1047
1047
1048 # urlify changesets - extrac revisions and make link out of them
1048 # urlify changesets - extrac revisions and make link out of them
1049 newtext = urlify_changesets(escaper(text_), repository)
1049 newtext = urlify_changesets(escaper(text_), repository)
1050
1050
1051 try:
1051 try:
1052 conf = config['app_conf']
1052 conf = config['app_conf']
1053
1053
1054 # allow multiple issue servers to be used
1054 # allow multiple issue servers to be used
1055 valid_indices = [
1055 valid_indices = [
1056 x.group(1)
1056 x.group(1)
1057 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1057 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1058 if x and 'issue_server_link%s' % x.group(1) in conf
1058 if x and 'issue_server_link%s' % x.group(1) in conf
1059 and 'issue_prefix%s' % x.group(1) in conf
1059 and 'issue_prefix%s' % x.group(1) in conf
1060 ]
1060 ]
1061
1061
1062 log.debug('found issue server suffixes `%s` during valuation of: %s'
1062 log.debug('found issue server suffixes `%s` during valuation of: %s'
1063 % (','.join(valid_indices), newtext))
1063 % (','.join(valid_indices), newtext))
1064
1064
1065 for pattern_index in valid_indices:
1065 for pattern_index in valid_indices:
1066 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1066 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1067 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1067 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1068 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1068 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1069
1069
1070 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1070 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1071 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1071 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1072 ISSUE_PREFIX))
1072 ISSUE_PREFIX))
1073
1073
1074 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1074 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1075
1075
1076 def url_func(match_obj):
1076 def url_func(match_obj):
1077 pref = ''
1077 pref = ''
1078 if match_obj.group().startswith(' '):
1078 if match_obj.group().startswith(' '):
1079 pref = ' '
1079 pref = ' '
1080
1080
1081 issue_id = ''.join(match_obj.groups())
1081 issue_id = ''.join(match_obj.groups())
1082 tmpl = (
1082 tmpl = (
1083 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1083 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1084 '%(issue-prefix)s%(id-repr)s'
1084 '%(issue-prefix)s%(id-repr)s'
1085 '</a>'
1085 '</a>'
1086 )
1086 )
1087 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1087 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1088 if repository:
1088 if repository:
1089 url = url.replace('{repo}', repository)
1089 url = url.replace('{repo}', repository)
1090 repo_name = repository.split(URL_SEP)[-1]
1090 repo_name = repository.split(URL_SEP)[-1]
1091 url = url.replace('{repo_name}', repo_name)
1091 url = url.replace('{repo_name}', repo_name)
1092
1092
1093 return tmpl % {
1093 return tmpl % {
1094 'pref': pref,
1094 'pref': pref,
1095 'cls': 'issue-tracker-link',
1095 'cls': 'issue-tracker-link',
1096 'url': url,
1096 'url': url,
1097 'id-repr': issue_id,
1097 'id-repr': issue_id,
1098 'issue-prefix': ISSUE_PREFIX,
1098 'issue-prefix': ISSUE_PREFIX,
1099 'serv': ISSUE_SERVER_LNK,
1099 'serv': ISSUE_SERVER_LNK,
1100 }
1100 }
1101 newtext = URL_PAT.sub(url_func, newtext)
1101 newtext = URL_PAT.sub(url_func, newtext)
1102 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1102 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1103
1103
1104 # if we actually did something above
1104 # if we actually did something above
1105 if link_:
1105 if link_:
1106 # wrap not links into final link => link_
1106 # wrap not links into final link => link_
1107 newtext = linkify_others(newtext, link_)
1107 newtext = linkify_others(newtext, link_)
1108 except:
1108 except:
1109 log.error(traceback.format_exc())
1109 log.error(traceback.format_exc())
1110 pass
1110 pass
1111
1111
1112 return literal(newtext)
1112 return literal(newtext)
1113
1113
1114
1114
1115 def rst(source):
1115 def rst(source):
1116 return literal('<div class="rst-block">%s</div>' %
1116 return literal('<div class="rst-block">%s</div>' %
1117 MarkupRenderer.rst(source))
1117 MarkupRenderer.rst(source))
1118
1118
1119
1119
1120 def rst_w_mentions(source):
1120 def rst_w_mentions(source):
1121 """
1121 """
1122 Wrapped rst renderer with @mention highlighting
1122 Wrapped rst renderer with @mention highlighting
1123
1123
1124 :param source:
1124 :param source:
1125 """
1125 """
1126 return literal('<div class="rst-block">%s</div>' %
1126 return literal('<div class="rst-block">%s</div>' %
1127 MarkupRenderer.rst_with_mentions(source))
1127 MarkupRenderer.rst_with_mentions(source))
1128
1128
1129
1129
1130 def changeset_status(repo, revision):
1130 def changeset_status(repo, revision):
1131 return ChangesetStatusModel().get_status(repo, revision)
1131 return ChangesetStatusModel().get_status(repo, revision)
1132
1132
1133
1133
1134 def changeset_status_lbl(changeset_status):
1134 def changeset_status_lbl(changeset_status):
1135 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1135 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1136
1136
1137
1137
1138 def get_permission_name(key):
1138 def get_permission_name(key):
1139 return dict(Permission.PERMS).get(key)
1139 return dict(Permission.PERMS).get(key)
1140
1140
1141
1141
1142 def journal_filter_help():
1142 def journal_filter_help():
1143 return _(textwrap.dedent('''
1143 return _(textwrap.dedent('''
1144 Example filter terms:
1144 Example filter terms:
1145 repository:vcs
1145 repository:vcs
1146 username:marcin
1146 username:marcin
1147 action:*push*
1147 action:*push*
1148 ip:127.0.0.1
1148 ip:127.0.0.1
1149 date:20120101
1149 date:20120101
1150 date:[20120101100000 TO 20120102]
1150 date:[20120101100000 TO 20120102]
1151
1151
1152 Generate wildcards using '*' character:
1152 Generate wildcards using '*' character:
1153 "repositroy:vcs*" - search everything starting with 'vcs'
1153 "repositroy:vcs*" - search everything starting with 'vcs'
1154 "repository:*vcs*" - search for repository containing 'vcs'
1154 "repository:*vcs*" - search for repository containing 'vcs'
1155
1155
1156 Optional AND / OR operators in queries
1156 Optional AND / OR operators in queries
1157 "repository:vcs OR repository:test"
1157 "repository:vcs OR repository:test"
1158 "username:test AND repository:test*"
1158 "username:test AND repository:test*"
1159 '''))
1159 '''))
1160
1161
1162 def not_mapped_error(repo_name):
1163 flash(_('%s repository is not mapped to db perhaps'
1164 ' it was created or renamed from the filesystem'
1165 ' please run the application again'
1166 ' in order to rescan repositories') % repo_name, category='error')
General Comments 0
You need to be logged in to leave comments. Login now