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