##// END OF EJS Templates
fixed issue with escaping < and > in changeset commits
marcink -
r2111:122f15a8 beta
parent child Browse files
Show More
@@ -1,583 +1,584 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7
7
8 1.3.4 (**2012-XX-XX**)
8 1.3.4 (**2012-XX-XX**)
9 ----------------------
9 ----------------------
10
10
11 :status: in-progress
11 :status: in-progress
12 :branch: beta
12 :branch: beta
13
13
14 news
14 news
15 ++++
15 ++++
16
16
17 - Whoosh logging is now controlled by the .ini files logging setup
17 - Whoosh logging is now controlled by the .ini files logging setup
18 - added clone-url into edit form on /settings page
18 - added clone-url into edit form on /settings page
19 - added help text into repo add/edit forms
19 - added help text into repo add/edit forms
20 - created rcextensions module with additional mappings (ref #322) and
20 - created rcextensions module with additional mappings (ref #322) and
21 post push/pull/create repo hooks callbacks
21 post push/pull/create repo hooks callbacks
22
22
23 fixes
23 fixes
24 +++++
24 +++++
25
25
26 - fixed #390 cache invalidation problems on repos inside group
26 - fixed #390 cache invalidation problems on repos inside group
27 - fixed #385 clone by ID url was loosing proxy prefix in URL
27 - fixed #385 clone by ID url was loosing proxy prefix in URL
28 - fixed some unicode problems with waitress
28 - fixed some unicode problems with waitress
29 - fixed issue with escaping < and > in changeset commits
29
30
30 1.3.3 (**2012-03-02**)
31 1.3.3 (**2012-03-02**)
31 ----------------------
32 ----------------------
32
33
33 news
34 news
34 ++++
35 ++++
35
36
36
37
37 fixes
38 fixes
38 +++++
39 +++++
39
40
40 - fixed some python2.5 compatibility issues
41 - fixed some python2.5 compatibility issues
41 - fixed issues with removed repos was accidentally added as groups, after
42 - fixed issues with removed repos was accidentally added as groups, after
42 full rescan of paths
43 full rescan of paths
43 - fixes #376 Cannot edit user (using container auth)
44 - fixes #376 Cannot edit user (using container auth)
44 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
45 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
45 configuration
46 configuration
46 - fixed initial sorting of repos inside repo group
47 - fixed initial sorting of repos inside repo group
47 - fixes issue when user tried to resubmit same permission into user/user_groups
48 - fixes issue when user tried to resubmit same permission into user/user_groups
48 - bumped beaker version that fixes #375 leap error bug
49 - bumped beaker version that fixes #375 leap error bug
49 - fixed raw_changeset for git. It was generated with hg patch headers
50 - fixed raw_changeset for git. It was generated with hg patch headers
50 - fixed vcs issue with last_changeset for filenodes
51 - fixed vcs issue with last_changeset for filenodes
51 - fixed missing commit after hook delete
52 - fixed missing commit after hook delete
52 - fixed #372 issues with git operation detection that caused a security issue
53 - fixed #372 issues with git operation detection that caused a security issue
53 for git repos
54 for git repos
54
55
55 1.3.2 (**2012-02-28**)
56 1.3.2 (**2012-02-28**)
56 ----------------------
57 ----------------------
57
58
58 news
59 news
59 ++++
60 ++++
60
61
61
62
62 fixes
63 fixes
63 +++++
64 +++++
64
65
65 - fixed git protocol issues with repos-groups
66 - fixed git protocol issues with repos-groups
66 - fixed git remote repos validator that prevented from cloning remote git repos
67 - fixed git remote repos validator that prevented from cloning remote git repos
67 - fixes #370 ending slashes fixes for repo and groups
68 - fixes #370 ending slashes fixes for repo and groups
68 - fixes #368 improved git-protocol detection to handle other clients
69 - fixes #368 improved git-protocol detection to handle other clients
69 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
70 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
70 Moved To Root
71 Moved To Root
71 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
72 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
72 - fixed #373 missing cascade drop on user_group_to_perm table
73 - fixed #373 missing cascade drop on user_group_to_perm table
73
74
74 1.3.1 (**2012-02-27**)
75 1.3.1 (**2012-02-27**)
75 ----------------------
76 ----------------------
76
77
77 news
78 news
78 ++++
79 ++++
79
80
80
81
81 fixes
82 fixes
82 +++++
83 +++++
83
84
84 - redirection loop occurs when remember-me wasn't checked during login
85 - redirection loop occurs when remember-me wasn't checked during login
85 - fixes issues with git blob history generation
86 - fixes issues with git blob history generation
86 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
87 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
87
88
88 1.3.0 (**2012-02-26**)
89 1.3.0 (**2012-02-26**)
89 ----------------------
90 ----------------------
90
91
91 news
92 news
92 ++++
93 ++++
93
94
94 - code review, inspired by github code-comments
95 - code review, inspired by github code-comments
95 - #215 rst and markdown README files support
96 - #215 rst and markdown README files support
96 - #252 Container-based and proxy pass-through authentication support
97 - #252 Container-based and proxy pass-through authentication support
97 - #44 branch browser. Filtering of changelog by branches
98 - #44 branch browser. Filtering of changelog by branches
98 - mercurial bookmarks support
99 - mercurial bookmarks support
99 - new hover top menu, optimized to add maximum size for important views
100 - new hover top menu, optimized to add maximum size for important views
100 - configurable clone url template with possibility to specify protocol like
101 - configurable clone url template with possibility to specify protocol like
101 ssh:// or http:// and also manually alter other parts of clone_url.
102 ssh:// or http:// and also manually alter other parts of clone_url.
102 - enabled largefiles extension by default
103 - enabled largefiles extension by default
103 - optimized summary file pages and saved a lot of unused space in them
104 - optimized summary file pages and saved a lot of unused space in them
104 - #239 option to manually mark repository as fork
105 - #239 option to manually mark repository as fork
105 - #320 mapping of commit authors to RhodeCode users
106 - #320 mapping of commit authors to RhodeCode users
106 - #304 hashes are displayed using monospace font
107 - #304 hashes are displayed using monospace font
107 - diff configuration, toggle white lines and context lines
108 - diff configuration, toggle white lines and context lines
108 - #307 configurable diffs, whitespace toggle, increasing context lines
109 - #307 configurable diffs, whitespace toggle, increasing context lines
109 - sorting on branches, tags and bookmarks using YUI datatable
110 - sorting on branches, tags and bookmarks using YUI datatable
110 - improved file filter on files page
111 - improved file filter on files page
111 - implements #330 api method for listing nodes ar particular revision
112 - implements #330 api method for listing nodes ar particular revision
112 - #73 added linking issues in commit messages to chosen issue tracker url
113 - #73 added linking issues in commit messages to chosen issue tracker url
113 based on user defined regular expression
114 based on user defined regular expression
114 - added linking of changesets in commit messages
115 - added linking of changesets in commit messages
115 - new compact changelog with expandable commit messages
116 - new compact changelog with expandable commit messages
116 - firstname and lastname are optional in user creation
117 - firstname and lastname are optional in user creation
117 - #348 added post-create repository hook
118 - #348 added post-create repository hook
118 - #212 global encoding settings is now configurable from .ini files
119 - #212 global encoding settings is now configurable from .ini files
119 - #227 added repository groups permissions
120 - #227 added repository groups permissions
120 - markdown gets codehilite extensions
121 - markdown gets codehilite extensions
121 - new API methods, delete_repositories, grante/revoke permissions for groups
122 - new API methods, delete_repositories, grante/revoke permissions for groups
122 and repos
123 and repos
123
124
124
125
125 fixes
126 fixes
126 +++++
127 +++++
127
128
128 - rewrote dbsession management for atomic operations, and better error handling
129 - rewrote dbsession management for atomic operations, and better error handling
129 - fixed sorting of repo tables
130 - fixed sorting of repo tables
130 - #326 escape of special html entities in diffs
131 - #326 escape of special html entities in diffs
131 - normalized user_name => username in api attributes
132 - normalized user_name => username in api attributes
132 - fixes #298 ldap created users with mixed case emails created conflicts
133 - fixes #298 ldap created users with mixed case emails created conflicts
133 on saving a form
134 on saving a form
134 - fixes issue when owner of a repo couldn't revoke permissions for users
135 - fixes issue when owner of a repo couldn't revoke permissions for users
135 and groups
136 and groups
136 - fixes #271 rare JSON serialization problem with statistics
137 - fixes #271 rare JSON serialization problem with statistics
137 - fixes #337 missing validation check for conflicting names of a group with a
138 - fixes #337 missing validation check for conflicting names of a group with a
138 repositories group
139 repositories group
139 - #340 fixed session problem for mysql and celery tasks
140 - #340 fixed session problem for mysql and celery tasks
140 - fixed #331 RhodeCode mangles repository names if the a repository group
141 - fixed #331 RhodeCode mangles repository names if the a repository group
141 contains the "full path" to the repositories
142 contains the "full path" to the repositories
142 - #355 RhodeCode doesn't store encrypted LDAP passwords
143 - #355 RhodeCode doesn't store encrypted LDAP passwords
143
144
144 1.2.5 (**2012-01-28**)
145 1.2.5 (**2012-01-28**)
145 ----------------------
146 ----------------------
146
147
147 news
148 news
148 ++++
149 ++++
149
150
150 fixes
151 fixes
151 +++++
152 +++++
152
153
153 - #340 Celery complains about MySQL server gone away, added session cleanup
154 - #340 Celery complains about MySQL server gone away, added session cleanup
154 for celery tasks
155 for celery tasks
155 - #341 "scanning for repositories in None" log message during Rescan was missing
156 - #341 "scanning for repositories in None" log message during Rescan was missing
156 a parameter
157 a parameter
157 - fixed creating archives with subrepos. Some hooks were triggered during that
158 - fixed creating archives with subrepos. Some hooks were triggered during that
158 operation leading to crash.
159 operation leading to crash.
159 - fixed missing email in account page.
160 - fixed missing email in account page.
160 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
161 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
161 forking on windows impossible
162 forking on windows impossible
162
163
163 1.2.4 (**2012-01-19**)
164 1.2.4 (**2012-01-19**)
164 ----------------------
165 ----------------------
165
166
166 news
167 news
167 ++++
168 ++++
168
169
169 - RhodeCode is bundled with mercurial series 2.0.X by default, with
170 - RhodeCode is bundled with mercurial series 2.0.X by default, with
170 full support to largefiles extension. Enabled by default in new installations
171 full support to largefiles extension. Enabled by default in new installations
171 - #329 Ability to Add/Remove Groups to/from a Repository via AP
172 - #329 Ability to Add/Remove Groups to/from a Repository via AP
172 - added requires.txt file with requirements
173 - added requires.txt file with requirements
173
174
174 fixes
175 fixes
175 +++++
176 +++++
176
177
177 - fixes db session issues with celery when emailing admins
178 - fixes db session issues with celery when emailing admins
178 - #331 RhodeCode mangles repository names if the a repository group
179 - #331 RhodeCode mangles repository names if the a repository group
179 contains the "full path" to the repositories
180 contains the "full path" to the repositories
180 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
181 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
181 - DB session cleanup after hg protocol operations, fixes issues with
182 - DB session cleanup after hg protocol operations, fixes issues with
182 `mysql has gone away` errors
183 `mysql has gone away` errors
183 - #333 doc fixes for get_repo api function
184 - #333 doc fixes for get_repo api function
184 - #271 rare JSON serialization problem with statistics enabled
185 - #271 rare JSON serialization problem with statistics enabled
185 - #337 Fixes issues with validation of repository name conflicting with
186 - #337 Fixes issues with validation of repository name conflicting with
186 a group name. A proper message is now displayed.
187 a group name. A proper message is now displayed.
187 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
188 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
188 doesn't work
189 doesn't work
189 - #316 fixes issues with web description in hgrc files
190 - #316 fixes issues with web description in hgrc files
190
191
191 1.2.3 (**2011-11-02**)
192 1.2.3 (**2011-11-02**)
192 ----------------------
193 ----------------------
193
194
194 news
195 news
195 ++++
196 ++++
196
197
197 - added option to manage repos group for non admin users
198 - added option to manage repos group for non admin users
198 - added following API methods for get_users, create_user, get_users_groups,
199 - added following API methods for get_users, create_user, get_users_groups,
199 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
200 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
200 get_repo, create_repo, add_user_to_repo
201 get_repo, create_repo, add_user_to_repo
201 - implements #237 added password confirmation for my account
202 - implements #237 added password confirmation for my account
202 and admin edit user.
203 and admin edit user.
203 - implements #291 email notification for global events are now sent to all
204 - implements #291 email notification for global events are now sent to all
204 administrator users, and global config email.
205 administrator users, and global config email.
205
206
206 fixes
207 fixes
207 +++++
208 +++++
208
209
209 - added option for passing auth method for smtp mailer
210 - added option for passing auth method for smtp mailer
210 - #276 issue with adding a single user with id>10 to usergroups
211 - #276 issue with adding a single user with id>10 to usergroups
211 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
212 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
212 - #288 fixes managing of repos in a group for non admin user
213 - #288 fixes managing of repos in a group for non admin user
213
214
214 1.2.2 (**2011-10-17**)
215 1.2.2 (**2011-10-17**)
215 ----------------------
216 ----------------------
216
217
217 news
218 news
218 ++++
219 ++++
219
220
220 - #226 repo groups are available by path instead of numerical id
221 - #226 repo groups are available by path instead of numerical id
221
222
222 fixes
223 fixes
223 +++++
224 +++++
224
225
225 - #259 Groups with the same name but with different parent group
226 - #259 Groups with the same name but with different parent group
226 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
227 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
227 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
228 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
228 - #265 ldap save fails sometimes on converting attributes to booleans,
229 - #265 ldap save fails sometimes on converting attributes to booleans,
229 added getter and setter into model that will prevent from this on db model level
230 added getter and setter into model that will prevent from this on db model level
230 - fixed problems with timestamps issues #251 and #213
231 - fixed problems with timestamps issues #251 and #213
231 - fixes #266 RhodeCode allows to create repo with the same name and in
232 - fixes #266 RhodeCode allows to create repo with the same name and in
232 the same parent as group
233 the same parent as group
233 - fixes #245 Rescan of the repositories on Windows
234 - fixes #245 Rescan of the repositories on Windows
234 - fixes #248 cannot edit repos inside a group on windows
235 - fixes #248 cannot edit repos inside a group on windows
235 - fixes #219 forking problems on windows
236 - fixes #219 forking problems on windows
236
237
237 1.2.1 (**2011-10-08**)
238 1.2.1 (**2011-10-08**)
238 ----------------------
239 ----------------------
239
240
240 news
241 news
241 ++++
242 ++++
242
243
243
244
244 fixes
245 fixes
245 +++++
246 +++++
246
247
247 - fixed problems with basic auth and push problems
248 - fixed problems with basic auth and push problems
248 - gui fixes
249 - gui fixes
249 - fixed logger
250 - fixed logger
250
251
251 1.2.0 (**2011-10-07**)
252 1.2.0 (**2011-10-07**)
252 ----------------------
253 ----------------------
253
254
254 news
255 news
255 ++++
256 ++++
256
257
257 - implemented #47 repository groups
258 - implemented #47 repository groups
258 - implemented #89 Can setup google analytics code from settings menu
259 - implemented #89 Can setup google analytics code from settings menu
259 - implemented #91 added nicer looking archive urls with more download options
260 - implemented #91 added nicer looking archive urls with more download options
260 like tags, branches
261 like tags, branches
261 - implemented #44 into file browsing, and added follow branch option
262 - implemented #44 into file browsing, and added follow branch option
262 - implemented #84 downloads can be enabled/disabled for each repository
263 - implemented #84 downloads can be enabled/disabled for each repository
263 - anonymous repository can be cloned without having to pass default:default
264 - anonymous repository can be cloned without having to pass default:default
264 into clone url
265 into clone url
265 - fixed #90 whoosh indexer can index chooses repositories passed in command
266 - fixed #90 whoosh indexer can index chooses repositories passed in command
266 line
267 line
267 - extended journal with day aggregates and paging
268 - extended journal with day aggregates and paging
268 - implemented #107 source code lines highlight ranges
269 - implemented #107 source code lines highlight ranges
269 - implemented #93 customizable changelog on combined revision ranges -
270 - implemented #93 customizable changelog on combined revision ranges -
270 equivalent of githubs compare view
271 equivalent of githubs compare view
271 - implemented #108 extended and more powerful LDAP configuration
272 - implemented #108 extended and more powerful LDAP configuration
272 - implemented #56 users groups
273 - implemented #56 users groups
273 - major code rewrites optimized codes for speed and memory usage
274 - major code rewrites optimized codes for speed and memory usage
274 - raw and diff downloads are now in git format
275 - raw and diff downloads are now in git format
275 - setup command checks for write access to given path
276 - setup command checks for write access to given path
276 - fixed many issues with international characters and unicode. It uses utf8
277 - fixed many issues with international characters and unicode. It uses utf8
277 decode with replace to provide less errors even with non utf8 encoded strings
278 decode with replace to provide less errors even with non utf8 encoded strings
278 - #125 added API KEY access to feeds
279 - #125 added API KEY access to feeds
279 - #109 Repository can be created from external Mercurial link (aka. remote
280 - #109 Repository can be created from external Mercurial link (aka. remote
280 repository, and manually updated (via pull) from admin panel
281 repository, and manually updated (via pull) from admin panel
281 - beta git support - push/pull server + basic view for git repos
282 - beta git support - push/pull server + basic view for git repos
282 - added followers page and forks page
283 - added followers page and forks page
283 - server side file creation (with binary file upload interface)
284 - server side file creation (with binary file upload interface)
284 and edition with commits powered by codemirror
285 and edition with commits powered by codemirror
285 - #111 file browser file finder, quick lookup files on whole file tree
286 - #111 file browser file finder, quick lookup files on whole file tree
286 - added quick login sliding menu into main page
287 - added quick login sliding menu into main page
287 - changelog uses lazy loading of affected files details, in some scenarios
288 - changelog uses lazy loading of affected files details, in some scenarios
288 this can improve speed of changelog page dramatically especially for
289 this can improve speed of changelog page dramatically especially for
289 larger repositories.
290 larger repositories.
290 - implements #214 added support for downloading subrepos in download menu.
291 - implements #214 added support for downloading subrepos in download menu.
291 - Added basic API for direct operations on rhodecode via JSON
292 - Added basic API for direct operations on rhodecode via JSON
292 - Implemented advanced hook management
293 - Implemented advanced hook management
293
294
294 fixes
295 fixes
295 +++++
296 +++++
296
297
297 - fixed file browser bug, when switching into given form revision the url was
298 - fixed file browser bug, when switching into given form revision the url was
298 not changing
299 not changing
299 - fixed propagation to error controller on simplehg and simplegit middlewares
300 - fixed propagation to error controller on simplehg and simplegit middlewares
300 - fixed error when trying to make a download on empty repository
301 - fixed error when trying to make a download on empty repository
301 - fixed problem with '[' chars in commit messages in journal
302 - fixed problem with '[' chars in commit messages in journal
302 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
303 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
303 - journal fork fixes
304 - journal fork fixes
304 - removed issue with space inside renamed repository after deletion
305 - removed issue with space inside renamed repository after deletion
305 - fixed strange issue on formencode imports
306 - fixed strange issue on formencode imports
306 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
307 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
307 - #150 fixes for errors on repositories mapped in db but corrupted in
308 - #150 fixes for errors on repositories mapped in db but corrupted in
308 filesystem
309 filesystem
309 - fixed problem with ascendant characters in realm #181
310 - fixed problem with ascendant characters in realm #181
310 - fixed problem with sqlite file based database connection pool
311 - fixed problem with sqlite file based database connection pool
311 - whoosh indexer and code stats share the same dynamic extensions map
312 - whoosh indexer and code stats share the same dynamic extensions map
312 - fixes #188 - relationship delete of repo_to_perm entry on user removal
313 - fixes #188 - relationship delete of repo_to_perm entry on user removal
313 - fixes issue #189 Trending source files shows "show more" when no more exist
314 - fixes issue #189 Trending source files shows "show more" when no more exist
314 - fixes issue #197 Relative paths for pidlocks
315 - fixes issue #197 Relative paths for pidlocks
315 - fixes issue #198 password will require only 3 chars now for login form
316 - fixes issue #198 password will require only 3 chars now for login form
316 - fixes issue #199 wrong redirection for non admin users after creating a repository
317 - fixes issue #199 wrong redirection for non admin users after creating a repository
317 - fixes issues #202, bad db constraint made impossible to attach same group
318 - fixes issues #202, bad db constraint made impossible to attach same group
318 more than one time. Affects only mysql/postgres
319 more than one time. Affects only mysql/postgres
319 - fixes #218 os.kill patch for windows was missing sig param
320 - fixes #218 os.kill patch for windows was missing sig param
320 - improved rendering of dag (they are not trimmed anymore when number of
321 - improved rendering of dag (they are not trimmed anymore when number of
321 heads exceeds 5)
322 heads exceeds 5)
322
323
323 1.1.8 (**2011-04-12**)
324 1.1.8 (**2011-04-12**)
324 ----------------------
325 ----------------------
325
326
326 news
327 news
327 ++++
328 ++++
328
329
329 - improved windows support
330 - improved windows support
330
331
331 fixes
332 fixes
332 +++++
333 +++++
333
334
334 - fixed #140 freeze of python dateutil library, since new version is python2.x
335 - fixed #140 freeze of python dateutil library, since new version is python2.x
335 incompatible
336 incompatible
336 - setup-app will check for write permission in given path
337 - setup-app will check for write permission in given path
337 - cleaned up license info issue #149
338 - cleaned up license info issue #149
338 - fixes for issues #137,#116 and problems with unicode and accented characters.
339 - fixes for issues #137,#116 and problems with unicode and accented characters.
339 - fixes crashes on gravatar, when passed in email as unicode
340 - fixes crashes on gravatar, when passed in email as unicode
340 - fixed tooltip flickering problems
341 - fixed tooltip flickering problems
341 - fixed came_from redirection on windows
342 - fixed came_from redirection on windows
342 - fixed logging modules, and sql formatters
343 - fixed logging modules, and sql formatters
343 - windows fixes for os.kill issue #133
344 - windows fixes for os.kill issue #133
344 - fixes path splitting for windows issues #148
345 - fixes path splitting for windows issues #148
345 - fixed issue #143 wrong import on migration to 1.1.X
346 - fixed issue #143 wrong import on migration to 1.1.X
346 - fixed problems with displaying binary files, thanks to Thomas Waldmann
347 - fixed problems with displaying binary files, thanks to Thomas Waldmann
347 - removed name from archive files since it's breaking ui for long repo names
348 - removed name from archive files since it's breaking ui for long repo names
348 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
349 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
349 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
350 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
350 Thomas Waldmann
351 Thomas Waldmann
351 - fixed issue #166 summary pager was skipping 10 revisions on second page
352 - fixed issue #166 summary pager was skipping 10 revisions on second page
352
353
353
354
354 1.1.7 (**2011-03-23**)
355 1.1.7 (**2011-03-23**)
355 ----------------------
356 ----------------------
356
357
357 news
358 news
358 ++++
359 ++++
359
360
360 fixes
361 fixes
361 +++++
362 +++++
362
363
363 - fixed (again) #136 installation support for FreeBSD
364 - fixed (again) #136 installation support for FreeBSD
364
365
365
366
366 1.1.6 (**2011-03-21**)
367 1.1.6 (**2011-03-21**)
367 ----------------------
368 ----------------------
368
369
369 news
370 news
370 ++++
371 ++++
371
372
372 fixes
373 fixes
373 +++++
374 +++++
374
375
375 - fixed #136 installation support for FreeBSD
376 - fixed #136 installation support for FreeBSD
376 - RhodeCode will check for python version during installation
377 - RhodeCode will check for python version during installation
377
378
378 1.1.5 (**2011-03-17**)
379 1.1.5 (**2011-03-17**)
379 ----------------------
380 ----------------------
380
381
381 news
382 news
382 ++++
383 ++++
383
384
384 - basic windows support, by exchanging pybcrypt into sha256 for windows only
385 - basic windows support, by exchanging pybcrypt into sha256 for windows only
385 highly inspired by idea of mantis406
386 highly inspired by idea of mantis406
386
387
387 fixes
388 fixes
388 +++++
389 +++++
389
390
390 - fixed sorting by author in main page
391 - fixed sorting by author in main page
391 - fixed crashes with diffs on binary files
392 - fixed crashes with diffs on binary files
392 - fixed #131 problem with boolean values for LDAP
393 - fixed #131 problem with boolean values for LDAP
393 - fixed #122 mysql problems thanks to striker69
394 - fixed #122 mysql problems thanks to striker69
394 - fixed problem with errors on calling raw/raw_files/annotate functions
395 - fixed problem with errors on calling raw/raw_files/annotate functions
395 with unknown revisions
396 with unknown revisions
396 - fixed returned rawfiles attachment names with international character
397 - fixed returned rawfiles attachment names with international character
397 - cleaned out docs, big thanks to Jason Harris
398 - cleaned out docs, big thanks to Jason Harris
398
399
399 1.1.4 (**2011-02-19**)
400 1.1.4 (**2011-02-19**)
400 ----------------------
401 ----------------------
401
402
402 news
403 news
403 ++++
404 ++++
404
405
405 fixes
406 fixes
406 +++++
407 +++++
407
408
408 - fixed formencode import problem on settings page, that caused server crash
409 - fixed formencode import problem on settings page, that caused server crash
409 when that page was accessed as first after server start
410 when that page was accessed as first after server start
410 - journal fixes
411 - journal fixes
411 - fixed option to access repository just by entering http://server/<repo_name>
412 - fixed option to access repository just by entering http://server/<repo_name>
412
413
413 1.1.3 (**2011-02-16**)
414 1.1.3 (**2011-02-16**)
414 ----------------------
415 ----------------------
415
416
416 news
417 news
417 ++++
418 ++++
418
419
419 - implemented #102 allowing the '.' character in username
420 - implemented #102 allowing the '.' character in username
420 - added option to access repository just by entering http://server/<repo_name>
421 - added option to access repository just by entering http://server/<repo_name>
421 - celery task ignores result for better performance
422 - celery task ignores result for better performance
422
423
423 fixes
424 fixes
424 +++++
425 +++++
425
426
426 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
427 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
427 apollo13 and Johan Walles
428 apollo13 and Johan Walles
428 - small fixes in journal
429 - small fixes in journal
429 - fixed problems with getting setting for celery from .ini files
430 - fixed problems with getting setting for celery from .ini files
430 - registration, password reset and login boxes share the same title as main
431 - registration, password reset and login boxes share the same title as main
431 application now
432 application now
432 - fixed #113: to high permissions to fork repository
433 - fixed #113: to high permissions to fork repository
433 - fixed problem with '[' chars in commit messages in journal
434 - fixed problem with '[' chars in commit messages in journal
434 - removed issue with space inside renamed repository after deletion
435 - removed issue with space inside renamed repository after deletion
435 - db transaction fixes when filesystem repository creation failed
436 - db transaction fixes when filesystem repository creation failed
436 - fixed #106 relation issues on databases different than sqlite
437 - fixed #106 relation issues on databases different than sqlite
437 - fixed static files paths links to use of url() method
438 - fixed static files paths links to use of url() method
438
439
439 1.1.2 (**2011-01-12**)
440 1.1.2 (**2011-01-12**)
440 ----------------------
441 ----------------------
441
442
442 news
443 news
443 ++++
444 ++++
444
445
445
446
446 fixes
447 fixes
447 +++++
448 +++++
448
449
449 - fixes #98 protection against float division of percentage stats
450 - fixes #98 protection against float division of percentage stats
450 - fixed graph bug
451 - fixed graph bug
451 - forced webhelpers version since it was making troubles during installation
452 - forced webhelpers version since it was making troubles during installation
452
453
453 1.1.1 (**2011-01-06**)
454 1.1.1 (**2011-01-06**)
454 ----------------------
455 ----------------------
455
456
456 news
457 news
457 ++++
458 ++++
458
459
459 - added force https option into ini files for easier https usage (no need to
460 - added force https option into ini files for easier https usage (no need to
460 set server headers with this options)
461 set server headers with this options)
461 - small css updates
462 - small css updates
462
463
463 fixes
464 fixes
464 +++++
465 +++++
465
466
466 - fixed #96 redirect loop on files view on repositories without changesets
467 - fixed #96 redirect loop on files view on repositories without changesets
467 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
468 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
468 and server crashed with errors
469 and server crashed with errors
469 - fixed large tooltips problems on main page
470 - fixed large tooltips problems on main page
470 - fixed #92 whoosh indexer is more error proof
471 - fixed #92 whoosh indexer is more error proof
471
472
472 1.1.0 (**2010-12-18**)
473 1.1.0 (**2010-12-18**)
473 ----------------------
474 ----------------------
474
475
475 news
476 news
476 ++++
477 ++++
477
478
478 - rewrite of internals for vcs >=0.1.10
479 - rewrite of internals for vcs >=0.1.10
479 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
480 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
480 with older clients
481 with older clients
481 - anonymous access, authentication via ldap
482 - anonymous access, authentication via ldap
482 - performance upgrade for cached repos list - each repository has its own
483 - performance upgrade for cached repos list - each repository has its own
483 cache that's invalidated when needed.
484 cache that's invalidated when needed.
484 - performance upgrades on repositories with large amount of commits (20K+)
485 - performance upgrades on repositories with large amount of commits (20K+)
485 - main page quick filter for filtering repositories
486 - main page quick filter for filtering repositories
486 - user dashboards with ability to follow chosen repositories actions
487 - user dashboards with ability to follow chosen repositories actions
487 - sends email to admin on new user registration
488 - sends email to admin on new user registration
488 - added cache/statistics reset options into repository settings
489 - added cache/statistics reset options into repository settings
489 - more detailed action logger (based on hooks) with pushed changesets lists
490 - more detailed action logger (based on hooks) with pushed changesets lists
490 and options to disable those hooks from admin panel
491 and options to disable those hooks from admin panel
491 - introduced new enhanced changelog for merges that shows more accurate results
492 - introduced new enhanced changelog for merges that shows more accurate results
492 - new improved and faster code stats (based on pygments lexers mapping tables,
493 - new improved and faster code stats (based on pygments lexers mapping tables,
493 showing up to 10 trending sources for each repository. Additionally stats
494 showing up to 10 trending sources for each repository. Additionally stats
494 can be disabled in repository settings.
495 can be disabled in repository settings.
495 - gui optimizations, fixed application width to 1024px
496 - gui optimizations, fixed application width to 1024px
496 - added cut off (for large files/changesets) limit into config files
497 - added cut off (for large files/changesets) limit into config files
497 - whoosh, celeryd, upgrade moved to paster command
498 - whoosh, celeryd, upgrade moved to paster command
498 - other than sqlite database backends can be used
499 - other than sqlite database backends can be used
499
500
500 fixes
501 fixes
501 +++++
502 +++++
502
503
503 - fixes #61 forked repo was showing only after cache expired
504 - fixes #61 forked repo was showing only after cache expired
504 - fixes #76 no confirmation on user deletes
505 - fixes #76 no confirmation on user deletes
505 - fixes #66 Name field misspelled
506 - fixes #66 Name field misspelled
506 - fixes #72 block user removal when he owns repositories
507 - fixes #72 block user removal when he owns repositories
507 - fixes #69 added password confirmation fields
508 - fixes #69 added password confirmation fields
508 - fixes #87 RhodeCode crashes occasionally on updating repository owner
509 - fixes #87 RhodeCode crashes occasionally on updating repository owner
509 - fixes #82 broken annotations on files with more than 1 blank line at the end
510 - fixes #82 broken annotations on files with more than 1 blank line at the end
510 - a lot of fixes and tweaks for file browser
511 - a lot of fixes and tweaks for file browser
511 - fixed detached session issues
512 - fixed detached session issues
512 - fixed when user had no repos he would see all repos listed in my account
513 - fixed when user had no repos he would see all repos listed in my account
513 - fixed ui() instance bug when global hgrc settings was loaded for server
514 - fixed ui() instance bug when global hgrc settings was loaded for server
514 instance and all hgrc options were merged with our db ui() object
515 instance and all hgrc options were merged with our db ui() object
515 - numerous small bugfixes
516 - numerous small bugfixes
516
517
517 (special thanks for TkSoh for detailed feedback)
518 (special thanks for TkSoh for detailed feedback)
518
519
519
520
520 1.0.2 (**2010-11-12**)
521 1.0.2 (**2010-11-12**)
521 ----------------------
522 ----------------------
522
523
523 news
524 news
524 ++++
525 ++++
525
526
526 - tested under python2.7
527 - tested under python2.7
527 - bumped sqlalchemy and celery versions
528 - bumped sqlalchemy and celery versions
528
529
529 fixes
530 fixes
530 +++++
531 +++++
531
532
532 - fixed #59 missing graph.js
533 - fixed #59 missing graph.js
533 - fixed repo_size crash when repository had broken symlinks
534 - fixed repo_size crash when repository had broken symlinks
534 - fixed python2.5 crashes.
535 - fixed python2.5 crashes.
535
536
536
537
537 1.0.1 (**2010-11-10**)
538 1.0.1 (**2010-11-10**)
538 ----------------------
539 ----------------------
539
540
540 news
541 news
541 ++++
542 ++++
542
543
543 - small css updated
544 - small css updated
544
545
545 fixes
546 fixes
546 +++++
547 +++++
547
548
548 - fixed #53 python2.5 incompatible enumerate calls
549 - fixed #53 python2.5 incompatible enumerate calls
549 - fixed #52 disable mercurial extension for web
550 - fixed #52 disable mercurial extension for web
550 - fixed #51 deleting repositories don't delete it's dependent objects
551 - fixed #51 deleting repositories don't delete it's dependent objects
551
552
552
553
553 1.0.0 (**2010-11-02**)
554 1.0.0 (**2010-11-02**)
554 ----------------------
555 ----------------------
555
556
556 - security bugfix simplehg wasn't checking for permissions on commands
557 - security bugfix simplehg wasn't checking for permissions on commands
557 other than pull or push.
558 other than pull or push.
558 - fixed doubled messages after push or pull in admin journal
559 - fixed doubled messages after push or pull in admin journal
559 - templating and css corrections, fixed repo switcher on chrome, updated titles
560 - templating and css corrections, fixed repo switcher on chrome, updated titles
560 - admin menu accessible from options menu on repository view
561 - admin menu accessible from options menu on repository view
561 - permissions cached queries
562 - permissions cached queries
562
563
563 1.0.0rc4 (**2010-10-12**)
564 1.0.0rc4 (**2010-10-12**)
564 --------------------------
565 --------------------------
565
566
566 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
567 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
567 - removed cache_manager settings from sqlalchemy meta
568 - removed cache_manager settings from sqlalchemy meta
568 - added sqlalchemy cache settings to ini files
569 - added sqlalchemy cache settings to ini files
569 - validated password length and added second try of failure on paster setup-app
570 - validated password length and added second try of failure on paster setup-app
570 - fixed setup database destroy prompt even when there was no db
571 - fixed setup database destroy prompt even when there was no db
571
572
572
573
573 1.0.0rc3 (**2010-10-11**)
574 1.0.0rc3 (**2010-10-11**)
574 -------------------------
575 -------------------------
575
576
576 - fixed i18n during installation.
577 - fixed i18n during installation.
577
578
578 1.0.0rc2 (**2010-10-11**)
579 1.0.0rc2 (**2010-10-11**)
579 -------------------------
580 -------------------------
580
581
581 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
582 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
582 occure. After vcs is fixed it'll be put back again.
583 occure. After vcs is fixed it'll be put back again.
583 - templating/css rewrites, optimized css. No newline at end of file
584 - templating/css rewrites, optimized css.
@@ -1,915 +1,926 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
12
13 from datetime import datetime
13 from datetime import datetime
14 from pygments.formatters.html import HtmlFormatter
14 from pygments.formatters.html import HtmlFormatter
15 from pygments import highlight as code_highlight
15 from pygments import highlight as code_highlight
16 from pylons import url, request, config
16 from pylons import url, request, config
17 from pylons.i18n.translation import _, ungettext
17 from pylons.i18n.translation import _, ungettext
18 from hashlib import md5
18 from hashlib import md5
19
19
20 from webhelpers.html import literal, HTML, escape
20 from webhelpers.html import literal, HTML, escape
21 from webhelpers.html.tools import *
21 from webhelpers.html.tools import *
22 from webhelpers.html.builder import make_tag
22 from webhelpers.html.builder import make_tag
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
26 submit, text, password, textarea, title, ul, xml_declaration, radio
26 submit, text, password, textarea, title, ul, xml_declaration, radio
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
29 from webhelpers.number import format_byte_size, format_bit_size
29 from webhelpers.number import format_byte_size, format_bit_size
30 from webhelpers.pylonslib import Flash as _Flash
30 from webhelpers.pylonslib import Flash as _Flash
31 from webhelpers.pylonslib.secure_form import secure_form
31 from webhelpers.pylonslib.secure_form import secure_form
32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
34 replace_whitespace, urlify, truncate, wrap_paragraphs
34 replace_whitespace, urlify, truncate, wrap_paragraphs
35 from webhelpers.date import time_ago_in_words
35 from webhelpers.date import time_ago_in_words
36 from webhelpers.paginate import Page
36 from webhelpers.paginate import Page
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
39
39
40 from rhodecode.lib.annotate import annotate_highlight
40 from rhodecode.lib.annotate import annotate_highlight
41 from rhodecode.lib.utils import repo_name_slug
41 from rhodecode.lib.utils import repo_name_slug
42 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
42 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
43 get_changeset_safe
43 get_changeset_safe
44 from rhodecode.lib.markup_renderer import MarkupRenderer
44 from rhodecode.lib.markup_renderer import MarkupRenderer
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
49 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
50 """
50 """
51 Reset button
51 Reset button
52 """
52 """
53 _set_input_attrs(attrs, type, name, value)
53 _set_input_attrs(attrs, type, name, value)
54 _set_id_attr(attrs, id, name)
54 _set_id_attr(attrs, id, name)
55 convert_boolean_attrs(attrs, ["disabled"])
55 convert_boolean_attrs(attrs, ["disabled"])
56 return HTML.input(**attrs)
56 return HTML.input(**attrs)
57
57
58 reset = _reset
58 reset = _reset
59 safeid = _make_safe_id_component
59 safeid = _make_safe_id_component
60
60
61
61
62 def FID(raw_id, path):
62 def FID(raw_id, path):
63 """
63 """
64 Creates a uniqe ID for filenode based on it's hash of path and revision
64 Creates a uniqe ID for filenode based on it's hash of path and revision
65 it's safe to use in urls
65 it's safe to use in urls
66
66
67 :param raw_id:
67 :param raw_id:
68 :param path:
68 :param path:
69 """
69 """
70
70
71 return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
71 return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
72
72
73
73
74 def get_token():
74 def get_token():
75 """Return the current authentication token, creating one if one doesn't
75 """Return the current authentication token, creating one if one doesn't
76 already exist.
76 already exist.
77 """
77 """
78 token_key = "_authentication_token"
78 token_key = "_authentication_token"
79 from pylons import session
79 from pylons import session
80 if not token_key in session:
80 if not token_key in session:
81 try:
81 try:
82 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
82 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
83 except AttributeError: # Python < 2.4
83 except AttributeError: # Python < 2.4
84 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
84 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
85 session[token_key] = token
85 session[token_key] = token
86 if hasattr(session, 'save'):
86 if hasattr(session, 'save'):
87 session.save()
87 session.save()
88 return session[token_key]
88 return session[token_key]
89
89
90 class _GetError(object):
90 class _GetError(object):
91 """Get error from form_errors, and represent it as span wrapped error
91 """Get error from form_errors, and represent it as span wrapped error
92 message
92 message
93
93
94 :param field_name: field to fetch errors for
94 :param field_name: field to fetch errors for
95 :param form_errors: form errors dict
95 :param form_errors: form errors dict
96 """
96 """
97
97
98 def __call__(self, field_name, form_errors):
98 def __call__(self, field_name, form_errors):
99 tmpl = """<span class="error_msg">%s</span>"""
99 tmpl = """<span class="error_msg">%s</span>"""
100 if form_errors and form_errors.has_key(field_name):
100 if form_errors and form_errors.has_key(field_name):
101 return literal(tmpl % form_errors.get(field_name))
101 return literal(tmpl % form_errors.get(field_name))
102
102
103 get_error = _GetError()
103 get_error = _GetError()
104
104
105 class _ToolTip(object):
105 class _ToolTip(object):
106
106
107 def __call__(self, tooltip_title, trim_at=50):
107 def __call__(self, tooltip_title, trim_at=50):
108 """Special function just to wrap our text into nice formatted
108 """Special function just to wrap our text into nice formatted
109 autowrapped text
109 autowrapped text
110
110
111 :param tooltip_title:
111 :param tooltip_title:
112 """
112 """
113 return escape(tooltip_title)
113 return escape(tooltip_title)
114 tooltip = _ToolTip()
114 tooltip = _ToolTip()
115
115
116 class _FilesBreadCrumbs(object):
116 class _FilesBreadCrumbs(object):
117
117
118 def __call__(self, repo_name, rev, paths):
118 def __call__(self, repo_name, rev, paths):
119 if isinstance(paths, str):
119 if isinstance(paths, str):
120 paths = safe_unicode(paths)
120 paths = safe_unicode(paths)
121 url_l = [link_to(repo_name, url('files_home',
121 url_l = [link_to(repo_name, url('files_home',
122 repo_name=repo_name,
122 repo_name=repo_name,
123 revision=rev, f_path=''))]
123 revision=rev, f_path=''))]
124 paths_l = paths.split('/')
124 paths_l = paths.split('/')
125 for cnt, p in enumerate(paths_l):
125 for cnt, p in enumerate(paths_l):
126 if p != '':
126 if p != '':
127 url_l.append(link_to(p,
127 url_l.append(link_to(p,
128 url('files_home',
128 url('files_home',
129 repo_name=repo_name,
129 repo_name=repo_name,
130 revision=rev,
130 revision=rev,
131 f_path='/'.join(paths_l[:cnt + 1])
131 f_path='/'.join(paths_l[:cnt + 1])
132 )
132 )
133 )
133 )
134 )
134 )
135
135
136 return literal('/'.join(url_l))
136 return literal('/'.join(url_l))
137
137
138 files_breadcrumbs = _FilesBreadCrumbs()
138 files_breadcrumbs = _FilesBreadCrumbs()
139
139
140 class CodeHtmlFormatter(HtmlFormatter):
140 class CodeHtmlFormatter(HtmlFormatter):
141 """My code Html Formatter for source codes
141 """My code Html Formatter for source codes
142 """
142 """
143
143
144 def wrap(self, source, outfile):
144 def wrap(self, source, outfile):
145 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
145 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
146
146
147 def _wrap_code(self, source):
147 def _wrap_code(self, source):
148 for cnt, it in enumerate(source):
148 for cnt, it in enumerate(source):
149 i, t = it
149 i, t = it
150 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
150 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
151 yield i, t
151 yield i, t
152
152
153 def _wrap_tablelinenos(self, inner):
153 def _wrap_tablelinenos(self, inner):
154 dummyoutfile = StringIO.StringIO()
154 dummyoutfile = StringIO.StringIO()
155 lncount = 0
155 lncount = 0
156 for t, line in inner:
156 for t, line in inner:
157 if t:
157 if t:
158 lncount += 1
158 lncount += 1
159 dummyoutfile.write(line)
159 dummyoutfile.write(line)
160
160
161 fl = self.linenostart
161 fl = self.linenostart
162 mw = len(str(lncount + fl - 1))
162 mw = len(str(lncount + fl - 1))
163 sp = self.linenospecial
163 sp = self.linenospecial
164 st = self.linenostep
164 st = self.linenostep
165 la = self.lineanchors
165 la = self.lineanchors
166 aln = self.anchorlinenos
166 aln = self.anchorlinenos
167 nocls = self.noclasses
167 nocls = self.noclasses
168 if sp:
168 if sp:
169 lines = []
169 lines = []
170
170
171 for i in range(fl, fl + lncount):
171 for i in range(fl, fl + lncount):
172 if i % st == 0:
172 if i % st == 0:
173 if i % sp == 0:
173 if i % sp == 0:
174 if aln:
174 if aln:
175 lines.append('<a href="#%s%d" class="special">%*d</a>' %
175 lines.append('<a href="#%s%d" class="special">%*d</a>' %
176 (la, i, mw, i))
176 (la, i, mw, i))
177 else:
177 else:
178 lines.append('<span class="special">%*d</span>' % (mw, i))
178 lines.append('<span class="special">%*d</span>' % (mw, i))
179 else:
179 else:
180 if aln:
180 if aln:
181 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
181 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
182 else:
182 else:
183 lines.append('%*d' % (mw, i))
183 lines.append('%*d' % (mw, i))
184 else:
184 else:
185 lines.append('')
185 lines.append('')
186 ls = '\n'.join(lines)
186 ls = '\n'.join(lines)
187 else:
187 else:
188 lines = []
188 lines = []
189 for i in range(fl, fl + lncount):
189 for i in range(fl, fl + lncount):
190 if i % st == 0:
190 if i % st == 0:
191 if aln:
191 if aln:
192 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
192 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
193 else:
193 else:
194 lines.append('%*d' % (mw, i))
194 lines.append('%*d' % (mw, i))
195 else:
195 else:
196 lines.append('')
196 lines.append('')
197 ls = '\n'.join(lines)
197 ls = '\n'.join(lines)
198
198
199 # in case you wonder about the seemingly redundant <div> here: since the
199 # in case you wonder about the seemingly redundant <div> here: since the
200 # content in the other cell also is wrapped in a div, some browsers in
200 # content in the other cell also is wrapped in a div, some browsers in
201 # some configurations seem to mess up the formatting...
201 # some configurations seem to mess up the formatting...
202 if nocls:
202 if nocls:
203 yield 0, ('<table class="%stable">' % self.cssclass +
203 yield 0, ('<table class="%stable">' % self.cssclass +
204 '<tr><td><div class="linenodiv" '
204 '<tr><td><div class="linenodiv" '
205 'style="background-color: #f0f0f0; padding-right: 10px">'
205 'style="background-color: #f0f0f0; padding-right: 10px">'
206 '<pre style="line-height: 125%">' +
206 '<pre style="line-height: 125%">' +
207 ls + '</pre></div></td><td id="hlcode" class="code">')
207 ls + '</pre></div></td><td id="hlcode" class="code">')
208 else:
208 else:
209 yield 0, ('<table class="%stable">' % self.cssclass +
209 yield 0, ('<table class="%stable">' % self.cssclass +
210 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
210 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
211 ls + '</pre></div></td><td id="hlcode" class="code">')
211 ls + '</pre></div></td><td id="hlcode" class="code">')
212 yield 0, dummyoutfile.getvalue()
212 yield 0, dummyoutfile.getvalue()
213 yield 0, '</td></tr></table>'
213 yield 0, '</td></tr></table>'
214
214
215
215
216 def pygmentize(filenode, **kwargs):
216 def pygmentize(filenode, **kwargs):
217 """pygmentize function using pygments
217 """pygmentize function using pygments
218
218
219 :param filenode:
219 :param filenode:
220 """
220 """
221
221
222 return literal(code_highlight(filenode.content,
222 return literal(code_highlight(filenode.content,
223 filenode.lexer, CodeHtmlFormatter(**kwargs)))
223 filenode.lexer, CodeHtmlFormatter(**kwargs)))
224
224
225
225
226 def pygmentize_annotation(repo_name, filenode, **kwargs):
226 def pygmentize_annotation(repo_name, filenode, **kwargs):
227 """
227 """
228 pygmentize function for annotation
228 pygmentize function for annotation
229
229
230 :param filenode:
230 :param filenode:
231 """
231 """
232
232
233 color_dict = {}
233 color_dict = {}
234
234
235 def gen_color(n=10000):
235 def gen_color(n=10000):
236 """generator for getting n of evenly distributed colors using
236 """generator for getting n of evenly distributed colors using
237 hsv color and golden ratio. It always return same order of colors
237 hsv color and golden ratio. It always return same order of colors
238
238
239 :returns: RGB tuple
239 :returns: RGB tuple
240 """
240 """
241
241
242 def hsv_to_rgb(h, s, v):
242 def hsv_to_rgb(h, s, v):
243 if s == 0.0:
243 if s == 0.0:
244 return v, v, v
244 return v, v, v
245 i = int(h * 6.0) # XXX assume int() truncates!
245 i = int(h * 6.0) # XXX assume int() truncates!
246 f = (h * 6.0) - i
246 f = (h * 6.0) - i
247 p = v * (1.0 - s)
247 p = v * (1.0 - s)
248 q = v * (1.0 - s * f)
248 q = v * (1.0 - s * f)
249 t = v * (1.0 - s * (1.0 - f))
249 t = v * (1.0 - s * (1.0 - f))
250 i = i % 6
250 i = i % 6
251 if i == 0:
251 if i == 0:
252 return v, t, p
252 return v, t, p
253 if i == 1:
253 if i == 1:
254 return q, v, p
254 return q, v, p
255 if i == 2:
255 if i == 2:
256 return p, v, t
256 return p, v, t
257 if i == 3:
257 if i == 3:
258 return p, q, v
258 return p, q, v
259 if i == 4:
259 if i == 4:
260 return t, p, v
260 return t, p, v
261 if i == 5:
261 if i == 5:
262 return v, p, q
262 return v, p, q
263
263
264 golden_ratio = 0.618033988749895
264 golden_ratio = 0.618033988749895
265 h = 0.22717784590367374
265 h = 0.22717784590367374
266
266
267 for _ in xrange(n):
267 for _ in xrange(n):
268 h += golden_ratio
268 h += golden_ratio
269 h %= 1
269 h %= 1
270 HSV_tuple = [h, 0.95, 0.95]
270 HSV_tuple = [h, 0.95, 0.95]
271 RGB_tuple = hsv_to_rgb(*HSV_tuple)
271 RGB_tuple = hsv_to_rgb(*HSV_tuple)
272 yield map(lambda x: str(int(x * 256)), RGB_tuple)
272 yield map(lambda x: str(int(x * 256)), RGB_tuple)
273
273
274 cgenerator = gen_color()
274 cgenerator = gen_color()
275
275
276 def get_color_string(cs):
276 def get_color_string(cs):
277 if cs in color_dict:
277 if cs in color_dict:
278 col = color_dict[cs]
278 col = color_dict[cs]
279 else:
279 else:
280 col = color_dict[cs] = cgenerator.next()
280 col = color_dict[cs] = cgenerator.next()
281 return "color: rgb(%s)! important;" % (', '.join(col))
281 return "color: rgb(%s)! important;" % (', '.join(col))
282
282
283 def url_func(repo_name):
283 def url_func(repo_name):
284
284
285 def _url_func(changeset):
285 def _url_func(changeset):
286 author = changeset.author
286 author = changeset.author
287 date = changeset.date
287 date = changeset.date
288 message = tooltip(changeset.message)
288 message = tooltip(changeset.message)
289
289
290 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
290 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
291 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
291 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
292 "</b> %s<br/></div>")
292 "</b> %s<br/></div>")
293
293
294 tooltip_html = tooltip_html % (author, date, message)
294 tooltip_html = tooltip_html % (author, date, message)
295 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
295 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
296 short_id(changeset.raw_id))
296 short_id(changeset.raw_id))
297 uri = link_to(
297 uri = link_to(
298 lnk_format,
298 lnk_format,
299 url('changeset_home', repo_name=repo_name,
299 url('changeset_home', repo_name=repo_name,
300 revision=changeset.raw_id),
300 revision=changeset.raw_id),
301 style=get_color_string(changeset.raw_id),
301 style=get_color_string(changeset.raw_id),
302 class_='tooltip',
302 class_='tooltip',
303 title=tooltip_html
303 title=tooltip_html
304 )
304 )
305
305
306 uri += '\n'
306 uri += '\n'
307 return uri
307 return uri
308 return _url_func
308 return _url_func
309
309
310 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
310 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
311
311
312
312
313 def is_following_repo(repo_name, user_id):
313 def is_following_repo(repo_name, user_id):
314 from rhodecode.model.scm import ScmModel
314 from rhodecode.model.scm import ScmModel
315 return ScmModel().is_following_repo(repo_name, user_id)
315 return ScmModel().is_following_repo(repo_name, user_id)
316
316
317 flash = _Flash()
317 flash = _Flash()
318
318
319 #==============================================================================
319 #==============================================================================
320 # SCM FILTERS available via h.
320 # SCM FILTERS available via h.
321 #==============================================================================
321 #==============================================================================
322 from rhodecode.lib.vcs.utils import author_name, author_email
322 from rhodecode.lib.vcs.utils import author_name, author_email
323 from rhodecode.lib.utils2 import credentials_filter, age as _age
323 from rhodecode.lib.utils2 import credentials_filter, age as _age
324 from rhodecode.model.db import User
324 from rhodecode.model.db import User
325
325
326 age = lambda x: _age(x)
326 age = lambda x: _age(x)
327 capitalize = lambda x: x.capitalize()
327 capitalize = lambda x: x.capitalize()
328 email = author_email
328 email = author_email
329 short_id = lambda x: x[:12]
329 short_id = lambda x: x[:12]
330 hide_credentials = lambda x: ''.join(credentials_filter(x))
330 hide_credentials = lambda x: ''.join(credentials_filter(x))
331
331
332
332
333 def is_git(repository):
333 def is_git(repository):
334 if hasattr(repository, 'alias'):
334 if hasattr(repository, 'alias'):
335 _type = repository.alias
335 _type = repository.alias
336 elif hasattr(repository, 'repo_type'):
336 elif hasattr(repository, 'repo_type'):
337 _type = repository.repo_type
337 _type = repository.repo_type
338 else:
338 else:
339 _type = repository
339 _type = repository
340 return _type == 'git'
340 return _type == 'git'
341
341
342
342
343 def is_hg(repository):
343 def is_hg(repository):
344 if hasattr(repository, 'alias'):
344 if hasattr(repository, 'alias'):
345 _type = repository.alias
345 _type = repository.alias
346 elif hasattr(repository, 'repo_type'):
346 elif hasattr(repository, 'repo_type'):
347 _type = repository.repo_type
347 _type = repository.repo_type
348 else:
348 else:
349 _type = repository
349 _type = repository
350 return _type == 'hg'
350 return _type == 'hg'
351
351
352
352
353 def email_or_none(author):
353 def email_or_none(author):
354 _email = email(author)
354 _email = email(author)
355 if _email != '':
355 if _email != '':
356 return _email
356 return _email
357
357
358 # See if it contains a username we can get an email from
358 # See if it contains a username we can get an email from
359 user = User.get_by_username(author_name(author), case_insensitive=True,
359 user = User.get_by_username(author_name(author), case_insensitive=True,
360 cache=True)
360 cache=True)
361 if user is not None:
361 if user is not None:
362 return user.email
362 return user.email
363
363
364 # No valid email, not a valid user in the system, none!
364 # No valid email, not a valid user in the system, none!
365 return None
365 return None
366
366
367
367
368 def person(author):
368 def person(author):
369 # attr to return from fetched user
369 # attr to return from fetched user
370 person_getter = lambda usr: usr.username
370 person_getter = lambda usr: usr.username
371
371
372 # Valid email in the attribute passed, see if they're in the system
372 # Valid email in the attribute passed, see if they're in the system
373 _email = email(author)
373 _email = email(author)
374 if _email != '':
374 if _email != '':
375 user = User.get_by_email(_email, case_insensitive=True, cache=True)
375 user = User.get_by_email(_email, case_insensitive=True, cache=True)
376 if user is not None:
376 if user is not None:
377 return person_getter(user)
377 return person_getter(user)
378 return _email
378 return _email
379
379
380 # Maybe it's a username?
380 # Maybe it's a username?
381 _author = author_name(author)
381 _author = author_name(author)
382 user = User.get_by_username(_author, case_insensitive=True,
382 user = User.get_by_username(_author, case_insensitive=True,
383 cache=True)
383 cache=True)
384 if user is not None:
384 if user is not None:
385 return person_getter(user)
385 return person_getter(user)
386
386
387 # Still nothing? Just pass back the author name then
387 # Still nothing? Just pass back the author name then
388 return _author
388 return _author
389
389
390
390
391 def bool2icon(value):
391 def bool2icon(value):
392 """Returns True/False values represented as small html image of true/false
392 """Returns True/False values represented as small html image of true/false
393 icons
393 icons
394
394
395 :param value: bool value
395 :param value: bool value
396 """
396 """
397
397
398 if value is True:
398 if value is True:
399 return HTML.tag('img', src=url("/images/icons/accept.png"),
399 return HTML.tag('img', src=url("/images/icons/accept.png"),
400 alt=_('True'))
400 alt=_('True'))
401
401
402 if value is False:
402 if value is False:
403 return HTML.tag('img', src=url("/images/icons/cancel.png"),
403 return HTML.tag('img', src=url("/images/icons/cancel.png"),
404 alt=_('False'))
404 alt=_('False'))
405
405
406 return value
406 return value
407
407
408
408
409 def action_parser(user_log, feed=False):
409 def action_parser(user_log, feed=False):
410 """
410 """
411 This helper will action_map the specified string action into translated
411 This helper will action_map the specified string action into translated
412 fancy names with icons and links
412 fancy names with icons and links
413
413
414 :param user_log: user log instance
414 :param user_log: user log instance
415 :param feed: use output for feeds (no html and fancy icons)
415 :param feed: use output for feeds (no html and fancy icons)
416 """
416 """
417
417
418 action = user_log.action
418 action = user_log.action
419 action_params = ' '
419 action_params = ' '
420
420
421 x = action.split(':')
421 x = action.split(':')
422
422
423 if len(x) > 1:
423 if len(x) > 1:
424 action, action_params = x
424 action, action_params = x
425
425
426 def get_cs_links():
426 def get_cs_links():
427 revs_limit = 3 # display this amount always
427 revs_limit = 3 # display this amount always
428 revs_top_limit = 50 # show upto this amount of changesets hidden
428 revs_top_limit = 50 # show upto this amount of changesets hidden
429 revs_ids = action_params.split(',')
429 revs_ids = action_params.split(',')
430 deleted = user_log.repository is None
430 deleted = user_log.repository is None
431 if deleted:
431 if deleted:
432 return ','.join(revs_ids)
432 return ','.join(revs_ids)
433
433
434 repo_name = user_log.repository.repo_name
434 repo_name = user_log.repository.repo_name
435
435
436 repo = user_log.repository.scm_instance
436 repo = user_log.repository.scm_instance
437
437
438 message = lambda rev: rev.message
438 message = lambda rev: rev.message
439 lnk = lambda rev, repo_name: (
439 lnk = lambda rev, repo_name: (
440 link_to('r%s:%s' % (rev.revision, rev.short_id),
440 link_to('r%s:%s' % (rev.revision, rev.short_id),
441 url('changeset_home', repo_name=repo_name,
441 url('changeset_home', repo_name=repo_name,
442 revision=rev.raw_id),
442 revision=rev.raw_id),
443 title=tooltip(message(rev)), class_='tooltip')
443 title=tooltip(message(rev)), class_='tooltip')
444 )
444 )
445 # get only max revs_top_limit of changeset for performance/ui reasons
445 # get only max revs_top_limit of changeset for performance/ui reasons
446 revs = [
446 revs = [
447 x for x in repo.get_changesets(revs_ids[0],
447 x for x in repo.get_changesets(revs_ids[0],
448 revs_ids[:revs_top_limit][-1])
448 revs_ids[:revs_top_limit][-1])
449 ]
449 ]
450
450
451 cs_links = []
451 cs_links = []
452 cs_links.append(" " + ', '.join(
452 cs_links.append(" " + ', '.join(
453 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
453 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
454 )
454 )
455 )
455 )
456
456
457 compare_view = (
457 compare_view = (
458 ' <div class="compare_view tooltip" title="%s">'
458 ' <div class="compare_view tooltip" title="%s">'
459 '<a href="%s">%s</a> </div>' % (
459 '<a href="%s">%s</a> </div>' % (
460 _('Show all combined changesets %s->%s') % (
460 _('Show all combined changesets %s->%s') % (
461 revs_ids[0], revs_ids[-1]
461 revs_ids[0], revs_ids[-1]
462 ),
462 ),
463 url('changeset_home', repo_name=repo_name,
463 url('changeset_home', repo_name=repo_name,
464 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
464 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
465 ),
465 ),
466 _('compare view')
466 _('compare view')
467 )
467 )
468 )
468 )
469
469
470 # if we have exactly one more than normally displayed
470 # if we have exactly one more than normally displayed
471 # just display it, takes less space than displaying
471 # just display it, takes less space than displaying
472 # "and 1 more revisions"
472 # "and 1 more revisions"
473 if len(revs_ids) == revs_limit + 1:
473 if len(revs_ids) == revs_limit + 1:
474 rev = revs[revs_limit]
474 rev = revs[revs_limit]
475 cs_links.append(", " + lnk(rev, repo_name))
475 cs_links.append(", " + lnk(rev, repo_name))
476
476
477 # hidden-by-default ones
477 # hidden-by-default ones
478 if len(revs_ids) > revs_limit + 1:
478 if len(revs_ids) > revs_limit + 1:
479 uniq_id = revs_ids[0]
479 uniq_id = revs_ids[0]
480 html_tmpl = (
480 html_tmpl = (
481 '<span> %s <a class="show_more" id="_%s" '
481 '<span> %s <a class="show_more" id="_%s" '
482 'href="#more">%s</a> %s</span>'
482 'href="#more">%s</a> %s</span>'
483 )
483 )
484 if not feed:
484 if not feed:
485 cs_links.append(html_tmpl % (
485 cs_links.append(html_tmpl % (
486 _('and'),
486 _('and'),
487 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
487 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
488 _('revisions')
488 _('revisions')
489 )
489 )
490 )
490 )
491
491
492 if not feed:
492 if not feed:
493 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
493 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
494 else:
494 else:
495 html_tmpl = '<span id="%s"> %s </span>'
495 html_tmpl = '<span id="%s"> %s </span>'
496
496
497 morelinks = ', '.join(
497 morelinks = ', '.join(
498 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
498 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
499 )
499 )
500
500
501 if len(revs_ids) > revs_top_limit:
501 if len(revs_ids) > revs_top_limit:
502 morelinks += ', ...'
502 morelinks += ', ...'
503
503
504 cs_links.append(html_tmpl % (uniq_id, morelinks))
504 cs_links.append(html_tmpl % (uniq_id, morelinks))
505 if len(revs) > 1:
505 if len(revs) > 1:
506 cs_links.append(compare_view)
506 cs_links.append(compare_view)
507 return ''.join(cs_links)
507 return ''.join(cs_links)
508
508
509 def get_fork_name():
509 def get_fork_name():
510 repo_name = action_params
510 repo_name = action_params
511 return _('fork name ') + str(link_to(action_params, url('summary_home',
511 return _('fork name ') + str(link_to(action_params, url('summary_home',
512 repo_name=repo_name,)))
512 repo_name=repo_name,)))
513
513
514 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
514 action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
515 'user_created_repo': (_('[created] repository'), None),
515 'user_created_repo': (_('[created] repository'), None),
516 'user_created_fork': (_('[created] repository as fork'), None),
516 'user_created_fork': (_('[created] repository as fork'), None),
517 'user_forked_repo': (_('[forked] repository'), get_fork_name),
517 'user_forked_repo': (_('[forked] repository'), get_fork_name),
518 'user_updated_repo': (_('[updated] repository'), None),
518 'user_updated_repo': (_('[updated] repository'), None),
519 'admin_deleted_repo': (_('[delete] repository'), None),
519 'admin_deleted_repo': (_('[delete] repository'), None),
520 'admin_created_repo': (_('[created] repository'), None),
520 'admin_created_repo': (_('[created] repository'), None),
521 'admin_forked_repo': (_('[forked] repository'), None),
521 'admin_forked_repo': (_('[forked] repository'), None),
522 'admin_updated_repo': (_('[updated] repository'), None),
522 'admin_updated_repo': (_('[updated] repository'), None),
523 'push': (_('[pushed] into'), get_cs_links),
523 'push': (_('[pushed] into'), get_cs_links),
524 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
524 'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
525 'push_remote': (_('[pulled from remote] into'), get_cs_links),
525 'push_remote': (_('[pulled from remote] into'), get_cs_links),
526 'pull': (_('[pulled] from'), None),
526 'pull': (_('[pulled] from'), None),
527 'started_following_repo': (_('[started following] repository'), None),
527 'started_following_repo': (_('[started following] repository'), None),
528 'stopped_following_repo': (_('[stopped following] repository'), None),
528 'stopped_following_repo': (_('[stopped following] repository'), None),
529 }
529 }
530
530
531 action_str = action_map.get(action, action)
531 action_str = action_map.get(action, action)
532 if feed:
532 if feed:
533 action = action_str[0].replace('[', '').replace(']', '')
533 action = action_str[0].replace('[', '').replace(']', '')
534 else:
534 else:
535 action = action_str[0]\
535 action = action_str[0]\
536 .replace('[', '<span class="journal_highlight">')\
536 .replace('[', '<span class="journal_highlight">')\
537 .replace(']', '</span>')
537 .replace(']', '</span>')
538
538
539 action_params_func = lambda: ""
539 action_params_func = lambda: ""
540
540
541 if callable(action_str[1]):
541 if callable(action_str[1]):
542 action_params_func = action_str[1]
542 action_params_func = action_str[1]
543
543
544 return [literal(action), action_params_func]
544 return [literal(action), action_params_func]
545
545
546
546
547 def action_parser_icon(user_log):
547 def action_parser_icon(user_log):
548 action = user_log.action
548 action = user_log.action
549 action_params = None
549 action_params = None
550 x = action.split(':')
550 x = action.split(':')
551
551
552 if len(x) > 1:
552 if len(x) > 1:
553 action, action_params = x
553 action, action_params = x
554
554
555 tmpl = """<img src="%s%s" alt="%s"/>"""
555 tmpl = """<img src="%s%s" alt="%s"/>"""
556 map = {'user_deleted_repo':'database_delete.png',
556 map = {'user_deleted_repo':'database_delete.png',
557 'user_created_repo':'database_add.png',
557 'user_created_repo':'database_add.png',
558 'user_created_fork':'arrow_divide.png',
558 'user_created_fork':'arrow_divide.png',
559 'user_forked_repo':'arrow_divide.png',
559 'user_forked_repo':'arrow_divide.png',
560 'user_updated_repo':'database_edit.png',
560 'user_updated_repo':'database_edit.png',
561 'admin_deleted_repo':'database_delete.png',
561 'admin_deleted_repo':'database_delete.png',
562 'admin_created_repo':'database_add.png',
562 'admin_created_repo':'database_add.png',
563 'admin_forked_repo':'arrow_divide.png',
563 'admin_forked_repo':'arrow_divide.png',
564 'admin_updated_repo':'database_edit.png',
564 'admin_updated_repo':'database_edit.png',
565 'push':'script_add.png',
565 'push':'script_add.png',
566 'push_local':'script_edit.png',
566 'push_local':'script_edit.png',
567 'push_remote':'connect.png',
567 'push_remote':'connect.png',
568 'pull':'down_16.png',
568 'pull':'down_16.png',
569 'started_following_repo':'heart_add.png',
569 'started_following_repo':'heart_add.png',
570 'stopped_following_repo':'heart_delete.png',
570 'stopped_following_repo':'heart_delete.png',
571 }
571 }
572 return literal(tmpl % ((url('/images/icons/')),
572 return literal(tmpl % ((url('/images/icons/')),
573 map.get(action, action), action))
573 map.get(action, action), action))
574
574
575
575
576 #==============================================================================
576 #==============================================================================
577 # PERMS
577 # PERMS
578 #==============================================================================
578 #==============================================================================
579 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
579 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
580 HasRepoPermissionAny, HasRepoPermissionAll
580 HasRepoPermissionAny, HasRepoPermissionAll
581
581
582
582
583 #==============================================================================
583 #==============================================================================
584 # GRAVATAR URL
584 # GRAVATAR URL
585 #==============================================================================
585 #==============================================================================
586
586
587 def gravatar_url(email_address, size=30):
587 def gravatar_url(email_address, size=30):
588 if (not str2bool(config['app_conf'].get('use_gravatar')) or
588 if (not str2bool(config['app_conf'].get('use_gravatar')) or
589 not email_address or email_address == 'anonymous@rhodecode.org'):
589 not email_address or email_address == 'anonymous@rhodecode.org'):
590 f = lambda a, l: min(l, key=lambda x: abs(x - a))
590 f = lambda a, l: min(l, key=lambda x: abs(x - a))
591 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
591 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
592
592
593 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
593 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
594 default = 'identicon'
594 default = 'identicon'
595 baseurl_nossl = "http://www.gravatar.com/avatar/"
595 baseurl_nossl = "http://www.gravatar.com/avatar/"
596 baseurl_ssl = "https://secure.gravatar.com/avatar/"
596 baseurl_ssl = "https://secure.gravatar.com/avatar/"
597 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
597 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
598
598
599 if isinstance(email_address, unicode):
599 if isinstance(email_address, unicode):
600 #hashlib crashes on unicode items
600 #hashlib crashes on unicode items
601 email_address = safe_str(email_address)
601 email_address = safe_str(email_address)
602 # construct the url
602 # construct the url
603 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
603 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
604 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
604 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
605
605
606 return gravatar_url
606 return gravatar_url
607
607
608
608
609 #==============================================================================
609 #==============================================================================
610 # REPO PAGER, PAGER FOR REPOSITORY
610 # REPO PAGER, PAGER FOR REPOSITORY
611 #==============================================================================
611 #==============================================================================
612 class RepoPage(Page):
612 class RepoPage(Page):
613
613
614 def __init__(self, collection, page=1, items_per_page=20,
614 def __init__(self, collection, page=1, items_per_page=20,
615 item_count=None, url=None, **kwargs):
615 item_count=None, url=None, **kwargs):
616
616
617 """Create a "RepoPage" instance. special pager for paging
617 """Create a "RepoPage" instance. special pager for paging
618 repository
618 repository
619 """
619 """
620 self._url_generator = url
620 self._url_generator = url
621
621
622 # Safe the kwargs class-wide so they can be used in the pager() method
622 # Safe the kwargs class-wide so they can be used in the pager() method
623 self.kwargs = kwargs
623 self.kwargs = kwargs
624
624
625 # Save a reference to the collection
625 # Save a reference to the collection
626 self.original_collection = collection
626 self.original_collection = collection
627
627
628 self.collection = collection
628 self.collection = collection
629
629
630 # The self.page is the number of the current page.
630 # The self.page is the number of the current page.
631 # The first page has the number 1!
631 # The first page has the number 1!
632 try:
632 try:
633 self.page = int(page) # make it int() if we get it as a string
633 self.page = int(page) # make it int() if we get it as a string
634 except (ValueError, TypeError):
634 except (ValueError, TypeError):
635 self.page = 1
635 self.page = 1
636
636
637 self.items_per_page = items_per_page
637 self.items_per_page = items_per_page
638
638
639 # Unless the user tells us how many items the collections has
639 # Unless the user tells us how many items the collections has
640 # we calculate that ourselves.
640 # we calculate that ourselves.
641 if item_count is not None:
641 if item_count is not None:
642 self.item_count = item_count
642 self.item_count = item_count
643 else:
643 else:
644 self.item_count = len(self.collection)
644 self.item_count = len(self.collection)
645
645
646 # Compute the number of the first and last available page
646 # Compute the number of the first and last available page
647 if self.item_count > 0:
647 if self.item_count > 0:
648 self.first_page = 1
648 self.first_page = 1
649 self.page_count = int(math.ceil(float(self.item_count) /
649 self.page_count = int(math.ceil(float(self.item_count) /
650 self.items_per_page))
650 self.items_per_page))
651 self.last_page = self.first_page + self.page_count - 1
651 self.last_page = self.first_page + self.page_count - 1
652
652
653 # Make sure that the requested page number is the range of
653 # Make sure that the requested page number is the range of
654 # valid pages
654 # valid pages
655 if self.page > self.last_page:
655 if self.page > self.last_page:
656 self.page = self.last_page
656 self.page = self.last_page
657 elif self.page < self.first_page:
657 elif self.page < self.first_page:
658 self.page = self.first_page
658 self.page = self.first_page
659
659
660 # Note: the number of items on this page can be less than
660 # Note: the number of items on this page can be less than
661 # items_per_page if the last page is not full
661 # items_per_page if the last page is not full
662 self.first_item = max(0, (self.item_count) - (self.page *
662 self.first_item = max(0, (self.item_count) - (self.page *
663 items_per_page))
663 items_per_page))
664 self.last_item = ((self.item_count - 1) - items_per_page *
664 self.last_item = ((self.item_count - 1) - items_per_page *
665 (self.page - 1))
665 (self.page - 1))
666
666
667 self.items = list(self.collection[self.first_item:self.last_item + 1])
667 self.items = list(self.collection[self.first_item:self.last_item + 1])
668
668
669 # Links to previous and next page
669 # Links to previous and next page
670 if self.page > self.first_page:
670 if self.page > self.first_page:
671 self.previous_page = self.page - 1
671 self.previous_page = self.page - 1
672 else:
672 else:
673 self.previous_page = None
673 self.previous_page = None
674
674
675 if self.page < self.last_page:
675 if self.page < self.last_page:
676 self.next_page = self.page + 1
676 self.next_page = self.page + 1
677 else:
677 else:
678 self.next_page = None
678 self.next_page = None
679
679
680 # No items available
680 # No items available
681 else:
681 else:
682 self.first_page = None
682 self.first_page = None
683 self.page_count = 0
683 self.page_count = 0
684 self.last_page = None
684 self.last_page = None
685 self.first_item = None
685 self.first_item = None
686 self.last_item = None
686 self.last_item = None
687 self.previous_page = None
687 self.previous_page = None
688 self.next_page = None
688 self.next_page = None
689 self.items = []
689 self.items = []
690
690
691 # This is a subclass of the 'list' type. Initialise the list now.
691 # This is a subclass of the 'list' type. Initialise the list now.
692 list.__init__(self, reversed(self.items))
692 list.__init__(self, reversed(self.items))
693
693
694
694
695 def changed_tooltip(nodes):
695 def changed_tooltip(nodes):
696 """
696 """
697 Generates a html string for changed nodes in changeset page.
697 Generates a html string for changed nodes in changeset page.
698 It limits the output to 30 entries
698 It limits the output to 30 entries
699
699
700 :param nodes: LazyNodesGenerator
700 :param nodes: LazyNodesGenerator
701 """
701 """
702 if nodes:
702 if nodes:
703 pref = ': <br/> '
703 pref = ': <br/> '
704 suf = ''
704 suf = ''
705 if len(nodes) > 30:
705 if len(nodes) > 30:
706 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
706 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
707 return literal(pref + '<br/> '.join([safe_unicode(x.path)
707 return literal(pref + '<br/> '.join([safe_unicode(x.path)
708 for x in nodes[:30]]) + suf)
708 for x in nodes[:30]]) + suf)
709 else:
709 else:
710 return ': ' + _('No Files')
710 return ': ' + _('No Files')
711
711
712
712
713 def repo_link(groups_and_repos):
713 def repo_link(groups_and_repos):
714 """
714 """
715 Makes a breadcrumbs link to repo within a group
715 Makes a breadcrumbs link to repo within a group
716 joins &raquo; on each group to create a fancy link
716 joins &raquo; on each group to create a fancy link
717
717
718 ex::
718 ex::
719 group >> subgroup >> repo
719 group >> subgroup >> repo
720
720
721 :param groups_and_repos:
721 :param groups_and_repos:
722 """
722 """
723 groups, repo_name = groups_and_repos
723 groups, repo_name = groups_and_repos
724
724
725 if not groups:
725 if not groups:
726 return repo_name
726 return repo_name
727 else:
727 else:
728 def make_link(group):
728 def make_link(group):
729 return link_to(group.name, url('repos_group_home',
729 return link_to(group.name, url('repos_group_home',
730 group_name=group.group_name))
730 group_name=group.group_name))
731 return literal(' &raquo; '.join(map(make_link, groups)) + \
731 return literal(' &raquo; '.join(map(make_link, groups)) + \
732 " &raquo; " + repo_name)
732 " &raquo; " + repo_name)
733
733
734
734
735 def fancy_file_stats(stats):
735 def fancy_file_stats(stats):
736 """
736 """
737 Displays a fancy two colored bar for number of added/deleted
737 Displays a fancy two colored bar for number of added/deleted
738 lines of code on file
738 lines of code on file
739
739
740 :param stats: two element list of added/deleted lines of code
740 :param stats: two element list of added/deleted lines of code
741 """
741 """
742
742
743 a, d, t = stats[0], stats[1], stats[0] + stats[1]
743 a, d, t = stats[0], stats[1], stats[0] + stats[1]
744 width = 100
744 width = 100
745 unit = float(width) / (t or 1)
745 unit = float(width) / (t or 1)
746
746
747 # needs > 9% of width to be visible or 0 to be hidden
747 # needs > 9% of width to be visible or 0 to be hidden
748 a_p = max(9, unit * a) if a > 0 else 0
748 a_p = max(9, unit * a) if a > 0 else 0
749 d_p = max(9, unit * d) if d > 0 else 0
749 d_p = max(9, unit * d) if d > 0 else 0
750 p_sum = a_p + d_p
750 p_sum = a_p + d_p
751
751
752 if p_sum > width:
752 if p_sum > width:
753 #adjust the percentage to be == 100% since we adjusted to 9
753 #adjust the percentage to be == 100% since we adjusted to 9
754 if a_p > d_p:
754 if a_p > d_p:
755 a_p = a_p - (p_sum - width)
755 a_p = a_p - (p_sum - width)
756 else:
756 else:
757 d_p = d_p - (p_sum - width)
757 d_p = d_p - (p_sum - width)
758
758
759 a_v = a if a > 0 else ''
759 a_v = a if a > 0 else ''
760 d_v = d if d > 0 else ''
760 d_v = d if d > 0 else ''
761
761
762 def cgen(l_type):
762 def cgen(l_type):
763 mapping = {'tr': 'top-right-rounded-corner',
763 mapping = {'tr': 'top-right-rounded-corner',
764 'tl': 'top-left-rounded-corner',
764 'tl': 'top-left-rounded-corner',
765 'br': 'bottom-right-rounded-corner',
765 'br': 'bottom-right-rounded-corner',
766 'bl': 'bottom-left-rounded-corner'}
766 'bl': 'bottom-left-rounded-corner'}
767 map_getter = lambda x: mapping[x]
767 map_getter = lambda x: mapping[x]
768
768
769 if l_type == 'a' and d_v:
769 if l_type == 'a' and d_v:
770 #case when added and deleted are present
770 #case when added and deleted are present
771 return ' '.join(map(map_getter, ['tl', 'bl']))
771 return ' '.join(map(map_getter, ['tl', 'bl']))
772
772
773 if l_type == 'a' and not d_v:
773 if l_type == 'a' and not d_v:
774 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
774 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
775
775
776 if l_type == 'd' and a_v:
776 if l_type == 'd' and a_v:
777 return ' '.join(map(map_getter, ['tr', 'br']))
777 return ' '.join(map(map_getter, ['tr', 'br']))
778
778
779 if l_type == 'd' and not a_v:
779 if l_type == 'd' and not a_v:
780 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
780 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
781
781
782 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
782 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
783 cgen('a'), a_p, a_v
783 cgen('a'), a_p, a_v
784 )
784 )
785 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
785 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
786 cgen('d'), d_p, d_v
786 cgen('d'), d_p, d_v
787 )
787 )
788 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
788 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
789
789
790
790
791 def urlify_text(text_):
791 def urlify_text(text_):
792 import re
792 import re
793
793
794 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
794 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
795 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
795 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
796
796
797 def url_func(match_obj):
797 def url_func(match_obj):
798 url_full = match_obj.groups()[0]
798 url_full = match_obj.groups()[0]
799 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
799 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
800
800
801 return literal(url_pat.sub(url_func, text_))
801 return literal(url_pat.sub(url_func, text_))
802
802
803
803
804 def urlify_changesets(text_, repository):
804 def urlify_changesets(text_, repository):
805 """
806 Extract revision ids from changeset and make link from them
807
808 :param text_:
809 :param repository:
810 """
805 import re
811 import re
806 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
812 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
807
813
808 def url_func(match_obj):
814 def url_func(match_obj):
809 rev = match_obj.groups()[0]
815 rev = match_obj.groups()[0]
810 pref = ''
816 pref = ''
811 if match_obj.group().startswith(' '):
817 if match_obj.group().startswith(' '):
812 pref = ' '
818 pref = ' '
813 tmpl = (
819 tmpl = (
814 '%(pref)s<a class="%(cls)s" href="%(url)s">'
820 '%(pref)s<a class="%(cls)s" href="%(url)s">'
815 '%(rev)s'
821 '%(rev)s'
816 '</a>'
822 '</a>'
817 )
823 )
818 return tmpl % {
824 return tmpl % {
819 'pref': pref,
825 'pref': pref,
820 'cls': 'revision-link',
826 'cls': 'revision-link',
821 'url': url('changeset_home', repo_name=repository, revision=rev),
827 'url': url('changeset_home', repo_name=repository, revision=rev),
822 'rev': rev,
828 'rev': rev,
823 }
829 }
824
830
825 newtext = URL_PAT.sub(url_func, text_)
831 newtext = URL_PAT.sub(url_func, text_)
826
832
827 return newtext
833 return newtext
828
834
829
835
830 def urlify_commit(text_, repository=None, link_=None):
836 def urlify_commit(text_, repository=None, link_=None):
831 """
837 """
832 Parses given text message and makes proper links.
838 Parses given text message and makes proper links.
833 issues are linked to given issue-server, and rest is a changeset link
839 issues are linked to given issue-server, and rest is a changeset link
834 if link_ is given, in other case it's a plain text
840 if link_ is given, in other case it's a plain text
835
841
836 :param text_:
842 :param text_:
837 :param repository:
843 :param repository:
838 :param link_: changeset link
844 :param link_: changeset link
839 """
845 """
840 import re
846 import re
841 import traceback
847 import traceback
842
848
843 # urlify changesets
849 def escaper(string):
844 text_ = urlify_changesets(text_, repository)
850 return string.replace('<', '&lt;').replace('>', '&gt;')
845
851
846 def linkify_others(t, l):
852 def linkify_others(t, l):
847 urls = re.compile(r'(\<a.*?\<\/a\>)',)
853 urls = re.compile(r'(\<a.*?\<\/a\>)',)
848 links = []
854 links = []
849 for e in urls.split(t):
855 for e in urls.split(t):
850 if not urls.match(e):
856 if not urls.match(e):
851 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
857 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
852 else:
858 else:
853 links.append(e)
859 links.append(e)
854
860
855 return ''.join(links)
861 return ''.join(links)
862
863
864 # urlify changesets - extrac revisions and make link out of them
865 text_ = urlify_changesets(escaper(text_), repository)
866
856 try:
867 try:
857 conf = config['app_conf']
868 conf = config['app_conf']
858
869
859 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
870 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
860
871
861 if URL_PAT:
872 if URL_PAT:
862 ISSUE_SERVER_LNK = conf.get('issue_server_link')
873 ISSUE_SERVER_LNK = conf.get('issue_server_link')
863 ISSUE_PREFIX = conf.get('issue_prefix')
874 ISSUE_PREFIX = conf.get('issue_prefix')
864
875
865 def url_func(match_obj):
876 def url_func(match_obj):
866 pref = ''
877 pref = ''
867 if match_obj.group().startswith(' '):
878 if match_obj.group().startswith(' '):
868 pref = ' '
879 pref = ' '
869
880
870 issue_id = ''.join(match_obj.groups())
881 issue_id = ''.join(match_obj.groups())
871 tmpl = (
882 tmpl = (
872 '%(pref)s<a class="%(cls)s" href="%(url)s">'
883 '%(pref)s<a class="%(cls)s" href="%(url)s">'
873 '%(issue-prefix)s%(id-repr)s'
884 '%(issue-prefix)s%(id-repr)s'
874 '</a>'
885 '</a>'
875 )
886 )
876 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
887 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
877 if repository:
888 if repository:
878 url = url.replace('{repo}', repository)
889 url = url.replace('{repo}', repository)
879
890
880 return tmpl % {
891 return tmpl % {
881 'pref': pref,
892 'pref': pref,
882 'cls': 'issue-tracker-link',
893 'cls': 'issue-tracker-link',
883 'url': url,
894 'url': url,
884 'id-repr': issue_id,
895 'id-repr': issue_id,
885 'issue-prefix': ISSUE_PREFIX,
896 'issue-prefix': ISSUE_PREFIX,
886 'serv': ISSUE_SERVER_LNK,
897 'serv': ISSUE_SERVER_LNK,
887 }
898 }
888
899
889 newtext = URL_PAT.sub(url_func, text_)
900 newtext = URL_PAT.sub(url_func, text_)
890
901
891 if link_:
902 if link_:
892 # wrap not links into final link => link_
903 # wrap not links into final link => link_
893 newtext = linkify_others(newtext, link_)
904 newtext = linkify_others(newtext, link_)
894
905
895 return literal(newtext)
906 return literal(newtext)
896 except:
907 except:
897 log.error(traceback.format_exc())
908 log.error(traceback.format_exc())
898 pass
909 pass
899
910
900 return text_
911 return text_
901
912
902
913
903 def rst(source):
914 def rst(source):
904 return literal('<div class="rst-block">%s</div>' %
915 return literal('<div class="rst-block">%s</div>' %
905 MarkupRenderer.rst(source))
916 MarkupRenderer.rst(source))
906
917
907
918
908 def rst_w_mentions(source):
919 def rst_w_mentions(source):
909 """
920 """
910 Wrapped rst renderer with @mention highlighting
921 Wrapped rst renderer with @mention highlighting
911
922
912 :param source:
923 :param source:
913 """
924 """
914 return literal('<div class="rst-block">%s</div>' %
925 return literal('<div class="rst-block">%s</div>' %
915 MarkupRenderer.rst_with_mentions(source))
926 MarkupRenderer.rst_with_mentions(source))
General Comments 0
You need to be logged in to leave comments. Login now