##// END OF EJS Templates
p2.5 fixes
marcink -
r2068:f664d3b5 beta
parent child Browse files
Show More
@@ -1,548 +1,550 b''
1 1 .. _changelog:
2 2
3 3 Changelog
4 4 =========
5 5
6 6
7 7 1.3.3 (**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
17 17 fixes
18 18 +++++
19 19
20 - fixed some python2.5 compatibility issues
21
20 22 1.3.2 (**2012-02-28**)
21 23 ----------------------
22 24
23 25 news
24 26 ++++
25 27
26 28
27 29 fixes
28 30 +++++
29 31
30 32 - fixed git protocol issues with repos-groups
31 33 - fixed git remote repos validator that prevented from cloning remote git repos
32 34 - fixes #370 ending slashes fixes for repo and groups
33 35 - fixes #368 improved git-protocol detection to handle other clients
34 36 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
35 37 Moved To Root
36 38 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
37 39 - fixed #373 missing cascade drop on user_group_to_perm table
38 40
39 41 1.3.1 (**2012-02-27**)
40 42 ----------------------
41 43
42 44 news
43 45 ++++
44 46
45 47
46 48 fixes
47 49 +++++
48 50
49 51 - redirection loop occurs when remember-me wasn't checked during login
50 52 - fixes issues with git blob history generation
51 53 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
52 54
53 55 1.3.0 (**2012-02-26**)
54 56 ----------------------
55 57
56 58 news
57 59 ++++
58 60
59 61 - code review, inspired by github code-comments
60 62 - #215 rst and markdown README files support
61 63 - #252 Container-based and proxy pass-through authentication support
62 64 - #44 branch browser. Filtering of changelog by branches
63 65 - mercurial bookmarks support
64 66 - new hover top menu, optimized to add maximum size for important views
65 67 - configurable clone url template with possibility to specify protocol like
66 68 ssh:// or http:// and also manually alter other parts of clone_url.
67 69 - enabled largefiles extension by default
68 70 - optimized summary file pages and saved a lot of unused space in them
69 71 - #239 option to manually mark repository as fork
70 72 - #320 mapping of commit authors to RhodeCode users
71 73 - #304 hashes are displayed using monospace font
72 74 - diff configuration, toggle white lines and context lines
73 75 - #307 configurable diffs, whitespace toggle, increasing context lines
74 76 - sorting on branches, tags and bookmarks using YUI datatable
75 77 - improved file filter on files page
76 78 - implements #330 api method for listing nodes ar particular revision
77 79 - #73 added linking issues in commit messages to chosen issue tracker url
78 80 based on user defined regular expression
79 81 - added linking of changesets in commit messages
80 82 - new compact changelog with expandable commit messages
81 83 - firstname and lastname are optional in user creation
82 84 - #348 added post-create repository hook
83 85 - #212 global encoding settings is now configurable from .ini files
84 86 - #227 added repository groups permissions
85 87 - markdown gets codehilite extensions
86 88 - new API methods, delete_repositories, grante/revoke permissions for groups
87 89 and repos
88 90
89 91
90 92 fixes
91 93 +++++
92 94
93 95 - rewrote dbsession management for atomic operations, and better error handling
94 96 - fixed sorting of repo tables
95 97 - #326 escape of special html entities in diffs
96 98 - normalized user_name => username in api attributes
97 99 - fixes #298 ldap created users with mixed case emails created conflicts
98 100 on saving a form
99 101 - fixes issue when owner of a repo couldn't revoke permissions for users
100 102 and groups
101 103 - fixes #271 rare JSON serialization problem with statistics
102 104 - fixes #337 missing validation check for conflicting names of a group with a
103 105 repositories group
104 106 - #340 fixed session problem for mysql and celery tasks
105 107 - fixed #331 RhodeCode mangles repository names if the a repository group
106 108 contains the "full path" to the repositories
107 109 - #355 RhodeCode doesn't store encrypted LDAP passwords
108 110
109 111 1.2.5 (**2012-01-28**)
110 112 ----------------------
111 113
112 114 news
113 115 ++++
114 116
115 117 fixes
116 118 +++++
117 119
118 120 - #340 Celery complains about MySQL server gone away, added session cleanup
119 121 for celery tasks
120 122 - #341 "scanning for repositories in None" log message during Rescan was missing
121 123 a parameter
122 124 - fixed creating archives with subrepos. Some hooks were triggered during that
123 125 operation leading to crash.
124 126 - fixed missing email in account page.
125 127 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
126 128 forking on windows impossible
127 129
128 130 1.2.4 (**2012-01-19**)
129 131 ----------------------
130 132
131 133 news
132 134 ++++
133 135
134 136 - RhodeCode is bundled with mercurial series 2.0.X by default, with
135 137 full support to largefiles extension. Enabled by default in new installations
136 138 - #329 Ability to Add/Remove Groups to/from a Repository via AP
137 139 - added requires.txt file with requirements
138 140
139 141 fixes
140 142 +++++
141 143
142 144 - fixes db session issues with celery when emailing admins
143 145 - #331 RhodeCode mangles repository names if the a repository group
144 146 contains the "full path" to the repositories
145 147 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
146 148 - DB session cleanup after hg protocol operations, fixes issues with
147 149 `mysql has gone away` errors
148 150 - #333 doc fixes for get_repo api function
149 151 - #271 rare JSON serialization problem with statistics enabled
150 152 - #337 Fixes issues with validation of repository name conflicting with
151 153 a group name. A proper message is now displayed.
152 154 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
153 155 doesn't work
154 156 - #316 fixes issues with web description in hgrc files
155 157
156 158 1.2.3 (**2011-11-02**)
157 159 ----------------------
158 160
159 161 news
160 162 ++++
161 163
162 164 - added option to manage repos group for non admin users
163 165 - added following API methods for get_users, create_user, get_users_groups,
164 166 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
165 167 get_repo, create_repo, add_user_to_repo
166 168 - implements #237 added password confirmation for my account
167 169 and admin edit user.
168 170 - implements #291 email notification for global events are now sent to all
169 171 administrator users, and global config email.
170 172
171 173 fixes
172 174 +++++
173 175
174 176 - added option for passing auth method for smtp mailer
175 177 - #276 issue with adding a single user with id>10 to usergroups
176 178 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
177 179 - #288 fixes managing of repos in a group for non admin user
178 180
179 181 1.2.2 (**2011-10-17**)
180 182 ----------------------
181 183
182 184 news
183 185 ++++
184 186
185 187 - #226 repo groups are available by path instead of numerical id
186 188
187 189 fixes
188 190 +++++
189 191
190 192 - #259 Groups with the same name but with different parent group
191 193 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
192 194 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
193 195 - #265 ldap save fails sometimes on converting attributes to booleans,
194 196 added getter and setter into model that will prevent from this on db model level
195 197 - fixed problems with timestamps issues #251 and #213
196 198 - fixes #266 RhodeCode allows to create repo with the same name and in
197 199 the same parent as group
198 200 - fixes #245 Rescan of the repositories on Windows
199 201 - fixes #248 cannot edit repos inside a group on windows
200 202 - fixes #219 forking problems on windows
201 203
202 204 1.2.1 (**2011-10-08**)
203 205 ----------------------
204 206
205 207 news
206 208 ++++
207 209
208 210
209 211 fixes
210 212 +++++
211 213
212 214 - fixed problems with basic auth and push problems
213 215 - gui fixes
214 216 - fixed logger
215 217
216 218 1.2.0 (**2011-10-07**)
217 219 ----------------------
218 220
219 221 news
220 222 ++++
221 223
222 224 - implemented #47 repository groups
223 225 - implemented #89 Can setup google analytics code from settings menu
224 226 - implemented #91 added nicer looking archive urls with more download options
225 227 like tags, branches
226 228 - implemented #44 into file browsing, and added follow branch option
227 229 - implemented #84 downloads can be enabled/disabled for each repository
228 230 - anonymous repository can be cloned without having to pass default:default
229 231 into clone url
230 232 - fixed #90 whoosh indexer can index chooses repositories passed in command
231 233 line
232 234 - extended journal with day aggregates and paging
233 235 - implemented #107 source code lines highlight ranges
234 236 - implemented #93 customizable changelog on combined revision ranges -
235 237 equivalent of githubs compare view
236 238 - implemented #108 extended and more powerful LDAP configuration
237 239 - implemented #56 users groups
238 240 - major code rewrites optimized codes for speed and memory usage
239 241 - raw and diff downloads are now in git format
240 242 - setup command checks for write access to given path
241 243 - fixed many issues with international characters and unicode. It uses utf8
242 244 decode with replace to provide less errors even with non utf8 encoded strings
243 245 - #125 added API KEY access to feeds
244 246 - #109 Repository can be created from external Mercurial link (aka. remote
245 247 repository, and manually updated (via pull) from admin panel
246 248 - beta git support - push/pull server + basic view for git repos
247 249 - added followers page and forks page
248 250 - server side file creation (with binary file upload interface)
249 251 and edition with commits powered by codemirror
250 252 - #111 file browser file finder, quick lookup files on whole file tree
251 253 - added quick login sliding menu into main page
252 254 - changelog uses lazy loading of affected files details, in some scenarios
253 255 this can improve speed of changelog page dramatically especially for
254 256 larger repositories.
255 257 - implements #214 added support for downloading subrepos in download menu.
256 258 - Added basic API for direct operations on rhodecode via JSON
257 259 - Implemented advanced hook management
258 260
259 261 fixes
260 262 +++++
261 263
262 264 - fixed file browser bug, when switching into given form revision the url was
263 265 not changing
264 266 - fixed propagation to error controller on simplehg and simplegit middlewares
265 267 - fixed error when trying to make a download on empty repository
266 268 - fixed problem with '[' chars in commit messages in journal
267 269 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
268 270 - journal fork fixes
269 271 - removed issue with space inside renamed repository after deletion
270 272 - fixed strange issue on formencode imports
271 273 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
272 274 - #150 fixes for errors on repositories mapped in db but corrupted in
273 275 filesystem
274 276 - fixed problem with ascendant characters in realm #181
275 277 - fixed problem with sqlite file based database connection pool
276 278 - whoosh indexer and code stats share the same dynamic extensions map
277 279 - fixes #188 - relationship delete of repo_to_perm entry on user removal
278 280 - fixes issue #189 Trending source files shows "show more" when no more exist
279 281 - fixes issue #197 Relative paths for pidlocks
280 282 - fixes issue #198 password will require only 3 chars now for login form
281 283 - fixes issue #199 wrong redirection for non admin users after creating a repository
282 284 - fixes issues #202, bad db constraint made impossible to attach same group
283 285 more than one time. Affects only mysql/postgres
284 286 - fixes #218 os.kill patch for windows was missing sig param
285 287 - improved rendering of dag (they are not trimmed anymore when number of
286 288 heads exceeds 5)
287 289
288 290 1.1.8 (**2011-04-12**)
289 291 ----------------------
290 292
291 293 news
292 294 ++++
293 295
294 296 - improved windows support
295 297
296 298 fixes
297 299 +++++
298 300
299 301 - fixed #140 freeze of python dateutil library, since new version is python2.x
300 302 incompatible
301 303 - setup-app will check for write permission in given path
302 304 - cleaned up license info issue #149
303 305 - fixes for issues #137,#116 and problems with unicode and accented characters.
304 306 - fixes crashes on gravatar, when passed in email as unicode
305 307 - fixed tooltip flickering problems
306 308 - fixed came_from redirection on windows
307 309 - fixed logging modules, and sql formatters
308 310 - windows fixes for os.kill issue #133
309 311 - fixes path splitting for windows issues #148
310 312 - fixed issue #143 wrong import on migration to 1.1.X
311 313 - fixed problems with displaying binary files, thanks to Thomas Waldmann
312 314 - removed name from archive files since it's breaking ui for long repo names
313 315 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
314 316 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
315 317 Thomas Waldmann
316 318 - fixed issue #166 summary pager was skipping 10 revisions on second page
317 319
318 320
319 321 1.1.7 (**2011-03-23**)
320 322 ----------------------
321 323
322 324 news
323 325 ++++
324 326
325 327 fixes
326 328 +++++
327 329
328 330 - fixed (again) #136 installation support for FreeBSD
329 331
330 332
331 333 1.1.6 (**2011-03-21**)
332 334 ----------------------
333 335
334 336 news
335 337 ++++
336 338
337 339 fixes
338 340 +++++
339 341
340 342 - fixed #136 installation support for FreeBSD
341 343 - RhodeCode will check for python version during installation
342 344
343 345 1.1.5 (**2011-03-17**)
344 346 ----------------------
345 347
346 348 news
347 349 ++++
348 350
349 351 - basic windows support, by exchanging pybcrypt into sha256 for windows only
350 352 highly inspired by idea of mantis406
351 353
352 354 fixes
353 355 +++++
354 356
355 357 - fixed sorting by author in main page
356 358 - fixed crashes with diffs on binary files
357 359 - fixed #131 problem with boolean values for LDAP
358 360 - fixed #122 mysql problems thanks to striker69
359 361 - fixed problem with errors on calling raw/raw_files/annotate functions
360 362 with unknown revisions
361 363 - fixed returned rawfiles attachment names with international character
362 364 - cleaned out docs, big thanks to Jason Harris
363 365
364 366 1.1.4 (**2011-02-19**)
365 367 ----------------------
366 368
367 369 news
368 370 ++++
369 371
370 372 fixes
371 373 +++++
372 374
373 375 - fixed formencode import problem on settings page, that caused server crash
374 376 when that page was accessed as first after server start
375 377 - journal fixes
376 378 - fixed option to access repository just by entering http://server/<repo_name>
377 379
378 380 1.1.3 (**2011-02-16**)
379 381 ----------------------
380 382
381 383 news
382 384 ++++
383 385
384 386 - implemented #102 allowing the '.' character in username
385 387 - added option to access repository just by entering http://server/<repo_name>
386 388 - celery task ignores result for better performance
387 389
388 390 fixes
389 391 +++++
390 392
391 393 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
392 394 apollo13 and Johan Walles
393 395 - small fixes in journal
394 396 - fixed problems with getting setting for celery from .ini files
395 397 - registration, password reset and login boxes share the same title as main
396 398 application now
397 399 - fixed #113: to high permissions to fork repository
398 400 - fixed problem with '[' chars in commit messages in journal
399 401 - removed issue with space inside renamed repository after deletion
400 402 - db transaction fixes when filesystem repository creation failed
401 403 - fixed #106 relation issues on databases different than sqlite
402 404 - fixed static files paths links to use of url() method
403 405
404 406 1.1.2 (**2011-01-12**)
405 407 ----------------------
406 408
407 409 news
408 410 ++++
409 411
410 412
411 413 fixes
412 414 +++++
413 415
414 416 - fixes #98 protection against float division of percentage stats
415 417 - fixed graph bug
416 418 - forced webhelpers version since it was making troubles during installation
417 419
418 420 1.1.1 (**2011-01-06**)
419 421 ----------------------
420 422
421 423 news
422 424 ++++
423 425
424 426 - added force https option into ini files for easier https usage (no need to
425 427 set server headers with this options)
426 428 - small css updates
427 429
428 430 fixes
429 431 +++++
430 432
431 433 - fixed #96 redirect loop on files view on repositories without changesets
432 434 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
433 435 and server crashed with errors
434 436 - fixed large tooltips problems on main page
435 437 - fixed #92 whoosh indexer is more error proof
436 438
437 439 1.1.0 (**2010-12-18**)
438 440 ----------------------
439 441
440 442 news
441 443 ++++
442 444
443 445 - rewrite of internals for vcs >=0.1.10
444 446 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
445 447 with older clients
446 448 - anonymous access, authentication via ldap
447 449 - performance upgrade for cached repos list - each repository has its own
448 450 cache that's invalidated when needed.
449 451 - performance upgrades on repositories with large amount of commits (20K+)
450 452 - main page quick filter for filtering repositories
451 453 - user dashboards with ability to follow chosen repositories actions
452 454 - sends email to admin on new user registration
453 455 - added cache/statistics reset options into repository settings
454 456 - more detailed action logger (based on hooks) with pushed changesets lists
455 457 and options to disable those hooks from admin panel
456 458 - introduced new enhanced changelog for merges that shows more accurate results
457 459 - new improved and faster code stats (based on pygments lexers mapping tables,
458 460 showing up to 10 trending sources for each repository. Additionally stats
459 461 can be disabled in repository settings.
460 462 - gui optimizations, fixed application width to 1024px
461 463 - added cut off (for large files/changesets) limit into config files
462 464 - whoosh, celeryd, upgrade moved to paster command
463 465 - other than sqlite database backends can be used
464 466
465 467 fixes
466 468 +++++
467 469
468 470 - fixes #61 forked repo was showing only after cache expired
469 471 - fixes #76 no confirmation on user deletes
470 472 - fixes #66 Name field misspelled
471 473 - fixes #72 block user removal when he owns repositories
472 474 - fixes #69 added password confirmation fields
473 475 - fixes #87 RhodeCode crashes occasionally on updating repository owner
474 476 - fixes #82 broken annotations on files with more than 1 blank line at the end
475 477 - a lot of fixes and tweaks for file browser
476 478 - fixed detached session issues
477 479 - fixed when user had no repos he would see all repos listed in my account
478 480 - fixed ui() instance bug when global hgrc settings was loaded for server
479 481 instance and all hgrc options were merged with our db ui() object
480 482 - numerous small bugfixes
481 483
482 484 (special thanks for TkSoh for detailed feedback)
483 485
484 486
485 487 1.0.2 (**2010-11-12**)
486 488 ----------------------
487 489
488 490 news
489 491 ++++
490 492
491 493 - tested under python2.7
492 494 - bumped sqlalchemy and celery versions
493 495
494 496 fixes
495 497 +++++
496 498
497 499 - fixed #59 missing graph.js
498 500 - fixed repo_size crash when repository had broken symlinks
499 501 - fixed python2.5 crashes.
500 502
501 503
502 504 1.0.1 (**2010-11-10**)
503 505 ----------------------
504 506
505 507 news
506 508 ++++
507 509
508 510 - small css updated
509 511
510 512 fixes
511 513 +++++
512 514
513 515 - fixed #53 python2.5 incompatible enumerate calls
514 516 - fixed #52 disable mercurial extension for web
515 517 - fixed #51 deleting repositories don't delete it's dependent objects
516 518
517 519
518 520 1.0.0 (**2010-11-02**)
519 521 ----------------------
520 522
521 523 - security bugfix simplehg wasn't checking for permissions on commands
522 524 other than pull or push.
523 525 - fixed doubled messages after push or pull in admin journal
524 526 - templating and css corrections, fixed repo switcher on chrome, updated titles
525 527 - admin menu accessible from options menu on repository view
526 528 - permissions cached queries
527 529
528 530 1.0.0rc4 (**2010-10-12**)
529 531 --------------------------
530 532
531 533 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
532 534 - removed cache_manager settings from sqlalchemy meta
533 535 - added sqlalchemy cache settings to ini files
534 536 - validated password length and added second try of failure on paster setup-app
535 537 - fixed setup database destroy prompt even when there was no db
536 538
537 539
538 540 1.0.0rc3 (**2010-10-11**)
539 541 -------------------------
540 542
541 543 - fixed i18n during installation.
542 544
543 545 1.0.0rc2 (**2010-10-11**)
544 546 -------------------------
545 547
546 548 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
547 549 occure. After vcs is fixed it'll be put back again.
548 550 - templating/css rewrites, optimized css. No newline at end of file
@@ -1,367 +1,367 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changeset
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 changeset controller for pylons showoing changes beetween
7 7 revisions
8 8
9 9 :created_on: Apr 25, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 28 from collections import defaultdict
29 29 from webob.exc import HTTPForbidden
30 30
31 31 from pylons import tmpl_context as c, url, request, response
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from pylons.decorators import jsonify
35 35
36 36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 37 ChangesetDoesNotExistError
38 38 from rhodecode.lib.vcs.nodes import FileNode
39 39
40 40 import rhodecode.lib.helpers as h
41 41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.utils import EmptyChangeset
44 44 from rhodecode.lib.compat import OrderedDict
45 45 from rhodecode.lib import diffs
46 46 from rhodecode.model.db import ChangesetComment
47 47 from rhodecode.model.comment import ChangesetCommentsModel
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.lib.diffs import wrapped_diff
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 def anchor_url(revision, path):
55 55 fid = h.FID(revision, path)
56 return h.url.current(anchor=fid, **request.GET)
56 return h.url.current(anchor=fid, **dict(request.GET))
57 57
58 58
59 59 def get_ignore_ws(fid, GET):
60 60 ig_ws_global = request.GET.get('ignorews')
61 61 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
62 62 if ig_ws:
63 63 try:
64 64 return int(ig_ws[0].split(':')[-1])
65 65 except:
66 66 pass
67 67 return ig_ws_global
68 68
69 69
70 70 def _ignorews_url(fileid=None):
71 71
72 72 params = defaultdict(list)
73 73 lbl = _('show white space')
74 74 ig_ws = get_ignore_ws(fileid, request.GET)
75 75 ln_ctx = get_line_ctx(fileid, request.GET)
76 76 # global option
77 77 if fileid is None:
78 78 if ig_ws is None:
79 79 params['ignorews'] += [1]
80 80 lbl = _('ignore white space')
81 81 ctx_key = 'context'
82 82 ctx_val = ln_ctx
83 83 # per file options
84 84 else:
85 85 if ig_ws is None:
86 86 params[fileid] += ['WS:1']
87 87 lbl = _('ignore white space')
88 88
89 89 ctx_key = fileid
90 90 ctx_val = 'C:%s' % ln_ctx
91 91 # if we have passed in ln_ctx pass it along to our params
92 92 if ln_ctx:
93 93 params[ctx_key] += [ctx_val]
94 94
95 95 params['anchor'] = fileid
96 96 img = h.image('/images/icons/text_strikethrough.png', lbl, class_='icon')
97 97 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
98 98
99 99
100 100 def get_line_ctx(fid, GET):
101 101 ln_ctx_global = request.GET.get('context')
102 102 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
103 103
104 104 if ln_ctx:
105 105 retval = ln_ctx[0].split(':')[-1]
106 106 else:
107 107 retval = ln_ctx_global
108 108
109 109 try:
110 110 return int(retval)
111 111 except:
112 112 return
113 113
114 114
115 115 def _context_url(fileid=None):
116 116 """
117 117 Generates url for context lines
118 118
119 119 :param fileid:
120 120 """
121 121 ig_ws = get_ignore_ws(fileid, request.GET)
122 122 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
123 123
124 124 params = defaultdict(list)
125 125
126 126 # global option
127 127 if fileid is None:
128 128 if ln_ctx > 0:
129 129 params['context'] += [ln_ctx]
130 130
131 131 if ig_ws:
132 132 ig_ws_key = 'ignorews'
133 133 ig_ws_val = 1
134 134
135 135 # per file option
136 136 else:
137 137 params[fileid] += ['C:%s' % ln_ctx]
138 138 ig_ws_key = fileid
139 139 ig_ws_val = 'WS:%s' % 1
140 140
141 141 if ig_ws:
142 142 params[ig_ws_key] += [ig_ws_val]
143 143
144 144 lbl = _('%s line context') % ln_ctx
145 145
146 146 params['anchor'] = fileid
147 147 img = h.image('/images/icons/table_add.png', lbl, class_='icon')
148 148 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
149 149
150 150
151 151 class ChangesetController(BaseRepoController):
152 152
153 153 @LoginRequired()
154 154 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
155 155 'repository.admin')
156 156 def __before__(self):
157 157 super(ChangesetController, self).__before__()
158 158 c.affected_files_cut_off = 60
159 159
160 160 def index(self, revision):
161 161
162 162 c.anchor_url = anchor_url
163 163 c.ignorews_url = _ignorews_url
164 164 c.context_url = _context_url
165 165
166 166 #get ranges of revisions if preset
167 167 rev_range = revision.split('...')[:2]
168 168 enable_comments = True
169 169 try:
170 170 if len(rev_range) == 2:
171 171 enable_comments = False
172 172 rev_start = rev_range[0]
173 173 rev_end = rev_range[1]
174 174 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
175 175 end=rev_end)
176 176 else:
177 177 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
178 178
179 179 c.cs_ranges = list(rev_ranges)
180 180 if not c.cs_ranges:
181 181 raise RepositoryError('Changeset range returned empty result')
182 182
183 183 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
184 184 log.error(traceback.format_exc())
185 185 h.flash(str(e), category='warning')
186 186 return redirect(url('home'))
187 187
188 188 c.changes = OrderedDict()
189 189
190 190 c.lines_added = 0 # count of lines added
191 191 c.lines_deleted = 0 # count of lines removes
192 192
193 193 cumulative_diff = 0
194 194 c.cut_off = False # defines if cut off limit is reached
195 195
196 196 c.comments = []
197 197 c.inline_comments = []
198 198 c.inline_cnt = 0
199 199 # Iterate over ranges (default changeset view is always one changeset)
200 200 for changeset in c.cs_ranges:
201 201 c.comments.extend(ChangesetCommentsModel()\
202 202 .get_comments(c.rhodecode_db_repo.repo_id,
203 203 changeset.raw_id))
204 204 inlines = ChangesetCommentsModel()\
205 205 .get_inline_comments(c.rhodecode_db_repo.repo_id,
206 206 changeset.raw_id)
207 207 c.inline_comments.extend(inlines)
208 208 c.changes[changeset.raw_id] = []
209 209 try:
210 210 changeset_parent = changeset.parents[0]
211 211 except IndexError:
212 212 changeset_parent = None
213 213
214 214 #==================================================================
215 215 # ADDED FILES
216 216 #==================================================================
217 217 for node in changeset.added:
218 218 fid = h.FID(revision, node.path)
219 219 line_context_lcl = get_line_ctx(fid, request.GET)
220 220 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
221 221 lim = self.cut_off_limit
222 222 if cumulative_diff > self.cut_off_limit:
223 223 lim = -1
224 224 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
225 225 filenode_new=node,
226 226 cut_off_limit=lim,
227 227 ignore_whitespace=ign_whitespace_lcl,
228 228 line_context=line_context_lcl,
229 229 enable_comments=enable_comments)
230 230 cumulative_diff += size
231 231 c.lines_added += st[0]
232 232 c.lines_deleted += st[1]
233 233 c.changes[changeset.raw_id].append(('added', node, diff,
234 234 cs1, cs2, st))
235 235
236 236 #==================================================================
237 237 # CHANGED FILES
238 238 #==================================================================
239 239 for node in changeset.changed:
240 240 try:
241 241 filenode_old = changeset_parent.get_node(node.path)
242 242 except ChangesetError:
243 243 log.warning('Unable to fetch parent node for diff')
244 244 filenode_old = FileNode(node.path, '', EmptyChangeset())
245 245
246 246 fid = h.FID(revision, node.path)
247 247 line_context_lcl = get_line_ctx(fid, request.GET)
248 248 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
249 249 lim = self.cut_off_limit
250 250 if cumulative_diff > self.cut_off_limit:
251 251 lim = -1
252 252 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
253 253 filenode_new=node,
254 254 cut_off_limit=lim,
255 255 ignore_whitespace=ign_whitespace_lcl,
256 256 line_context=line_context_lcl,
257 257 enable_comments=enable_comments)
258 258 cumulative_diff += size
259 259 c.lines_added += st[0]
260 260 c.lines_deleted += st[1]
261 261 c.changes[changeset.raw_id].append(('changed', node, diff,
262 262 cs1, cs2, st))
263 263
264 264 #==================================================================
265 265 # REMOVED FILES
266 266 #==================================================================
267 267 for node in changeset.removed:
268 268 c.changes[changeset.raw_id].append(('removed', node, None,
269 269 None, None, (0, 0)))
270 270
271 271 # count inline comments
272 272 for path, lines in c.inline_comments:
273 273 for comments in lines.values():
274 274 c.inline_cnt += len(comments)
275 275
276 276 if len(c.cs_ranges) == 1:
277 277 c.changeset = c.cs_ranges[0]
278 278 c.changes = c.changes[c.changeset.raw_id]
279 279
280 280 return render('changeset/changeset.html')
281 281 else:
282 282 return render('changeset/changeset_range.html')
283 283
284 284 def raw_changeset(self, revision):
285 285
286 286 method = request.GET.get('diff', 'show')
287 287 ignore_whitespace = request.GET.get('ignorews') == '1'
288 288 line_context = request.GET.get('context', 3)
289 289 try:
290 290 c.scm_type = c.rhodecode_repo.alias
291 291 c.changeset = c.rhodecode_repo.get_changeset(revision)
292 292 except RepositoryError:
293 293 log.error(traceback.format_exc())
294 294 return redirect(url('home'))
295 295 else:
296 296 try:
297 297 c.changeset_parent = c.changeset.parents[0]
298 298 except IndexError:
299 299 c.changeset_parent = None
300 300 c.changes = []
301 301
302 302 for node in c.changeset.added:
303 303 filenode_old = FileNode(node.path, '')
304 304 if filenode_old.is_binary or node.is_binary:
305 305 diff = _('binary file') + '\n'
306 306 else:
307 307 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
308 308 ignore_whitespace=ignore_whitespace,
309 309 context=line_context)
310 310 diff = diffs.DiffProcessor(f_gitdiff,
311 311 format='gitdiff').raw_diff()
312 312
313 313 cs1 = None
314 314 cs2 = node.last_changeset.raw_id
315 315 c.changes.append(('added', node, diff, cs1, cs2))
316 316
317 317 for node in c.changeset.changed:
318 318 filenode_old = c.changeset_parent.get_node(node.path)
319 319 if filenode_old.is_binary or node.is_binary:
320 320 diff = _('binary file')
321 321 else:
322 322 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
323 323 ignore_whitespace=ignore_whitespace,
324 324 context=line_context)
325 325 diff = diffs.DiffProcessor(f_gitdiff,
326 326 format='gitdiff').raw_diff()
327 327
328 328 cs1 = filenode_old.last_changeset.raw_id
329 329 cs2 = node.last_changeset.raw_id
330 330 c.changes.append(('changed', node, diff, cs1, cs2))
331 331
332 332 response.content_type = 'text/plain'
333 333
334 334 if method == 'download':
335 335 response.content_disposition = 'attachment; filename=%s.patch' \
336 336 % revision
337 337
338 338 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
339 339 c.changeset.parents])
340 340
341 341 c.diffs = ''
342 342 for x in c.changes:
343 343 c.diffs += x[2]
344 344
345 345 return render('changeset/raw_changeset.html')
346 346
347 347 def comment(self, repo_name, revision):
348 348 ChangesetCommentsModel().create(text=request.POST.get('text'),
349 349 repo_id=c.rhodecode_db_repo.repo_id,
350 350 user_id=c.rhodecode_user.user_id,
351 351 revision=revision,
352 352 f_path=request.POST.get('f_path'),
353 353 line_no=request.POST.get('line'))
354 354 Session.commit()
355 355 return redirect(h.url('changeset_home', repo_name=repo_name,
356 356 revision=revision))
357 357
358 358 @jsonify
359 359 def delete_comment(self, repo_name, comment_id):
360 360 co = ChangesetComment.get(comment_id)
361 361 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
362 362 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
363 363 ChangesetCommentsModel().delete(comment=co)
364 364 Session.commit()
365 365 return True
366 366 else:
367 367 raise HTTPForbidden()
@@ -1,233 +1,233 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.summary
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Summary controller for Rhodecode
7 7
8 8 :created_on: Apr 18, 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 traceback
27 27 import calendar
28 28 import logging
29 29 from time import mktime
30 30 from datetime import timedelta, date
31 from itertools import product
32 31 from urlparse import urlparse
32 from rhodecode.lib.compat import product
33 33
34 34 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
35 35 NodeDoesNotExistError
36 36
37 37 from pylons import tmpl_context as c, request, url, config
38 38 from pylons.i18n.translation import _
39 39
40 40 from beaker.cache import cache_region, region_invalidate
41 41
42 42 from rhodecode.model.db import Statistics, CacheInvalidation
43 43 from rhodecode.lib import ALL_READMES, ALL_EXTS
44 44 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
45 45 from rhodecode.lib.base import BaseRepoController, render
46 46 from rhodecode.lib.utils import EmptyChangeset
47 47 from rhodecode.lib.markup_renderer import MarkupRenderer
48 48 from rhodecode.lib.celerylib import run_task
49 49 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
50 50 LANGUAGES_EXTENSIONS_MAP
51 51 from rhodecode.lib.helpers import RepoPage
52 52 from rhodecode.lib.compat import json, OrderedDict
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
57 57 sorted(list(product(ALL_READMES, ALL_EXTS)),
58 58 key=lambda y:y[0][1] + y[1][1])]
59 59
60 60
61 61 class SummaryController(BaseRepoController):
62 62
63 63 @LoginRequired()
64 64 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
65 65 'repository.admin')
66 66 def __before__(self):
67 67 super(SummaryController, self).__before__()
68 68
69 69 def index(self, repo_name):
70 70 c.dbrepo = dbrepo = c.rhodecode_db_repo
71 71 c.following = self.scm_model.is_following_repo(repo_name,
72 72 self.rhodecode_user.user_id)
73 73
74 74 def url_generator(**kw):
75 75 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
76 76
77 77 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
78 78 items_per_page=10, url=url_generator)
79 79
80 80 if self.rhodecode_user.username == 'default':
81 81 # for default(anonymous) user we don't need to pass credentials
82 82 username = ''
83 83 password = ''
84 84 else:
85 85 username = str(self.rhodecode_user.username)
86 86 password = '@'
87 87
88 88 parsed_url = urlparse(url.current(qualified=True))
89 89
90 90 default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
91 91
92 92 uri_tmpl = config.get('clone_uri', default_clone_uri)
93 93 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
94 94
95 95 uri_dict = {
96 96 'user': username,
97 97 'pass': password,
98 98 'scheme': parsed_url.scheme,
99 99 'netloc': parsed_url.netloc,
100 100 'path': parsed_url.path
101 101 }
102 102 uri = uri_tmpl % uri_dict
103 103 # generate another clone url by id
104 104 uri_dict.update({'path': '/_%s' % c.dbrepo.repo_id})
105 105 uri_id = uri_tmpl % uri_dict
106 106
107 107 c.clone_repo_url = uri
108 108 c.clone_repo_url_id = uri_id
109 109 c.repo_tags = OrderedDict()
110 110 for name, hash in c.rhodecode_repo.tags.items()[:10]:
111 111 try:
112 112 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
113 113 except ChangesetError:
114 114 c.repo_tags[name] = EmptyChangeset(hash)
115 115
116 116 c.repo_branches = OrderedDict()
117 117 for name, hash in c.rhodecode_repo.branches.items()[:10]:
118 118 try:
119 119 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
120 120 except ChangesetError:
121 121 c.repo_branches[name] = EmptyChangeset(hash)
122 122
123 123 td = date.today() + timedelta(days=1)
124 124 td_1m = td - timedelta(days=calendar.mdays[td.month])
125 125 td_1y = td - timedelta(days=365)
126 126
127 127 ts_min_m = mktime(td_1m.timetuple())
128 128 ts_min_y = mktime(td_1y.timetuple())
129 129 ts_max_y = mktime(td.timetuple())
130 130
131 131 if dbrepo.enable_statistics:
132 132 c.show_stats = True
133 133 c.no_data_msg = _('No data loaded yet')
134 134 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
135 135 else:
136 136 c.show_stats = False
137 137 c.no_data_msg = _('Statistics are disabled for this repository')
138 138 c.ts_min = ts_min_m
139 139 c.ts_max = ts_max_y
140 140
141 141 stats = self.sa.query(Statistics)\
142 142 .filter(Statistics.repository == dbrepo)\
143 143 .scalar()
144 144
145 145 c.stats_percentage = 0
146 146
147 147 if stats and stats.languages:
148 148 c.no_data = False is dbrepo.enable_statistics
149 149 lang_stats_d = json.loads(stats.languages)
150 150 c.commit_data = stats.commit_activity
151 151 c.overview_data = stats.commit_activity_combined
152 152
153 153 lang_stats = ((x, {"count": y,
154 154 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
155 155 for x, y in lang_stats_d.items())
156 156
157 157 c.trending_languages = json.dumps(
158 158 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
159 159 )
160 160 last_rev = stats.stat_on_revision + 1
161 161 c.repo_last_rev = c.rhodecode_repo.count()\
162 162 if c.rhodecode_repo.revisions else 0
163 163 if last_rev == 0 or c.repo_last_rev == 0:
164 164 pass
165 165 else:
166 166 c.stats_percentage = '%.2f' % ((float((last_rev)) /
167 167 c.repo_last_rev) * 100)
168 168 else:
169 169 c.commit_data = json.dumps({})
170 170 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
171 171 c.trending_languages = json.dumps({})
172 172 c.no_data = True
173 173
174 174 c.enable_downloads = dbrepo.enable_downloads
175 175 if c.enable_downloads:
176 176 c.download_options = self._get_download_links(c.rhodecode_repo)
177 177
178 178 c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_repo)
179 179 return render('summary/summary.html')
180 180
181 181 def __get_readme_data(self, repo):
182 182
183 183 @cache_region('long_term')
184 184 def _get_readme_from_cache(key):
185 185 readme_data = None
186 186 readme_file = None
187 187 log.debug('Fetching readme file')
188 188 try:
189 189 cs = repo.get_changeset('tip')
190 190 renderer = MarkupRenderer()
191 191 for f in README_FILES:
192 192 try:
193 193 readme = cs.get_node(f)
194 194 readme_file = f
195 195 readme_data = renderer.render(readme.content, f)
196 196 log.debug('Found readme %s' % readme_file)
197 197 break
198 198 except NodeDoesNotExistError:
199 199 continue
200 200 except ChangesetError:
201 201 pass
202 202 except EmptyRepositoryError:
203 203 pass
204 204 except Exception:
205 205 log.error(traceback.format_exc())
206 206
207 207 return readme_data, readme_file
208 208
209 209 key = repo.name + '_README'
210 210 inv = CacheInvalidation.invalidate(key)
211 211 if inv is not None:
212 212 region_invalidate(_get_readme_from_cache, None, key)
213 213 CacheInvalidation.set_valid(inv.cache_key)
214 214 return _get_readme_from_cache(key)
215 215
216 216 def _get_download_links(self, repo):
217 217
218 218 download_l = []
219 219
220 220 branches_group = ([], _("Branches"))
221 221 tags_group = ([], _("Tags"))
222 222
223 223 for name, chs in c.rhodecode_repo.branches.items():
224 224 #chs = chs.split(':')[-1]
225 225 branches_group[0].append((chs, name),)
226 226 download_l.append(branches_group)
227 227
228 228 for name, chs in c.rhodecode_repo.tags.items():
229 229 #chs = chs.split(':')[-1]
230 230 tags_group[0].append((chs, name),)
231 231 download_l.append(tags_group)
232 232
233 233 return download_l
@@ -1,381 +1,399 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.compat
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Python backward compatibility functions and common libs
7 7
8 8
9 9 :created_on: Oct 7, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2010-2010 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 from rhodecode import __platform__, PLATFORM_WIN
29 29
30 30 #==============================================================================
31 31 # json
32 32 #==============================================================================
33 33 try:
34 34 import json
35 35 except ImportError:
36 36 import simplejson as json
37 37
38 38
39 39 #==============================================================================
40 40 # izip_longest
41 41 #==============================================================================
42 42 try:
43 43 from itertools import izip_longest
44 44 except ImportError:
45 45 import itertools
46 46
47 47 def izip_longest(*args, **kwds): # noqa
48 48 fillvalue = kwds.get("fillvalue")
49 49
50 50 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
51 51 yield counter() # yields the fillvalue, or raises IndexError
52 52
53 53 fillers = itertools.repeat(fillvalue)
54 54 iters = [itertools.chain(it, sentinel(), fillers)
55 55 for it in args]
56 56 try:
57 57 for tup in itertools.izip(*iters):
58 58 yield tup
59 59 except IndexError:
60 60 pass
61 61
62 62
63 63 #==============================================================================
64 64 # OrderedDict
65 65 #==============================================================================
66 66
67 67 # Python Software Foundation License
68 68
69 69 # XXX: it feels like using the class with "is" and "is not" instead of "==" and
70 70 # "!=" should be faster.
71 71 class _Nil(object):
72 72
73 73 def __repr__(self):
74 74 return "nil"
75 75
76 76 def __eq__(self, other):
77 77 if (isinstance(other, _Nil)):
78 78 return True
79 79 else:
80 80 return NotImplemented
81 81
82 82 def __ne__(self, other):
83 83 if (isinstance(other, _Nil)):
84 84 return False
85 85 else:
86 86 return NotImplemented
87 87
88 88 _nil = _Nil()
89 89
90 90
91 91 class _odict(object):
92 92 """Ordered dict data structure, with O(1) complexity for dict operations
93 93 that modify one element.
94 94
95 95 Overwriting values doesn't change their original sequential order.
96 96 """
97 97
98 98 def _dict_impl(self):
99 99 return None
100 100
101 101 def __init__(self, data=(), **kwds):
102 102 """This doesn't accept keyword initialization as normal dicts to avoid
103 103 a trap - inside a function or method the keyword args are accessible
104 104 only as a dict, without a defined order, so their original order is
105 105 lost.
106 106 """
107 107 if kwds:
108 108 raise TypeError("__init__() of ordered dict takes no keyword "
109 109 "arguments to avoid an ordering trap.")
110 110 self._dict_impl().__init__(self)
111 111 # If you give a normal dict, then the order of elements is undefined
112 112 if hasattr(data, "iteritems"):
113 113 for key, val in data.iteritems():
114 114 self[key] = val
115 115 else:
116 116 for key, val in data:
117 117 self[key] = val
118 118
119 119 # Double-linked list header
120 120 def _get_lh(self):
121 121 dict_impl = self._dict_impl()
122 122 if not hasattr(self, '_lh'):
123 123 dict_impl.__setattr__(self, '_lh', _nil)
124 124 return dict_impl.__getattribute__(self, '_lh')
125 125
126 126 def _set_lh(self, val):
127 127 self._dict_impl().__setattr__(self, '_lh', val)
128 128
129 129 lh = property(_get_lh, _set_lh)
130 130
131 131 # Double-linked list tail
132 132 def _get_lt(self):
133 133 dict_impl = self._dict_impl()
134 134 if not hasattr(self, '_lt'):
135 135 dict_impl.__setattr__(self, '_lt', _nil)
136 136 return dict_impl.__getattribute__(self, '_lt')
137 137
138 138 def _set_lt(self, val):
139 139 self._dict_impl().__setattr__(self, '_lt', val)
140 140
141 141 lt = property(_get_lt, _set_lt)
142 142
143 143 def __getitem__(self, key):
144 144 return self._dict_impl().__getitem__(self, key)[1]
145 145
146 146 def __setitem__(self, key, val):
147 147 dict_impl = self._dict_impl()
148 148 try:
149 149 dict_impl.__getitem__(self, key)[1] = val
150 150 except KeyError:
151 151 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
152 152 dict_impl.__setitem__(self, key, new)
153 153 if dict_impl.__getattribute__(self, 'lt') == _nil:
154 154 dict_impl.__setattr__(self, 'lh', key)
155 155 else:
156 156 dict_impl.__getitem__(
157 157 self, dict_impl.__getattribute__(self, 'lt'))[2] = key
158 158 dict_impl.__setattr__(self, 'lt', key)
159 159
160 160 def __delitem__(self, key):
161 161 dict_impl = self._dict_impl()
162 162 pred, _, succ = self._dict_impl().__getitem__(self, key)
163 163 if pred == _nil:
164 164 dict_impl.__setattr__(self, 'lh', succ)
165 165 else:
166 166 dict_impl.__getitem__(self, pred)[2] = succ
167 167 if succ == _nil:
168 168 dict_impl.__setattr__(self, 'lt', pred)
169 169 else:
170 170 dict_impl.__getitem__(self, succ)[0] = pred
171 171 dict_impl.__delitem__(self, key)
172 172
173 173 def __contains__(self, key):
174 174 return key in self.keys()
175 175
176 176 def __len__(self):
177 177 return len(self.keys())
178 178
179 179 def __str__(self):
180 180 pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
181 181 return "{%s}" % ", ".join(pairs)
182 182
183 183 def __repr__(self):
184 184 if self:
185 185 pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
186 186 return "odict([%s])" % ", ".join(pairs)
187 187 else:
188 188 return "odict()"
189 189
190 190 def get(self, k, x=None):
191 191 if k in self:
192 192 return self._dict_impl().__getitem__(self, k)[1]
193 193 else:
194 194 return x
195 195
196 196 def __iter__(self):
197 197 dict_impl = self._dict_impl()
198 198 curr_key = dict_impl.__getattribute__(self, 'lh')
199 199 while curr_key != _nil:
200 200 yield curr_key
201 201 curr_key = dict_impl.__getitem__(self, curr_key)[2]
202 202
203 203 iterkeys = __iter__
204 204
205 205 def keys(self):
206 206 return list(self.iterkeys())
207 207
208 208 def itervalues(self):
209 209 dict_impl = self._dict_impl()
210 210 curr_key = dict_impl.__getattribute__(self, 'lh')
211 211 while curr_key != _nil:
212 212 _, val, curr_key = dict_impl.__getitem__(self, curr_key)
213 213 yield val
214 214
215 215 def values(self):
216 216 return list(self.itervalues())
217 217
218 218 def iteritems(self):
219 219 dict_impl = self._dict_impl()
220 220 curr_key = dict_impl.__getattribute__(self, 'lh')
221 221 while curr_key != _nil:
222 222 _, val, next_key = dict_impl.__getitem__(self, curr_key)
223 223 yield curr_key, val
224 224 curr_key = next_key
225 225
226 226 def items(self):
227 227 return list(self.iteritems())
228 228
229 229 def sort(self, cmp=None, key=None, reverse=False):
230 230 items = [(k, v) for k, v in self.items()]
231 231 if cmp is not None:
232 232 items = sorted(items, cmp=cmp)
233 233 elif key is not None:
234 234 items = sorted(items, key=key)
235 235 else:
236 236 items = sorted(items, key=lambda x: x[1])
237 237 if reverse:
238 238 items.reverse()
239 239 self.clear()
240 240 self.__init__(items)
241 241
242 242 def clear(self):
243 243 dict_impl = self._dict_impl()
244 244 dict_impl.clear(self)
245 245 dict_impl.__setattr__(self, 'lh', _nil)
246 246 dict_impl.__setattr__(self, 'lt', _nil)
247 247
248 248 def copy(self):
249 249 return self.__class__(self)
250 250
251 251 def update(self, data=(), **kwds):
252 252 if kwds:
253 253 raise TypeError("update() of ordered dict takes no keyword "
254 254 "arguments to avoid an ordering trap.")
255 255 if hasattr(data, "iteritems"):
256 256 data = data.iteritems()
257 257 for key, val in data:
258 258 self[key] = val
259 259
260 260 def setdefault(self, k, x=None):
261 261 try:
262 262 return self[k]
263 263 except KeyError:
264 264 self[k] = x
265 265 return x
266 266
267 267 def pop(self, k, x=_nil):
268 268 try:
269 269 val = self[k]
270 270 del self[k]
271 271 return val
272 272 except KeyError:
273 273 if x == _nil:
274 274 raise
275 275 return x
276 276
277 277 def popitem(self):
278 278 try:
279 279 dict_impl = self._dict_impl()
280 280 key = dict_impl.__getattribute__(self, 'lt')
281 281 return key, self.pop(key)
282 282 except KeyError:
283 283 raise KeyError("'popitem(): ordered dictionary is empty'")
284 284
285 285 def riterkeys(self):
286 286 """To iterate on keys in reversed order.
287 287 """
288 288 dict_impl = self._dict_impl()
289 289 curr_key = dict_impl.__getattribute__(self, 'lt')
290 290 while curr_key != _nil:
291 291 yield curr_key
292 292 curr_key = dict_impl.__getitem__(self, curr_key)[0]
293 293
294 294 __reversed__ = riterkeys
295 295
296 296 def rkeys(self):
297 297 """List of the keys in reversed order.
298 298 """
299 299 return list(self.riterkeys())
300 300
301 301 def ritervalues(self):
302 302 """To iterate on values in reversed order.
303 303 """
304 304 dict_impl = self._dict_impl()
305 305 curr_key = dict_impl.__getattribute__(self, 'lt')
306 306 while curr_key != _nil:
307 307 curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
308 308 yield val
309 309
310 310 def rvalues(self):
311 311 """List of the values in reversed order.
312 312 """
313 313 return list(self.ritervalues())
314 314
315 315 def riteritems(self):
316 316 """To iterate on (key, value) in reversed order.
317 317 """
318 318 dict_impl = self._dict_impl()
319 319 curr_key = dict_impl.__getattribute__(self, 'lt')
320 320 while curr_key != _nil:
321 321 pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
322 322 yield curr_key, val
323 323 curr_key = pred_key
324 324
325 325 def ritems(self):
326 326 """List of the (key, value) in reversed order.
327 327 """
328 328 return list(self.riteritems())
329 329
330 330 def firstkey(self):
331 331 if self:
332 332 return self._dict_impl().__getattribute__(self, 'lh')
333 333 else:
334 334 raise KeyError("'firstkey(): ordered dictionary is empty'")
335 335
336 336 def lastkey(self):
337 337 if self:
338 338 return self._dict_impl().__getattribute__(self, 'lt')
339 339 else:
340 340 raise KeyError("'lastkey(): ordered dictionary is empty'")
341 341
342 342 def as_dict(self):
343 343 return self._dict_impl()(self.items())
344 344
345 345 def _repr(self):
346 346 """_repr(): low level repr of the whole data contained in the odict.
347 347 Useful for debugging.
348 348 """
349 349 dict_impl = self._dict_impl()
350 350 form = "odict low level repr lh,lt,data: %r, %r, %s"
351 351 return form % (dict_impl.__getattribute__(self, 'lh'),
352 352 dict_impl.__getattribute__(self, 'lt'),
353 353 dict_impl.__repr__(self))
354 354
355 355
356 356 class OrderedDict(_odict, dict):
357 357
358 358 def _dict_impl(self):
359 359 return dict
360 360
361 361
362 362 #==============================================================================
363 363 # OrderedSet
364 364 #==============================================================================
365 365 from sqlalchemy.util import OrderedSet
366 366
367 367
368 368 #==============================================================================
369 369 # kill FUNCTIONS
370 370 #==============================================================================
371 371 if __platform__ in PLATFORM_WIN:
372 372 import ctypes
373 373
374 374 def kill(pid, sig):
375 375 """kill function for Win32"""
376 376 kernel32 = ctypes.windll.kernel32
377 377 handle = kernel32.OpenProcess(1, 0, pid)
378 378 return (0 != kernel32.TerminateProcess(handle, 0))
379 379
380 380 else:
381 381 kill = os.kill
382
383
384 #==============================================================================
385 # itertools.product
386 #==============================================================================
387
388 try:
389 from itertools import product
390 except ImportError:
391 def product(*args, **kwds):
392 # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
393 # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
394 pools = map(tuple, args) * kwds.get('repeat', 1)
395 result = [[]]
396 for pool in pools:
397 result = [x + [y] for x in result for y in pool]
398 for prod in result:
399 yield tuple(prod)
@@ -1,124 +1,124 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repositories administration')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Repositories')}
11 11 </%def>
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15 <%def name="main()">
16 16 <div class="box">
17 17
18 18 <div class="title">
19 19 ${self.breadcrumbs()}
20 20 <ul class="links">
21 21 <li>
22 22 <span>${h.link_to(_(u'ADD REPOSITORY'),h.url('new_repo'))}</span>
23 23 </li>
24 24 </ul>
25 25 </div>
26 26
27 27 <div class="table">
28 28 <div id='repos_list_wrap' class="yui-skin-sam">
29 29 <%cnt=0%>
30 30 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
31 31
32 32 <table id="repos_list">
33 33 <thead>
34 34 <tr>
35 35 <th class="left"></th>
36 36 <th class="left">${_('Name')}</th>
37 37 <th class="left">${_('Description')}</th>
38 38 <th class="left">${_('Last change')}</th>
39 39 <th class="left">${_('Tip')}</th>
40 40 <th class="left">${_('Contact')}</th>
41 41 <th class="left">${_('Action')}</th>
42 42 </tr>
43 43 </thead>
44 44
45 %for cnt,repo in enumerate(c.repos_list,1):
46 <tr class="parity${cnt%2}">
45 %for cnt,repo in enumerate(c.repos_list):
46 <tr class="parity${(cnt+1)%2}">
47 47 <td class="quick_repo_menu">
48 48 ${dt.quick_menu(repo['name'])}
49 49 </td>
50 50 <td class="reponame">
51 51 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
52 52 </td>
53 53 ##DESCRIPTION
54 54 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
55 55 ${h.truncate(repo['description'],60)}</span>
56 56 </td>
57 57 ##LAST CHANGE
58 58 <td>
59 59 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
60 60 </td>
61 61 ##LAST REVISION
62 62 <td>
63 63 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
64 64 </td>
65 65 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
66 66 <td>
67 67 ${h.form(url('repo', repo_name=repo['name']),method='delete')}
68 68 ${h.submit('remove_%s' % repo['name'],_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo['name']+"');")}
69 69 ${h.end_form()}
70 70 </td>
71 71 </tr>
72 72 %endfor
73 73 </table>
74 74 </div>
75 75 </div>
76 76 </div>
77 77 <script>
78 78
79 79 // main table sorting
80 80 var myColumnDefs = [
81 81 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
82 82 {key:"name",label:"${_('Name')}",sortable:true,
83 83 sortOptions: { sortFunction: nameSort }},
84 84 {key:"desc",label:"${_('Description')}",sortable:true},
85 85 {key:"last_change",label:"${_('Last Change')}",sortable:true,
86 86 sortOptions: { sortFunction: ageSort }},
87 87 {key:"tip",label:"${_('Tip')}",sortable:true,
88 88 sortOptions: { sortFunction: revisionSort }},
89 89 {key:"owner",label:"${_('Owner')}",sortable:true},
90 90 {key:"action",label:"${_('Action')}",sortable:false},
91 91 ];
92 92
93 93 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
94 94
95 95 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
96 96
97 97 myDataSource.responseSchema = {
98 98 fields: [
99 99 {key:"menu"},
100 100 {key:"name"},
101 101 {key:"desc"},
102 102 {key:"last_change"},
103 103 {key:"tip"},
104 104 {key:"owner"},
105 105 {key:"action"},
106 106 ]
107 107 };
108 108
109 109 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
110 110 {
111 111 sortedBy:{key:"name",dir:"asc"},
112 112 MSG_SORTASC:"${_('Click to sort ascending')}",
113 113 MSG_SORTDESC:"${_('Click to sort descending')}",
114 114 MSG_EMPTY:"${_('No records found.')}",
115 115 MSG_ERROR:"${_('Data error.')}",
116 116 MSG_LOADING:"${_('Loading...')}",
117 117 }
118 118 );
119 119 myDataTable.subscribe('postRenderEvent',function(oArgs) {
120 120 tooltip_activate();
121 121 quick_repo_menu();
122 122 });
123 123 </script>
124 124 </%def>
@@ -1,197 +1,197 b''
1 1 <%page args="parent" />
2 2 <div class="box">
3 3 <!-- box / title -->
4 4 <div class="title">
5 5 <h5>
6 6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
7 7 </h5>
8 8 %if c.rhodecode_user.username != 'default':
9 9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 10 <ul class="links">
11 11 <li>
12 12 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
13 13 </li>
14 14 </ul>
15 15 %endif
16 16 %endif
17 17 </div>
18 18 <!-- end box / title -->
19 19 <div class="table">
20 20 % if c.groups:
21 21 <div id='groups_list_wrap' class="yui-skin-sam">
22 22 <table id="groups_list">
23 23 <thead>
24 24 <tr>
25 25 <th class="left"><a href="#">${_('Group name')}</a></th>
26 26 <th class="left"><a href="#">${_('Description')}</a></th>
27 27 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
28 28 </tr>
29 29 </thead>
30 30
31 31 ## REPO GROUPS
32 32 % for gr in c.groups:
33 33 <tr>
34 34 <td>
35 35 <div style="white-space: nowrap">
36 36 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
37 37 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
38 38 </div>
39 39 </td>
40 40 <td>${gr.group_description}</td>
41 41 ## this is commented out since for multi nested repos can be HEAVY!
42 42 ## in number of executed queries during traversing uncomment at will
43 43 ##<td><b>${gr.repositories_recursive_count}</b></td>
44 44 </tr>
45 45 % endfor
46 46
47 47 </table>
48 48 </div>
49 49 <div style="height: 20px"></div>
50 50 % endif
51 51 <div id="welcome" style="display:none;text-align:center">
52 52 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
53 53 </div>
54 54 <div id='repos_list_wrap' class="yui-skin-sam">
55 55 <%cnt=0%>
56 56 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
57 57
58 58 <table id="repos_list">
59 59 <thead>
60 60 <tr>
61 61 <th class="left"></th>
62 62 <th class="left">${_('Name')}</th>
63 63 <th class="left">${_('Description')}</th>
64 64 <th class="left">${_('Last change')}</th>
65 65 <th class="left">${_('Tip')}</th>
66 66 <th class="left">${_('Owner')}</th>
67 67 <th class="left">${_('RSS')}</th>
68 68 <th class="left">${_('Atom')}</th>
69 69 </tr>
70 70 </thead>
71 71 <tbody>
72 %for cnt,repo in enumerate(c.repos_list,1):
73 <tr class="parity${cnt%2}">
72 %for cnt,repo in enumerate(c.repos_list):
73 <tr class="parity${(cnt+1)%2}">
74 74 ##QUICK MENU
75 75 <td class="quick_repo_menu">
76 76 ${dt.quick_menu(repo['name'])}
77 77 </td>
78 78 ##REPO NAME AND ICONS
79 79 <td class="reponame">
80 80 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
81 81 </td>
82 82 ##DESCRIPTION
83 83 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
84 84 ${h.truncate(repo['description'],60)}</span>
85 85 </td>
86 86 ##LAST CHANGE DATE
87 87 <td>
88 88 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
89 89 </td>
90 90 ##LAST REVISION
91 91 <td>
92 92 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
93 93 </td>
94 94 ##
95 95 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
96 96 <td>
97 97 %if c.rhodecode_user.username != 'default':
98 98 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
99 99 %else:
100 100 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
101 101 %endif:
102 102 </td>
103 103 <td>
104 104 %if c.rhodecode_user.username != 'default':
105 105 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
106 106 %else:
107 107 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
108 108 %endif:
109 109 </td>
110 110 </tr>
111 111 %endfor
112 112 </tbody>
113 113 </table>
114 114 </div>
115 115 </div>
116 116 </div>
117 117 <script>
118 YUD.get('repo_count').innerHTML = ${cnt};
118 YUD.get('repo_count').innerHTML = ${cnt+1};
119 119 var func = function(node){
120 120 return node.parentNode.parentNode.parentNode.parentNode;
121 121 }
122 122
123 123
124 124 // groups table sorting
125 125 var myColumnDefs = [
126 126 {key:"name",label:"${_('Group Name')}",sortable:true,
127 127 sortOptions: { sortFunction: groupNameSort }},
128 128 {key:"desc",label:"${_('Description')}",sortable:true},
129 129 ];
130 130
131 131 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
132 132
133 133 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
134 134 myDataSource.responseSchema = {
135 135 fields: [
136 136 {key:"name"},
137 137 {key:"desc"},
138 138 ]
139 139 };
140 140
141 141 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
142 142 {
143 143 sortedBy:{key:"name",dir:"asc"},
144 144 MSG_SORTASC:"${_('Click to sort ascending')}",
145 145 MSG_SORTDESC:"${_('Click to sort descending')}"
146 146 }
147 147 );
148 148
149 149 // main table sorting
150 150 var myColumnDefs = [
151 151 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
152 152 {key:"name",label:"${_('Name')}",sortable:true,
153 153 sortOptions: { sortFunction: nameSort }},
154 154 {key:"desc",label:"${_('Description')}",sortable:true},
155 155 {key:"last_change",label:"${_('Last Change')}",sortable:true,
156 156 sortOptions: { sortFunction: ageSort }},
157 157 {key:"tip",label:"${_('Tip')}",sortable:true,
158 158 sortOptions: { sortFunction: revisionSort }},
159 159 {key:"owner",label:"${_('Owner')}",sortable:true},
160 160 {key:"rss",label:"",sortable:false},
161 161 {key:"atom",label:"",sortable:false},
162 162 ];
163 163
164 164 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
165 165
166 166 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
167 167
168 168 myDataSource.responseSchema = {
169 169 fields: [
170 170 {key:"menu"},
171 171 {key:"name"},
172 172 {key:"desc"},
173 173 {key:"last_change"},
174 174 {key:"tip"},
175 175 {key:"owner"},
176 176 {key:"rss"},
177 177 {key:"atom"},
178 178 ]
179 179 };
180 180
181 181 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
182 182 {
183 183 sortedBy:{key:"name",dir:"asc"},
184 184 MSG_SORTASC:"${_('Click to sort ascending')}",
185 185 MSG_SORTDESC:"${_('Click to sort descending')}",
186 186 MSG_EMPTY:"${_('No records found.')}",
187 187 MSG_ERROR:"${_('Data error.')}",
188 188 MSG_LOADING:"${_('Loading...')}",
189 189 }
190 190 );
191 191 myDataTable.subscribe('postRenderEvent',function(oArgs) {
192 192 tooltip_activate();
193 193 quick_repo_menu();
194 194 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
195 195 });
196 196
197 197 </script>
General Comments 0
You need to be logged in to leave comments. Login now