##// END OF EJS Templates
fixes #550 mercurial repositories comparision failed when origin repo had...
marcink -
r2801:69420c48 beta
parent child Browse files
Show More
@@ -1,740 +1,741 b''
1 1 .. _changelog:
2 2
3 3 =========
4 4 Changelog
5 5 =========
6 6
7 7
8 8 1.4.1 (**2012-09-04**)
9 9 ----------------------
10 10
11 11 :status: in-progress
12 12 :branch: beta
13 13
14 14 news
15 15 ++++
16 16
17 17 - always put a comment about code-review status change even if user send
18 18 empty data
19 19 - modified_on column saves repository update and it's going to be used
20 20 later for light version of main page ref #500
21 21 - pull request notifications send much nicer emails with details about pull
22 22 request
23 23
24 24 fixes
25 25 +++++
26 26
27 27 - fixed migrations of permissions that can lead to inconsistency.
28 28 Some users sent feedback that after upgrading from older versions issues
29 29 with updating default permissions occurred. RhodeCode detects that now and
30 30 resets default user permission to initial state if there is a need for that.
31 31 Also forces users to set the default value for new forking permission.
32 32 - #535 improved apache wsgi example configuration in docs
33
33 - fixes #550 mercurial repositories comparision failed when origin repo had
34 additional not-common changesets
34 35
35 36 1.4.0 (**2012-09-03**)
36 37 ----------------------
37 38
38 39 news
39 40 ++++
40 41
41 42 - new codereview system
42 43 - email map, allowing users to have multiple email addresses mapped into
43 44 their accounts
44 45 - improved git-hook system. Now all actions for git are logged into journal
45 46 including pushed revisions, user and IP address
46 47 - changed setup-app into setup-rhodecode and added default options to it.
47 48 - new git repos are created as bare now by default
48 49 - #464 added links to groups in permission box
49 50 - #465 mentions autocomplete inside comments boxes
50 51 - #469 added --update-only option to whoosh to re-index only given list
51 52 of repos in index
52 53 - rhodecode-api CLI client
53 54 - new git http protocol replaced buggy dulwich implementation.
54 55 Now based on pygrack & gitweb
55 56 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
56 57 reformated based on user suggestions. Additional rss/atom feeds for user
57 58 journal
58 59 - various i18n improvements
59 60 - #478 permissions overview for admin in user edit view
60 61 - File view now displays small gravatars off all authors of given file
61 62 - Implemented landing revisions. Each repository will get landing_rev attribute
62 63 that defines 'default' revision/branch for generating readme files
63 64 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
64 65 earliest possible call.
65 66 - Import remote svn repositories to mercurial using hgsubversion.
66 67 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
67 68 - RhodeCode can use alternative server for generating avatar icons
68 69 - implemented repositories locking. Pull locks, push unlocks. Also can be done
69 70 via API calls
70 71 - #538 form for permissions can handle multiple users at once
71 72
72 73 fixes
73 74 +++++
74 75
75 76 - improved translations
76 77 - fixes issue #455 Creating an archive generates an exception on Windows
77 78 - fixes #448 Download ZIP archive keeps file in /tmp open and results
78 79 in out of disk space
79 80 - fixes issue #454 Search results under Windows include proceeding
80 81 backslash
81 82 - fixed issue #450. Rhodecode no longer will crash when bad revision is
82 83 present in journal data.
83 84 - fix for issue #417, git execution was broken on windows for certain
84 85 commands.
85 86 - fixed #413. Don't disable .git directory for bare repos on deleting
86 87 - fixed issue #459. Changed the way of obtaining logger in reindex task.
87 88 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
88 89 reindexing modified files
89 90 - fixed #481 rhodecode emails are sent without Date header
90 91 - fixed #458 wrong count when no repos are present
91 92 - fixed issue #492 missing `\ No newline at end of file` test at the end of
92 93 new chunk in html diff
93 94 - full text search now works also for commit messages
94 95
95 96 1.3.6 (**2012-05-17**)
96 97 ----------------------
97 98
98 99 news
99 100 ++++
100 101
101 102 - chinese traditional translation
102 103 - changed setup-app into setup-rhodecode and added arguments for auto-setup
103 104 mode that doesn't need user interaction
104 105
105 106 fixes
106 107 +++++
107 108
108 109 - fixed no scm found warning
109 110 - fixed __future__ import error on rcextensions
110 111 - made simplejson required lib for speedup on JSON encoding
111 112 - fixes #449 bad regex could get more than revisions from parsing history
112 113 - don't clear DB session when CELERY_EAGER is turned ON
113 114
114 115 1.3.5 (**2012-05-10**)
115 116 ----------------------
116 117
117 118 news
118 119 ++++
119 120
120 121 - use ext_json for json module
121 122 - unified annotation view with file source view
122 123 - notification improvements, better inbox + css
123 124 - #419 don't strip passwords for login forms, make rhodecode
124 125 more compatible with LDAP servers
125 126 - Added HTTP_X_FORWARDED_FOR as another method of extracting
126 127 IP for pull/push logs. - moved all to base controller
127 128 - #415: Adding comment to changeset causes reload.
128 129 Comments are now added via ajax and doesn't reload the page
129 130 - #374 LDAP config is discarded when LDAP can't be activated
130 131 - limited push/pull operations are now logged for git in the journal
131 132 - bumped mercurial to 2.2.X series
132 133 - added support for displaying submodules in file-browser
133 134 - #421 added bookmarks in changelog view
134 135
135 136 fixes
136 137 +++++
137 138
138 139 - fixed dev-version marker for stable when served from source codes
139 140 - fixed missing permission checks on show forks page
140 141 - #418 cast to unicode fixes in notification objects
141 142 - #426 fixed mention extracting regex
142 143 - fixed remote-pulling for git remotes remopositories
143 144 - fixed #434: Error when accessing files or changesets of a git repository
144 145 with submodules
145 146 - fixed issue with empty APIKEYS for users after registration ref. #438
146 147 - fixed issue with getting README files from git repositories
147 148
148 149 1.3.4 (**2012-03-28**)
149 150 ----------------------
150 151
151 152 news
152 153 ++++
153 154
154 155 - Whoosh logging is now controlled by the .ini files logging setup
155 156 - added clone-url into edit form on /settings page
156 157 - added help text into repo add/edit forms
157 158 - created rcextensions module with additional mappings (ref #322) and
158 159 post push/pull/create repo hooks callbacks
159 160 - implemented #377 Users view for his own permissions on account page
160 161 - #399 added inheritance of permissions for users group on repos groups
161 162 - #401 repository group is automatically pre-selected when adding repos
162 163 inside a repository group
163 164 - added alternative HTTP 403 response when client failed to authenticate. Helps
164 165 solving issues with Mercurial and LDAP
165 166 - #402 removed group prefix from repository name when listing repositories
166 167 inside a group
167 168 - added gravatars into permission view and permissions autocomplete
168 169 - #347 when running multiple RhodeCode instances, properly invalidates cache
169 170 for all registered servers
170 171
171 172 fixes
172 173 +++++
173 174
174 175 - fixed #390 cache invalidation problems on repos inside group
175 176 - fixed #385 clone by ID url was loosing proxy prefix in URL
176 177 - fixed some unicode problems with waitress
177 178 - fixed issue with escaping < and > in changeset commits
178 179 - fixed error occurring during recursive group creation in API
179 180 create_repo function
180 181 - fixed #393 py2.5 fixes for routes url generator
181 182 - fixed #397 Private repository groups shows up before login
182 183 - fixed #396 fixed problems with revoking users in nested groups
183 184 - fixed mysql unicode issues + specified InnoDB as default engine with
184 185 utf8 charset
185 186 - #406 trim long branch/tag names in changelog to not break UI
186 187
187 188 1.3.3 (**2012-03-02**)
188 189 ----------------------
189 190
190 191 news
191 192 ++++
192 193
193 194
194 195 fixes
195 196 +++++
196 197
197 198 - fixed some python2.5 compatibility issues
198 199 - fixed issues with removed repos was accidentally added as groups, after
199 200 full rescan of paths
200 201 - fixes #376 Cannot edit user (using container auth)
201 202 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
202 203 configuration
203 204 - fixed initial sorting of repos inside repo group
204 205 - fixes issue when user tried to resubmit same permission into user/user_groups
205 206 - bumped beaker version that fixes #375 leap error bug
206 207 - fixed raw_changeset for git. It was generated with hg patch headers
207 208 - fixed vcs issue with last_changeset for filenodes
208 209 - fixed missing commit after hook delete
209 210 - fixed #372 issues with git operation detection that caused a security issue
210 211 for git repos
211 212
212 213 1.3.2 (**2012-02-28**)
213 214 ----------------------
214 215
215 216 news
216 217 ++++
217 218
218 219
219 220 fixes
220 221 +++++
221 222
222 223 - fixed git protocol issues with repos-groups
223 224 - fixed git remote repos validator that prevented from cloning remote git repos
224 225 - fixes #370 ending slashes fixes for repo and groups
225 226 - fixes #368 improved git-protocol detection to handle other clients
226 227 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
227 228 Moved To Root
228 229 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
229 230 - fixed #373 missing cascade drop on user_group_to_perm table
230 231
231 232 1.3.1 (**2012-02-27**)
232 233 ----------------------
233 234
234 235 news
235 236 ++++
236 237
237 238
238 239 fixes
239 240 +++++
240 241
241 242 - redirection loop occurs when remember-me wasn't checked during login
242 243 - fixes issues with git blob history generation
243 244 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
244 245
245 246 1.3.0 (**2012-02-26**)
246 247 ----------------------
247 248
248 249 news
249 250 ++++
250 251
251 252 - code review, inspired by github code-comments
252 253 - #215 rst and markdown README files support
253 254 - #252 Container-based and proxy pass-through authentication support
254 255 - #44 branch browser. Filtering of changelog by branches
255 256 - mercurial bookmarks support
256 257 - new hover top menu, optimized to add maximum size for important views
257 258 - configurable clone url template with possibility to specify protocol like
258 259 ssh:// or http:// and also manually alter other parts of clone_url.
259 260 - enabled largefiles extension by default
260 261 - optimized summary file pages and saved a lot of unused space in them
261 262 - #239 option to manually mark repository as fork
262 263 - #320 mapping of commit authors to RhodeCode users
263 264 - #304 hashes are displayed using monospace font
264 265 - diff configuration, toggle white lines and context lines
265 266 - #307 configurable diffs, whitespace toggle, increasing context lines
266 267 - sorting on branches, tags and bookmarks using YUI datatable
267 268 - improved file filter on files page
268 269 - implements #330 api method for listing nodes ar particular revision
269 270 - #73 added linking issues in commit messages to chosen issue tracker url
270 271 based on user defined regular expression
271 272 - added linking of changesets in commit messages
272 273 - new compact changelog with expandable commit messages
273 274 - firstname and lastname are optional in user creation
274 275 - #348 added post-create repository hook
275 276 - #212 global encoding settings is now configurable from .ini files
276 277 - #227 added repository groups permissions
277 278 - markdown gets codehilite extensions
278 279 - new API methods, delete_repositories, grante/revoke permissions for groups
279 280 and repos
280 281
281 282
282 283 fixes
283 284 +++++
284 285
285 286 - rewrote dbsession management for atomic operations, and better error handling
286 287 - fixed sorting of repo tables
287 288 - #326 escape of special html entities in diffs
288 289 - normalized user_name => username in api attributes
289 290 - fixes #298 ldap created users with mixed case emails created conflicts
290 291 on saving a form
291 292 - fixes issue when owner of a repo couldn't revoke permissions for users
292 293 and groups
293 294 - fixes #271 rare JSON serialization problem with statistics
294 295 - fixes #337 missing validation check for conflicting names of a group with a
295 296 repositories group
296 297 - #340 fixed session problem for mysql and celery tasks
297 298 - fixed #331 RhodeCode mangles repository names if the a repository group
298 299 contains the "full path" to the repositories
299 300 - #355 RhodeCode doesn't store encrypted LDAP passwords
300 301
301 302 1.2.5 (**2012-01-28**)
302 303 ----------------------
303 304
304 305 news
305 306 ++++
306 307
307 308 fixes
308 309 +++++
309 310
310 311 - #340 Celery complains about MySQL server gone away, added session cleanup
311 312 for celery tasks
312 313 - #341 "scanning for repositories in None" log message during Rescan was missing
313 314 a parameter
314 315 - fixed creating archives with subrepos. Some hooks were triggered during that
315 316 operation leading to crash.
316 317 - fixed missing email in account page.
317 318 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
318 319 forking on windows impossible
319 320
320 321 1.2.4 (**2012-01-19**)
321 322 ----------------------
322 323
323 324 news
324 325 ++++
325 326
326 327 - RhodeCode is bundled with mercurial series 2.0.X by default, with
327 328 full support to largefiles extension. Enabled by default in new installations
328 329 - #329 Ability to Add/Remove Groups to/from a Repository via AP
329 330 - added requires.txt file with requirements
330 331
331 332 fixes
332 333 +++++
333 334
334 335 - fixes db session issues with celery when emailing admins
335 336 - #331 RhodeCode mangles repository names if the a repository group
336 337 contains the "full path" to the repositories
337 338 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
338 339 - DB session cleanup after hg protocol operations, fixes issues with
339 340 `mysql has gone away` errors
340 341 - #333 doc fixes for get_repo api function
341 342 - #271 rare JSON serialization problem with statistics enabled
342 343 - #337 Fixes issues with validation of repository name conflicting with
343 344 a group name. A proper message is now displayed.
344 345 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
345 346 doesn't work
346 347 - #316 fixes issues with web description in hgrc files
347 348
348 349 1.2.3 (**2011-11-02**)
349 350 ----------------------
350 351
351 352 news
352 353 ++++
353 354
354 355 - added option to manage repos group for non admin users
355 356 - added following API methods for get_users, create_user, get_users_groups,
356 357 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
357 358 get_repo, create_repo, add_user_to_repo
358 359 - implements #237 added password confirmation for my account
359 360 and admin edit user.
360 361 - implements #291 email notification for global events are now sent to all
361 362 administrator users, and global config email.
362 363
363 364 fixes
364 365 +++++
365 366
366 367 - added option for passing auth method for smtp mailer
367 368 - #276 issue with adding a single user with id>10 to usergroups
368 369 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
369 370 - #288 fixes managing of repos in a group for non admin user
370 371
371 372 1.2.2 (**2011-10-17**)
372 373 ----------------------
373 374
374 375 news
375 376 ++++
376 377
377 378 - #226 repo groups are available by path instead of numerical id
378 379
379 380 fixes
380 381 +++++
381 382
382 383 - #259 Groups with the same name but with different parent group
383 384 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
384 385 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
385 386 - #265 ldap save fails sometimes on converting attributes to booleans,
386 387 added getter and setter into model that will prevent from this on db model level
387 388 - fixed problems with timestamps issues #251 and #213
388 389 - fixes #266 RhodeCode allows to create repo with the same name and in
389 390 the same parent as group
390 391 - fixes #245 Rescan of the repositories on Windows
391 392 - fixes #248 cannot edit repos inside a group on windows
392 393 - fixes #219 forking problems on windows
393 394
394 395 1.2.1 (**2011-10-08**)
395 396 ----------------------
396 397
397 398 news
398 399 ++++
399 400
400 401
401 402 fixes
402 403 +++++
403 404
404 405 - fixed problems with basic auth and push problems
405 406 - gui fixes
406 407 - fixed logger
407 408
408 409 1.2.0 (**2011-10-07**)
409 410 ----------------------
410 411
411 412 news
412 413 ++++
413 414
414 415 - implemented #47 repository groups
415 416 - implemented #89 Can setup google analytics code from settings menu
416 417 - implemented #91 added nicer looking archive urls with more download options
417 418 like tags, branches
418 419 - implemented #44 into file browsing, and added follow branch option
419 420 - implemented #84 downloads can be enabled/disabled for each repository
420 421 - anonymous repository can be cloned without having to pass default:default
421 422 into clone url
422 423 - fixed #90 whoosh indexer can index chooses repositories passed in command
423 424 line
424 425 - extended journal with day aggregates and paging
425 426 - implemented #107 source code lines highlight ranges
426 427 - implemented #93 customizable changelog on combined revision ranges -
427 428 equivalent of githubs compare view
428 429 - implemented #108 extended and more powerful LDAP configuration
429 430 - implemented #56 users groups
430 431 - major code rewrites optimized codes for speed and memory usage
431 432 - raw and diff downloads are now in git format
432 433 - setup command checks for write access to given path
433 434 - fixed many issues with international characters and unicode. It uses utf8
434 435 decode with replace to provide less errors even with non utf8 encoded strings
435 436 - #125 added API KEY access to feeds
436 437 - #109 Repository can be created from external Mercurial link (aka. remote
437 438 repository, and manually updated (via pull) from admin panel
438 439 - beta git support - push/pull server + basic view for git repos
439 440 - added followers page and forks page
440 441 - server side file creation (with binary file upload interface)
441 442 and edition with commits powered by codemirror
442 443 - #111 file browser file finder, quick lookup files on whole file tree
443 444 - added quick login sliding menu into main page
444 445 - changelog uses lazy loading of affected files details, in some scenarios
445 446 this can improve speed of changelog page dramatically especially for
446 447 larger repositories.
447 448 - implements #214 added support for downloading subrepos in download menu.
448 449 - Added basic API for direct operations on rhodecode via JSON
449 450 - Implemented advanced hook management
450 451
451 452 fixes
452 453 +++++
453 454
454 455 - fixed file browser bug, when switching into given form revision the url was
455 456 not changing
456 457 - fixed propagation to error controller on simplehg and simplegit middlewares
457 458 - fixed error when trying to make a download on empty repository
458 459 - fixed problem with '[' chars in commit messages in journal
459 460 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
460 461 - journal fork fixes
461 462 - removed issue with space inside renamed repository after deletion
462 463 - fixed strange issue on formencode imports
463 464 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
464 465 - #150 fixes for errors on repositories mapped in db but corrupted in
465 466 filesystem
466 467 - fixed problem with ascendant characters in realm #181
467 468 - fixed problem with sqlite file based database connection pool
468 469 - whoosh indexer and code stats share the same dynamic extensions map
469 470 - fixes #188 - relationship delete of repo_to_perm entry on user removal
470 471 - fixes issue #189 Trending source files shows "show more" when no more exist
471 472 - fixes issue #197 Relative paths for pidlocks
472 473 - fixes issue #198 password will require only 3 chars now for login form
473 474 - fixes issue #199 wrong redirection for non admin users after creating a repository
474 475 - fixes issues #202, bad db constraint made impossible to attach same group
475 476 more than one time. Affects only mysql/postgres
476 477 - fixes #218 os.kill patch for windows was missing sig param
477 478 - improved rendering of dag (they are not trimmed anymore when number of
478 479 heads exceeds 5)
479 480
480 481 1.1.8 (**2011-04-12**)
481 482 ----------------------
482 483
483 484 news
484 485 ++++
485 486
486 487 - improved windows support
487 488
488 489 fixes
489 490 +++++
490 491
491 492 - fixed #140 freeze of python dateutil library, since new version is python2.x
492 493 incompatible
493 494 - setup-app will check for write permission in given path
494 495 - cleaned up license info issue #149
495 496 - fixes for issues #137,#116 and problems with unicode and accented characters.
496 497 - fixes crashes on gravatar, when passed in email as unicode
497 498 - fixed tooltip flickering problems
498 499 - fixed came_from redirection on windows
499 500 - fixed logging modules, and sql formatters
500 501 - windows fixes for os.kill issue #133
501 502 - fixes path splitting for windows issues #148
502 503 - fixed issue #143 wrong import on migration to 1.1.X
503 504 - fixed problems with displaying binary files, thanks to Thomas Waldmann
504 505 - removed name from archive files since it's breaking ui for long repo names
505 506 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
506 507 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
507 508 Thomas Waldmann
508 509 - fixed issue #166 summary pager was skipping 10 revisions on second page
509 510
510 511
511 512 1.1.7 (**2011-03-23**)
512 513 ----------------------
513 514
514 515 news
515 516 ++++
516 517
517 518 fixes
518 519 +++++
519 520
520 521 - fixed (again) #136 installation support for FreeBSD
521 522
522 523
523 524 1.1.6 (**2011-03-21**)
524 525 ----------------------
525 526
526 527 news
527 528 ++++
528 529
529 530 fixes
530 531 +++++
531 532
532 533 - fixed #136 installation support for FreeBSD
533 534 - RhodeCode will check for python version during installation
534 535
535 536 1.1.5 (**2011-03-17**)
536 537 ----------------------
537 538
538 539 news
539 540 ++++
540 541
541 542 - basic windows support, by exchanging pybcrypt into sha256 for windows only
542 543 highly inspired by idea of mantis406
543 544
544 545 fixes
545 546 +++++
546 547
547 548 - fixed sorting by author in main page
548 549 - fixed crashes with diffs on binary files
549 550 - fixed #131 problem with boolean values for LDAP
550 551 - fixed #122 mysql problems thanks to striker69
551 552 - fixed problem with errors on calling raw/raw_files/annotate functions
552 553 with unknown revisions
553 554 - fixed returned rawfiles attachment names with international character
554 555 - cleaned out docs, big thanks to Jason Harris
555 556
556 557 1.1.4 (**2011-02-19**)
557 558 ----------------------
558 559
559 560 news
560 561 ++++
561 562
562 563 fixes
563 564 +++++
564 565
565 566 - fixed formencode import problem on settings page, that caused server crash
566 567 when that page was accessed as first after server start
567 568 - journal fixes
568 569 - fixed option to access repository just by entering http://server/<repo_name>
569 570
570 571 1.1.3 (**2011-02-16**)
571 572 ----------------------
572 573
573 574 news
574 575 ++++
575 576
576 577 - implemented #102 allowing the '.' character in username
577 578 - added option to access repository just by entering http://server/<repo_name>
578 579 - celery task ignores result for better performance
579 580
580 581 fixes
581 582 +++++
582 583
583 584 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
584 585 apollo13 and Johan Walles
585 586 - small fixes in journal
586 587 - fixed problems with getting setting for celery from .ini files
587 588 - registration, password reset and login boxes share the same title as main
588 589 application now
589 590 - fixed #113: to high permissions to fork repository
590 591 - fixed problem with '[' chars in commit messages in journal
591 592 - removed issue with space inside renamed repository after deletion
592 593 - db transaction fixes when filesystem repository creation failed
593 594 - fixed #106 relation issues on databases different than sqlite
594 595 - fixed static files paths links to use of url() method
595 596
596 597 1.1.2 (**2011-01-12**)
597 598 ----------------------
598 599
599 600 news
600 601 ++++
601 602
602 603
603 604 fixes
604 605 +++++
605 606
606 607 - fixes #98 protection against float division of percentage stats
607 608 - fixed graph bug
608 609 - forced webhelpers version since it was making troubles during installation
609 610
610 611 1.1.1 (**2011-01-06**)
611 612 ----------------------
612 613
613 614 news
614 615 ++++
615 616
616 617 - added force https option into ini files for easier https usage (no need to
617 618 set server headers with this options)
618 619 - small css updates
619 620
620 621 fixes
621 622 +++++
622 623
623 624 - fixed #96 redirect loop on files view on repositories without changesets
624 625 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
625 626 and server crashed with errors
626 627 - fixed large tooltips problems on main page
627 628 - fixed #92 whoosh indexer is more error proof
628 629
629 630 1.1.0 (**2010-12-18**)
630 631 ----------------------
631 632
632 633 news
633 634 ++++
634 635
635 636 - rewrite of internals for vcs >=0.1.10
636 637 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
637 638 with older clients
638 639 - anonymous access, authentication via ldap
639 640 - performance upgrade for cached repos list - each repository has its own
640 641 cache that's invalidated when needed.
641 642 - performance upgrades on repositories with large amount of commits (20K+)
642 643 - main page quick filter for filtering repositories
643 644 - user dashboards with ability to follow chosen repositories actions
644 645 - sends email to admin on new user registration
645 646 - added cache/statistics reset options into repository settings
646 647 - more detailed action logger (based on hooks) with pushed changesets lists
647 648 and options to disable those hooks from admin panel
648 649 - introduced new enhanced changelog for merges that shows more accurate results
649 650 - new improved and faster code stats (based on pygments lexers mapping tables,
650 651 showing up to 10 trending sources for each repository. Additionally stats
651 652 can be disabled in repository settings.
652 653 - gui optimizations, fixed application width to 1024px
653 654 - added cut off (for large files/changesets) limit into config files
654 655 - whoosh, celeryd, upgrade moved to paster command
655 656 - other than sqlite database backends can be used
656 657
657 658 fixes
658 659 +++++
659 660
660 661 - fixes #61 forked repo was showing only after cache expired
661 662 - fixes #76 no confirmation on user deletes
662 663 - fixes #66 Name field misspelled
663 664 - fixes #72 block user removal when he owns repositories
664 665 - fixes #69 added password confirmation fields
665 666 - fixes #87 RhodeCode crashes occasionally on updating repository owner
666 667 - fixes #82 broken annotations on files with more than 1 blank line at the end
667 668 - a lot of fixes and tweaks for file browser
668 669 - fixed detached session issues
669 670 - fixed when user had no repos he would see all repos listed in my account
670 671 - fixed ui() instance bug when global hgrc settings was loaded for server
671 672 instance and all hgrc options were merged with our db ui() object
672 673 - numerous small bugfixes
673 674
674 675 (special thanks for TkSoh for detailed feedback)
675 676
676 677
677 678 1.0.2 (**2010-11-12**)
678 679 ----------------------
679 680
680 681 news
681 682 ++++
682 683
683 684 - tested under python2.7
684 685 - bumped sqlalchemy and celery versions
685 686
686 687 fixes
687 688 +++++
688 689
689 690 - fixed #59 missing graph.js
690 691 - fixed repo_size crash when repository had broken symlinks
691 692 - fixed python2.5 crashes.
692 693
693 694
694 695 1.0.1 (**2010-11-10**)
695 696 ----------------------
696 697
697 698 news
698 699 ++++
699 700
700 701 - small css updated
701 702
702 703 fixes
703 704 +++++
704 705
705 706 - fixed #53 python2.5 incompatible enumerate calls
706 707 - fixed #52 disable mercurial extension for web
707 708 - fixed #51 deleting repositories don't delete it's dependent objects
708 709
709 710
710 711 1.0.0 (**2010-11-02**)
711 712 ----------------------
712 713
713 714 - security bugfix simplehg wasn't checking for permissions on commands
714 715 other than pull or push.
715 716 - fixed doubled messages after push or pull in admin journal
716 717 - templating and css corrections, fixed repo switcher on chrome, updated titles
717 718 - admin menu accessible from options menu on repository view
718 719 - permissions cached queries
719 720
720 721 1.0.0rc4 (**2010-10-12**)
721 722 --------------------------
722 723
723 724 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
724 725 - removed cache_manager settings from sqlalchemy meta
725 726 - added sqlalchemy cache settings to ini files
726 727 - validated password length and added second try of failure on paster setup-app
727 728 - fixed setup database destroy prompt even when there was no db
728 729
729 730
730 731 1.0.0rc3 (**2010-10-11**)
731 732 -------------------------
732 733
733 734 - fixed i18n during installation.
734 735
735 736 1.0.0rc2 (**2010-10-11**)
736 737 -------------------------
737 738
738 739 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
739 740 occure. After vcs is fixed it'll be put back again.
740 741 - templating/css rewrites, optimized css. No newline at end of file
@@ -1,636 +1,636 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.diffs
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Set of diffing helpers, previously part of vcs
7 7
8 8
9 9 :created_on: Dec 4, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :original copyright: 2007-2008 by Armin Ronacher
13 13 :license: GPLv3, see COPYING for more details.
14 14 """
15 15 # This program is free software: you can redistribute it and/or modify
16 16 # it under the terms of the GNU General Public License as published by
17 17 # the Free Software Foundation, either version 3 of the License, or
18 18 # (at your option) any later version.
19 19 #
20 20 # This program is distributed in the hope that it will be useful,
21 21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 23 # GNU General Public License for more details.
24 24 #
25 25 # You should have received a copy of the GNU General Public License
26 26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 27
28 28 import re
29 29 import difflib
30 30 import markupsafe
31 31
32 32 from itertools import tee, imap
33 33
34 34 from mercurial import patch
35 35 from mercurial.mdiff import diffopts
36 36 from mercurial.bundlerepo import bundlerepository
37 37
38 38 from pylons.i18n.translation import _
39 39
40 40 from rhodecode.lib.compat import BytesIO
41 41 from rhodecode.lib.vcs.utils.hgcompat import localrepo
42 42 from rhodecode.lib.vcs.exceptions import VCSError
43 43 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
44 44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 45 from rhodecode.lib.helpers import escape
46 46 from rhodecode.lib.utils import make_ui
47 47
48 48
49 49 def wrap_to_table(str_):
50 50 return '''<table class="code-difftable">
51 51 <tr class="line no-comment">
52 52 <td class="lineno new"></td>
53 53 <td class="code no-comment"><pre>%s</pre></td>
54 54 </tr>
55 55 </table>''' % str_
56 56
57 57
58 58 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
59 59 ignore_whitespace=True, line_context=3,
60 60 enable_comments=False):
61 61 """
62 62 returns a wrapped diff into a table, checks for cut_off_limit and presents
63 63 proper message
64 64 """
65 65
66 66 if filenode_old is None:
67 67 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
68 68
69 69 if filenode_old.is_binary or filenode_new.is_binary:
70 70 diff = wrap_to_table(_('binary file'))
71 71 stats = (0, 0)
72 72 size = 0
73 73
74 74 elif cut_off_limit != -1 and (cut_off_limit is None or
75 75 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
76 76
77 77 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
78 78 ignore_whitespace=ignore_whitespace,
79 79 context=line_context)
80 80 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
81 81
82 82 diff = diff_processor.as_html(enable_comments=enable_comments)
83 83 stats = diff_processor.stat()
84 84 size = len(diff or '')
85 85 else:
86 86 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
87 87 'diff menu to display this diff'))
88 88 stats = (0, 0)
89 89 size = 0
90 90 if not diff:
91 91 submodules = filter(lambda o: isinstance(o, SubModuleNode),
92 92 [filenode_new, filenode_old])
93 93 if submodules:
94 94 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
95 95 else:
96 96 diff = wrap_to_table(_('No changes detected'))
97 97
98 98 cs1 = filenode_old.changeset.raw_id
99 99 cs2 = filenode_new.changeset.raw_id
100 100
101 101 return size, cs1, cs2, diff, stats
102 102
103 103
104 104 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
105 105 """
106 106 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
107 107
108 108 :param ignore_whitespace: ignore whitespaces in diff
109 109 """
110 110 # make sure we pass in default context
111 111 context = context or 3
112 112 submodules = filter(lambda o: isinstance(o, SubModuleNode),
113 113 [filenode_new, filenode_old])
114 114 if submodules:
115 115 return ''
116 116
117 117 for filenode in (filenode_old, filenode_new):
118 118 if not isinstance(filenode, FileNode):
119 119 raise VCSError("Given object should be FileNode object, not %s"
120 120 % filenode.__class__)
121 121
122 122 repo = filenode_new.changeset.repository
123 123 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
124 124 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
125 125
126 126 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
127 127 ignore_whitespace, context)
128 128 return vcs_gitdiff
129 129
130 130
131 131 class DiffProcessor(object):
132 132 """
133 133 Give it a unified diff and it returns a list of the files that were
134 134 mentioned in the diff together with a dict of meta information that
135 135 can be used to render it in a HTML template.
136 136 """
137 137 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
138 138 _newline_marker = '\\ No newline at end of file\n'
139 139
140 140 def __init__(self, diff, differ='diff', format='gitdiff'):
141 141 """
142 142 :param diff: a text in diff format or generator
143 143 :param format: format of diff passed, `udiff` or `gitdiff`
144 144 """
145 145 if isinstance(diff, basestring):
146 146 diff = [diff]
147 147
148 148 self.__udiff = diff
149 149 self.__format = format
150 150 self.adds = 0
151 151 self.removes = 0
152 152
153 153 if isinstance(self.__udiff, basestring):
154 154 self.lines = iter(self.__udiff.splitlines(1))
155 155
156 156 elif self.__format == 'gitdiff':
157 157 udiff_copy = self.copy_iterator()
158 158 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
159 159 else:
160 160 udiff_copy = self.copy_iterator()
161 161 self.lines = imap(self.escaper, udiff_copy)
162 162
163 163 # Select a differ.
164 164 if differ == 'difflib':
165 165 self.differ = self._highlight_line_difflib
166 166 else:
167 167 self.differ = self._highlight_line_udiff
168 168
169 169 def escaper(self, string):
170 170 return markupsafe.escape(string)
171 171
172 172 def copy_iterator(self):
173 173 """
174 174 make a fresh copy of generator, we should not iterate thru
175 175 an original as it's needed for repeating operations on
176 176 this instance of DiffProcessor
177 177 """
178 178 self.__udiff, iterator_copy = tee(self.__udiff)
179 179 return iterator_copy
180 180
181 181 def _extract_rev(self, line1, line2):
182 182 """
183 183 Extract the operation (A/M/D), filename and revision hint from a line.
184 184 """
185 185
186 186 try:
187 187 if line1.startswith('--- ') and line2.startswith('+++ '):
188 188 l1 = line1[4:].split(None, 1)
189 189 old_filename = (l1[0].replace('a/', '', 1)
190 190 if len(l1) >= 1 else None)
191 191 old_rev = l1[1] if len(l1) == 2 else 'old'
192 192
193 193 l2 = line2[4:].split(None, 1)
194 194 new_filename = (l2[0].replace('b/', '', 1)
195 195 if len(l1) >= 1 else None)
196 196 new_rev = l2[1] if len(l2) == 2 else 'new'
197 197
198 198 filename = (old_filename
199 199 if old_filename != '/dev/null' else new_filename)
200 200
201 201 operation = 'D' if new_filename == '/dev/null' else None
202 202 if not operation:
203 203 operation = 'M' if old_filename != '/dev/null' else 'A'
204 204
205 205 return operation, filename, new_rev, old_rev
206 206 except (ValueError, IndexError):
207 207 pass
208 208
209 209 return None, None, None, None
210 210
211 211 def _parse_gitdiff(self, diffiterator):
212 212 def line_decoder(l):
213 213 if l.startswith('+') and not l.startswith('+++'):
214 214 self.adds += 1
215 215 elif l.startswith('-') and not l.startswith('---'):
216 216 self.removes += 1
217 217 return l.decode('utf8', 'replace')
218 218
219 219 output = list(diffiterator)
220 220 size = len(output)
221 221
222 222 if size == 2:
223 223 l = []
224 224 l.extend([output[0]])
225 225 l.extend(output[1].splitlines(1))
226 226 return map(line_decoder, l)
227 227 elif size == 1:
228 228 return map(line_decoder, output[0].splitlines(1))
229 229 elif size == 0:
230 230 return []
231 231
232 232 raise Exception('wrong size of diff %s' % size)
233 233
234 234 def _highlight_line_difflib(self, line, next_):
235 235 """
236 236 Highlight inline changes in both lines.
237 237 """
238 238
239 239 if line['action'] == 'del':
240 240 old, new = line, next_
241 241 else:
242 242 old, new = next_, line
243 243
244 244 oldwords = re.split(r'(\W)', old['line'])
245 245 newwords = re.split(r'(\W)', new['line'])
246 246
247 247 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
248 248
249 249 oldfragments, newfragments = [], []
250 250 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
251 251 oldfrag = ''.join(oldwords[i1:i2])
252 252 newfrag = ''.join(newwords[j1:j2])
253 253 if tag != 'equal':
254 254 if oldfrag:
255 255 oldfrag = '<del>%s</del>' % oldfrag
256 256 if newfrag:
257 257 newfrag = '<ins>%s</ins>' % newfrag
258 258 oldfragments.append(oldfrag)
259 259 newfragments.append(newfrag)
260 260
261 261 old['line'] = "".join(oldfragments)
262 262 new['line'] = "".join(newfragments)
263 263
264 264 def _highlight_line_udiff(self, line, next_):
265 265 """
266 266 Highlight inline changes in both lines.
267 267 """
268 268 start = 0
269 269 limit = min(len(line['line']), len(next_['line']))
270 270 while start < limit and line['line'][start] == next_['line'][start]:
271 271 start += 1
272 272 end = -1
273 273 limit -= start
274 274 while -end <= limit and line['line'][end] == next_['line'][end]:
275 275 end -= 1
276 276 end += 1
277 277 if start or end:
278 278 def do(l):
279 279 last = end + len(l['line'])
280 280 if l['action'] == 'add':
281 281 tag = 'ins'
282 282 else:
283 283 tag = 'del'
284 284 l['line'] = '%s<%s>%s</%s>%s' % (
285 285 l['line'][:start],
286 286 tag,
287 287 l['line'][start:last],
288 288 tag,
289 289 l['line'][last:]
290 290 )
291 291 do(line)
292 292 do(next_)
293 293
294 294 def _parse_udiff(self, inline_diff=True):
295 295 """
296 296 Parse the diff an return data for the template.
297 297 """
298 298 lineiter = self.lines
299 299 files = []
300 300 try:
301 301 line = lineiter.next()
302 302 while 1:
303 303 # continue until we found the old file
304 304 if not line.startswith('--- '):
305 305 line = lineiter.next()
306 306 continue
307 307
308 308 chunks = []
309 309 stats = [0, 0]
310 310 operation, filename, old_rev, new_rev = \
311 311 self._extract_rev(line, lineiter.next())
312 312 files.append({
313 313 'filename': filename,
314 314 'old_revision': old_rev,
315 315 'new_revision': new_rev,
316 316 'chunks': chunks,
317 317 'operation': operation,
318 318 'stats': stats,
319 319 })
320 320
321 321 line = lineiter.next()
322 322 while line:
323 323 match = self._chunk_re.match(line)
324 324 if not match:
325 325 break
326 326
327 327 lines = []
328 328 chunks.append(lines)
329 329
330 330 old_line, old_end, new_line, new_end = \
331 331 [int(x or 1) for x in match.groups()[:-1]]
332 332 old_line -= 1
333 333 new_line -= 1
334 334 gr = match.groups()
335 335 context = len(gr) == 5
336 336 old_end += old_line
337 337 new_end += new_line
338 338
339 339 if context:
340 340 # skip context only if it's first line
341 341 if int(gr[0]) > 1:
342 342 lines.append({
343 343 'old_lineno': '...',
344 344 'new_lineno': '...',
345 345 'action': 'context',
346 346 'line': line,
347 347 })
348 348
349 349 line = lineiter.next()
350 350
351 351 while old_line < old_end or new_line < new_end:
352 352 if line:
353 353 command = line[0]
354 354 if command in ['+', '-', ' ']:
355 355 #only modify the line if it's actually a diff
356 356 # thing
357 357 line = line[1:]
358 358 else:
359 359 command = ' '
360 360
361 361 affects_old = affects_new = False
362 362
363 363 # ignore those if we don't expect them
364 364 if command in '#@':
365 365 continue
366 366 elif command == '+':
367 367 affects_new = True
368 368 action = 'add'
369 369 stats[0] += 1
370 370 elif command == '-':
371 371 affects_old = True
372 372 action = 'del'
373 373 stats[1] += 1
374 374 else:
375 375 affects_old = affects_new = True
376 376 action = 'unmod'
377 377
378 378 if line != self._newline_marker:
379 379 old_line += affects_old
380 380 new_line += affects_new
381 381 lines.append({
382 382 'old_lineno': affects_old and old_line or '',
383 383 'new_lineno': affects_new and new_line or '',
384 384 'action': action,
385 385 'line': line
386 386 })
387 387
388 388 line = lineiter.next()
389 389 if line == self._newline_marker:
390 390 # we need to append to lines, since this is not
391 391 # counted in the line specs of diff
392 392 lines.append({
393 393 'old_lineno': '...',
394 394 'new_lineno': '...',
395 395 'action': 'context',
396 396 'line': line
397 397 })
398 398
399 399 except StopIteration:
400 400 pass
401 401
402 402 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
403 403 if inline_diff is False:
404 404 return sorted(files, key=sorter)
405 405
406 406 # highlight inline changes
407 407 for diff_data in files:
408 408 for chunk in diff_data['chunks']:
409 409 lineiter = iter(chunk)
410 410 try:
411 411 while 1:
412 412 line = lineiter.next()
413 413 if line['action'] not in ['unmod', 'context']:
414 414 nextline = lineiter.next()
415 415 if nextline['action'] in ['unmod', 'context'] or \
416 416 nextline['action'] == line['action']:
417 417 continue
418 418 self.differ(line, nextline)
419 419 except StopIteration:
420 420 pass
421 421
422 422 return sorted(files, key=sorter)
423 423
424 424 def prepare(self, inline_diff=True):
425 425 """
426 426 Prepare the passed udiff for HTML rendering. It'l return a list
427 427 of dicts
428 428 """
429 429 return self._parse_udiff(inline_diff=inline_diff)
430 430
431 431 def _safe_id(self, idstring):
432 432 """Make a string safe for including in an id attribute.
433 433
434 434 The HTML spec says that id attributes 'must begin with
435 435 a letter ([A-Za-z]) and may be followed by any number
436 436 of letters, digits ([0-9]), hyphens ("-"), underscores
437 437 ("_"), colons (":"), and periods (".")'. These regexps
438 438 are slightly over-zealous, in that they remove colons
439 439 and periods unnecessarily.
440 440
441 441 Whitespace is transformed into underscores, and then
442 442 anything which is not a hyphen or a character that
443 443 matches \w (alphanumerics and underscore) is removed.
444 444
445 445 """
446 446 # Transform all whitespace to underscore
447 447 idstring = re.sub(r'\s', "_", '%s' % idstring)
448 448 # Remove everything that is not a hyphen or a member of \w
449 449 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
450 450 return idstring
451 451
452 452 def raw_diff(self):
453 453 """
454 454 Returns raw string as udiff
455 455 """
456 456 udiff_copy = self.copy_iterator()
457 457 if self.__format == 'gitdiff':
458 458 udiff_copy = self._parse_gitdiff(udiff_copy)
459 459 return u''.join(udiff_copy)
460 460
461 461 def as_html(self, table_class='code-difftable', line_class='line',
462 462 new_lineno_class='lineno old', old_lineno_class='lineno new',
463 463 code_class='code', enable_comments=False, diff_lines=None):
464 464 """
465 465 Return given diff as html table with customized css classes
466 466 """
467 467 def _link_to_if(condition, label, url):
468 468 """
469 469 Generates a link if condition is meet or just the label if not.
470 470 """
471 471
472 472 if condition:
473 473 return '''<a href="%(url)s">%(label)s</a>''' % {
474 474 'url': url,
475 475 'label': label
476 476 }
477 477 else:
478 478 return label
479 479 if diff_lines is None:
480 480 diff_lines = self.prepare()
481 481 _html_empty = True
482 482 _html = []
483 483 _html.append('''<table class="%(table_class)s">\n''' % {
484 484 'table_class': table_class
485 485 })
486 486 for diff in diff_lines:
487 487 for line in diff['chunks']:
488 488 _html_empty = False
489 489 for change in line:
490 490 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
491 491 'lc': line_class,
492 492 'action': change['action']
493 493 })
494 494 anchor_old_id = ''
495 495 anchor_new_id = ''
496 496 anchor_old = "%(filename)s_o%(oldline_no)s" % {
497 497 'filename': self._safe_id(diff['filename']),
498 498 'oldline_no': change['old_lineno']
499 499 }
500 500 anchor_new = "%(filename)s_n%(oldline_no)s" % {
501 501 'filename': self._safe_id(diff['filename']),
502 502 'oldline_no': change['new_lineno']
503 503 }
504 504 cond_old = (change['old_lineno'] != '...' and
505 505 change['old_lineno'])
506 506 cond_new = (change['new_lineno'] != '...' and
507 507 change['new_lineno'])
508 508 if cond_old:
509 509 anchor_old_id = 'id="%s"' % anchor_old
510 510 if cond_new:
511 511 anchor_new_id = 'id="%s"' % anchor_new
512 512 ###########################################################
513 513 # OLD LINE NUMBER
514 514 ###########################################################
515 515 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
516 516 'a_id': anchor_old_id,
517 517 'olc': old_lineno_class
518 518 })
519 519
520 520 _html.append('''%(link)s''' % {
521 521 'link': _link_to_if(True, change['old_lineno'],
522 522 '#%s' % anchor_old)
523 523 })
524 524 _html.append('''</td>\n''')
525 525 ###########################################################
526 526 # NEW LINE NUMBER
527 527 ###########################################################
528 528
529 529 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
530 530 'a_id': anchor_new_id,
531 531 'nlc': new_lineno_class
532 532 })
533 533
534 534 _html.append('''%(link)s''' % {
535 535 'link': _link_to_if(True, change['new_lineno'],
536 536 '#%s' % anchor_new)
537 537 })
538 538 _html.append('''</td>\n''')
539 539 ###########################################################
540 540 # CODE
541 541 ###########################################################
542 542 comments = '' if enable_comments else 'no-comment'
543 543 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
544 544 'cc': code_class,
545 545 'inc': comments
546 546 })
547 547 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
548 548 'code': change['line']
549 549 })
550 550 _html.append('''\t</td>''')
551 551 _html.append('''\n</tr>\n''')
552 552 _html.append('''</table>''')
553 553 if _html_empty:
554 554 return None
555 555 return ''.join(_html)
556 556
557 557 def stat(self):
558 558 """
559 559 Returns tuple of added, and removed lines for this instance
560 560 """
561 561 return self.adds, self.removes
562 562
563 563
564 564 class InMemoryBundleRepo(bundlerepository):
565 565 def __init__(self, ui, path, bundlestream):
566 566 self._tempparent = None
567 567 localrepo.localrepository.__init__(self, ui, path)
568 568 self.ui.setconfig('phases', 'publish', False)
569 569
570 570 self.bundle = bundlestream
571 571
572 572 # dict with the mapping 'filename' -> position in the bundle
573 573 self.bundlefilespos = {}
574 574
575 575
576 576 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None):
577 577 """
578 578 General differ between branches, bookmarks or separate but releated
579 579 repositories
580 580
581 581 :param org_repo:
582 582 :type org_repo:
583 583 :param org_ref:
584 584 :type org_ref:
585 585 :param other_repo:
586 586 :type other_repo:
587 587 :param other_ref:
588 588 :type other_ref:
589 589 """
590 590
591 591 bundlerepo = None
592 592 ignore_whitespace = False
593 593 context = 3
594 594 org_repo = org_repo.scm_instance._repo
595 595 other_repo = other_repo.scm_instance._repo
596 596 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
597 597 org_ref = org_ref[1]
598 598 other_ref = other_ref[1]
599 599
600 600 if org_repo != other_repo:
601 601
602 602 common, incoming, rheads = discovery_data
603 603 other_repo_peer = localrepo.locallegacypeer(other_repo.local())
604 604 # create a bundle (uncompressed if other repo is not local)
605 605 if other_repo_peer.capable('getbundle') and incoming:
606 606 # disable repo hooks here since it's just bundle !
607 607 # patch and reset hooks section of UI config to not run any
608 608 # hooks on fetching archives with subrepos
609 609 for k, _ in other_repo.ui.configitems('hooks'):
610 610 other_repo.ui.setconfig('hooks', k, None)
611 611
612 612 unbundle = other_repo.getbundle('incoming', common=common,
613 heads=rheads)
613 heads=None)
614 614
615 615 buf = BytesIO()
616 616 while True:
617 617 chunk = unbundle._stream.read(1024 * 4)
618 618 if not chunk:
619 619 break
620 620 buf.write(chunk)
621 621
622 622 buf.seek(0)
623 623 # replace chunked _stream with data that can do tell() and seek()
624 624 unbundle._stream = buf
625 625
626 626 ui = make_ui('db')
627 627 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
628 628 bundlestream=unbundle)
629 629
630 630 return ''.join(patch.diff(bundlerepo or org_repo,
631 631 node1=org_repo[org_ref].node(),
632 632 node2=other_repo[other_ref].node(),
633 633 opts=opts))
634 634 else:
635 635 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
636 636 opts=opts))
@@ -1,17 +1,18 b''
1 1 """
2 2 Mercurial libs compatibility
3 3 """
4 4
5 5 from mercurial import archival, merge as hg_merge, patch, ui
6 6 from mercurial.commands import clone, nullid, pull
7 7 from mercurial.context import memctx, memfilectx
8 8 from mercurial.error import RepoError, RepoLookupError, Abort
9 9 from mercurial.hgweb.common import get_contact
10 10 from mercurial.localrepo import localrepository
11 11 from mercurial.match import match
12 12 from mercurial.mdiff import diffopts
13 13 from mercurial.node import hex
14 14 from mercurial.encoding import tolocal
15 15 from mercurial import discovery
16 16 from mercurial import localrepo
17 from mercurial import scmutil No newline at end of file
17 from mercurial import scmutil
18 from mercurial.discovery import findcommonoutgoing No newline at end of file
@@ -1,257 +1,260 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.pull_request
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 pull request model for RhodeCode
7 7
8 8 :created_on: Jun 6, 2012
9 9 :author: marcink
10 10 :copyright: (C) 2012-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 binascii
28 28 import datetime
29 29
30 30 from pylons.i18n.translation import _
31 31
32 32 from rhodecode.model.meta import Session
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.model import BaseModel
35 35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
36 36 from rhodecode.model.notification import NotificationModel
37 37 from rhodecode.lib.utils2 import safe_unicode
38 38
39 from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil
39 from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil, \
40 findcommonoutgoing
40 41
41 42 log = logging.getLogger(__name__)
42 43
43 44
44 45 class PullRequestModel(BaseModel):
45 46
46 47 cls = PullRequest
47 48
48 49 def __get_pull_request(self, pull_request):
49 50 return self._get_instance(PullRequest, pull_request)
50 51
51 52 def get_all(self, repo):
52 53 repo = self._get_repo(repo)
53 54 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
54 55
55 56 def create(self, created_by, org_repo, org_ref, other_repo,
56 57 other_ref, revisions, reviewers, title, description=None):
57 58
58 59 created_by_user = self._get_user(created_by)
59 60 org_repo = self._get_repo(org_repo)
60 61 other_repo = self._get_repo(other_repo)
61 62
62 63 new = PullRequest()
63 64 new.org_repo = org_repo
64 65 new.org_ref = org_ref
65 66 new.other_repo = other_repo
66 67 new.other_ref = other_ref
67 68 new.revisions = revisions
68 69 new.title = title
69 70 new.description = description
70 71 new.author = created_by_user
71 72 self.sa.add(new)
72 73 Session().flush()
73 74 #members
74 75 for member in reviewers:
75 76 _usr = self._get_user(member)
76 77 reviewer = PullRequestReviewers(_usr, new)
77 78 self.sa.add(reviewer)
78 79
79 80 #notification to reviewers
80 81 notif = NotificationModel()
81 82
82 83 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
83 84 pull_request_id=new.pull_request_id,
84 85 qualified=True,
85 86 )
86 87 subject = safe_unicode(
87 88 h.link_to(
88 89 _('%(user)s wants you to review pull request #%(pr_id)s') % \
89 90 {'user': created_by_user.username,
90 91 'pr_id': new.pull_request_id},
91 92 pr_url
92 93 )
93 94 )
94 95 body = description
95 96 kwargs = {
96 97 'pr_title': title,
97 98 'pr_user_created': h.person(created_by_user.email),
98 99 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
99 100 qualified=True,),
100 101 'pr_url': pr_url,
101 102 'pr_revisions': revisions
102 103 }
103 104 notif.create(created_by=created_by_user, subject=subject, body=body,
104 105 recipients=reviewers,
105 106 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
106 107 return new
107 108
108 109 def update_reviewers(self, pull_request, reviewers_ids):
109 110 reviewers_ids = set(reviewers_ids)
110 111 pull_request = self.__get_pull_request(pull_request)
111 112 current_reviewers = PullRequestReviewers.query()\
112 113 .filter(PullRequestReviewers.pull_request==
113 114 pull_request)\
114 115 .all()
115 116 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
116 117
117 118 to_add = reviewers_ids.difference(current_reviewers_ids)
118 119 to_remove = current_reviewers_ids.difference(reviewers_ids)
119 120
120 121 log.debug("Adding %s reviewers" % to_add)
121 122 log.debug("Removing %s reviewers" % to_remove)
122 123
123 124 for uid in to_add:
124 125 _usr = self._get_user(uid)
125 126 reviewer = PullRequestReviewers(_usr, pull_request)
126 127 self.sa.add(reviewer)
127 128
128 129 for uid in to_remove:
129 130 reviewer = PullRequestReviewers.query()\
130 131 .filter(PullRequestReviewers.user_id==uid,
131 132 PullRequestReviewers.pull_request==pull_request)\
132 133 .scalar()
133 134 if reviewer:
134 135 self.sa.delete(reviewer)
135 136
136 137 def delete(self, pull_request):
137 138 pull_request = self.__get_pull_request(pull_request)
138 139 Session().delete(pull_request)
139 140
140 141 def close_pull_request(self, pull_request):
141 142 pull_request = self.__get_pull_request(pull_request)
142 143 pull_request.status = PullRequest.STATUS_CLOSED
143 144 pull_request.updated_on = datetime.datetime.now()
144 145 self.sa.add(pull_request)
145 146
146 147 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref,
147 148 discovery_data):
148 149 """
149 150 Returns a list of changesets that are incoming from org_repo@org_ref
150 151 to other_repo@other_ref
151 152
152 153 :param org_repo:
153 154 :type org_repo:
154 155 :param org_ref:
155 156 :type org_ref:
156 157 :param other_repo:
157 158 :type other_repo:
158 159 :param other_ref:
159 160 :type other_ref:
160 161 :param tmp:
161 162 :type tmp:
162 163 """
163 164 changesets = []
164 165 #case two independent repos
165 166 common, incoming, rheads = discovery_data
166 167 if org_repo != other_repo and incoming:
167 revs = org_repo._repo.changelog.findmissing(common, rheads)
168 obj = findcommonoutgoing(org_repo._repo,
169 localrepo.locallegacypeer(other_repo._repo.local()))
170 revs = obj.missing
168 171
169 172 for cs in reversed(map(binascii.hexlify, revs)):
170 173 changesets.append(org_repo.get_changeset(cs))
171 174 else:
172 175 _revset_predicates = {
173 176 'branch': 'branch',
174 177 'book': 'bookmark',
175 178 'tag': 'tag',
176 179 'rev': 'id',
177 180 }
178 181
179 182 revs = [
180 183 "ancestors(%s('%s')) and not ancestors(%s('%s'))" % (
181 184 _revset_predicates[org_ref[0]], org_ref[1],
182 185 _revset_predicates[other_ref[0]], other_ref[1]
183 186 )
184 187 ]
185 188
186 189 out = scmutil.revrange(org_repo._repo, revs)
187 190 for cs in reversed(out):
188 191 changesets.append(org_repo.get_changeset(cs))
189 192
190 193 return changesets
191 194
192 195 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
193 196 """
194 197 Get's mercurial discovery data used to calculate difference between
195 198 repos and refs
196 199
197 200 :param org_repo:
198 201 :type org_repo:
199 202 :param org_ref:
200 203 :type org_ref:
201 204 :param other_repo:
202 205 :type other_repo:
203 206 :param other_ref:
204 207 :type other_ref:
205 208 """
206 209
207 210 _org_repo = org_repo._repo
208 211 org_rev_type, org_rev = org_ref
209 212
210 213 _other_repo = other_repo._repo
211 214 other_rev_type, other_rev = other_ref
212 215
213 216 log.debug('Doing discovery for %s@%s vs %s@%s' % (
214 217 org_repo, org_ref, other_repo, other_ref)
215 218 )
216 219 #log.debug('Filter heads are %s[%s]' % ('', org_ref[1]))
217 220 org_peer = localrepo.locallegacypeer(_org_repo.local())
218 221 tmp = discovery.findcommonincoming(
219 222 repo=_other_repo, # other_repo we check for incoming
220 223 remote=org_peer, # org_repo source for incoming
221 224 heads=[_other_repo[other_rev].node(),
222 225 _org_repo[org_rev].node()],
223 226 force=False
224 227 )
225 228 return tmp
226 229
227 230 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
228 231 """
229 232 Returns a tuple of incomming changesets, and discoverydata cache
230 233
231 234 :param org_repo:
232 235 :type org_repo:
233 236 :param org_ref:
234 237 :type org_ref:
235 238 :param other_repo:
236 239 :type other_repo:
237 240 :param other_ref:
238 241 :type other_ref:
239 242 """
240 243
241 244 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
242 245 raise Exception('org_ref must be a two element list/tuple')
243 246
244 247 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
245 248 raise Exception('other_ref must be a two element list/tuple')
246 249
247 250 discovery_data = self._get_discovery(org_repo.scm_instance,
248 251 org_ref,
249 252 other_repo.scm_instance,
250 253 other_ref)
251 254 cs_ranges = self._get_changesets(org_repo.scm_instance,
252 255 org_ref,
253 256 other_repo.scm_instance,
254 257 other_ref,
255 258 discovery_data)
256 259
257 260 return cs_ranges, discovery_data
@@ -1,189 +1,293 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.repo import RepoModel
3 3 from rhodecode.model.meta import Session
4 4 from rhodecode.model.db import Repository
5 5 from rhodecode.model.scm import ScmModel
6 6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
7 7
8 8
9 9 class TestCompareController(TestController):
10 10
11 11 def test_index_tag(self):
12 12 self.log_user()
13 13 tag1 = '0.1.3'
14 14 tag2 = '0.1.2'
15 15 response = self.app.get(url(controller='compare', action='index',
16 16 repo_name=HG_REPO,
17 17 org_ref_type="tag",
18 18 org_ref=tag1,
19 19 other_ref_type="tag",
20 20 other_ref=tag2,
21 21 ))
22 22 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
23 23 ## outgoing changesets between tags
24 24 response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
25 25 response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
26 26 response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
27 27 response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
28 28 response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
29 29 response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
30 30 response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
31 31
32 32 ## files diff
33 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
34 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
35 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
36 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
37 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
38 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
39 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
40 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
41 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
42 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
43 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
33 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
34 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
35 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
36 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
37 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
38 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
39 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
40 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
41 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
42 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
43 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
44 44
45 45 def test_index_branch(self):
46 46 self.log_user()
47 47 response = self.app.get(url(controller='compare', action='index',
48 48 repo_name=HG_REPO,
49 49 org_ref_type="branch",
50 50 org_ref='default',
51 51 other_ref_type="branch",
52 52 other_ref='default',
53 53 ))
54 54
55 55 response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO))
56 56 # branch are equal
57 57 response.mustcontain('<tr><td>No changesets</td></tr>')
58 58
59 59 def test_compare_revisions(self):
60 60 self.log_user()
61 61 rev1 = '3d8f361e72ab'
62 62 rev2 = 'b986218ba1c9'
63 63 response = self.app.get(url(controller='compare', action='index',
64 64 repo_name=HG_REPO,
65 65 org_ref_type="rev",
66 66 org_ref=rev1,
67 67 other_ref_type="rev",
68 68 other_ref=rev2,
69 69 ))
70 70 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
71 71 ## outgoing changesets between those revisions
72 72 response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev1))
73 73
74 74 ## files
75 75 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (HG_REPO, rev1, rev2))
76 76
77 77 def test_compare_remote_repos(self):
78 78 self.log_user()
79 79
80 80 form_data = dict(
81 81 repo_name=HG_FORK,
82 82 repo_name_full=HG_FORK,
83 83 repo_group=None,
84 84 repo_type='hg',
85 85 description='',
86 86 private=False,
87 87 copy_permissions=False,
88 88 landing_rev='tip',
89 89 update_after_clone=False,
90 90 fork_parent_id=Repository.get_by_repo_name(HG_REPO),
91 91 )
92 92 RepoModel().create_fork(form_data, cur_user=TEST_USER_ADMIN_LOGIN)
93 93
94 94 Session().commit()
95 95
96 96 rev1 = '7d4bc8ec6be5'
97 97 rev2 = '56349e29c2af'
98 98
99 99 response = self.app.get(url(controller='compare', action='index',
100 100 repo_name=HG_REPO,
101 101 org_ref_type="rev",
102 102 org_ref=rev1,
103 103 other_ref_type="rev",
104 104 other_ref=rev2,
105 105 repo=HG_FORK
106 106 ))
107 107
108 108 try:
109 109 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
110 110 ## outgoing changesets between those revisions
111 111
112 112 response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_REPO, rev1))
113 113 response.mustcontain("""<a href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_REPO))
114 114 response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO))
115 115
116 116 ## files
117 117 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2))
118 118 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2))
119 119 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2))
120 120 finally:
121 121 RepoModel().delete(HG_FORK)
122 122
123 123 def test_compare_extra_commits(self):
124 124 self.log_user()
125 125
126 126 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
127 127 description='diff-test',
128 128 owner=TEST_USER_ADMIN_LOGIN)
129 129
130 130 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
131 131 description='diff-test',
132 132 owner=TEST_USER_ADMIN_LOGIN)
133 133
134 134 Session().commit()
135 135 r1_id = repo1.repo_id
136 136 r1_name = repo1.repo_name
137 137 r2_id = repo2.repo_id
138 138 r2_name = repo2.repo_name
139 139
140 140 #commit something !
141 141 cs0 = ScmModel().create_node(
142 142 repo=repo1.scm_instance, repo_name=r1_name,
143 143 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
144 144 author=TEST_USER_ADMIN_LOGIN,
145 145 message='commit1',
146 146 content='line1',
147 147 f_path='file1'
148 148 )
149 149
150 150 cs0_prim = ScmModel().create_node(
151 151 repo=repo2.scm_instance, repo_name=r2_name,
152 152 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
153 153 author=TEST_USER_ADMIN_LOGIN,
154 154 message='commit1',
155 155 content='line1',
156 156 f_path='file1'
157 157 )
158 158
159 159 cs1 = ScmModel().commit_change(
160 160 repo=repo2.scm_instance, repo_name=r2_name,
161 161 cs=cs0_prim, user=TEST_USER_ADMIN_LOGIN, author=TEST_USER_ADMIN_LOGIN,
162 162 message='commit2',
163 163 content='line1\nline2',
164 164 f_path='file1'
165 165 )
166 166
167 167 rev1 = 'default'
168 168 rev2 = 'default'
169 169 response = self.app.get(url(controller='compare', action='index',
170 170 repo_name=r2_name,
171 171 org_ref_type="branch",
172 172 org_ref=rev1,
173 173 other_ref_type="branch",
174 174 other_ref=rev2,
175 175 repo=r1_name
176 176 ))
177 177
178 178 try:
179 179 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
180 180
181 181 response.mustcontain("""<div class="message">commit2</div>""")
182 182 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (r2_name, cs1.raw_id, cs1.short_id))
183 183 ## files
184 184 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s#C--826e8142e6ba">file1</a>""" % (r2_name, rev1, rev2))
185 185
186
187 186 finally:
188 187 RepoModel().delete(r1_id)
189 188 RepoModel().delete(r2_id)
189
190 def test_org_repo_new_commits_after_forking(self):
191 self.log_user()
192
193 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
194 description='diff-test',
195 owner=TEST_USER_ADMIN_LOGIN)
196
197 Session().commit()
198 r1_id = repo1.repo_id
199 r1_name = repo1.repo_name
200
201 #commit something initially !
202 cs0 = ScmModel().create_node(
203 repo=repo1.scm_instance, repo_name=r1_name,
204 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
205 author=TEST_USER_ADMIN_LOGIN,
206 message='commit1',
207 content='line1',
208 f_path='file1'
209 )
210 Session().commit()
211 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
212 #fork the repo1
213 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
214 description='compare-test',
215 clone_uri=repo1.repo_full_path,
216 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
217 Session().commit()
218 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
219 r2_id = repo2.repo_id
220 r2_name = repo2.repo_name
221
222 #make 3 new commits in fork
223 cs1 = ScmModel().create_node(
224 repo=repo2.scm_instance, repo_name=r2_name,
225 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
226 author=TEST_USER_ADMIN_LOGIN,
227 message='commit1-fork',
228 content='file1-line1-from-fork',
229 f_path='file1-fork'
230 )
231 cs2 = ScmModel().create_node(
232 repo=repo2.scm_instance, repo_name=r2_name,
233 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
234 author=TEST_USER_ADMIN_LOGIN,
235 message='commit2-fork',
236 content='file2-line1-from-fork',
237 f_path='file2-fork'
238 )
239 cs3 = ScmModel().create_node(
240 repo=repo2.scm_instance, repo_name=r2_name,
241 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
242 author=TEST_USER_ADMIN_LOGIN,
243 message='commit3-fork',
244 content='file3-line1-from-fork',
245 f_path='file3-fork'
246 )
247
248 #compare !
249 rev1 = 'default'
250 rev2 = 'default'
251 response = self.app.get(url(controller='compare', action='index',
252 repo_name=r2_name,
253 org_ref_type="branch",
254 org_ref=rev1,
255 other_ref_type="branch",
256 other_ref=rev2,
257 repo=r1_name
258 ))
259
260 try:
261 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
262 response.mustcontain("""file1-line1-from-fork""")
263 response.mustcontain("""file2-line1-from-fork""")
264 response.mustcontain("""file3-line1-from-fork""")
265
266 #add new commit into parent !
267 cs0 = ScmModel().create_node(
268 repo=repo1.scm_instance, repo_name=r1_name,
269 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
270 author=TEST_USER_ADMIN_LOGIN,
271 message='commit2',
272 content='line1',
273 f_path='file2'
274 )
275 #compare !
276 rev1 = 'default'
277 rev2 = 'default'
278 response = self.app.get(url(controller='compare', action='index',
279 repo_name=r2_name,
280 org_ref_type="branch",
281 org_ref=rev1,
282 other_ref_type="branch",
283 other_ref=rev2,
284 repo=r1_name
285 ))
286
287 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
288 response.mustcontain("""file1-line1-from-fork""")
289 response.mustcontain("""file2-line1-from-fork""")
290 response.mustcontain("""file3-line1-from-fork""")
291 finally:
292 RepoModel().delete(r1_id)
293 RepoModel().delete(r2_id)
General Comments 0
You need to be logged in to leave comments. Login now