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