##// END OF EJS Templates
git forks were not created as bare repos
marcink -
r2807:57456b1c beta
parent child Browse files
Show More
@@ -1,742 +1,743 b''
1 1 .. _changelog:
2 2
3 3 =========
4 4 Changelog
5 5 =========
6 6
7 7
8 8 1.4.1 (**2012-09-05**)
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 34 additional not-common changesets
35 35 - fixed status of code-review in preview windows of pull request
36 - git forks were not initialized at bare repos
36 37
37 38 1.4.0 (**2012-09-03**)
38 39 ----------------------
39 40
40 41 news
41 42 ++++
42 43
43 44 - new codereview system
44 45 - email map, allowing users to have multiple email addresses mapped into
45 46 their accounts
46 47 - improved git-hook system. Now all actions for git are logged into journal
47 48 including pushed revisions, user and IP address
48 49 - changed setup-app into setup-rhodecode and added default options to it.
49 50 - new git repos are created as bare now by default
50 51 - #464 added links to groups in permission box
51 52 - #465 mentions autocomplete inside comments boxes
52 53 - #469 added --update-only option to whoosh to re-index only given list
53 54 of repos in index
54 55 - rhodecode-api CLI client
55 56 - new git http protocol replaced buggy dulwich implementation.
56 57 Now based on pygrack & gitweb
57 58 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
58 59 reformated based on user suggestions. Additional rss/atom feeds for user
59 60 journal
60 61 - various i18n improvements
61 62 - #478 permissions overview for admin in user edit view
62 63 - File view now displays small gravatars off all authors of given file
63 64 - Implemented landing revisions. Each repository will get landing_rev attribute
64 65 that defines 'default' revision/branch for generating readme files
65 66 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
66 67 earliest possible call.
67 68 - Import remote svn repositories to mercurial using hgsubversion.
68 69 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
69 70 - RhodeCode can use alternative server for generating avatar icons
70 71 - implemented repositories locking. Pull locks, push unlocks. Also can be done
71 72 via API calls
72 73 - #538 form for permissions can handle multiple users at once
73 74
74 75 fixes
75 76 +++++
76 77
77 78 - improved translations
78 79 - fixes issue #455 Creating an archive generates an exception on Windows
79 80 - fixes #448 Download ZIP archive keeps file in /tmp open and results
80 81 in out of disk space
81 82 - fixes issue #454 Search results under Windows include proceeding
82 83 backslash
83 84 - fixed issue #450. Rhodecode no longer will crash when bad revision is
84 85 present in journal data.
85 86 - fix for issue #417, git execution was broken on windows for certain
86 87 commands.
87 88 - fixed #413. Don't disable .git directory for bare repos on deleting
88 89 - fixed issue #459. Changed the way of obtaining logger in reindex task.
89 90 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
90 91 reindexing modified files
91 92 - fixed #481 rhodecode emails are sent without Date header
92 93 - fixed #458 wrong count when no repos are present
93 94 - fixed issue #492 missing `\ No newline at end of file` test at the end of
94 95 new chunk in html diff
95 96 - full text search now works also for commit messages
96 97
97 98 1.3.6 (**2012-05-17**)
98 99 ----------------------
99 100
100 101 news
101 102 ++++
102 103
103 104 - chinese traditional translation
104 105 - changed setup-app into setup-rhodecode and added arguments for auto-setup
105 106 mode that doesn't need user interaction
106 107
107 108 fixes
108 109 +++++
109 110
110 111 - fixed no scm found warning
111 112 - fixed __future__ import error on rcextensions
112 113 - made simplejson required lib for speedup on JSON encoding
113 114 - fixes #449 bad regex could get more than revisions from parsing history
114 115 - don't clear DB session when CELERY_EAGER is turned ON
115 116
116 117 1.3.5 (**2012-05-10**)
117 118 ----------------------
118 119
119 120 news
120 121 ++++
121 122
122 123 - use ext_json for json module
123 124 - unified annotation view with file source view
124 125 - notification improvements, better inbox + css
125 126 - #419 don't strip passwords for login forms, make rhodecode
126 127 more compatible with LDAP servers
127 128 - Added HTTP_X_FORWARDED_FOR as another method of extracting
128 129 IP for pull/push logs. - moved all to base controller
129 130 - #415: Adding comment to changeset causes reload.
130 131 Comments are now added via ajax and doesn't reload the page
131 132 - #374 LDAP config is discarded when LDAP can't be activated
132 133 - limited push/pull operations are now logged for git in the journal
133 134 - bumped mercurial to 2.2.X series
134 135 - added support for displaying submodules in file-browser
135 136 - #421 added bookmarks in changelog view
136 137
137 138 fixes
138 139 +++++
139 140
140 141 - fixed dev-version marker for stable when served from source codes
141 142 - fixed missing permission checks on show forks page
142 143 - #418 cast to unicode fixes in notification objects
143 144 - #426 fixed mention extracting regex
144 145 - fixed remote-pulling for git remotes remopositories
145 146 - fixed #434: Error when accessing files or changesets of a git repository
146 147 with submodules
147 148 - fixed issue with empty APIKEYS for users after registration ref. #438
148 149 - fixed issue with getting README files from git repositories
149 150
150 151 1.3.4 (**2012-03-28**)
151 152 ----------------------
152 153
153 154 news
154 155 ++++
155 156
156 157 - Whoosh logging is now controlled by the .ini files logging setup
157 158 - added clone-url into edit form on /settings page
158 159 - added help text into repo add/edit forms
159 160 - created rcextensions module with additional mappings (ref #322) and
160 161 post push/pull/create repo hooks callbacks
161 162 - implemented #377 Users view for his own permissions on account page
162 163 - #399 added inheritance of permissions for users group on repos groups
163 164 - #401 repository group is automatically pre-selected when adding repos
164 165 inside a repository group
165 166 - added alternative HTTP 403 response when client failed to authenticate. Helps
166 167 solving issues with Mercurial and LDAP
167 168 - #402 removed group prefix from repository name when listing repositories
168 169 inside a group
169 170 - added gravatars into permission view and permissions autocomplete
170 171 - #347 when running multiple RhodeCode instances, properly invalidates cache
171 172 for all registered servers
172 173
173 174 fixes
174 175 +++++
175 176
176 177 - fixed #390 cache invalidation problems on repos inside group
177 178 - fixed #385 clone by ID url was loosing proxy prefix in URL
178 179 - fixed some unicode problems with waitress
179 180 - fixed issue with escaping < and > in changeset commits
180 181 - fixed error occurring during recursive group creation in API
181 182 create_repo function
182 183 - fixed #393 py2.5 fixes for routes url generator
183 184 - fixed #397 Private repository groups shows up before login
184 185 - fixed #396 fixed problems with revoking users in nested groups
185 186 - fixed mysql unicode issues + specified InnoDB as default engine with
186 187 utf8 charset
187 188 - #406 trim long branch/tag names in changelog to not break UI
188 189
189 190 1.3.3 (**2012-03-02**)
190 191 ----------------------
191 192
192 193 news
193 194 ++++
194 195
195 196
196 197 fixes
197 198 +++++
198 199
199 200 - fixed some python2.5 compatibility issues
200 201 - fixed issues with removed repos was accidentally added as groups, after
201 202 full rescan of paths
202 203 - fixes #376 Cannot edit user (using container auth)
203 204 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
204 205 configuration
205 206 - fixed initial sorting of repos inside repo group
206 207 - fixes issue when user tried to resubmit same permission into user/user_groups
207 208 - bumped beaker version that fixes #375 leap error bug
208 209 - fixed raw_changeset for git. It was generated with hg patch headers
209 210 - fixed vcs issue with last_changeset for filenodes
210 211 - fixed missing commit after hook delete
211 212 - fixed #372 issues with git operation detection that caused a security issue
212 213 for git repos
213 214
214 215 1.3.2 (**2012-02-28**)
215 216 ----------------------
216 217
217 218 news
218 219 ++++
219 220
220 221
221 222 fixes
222 223 +++++
223 224
224 225 - fixed git protocol issues with repos-groups
225 226 - fixed git remote repos validator that prevented from cloning remote git repos
226 227 - fixes #370 ending slashes fixes for repo and groups
227 228 - fixes #368 improved git-protocol detection to handle other clients
228 229 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
229 230 Moved To Root
230 231 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
231 232 - fixed #373 missing cascade drop on user_group_to_perm table
232 233
233 234 1.3.1 (**2012-02-27**)
234 235 ----------------------
235 236
236 237 news
237 238 ++++
238 239
239 240
240 241 fixes
241 242 +++++
242 243
243 244 - redirection loop occurs when remember-me wasn't checked during login
244 245 - fixes issues with git blob history generation
245 246 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
246 247
247 248 1.3.0 (**2012-02-26**)
248 249 ----------------------
249 250
250 251 news
251 252 ++++
252 253
253 254 - code review, inspired by github code-comments
254 255 - #215 rst and markdown README files support
255 256 - #252 Container-based and proxy pass-through authentication support
256 257 - #44 branch browser. Filtering of changelog by branches
257 258 - mercurial bookmarks support
258 259 - new hover top menu, optimized to add maximum size for important views
259 260 - configurable clone url template with possibility to specify protocol like
260 261 ssh:// or http:// and also manually alter other parts of clone_url.
261 262 - enabled largefiles extension by default
262 263 - optimized summary file pages and saved a lot of unused space in them
263 264 - #239 option to manually mark repository as fork
264 265 - #320 mapping of commit authors to RhodeCode users
265 266 - #304 hashes are displayed using monospace font
266 267 - diff configuration, toggle white lines and context lines
267 268 - #307 configurable diffs, whitespace toggle, increasing context lines
268 269 - sorting on branches, tags and bookmarks using YUI datatable
269 270 - improved file filter on files page
270 271 - implements #330 api method for listing nodes ar particular revision
271 272 - #73 added linking issues in commit messages to chosen issue tracker url
272 273 based on user defined regular expression
273 274 - added linking of changesets in commit messages
274 275 - new compact changelog with expandable commit messages
275 276 - firstname and lastname are optional in user creation
276 277 - #348 added post-create repository hook
277 278 - #212 global encoding settings is now configurable from .ini files
278 279 - #227 added repository groups permissions
279 280 - markdown gets codehilite extensions
280 281 - new API methods, delete_repositories, grante/revoke permissions for groups
281 282 and repos
282 283
283 284
284 285 fixes
285 286 +++++
286 287
287 288 - rewrote dbsession management for atomic operations, and better error handling
288 289 - fixed sorting of repo tables
289 290 - #326 escape of special html entities in diffs
290 291 - normalized user_name => username in api attributes
291 292 - fixes #298 ldap created users with mixed case emails created conflicts
292 293 on saving a form
293 294 - fixes issue when owner of a repo couldn't revoke permissions for users
294 295 and groups
295 296 - fixes #271 rare JSON serialization problem with statistics
296 297 - fixes #337 missing validation check for conflicting names of a group with a
297 298 repositories group
298 299 - #340 fixed session problem for mysql and celery tasks
299 300 - fixed #331 RhodeCode mangles repository names if the a repository group
300 301 contains the "full path" to the repositories
301 302 - #355 RhodeCode doesn't store encrypted LDAP passwords
302 303
303 304 1.2.5 (**2012-01-28**)
304 305 ----------------------
305 306
306 307 news
307 308 ++++
308 309
309 310 fixes
310 311 +++++
311 312
312 313 - #340 Celery complains about MySQL server gone away, added session cleanup
313 314 for celery tasks
314 315 - #341 "scanning for repositories in None" log message during Rescan was missing
315 316 a parameter
316 317 - fixed creating archives with subrepos. Some hooks were triggered during that
317 318 operation leading to crash.
318 319 - fixed missing email in account page.
319 320 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
320 321 forking on windows impossible
321 322
322 323 1.2.4 (**2012-01-19**)
323 324 ----------------------
324 325
325 326 news
326 327 ++++
327 328
328 329 - RhodeCode is bundled with mercurial series 2.0.X by default, with
329 330 full support to largefiles extension. Enabled by default in new installations
330 331 - #329 Ability to Add/Remove Groups to/from a Repository via AP
331 332 - added requires.txt file with requirements
332 333
333 334 fixes
334 335 +++++
335 336
336 337 - fixes db session issues with celery when emailing admins
337 338 - #331 RhodeCode mangles repository names if the a repository group
338 339 contains the "full path" to the repositories
339 340 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
340 341 - DB session cleanup after hg protocol operations, fixes issues with
341 342 `mysql has gone away` errors
342 343 - #333 doc fixes for get_repo api function
343 344 - #271 rare JSON serialization problem with statistics enabled
344 345 - #337 Fixes issues with validation of repository name conflicting with
345 346 a group name. A proper message is now displayed.
346 347 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
347 348 doesn't work
348 349 - #316 fixes issues with web description in hgrc files
349 350
350 351 1.2.3 (**2011-11-02**)
351 352 ----------------------
352 353
353 354 news
354 355 ++++
355 356
356 357 - added option to manage repos group for non admin users
357 358 - added following API methods for get_users, create_user, get_users_groups,
358 359 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
359 360 get_repo, create_repo, add_user_to_repo
360 361 - implements #237 added password confirmation for my account
361 362 and admin edit user.
362 363 - implements #291 email notification for global events are now sent to all
363 364 administrator users, and global config email.
364 365
365 366 fixes
366 367 +++++
367 368
368 369 - added option for passing auth method for smtp mailer
369 370 - #276 issue with adding a single user with id>10 to usergroups
370 371 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
371 372 - #288 fixes managing of repos in a group for non admin user
372 373
373 374 1.2.2 (**2011-10-17**)
374 375 ----------------------
375 376
376 377 news
377 378 ++++
378 379
379 380 - #226 repo groups are available by path instead of numerical id
380 381
381 382 fixes
382 383 +++++
383 384
384 385 - #259 Groups with the same name but with different parent group
385 386 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
386 387 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
387 388 - #265 ldap save fails sometimes on converting attributes to booleans,
388 389 added getter and setter into model that will prevent from this on db model level
389 390 - fixed problems with timestamps issues #251 and #213
390 391 - fixes #266 RhodeCode allows to create repo with the same name and in
391 392 the same parent as group
392 393 - fixes #245 Rescan of the repositories on Windows
393 394 - fixes #248 cannot edit repos inside a group on windows
394 395 - fixes #219 forking problems on windows
395 396
396 397 1.2.1 (**2011-10-08**)
397 398 ----------------------
398 399
399 400 news
400 401 ++++
401 402
402 403
403 404 fixes
404 405 +++++
405 406
406 407 - fixed problems with basic auth and push problems
407 408 - gui fixes
408 409 - fixed logger
409 410
410 411 1.2.0 (**2011-10-07**)
411 412 ----------------------
412 413
413 414 news
414 415 ++++
415 416
416 417 - implemented #47 repository groups
417 418 - implemented #89 Can setup google analytics code from settings menu
418 419 - implemented #91 added nicer looking archive urls with more download options
419 420 like tags, branches
420 421 - implemented #44 into file browsing, and added follow branch option
421 422 - implemented #84 downloads can be enabled/disabled for each repository
422 423 - anonymous repository can be cloned without having to pass default:default
423 424 into clone url
424 425 - fixed #90 whoosh indexer can index chooses repositories passed in command
425 426 line
426 427 - extended journal with day aggregates and paging
427 428 - implemented #107 source code lines highlight ranges
428 429 - implemented #93 customizable changelog on combined revision ranges -
429 430 equivalent of githubs compare view
430 431 - implemented #108 extended and more powerful LDAP configuration
431 432 - implemented #56 users groups
432 433 - major code rewrites optimized codes for speed and memory usage
433 434 - raw and diff downloads are now in git format
434 435 - setup command checks for write access to given path
435 436 - fixed many issues with international characters and unicode. It uses utf8
436 437 decode with replace to provide less errors even with non utf8 encoded strings
437 438 - #125 added API KEY access to feeds
438 439 - #109 Repository can be created from external Mercurial link (aka. remote
439 440 repository, and manually updated (via pull) from admin panel
440 441 - beta git support - push/pull server + basic view for git repos
441 442 - added followers page and forks page
442 443 - server side file creation (with binary file upload interface)
443 444 and edition with commits powered by codemirror
444 445 - #111 file browser file finder, quick lookup files on whole file tree
445 446 - added quick login sliding menu into main page
446 447 - changelog uses lazy loading of affected files details, in some scenarios
447 448 this can improve speed of changelog page dramatically especially for
448 449 larger repositories.
449 450 - implements #214 added support for downloading subrepos in download menu.
450 451 - Added basic API for direct operations on rhodecode via JSON
451 452 - Implemented advanced hook management
452 453
453 454 fixes
454 455 +++++
455 456
456 457 - fixed file browser bug, when switching into given form revision the url was
457 458 not changing
458 459 - fixed propagation to error controller on simplehg and simplegit middlewares
459 460 - fixed error when trying to make a download on empty repository
460 461 - fixed problem with '[' chars in commit messages in journal
461 462 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
462 463 - journal fork fixes
463 464 - removed issue with space inside renamed repository after deletion
464 465 - fixed strange issue on formencode imports
465 466 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
466 467 - #150 fixes for errors on repositories mapped in db but corrupted in
467 468 filesystem
468 469 - fixed problem with ascendant characters in realm #181
469 470 - fixed problem with sqlite file based database connection pool
470 471 - whoosh indexer and code stats share the same dynamic extensions map
471 472 - fixes #188 - relationship delete of repo_to_perm entry on user removal
472 473 - fixes issue #189 Trending source files shows "show more" when no more exist
473 474 - fixes issue #197 Relative paths for pidlocks
474 475 - fixes issue #198 password will require only 3 chars now for login form
475 476 - fixes issue #199 wrong redirection for non admin users after creating a repository
476 477 - fixes issues #202, bad db constraint made impossible to attach same group
477 478 more than one time. Affects only mysql/postgres
478 479 - fixes #218 os.kill patch for windows was missing sig param
479 480 - improved rendering of dag (they are not trimmed anymore when number of
480 481 heads exceeds 5)
481 482
482 483 1.1.8 (**2011-04-12**)
483 484 ----------------------
484 485
485 486 news
486 487 ++++
487 488
488 489 - improved windows support
489 490
490 491 fixes
491 492 +++++
492 493
493 494 - fixed #140 freeze of python dateutil library, since new version is python2.x
494 495 incompatible
495 496 - setup-app will check for write permission in given path
496 497 - cleaned up license info issue #149
497 498 - fixes for issues #137,#116 and problems with unicode and accented characters.
498 499 - fixes crashes on gravatar, when passed in email as unicode
499 500 - fixed tooltip flickering problems
500 501 - fixed came_from redirection on windows
501 502 - fixed logging modules, and sql formatters
502 503 - windows fixes for os.kill issue #133
503 504 - fixes path splitting for windows issues #148
504 505 - fixed issue #143 wrong import on migration to 1.1.X
505 506 - fixed problems with displaying binary files, thanks to Thomas Waldmann
506 507 - removed name from archive files since it's breaking ui for long repo names
507 508 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
508 509 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
509 510 Thomas Waldmann
510 511 - fixed issue #166 summary pager was skipping 10 revisions on second page
511 512
512 513
513 514 1.1.7 (**2011-03-23**)
514 515 ----------------------
515 516
516 517 news
517 518 ++++
518 519
519 520 fixes
520 521 +++++
521 522
522 523 - fixed (again) #136 installation support for FreeBSD
523 524
524 525
525 526 1.1.6 (**2011-03-21**)
526 527 ----------------------
527 528
528 529 news
529 530 ++++
530 531
531 532 fixes
532 533 +++++
533 534
534 535 - fixed #136 installation support for FreeBSD
535 536 - RhodeCode will check for python version during installation
536 537
537 538 1.1.5 (**2011-03-17**)
538 539 ----------------------
539 540
540 541 news
541 542 ++++
542 543
543 544 - basic windows support, by exchanging pybcrypt into sha256 for windows only
544 545 highly inspired by idea of mantis406
545 546
546 547 fixes
547 548 +++++
548 549
549 550 - fixed sorting by author in main page
550 551 - fixed crashes with diffs on binary files
551 552 - fixed #131 problem with boolean values for LDAP
552 553 - fixed #122 mysql problems thanks to striker69
553 554 - fixed problem with errors on calling raw/raw_files/annotate functions
554 555 with unknown revisions
555 556 - fixed returned rawfiles attachment names with international character
556 557 - cleaned out docs, big thanks to Jason Harris
557 558
558 559 1.1.4 (**2011-02-19**)
559 560 ----------------------
560 561
561 562 news
562 563 ++++
563 564
564 565 fixes
565 566 +++++
566 567
567 568 - fixed formencode import problem on settings page, that caused server crash
568 569 when that page was accessed as first after server start
569 570 - journal fixes
570 571 - fixed option to access repository just by entering http://server/<repo_name>
571 572
572 573 1.1.3 (**2011-02-16**)
573 574 ----------------------
574 575
575 576 news
576 577 ++++
577 578
578 579 - implemented #102 allowing the '.' character in username
579 580 - added option to access repository just by entering http://server/<repo_name>
580 581 - celery task ignores result for better performance
581 582
582 583 fixes
583 584 +++++
584 585
585 586 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
586 587 apollo13 and Johan Walles
587 588 - small fixes in journal
588 589 - fixed problems with getting setting for celery from .ini files
589 590 - registration, password reset and login boxes share the same title as main
590 591 application now
591 592 - fixed #113: to high permissions to fork repository
592 593 - fixed problem with '[' chars in commit messages in journal
593 594 - removed issue with space inside renamed repository after deletion
594 595 - db transaction fixes when filesystem repository creation failed
595 596 - fixed #106 relation issues on databases different than sqlite
596 597 - fixed static files paths links to use of url() method
597 598
598 599 1.1.2 (**2011-01-12**)
599 600 ----------------------
600 601
601 602 news
602 603 ++++
603 604
604 605
605 606 fixes
606 607 +++++
607 608
608 609 - fixes #98 protection against float division of percentage stats
609 610 - fixed graph bug
610 611 - forced webhelpers version since it was making troubles during installation
611 612
612 613 1.1.1 (**2011-01-06**)
613 614 ----------------------
614 615
615 616 news
616 617 ++++
617 618
618 619 - added force https option into ini files for easier https usage (no need to
619 620 set server headers with this options)
620 621 - small css updates
621 622
622 623 fixes
623 624 +++++
624 625
625 626 - fixed #96 redirect loop on files view on repositories without changesets
626 627 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
627 628 and server crashed with errors
628 629 - fixed large tooltips problems on main page
629 630 - fixed #92 whoosh indexer is more error proof
630 631
631 632 1.1.0 (**2010-12-18**)
632 633 ----------------------
633 634
634 635 news
635 636 ++++
636 637
637 638 - rewrite of internals for vcs >=0.1.10
638 639 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
639 640 with older clients
640 641 - anonymous access, authentication via ldap
641 642 - performance upgrade for cached repos list - each repository has its own
642 643 cache that's invalidated when needed.
643 644 - performance upgrades on repositories with large amount of commits (20K+)
644 645 - main page quick filter for filtering repositories
645 646 - user dashboards with ability to follow chosen repositories actions
646 647 - sends email to admin on new user registration
647 648 - added cache/statistics reset options into repository settings
648 649 - more detailed action logger (based on hooks) with pushed changesets lists
649 650 and options to disable those hooks from admin panel
650 651 - introduced new enhanced changelog for merges that shows more accurate results
651 652 - new improved and faster code stats (based on pygments lexers mapping tables,
652 653 showing up to 10 trending sources for each repository. Additionally stats
653 654 can be disabled in repository settings.
654 655 - gui optimizations, fixed application width to 1024px
655 656 - added cut off (for large files/changesets) limit into config files
656 657 - whoosh, celeryd, upgrade moved to paster command
657 658 - other than sqlite database backends can be used
658 659
659 660 fixes
660 661 +++++
661 662
662 663 - fixes #61 forked repo was showing only after cache expired
663 664 - fixes #76 no confirmation on user deletes
664 665 - fixes #66 Name field misspelled
665 666 - fixes #72 block user removal when he owns repositories
666 667 - fixes #69 added password confirmation fields
667 668 - fixes #87 RhodeCode crashes occasionally on updating repository owner
668 669 - fixes #82 broken annotations on files with more than 1 blank line at the end
669 670 - a lot of fixes and tweaks for file browser
670 671 - fixed detached session issues
671 672 - fixed when user had no repos he would see all repos listed in my account
672 673 - fixed ui() instance bug when global hgrc settings was loaded for server
673 674 instance and all hgrc options were merged with our db ui() object
674 675 - numerous small bugfixes
675 676
676 677 (special thanks for TkSoh for detailed feedback)
677 678
678 679
679 680 1.0.2 (**2010-11-12**)
680 681 ----------------------
681 682
682 683 news
683 684 ++++
684 685
685 686 - tested under python2.7
686 687 - bumped sqlalchemy and celery versions
687 688
688 689 fixes
689 690 +++++
690 691
691 692 - fixed #59 missing graph.js
692 693 - fixed repo_size crash when repository had broken symlinks
693 694 - fixed python2.5 crashes.
694 695
695 696
696 697 1.0.1 (**2010-11-10**)
697 698 ----------------------
698 699
699 700 news
700 701 ++++
701 702
702 703 - small css updated
703 704
704 705 fixes
705 706 +++++
706 707
707 708 - fixed #53 python2.5 incompatible enumerate calls
708 709 - fixed #52 disable mercurial extension for web
709 710 - fixed #51 deleting repositories don't delete it's dependent objects
710 711
711 712
712 713 1.0.0 (**2010-11-02**)
713 714 ----------------------
714 715
715 716 - security bugfix simplehg wasn't checking for permissions on commands
716 717 other than pull or push.
717 718 - fixed doubled messages after push or pull in admin journal
718 719 - templating and css corrections, fixed repo switcher on chrome, updated titles
719 720 - admin menu accessible from options menu on repository view
720 721 - permissions cached queries
721 722
722 723 1.0.0rc4 (**2010-10-12**)
723 724 --------------------------
724 725
725 726 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
726 727 - removed cache_manager settings from sqlalchemy meta
727 728 - added sqlalchemy cache settings to ini files
728 729 - validated password length and added second try of failure on paster setup-app
729 730 - fixed setup database destroy prompt even when there was no db
730 731
731 732
732 733 1.0.0rc3 (**2010-10-11**)
733 734 -------------------------
734 735
735 736 - fixed i18n during installation.
736 737
737 738 1.0.0rc2 (**2010-10-11**)
738 739 -------------------------
739 740
740 741 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
741 742 occure. After vcs is fixed it'll be put back again.
742 743 - templating/css rewrites, optimized css. No newline at end of file
@@ -1,435 +1,435 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.tasks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode task modules, containing all task that suppose to be run
7 7 by celery daemon
8 8
9 9 :created_on: Oct 6, 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 from celery.decorators import task
27 27
28 28 import os
29 29 import traceback
30 30 import logging
31 31 from os.path import join as jn
32 32
33 33 from time import mktime
34 34 from operator import itemgetter
35 35 from string import lower
36 36
37 37 from pylons import config, url
38 38 from pylons.i18n.translation import _
39 39
40 40 from rhodecode.lib.vcs import get_backend
41 41
42 42 from rhodecode import CELERY_ON, CELERY_EAGER
43 43 from rhodecode.lib.utils2 import safe_str
44 44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
45 45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
46 46 from rhodecode.lib.helpers import person
47 47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
48 48 from rhodecode.lib.utils import add_cache, action_logger
49 49 from rhodecode.lib.compat import json, OrderedDict
50 50 from rhodecode.lib.hooks import log_create_repository
51 51
52 52 from rhodecode.model.db import Statistics, Repository, User
53 53
54 54
55 55 add_cache(config)
56 56
57 57 __all__ = ['whoosh_index', 'get_commits_stats',
58 58 'reset_user_password', 'send_email']
59 59
60 60
61 61 def get_logger(cls):
62 62 if CELERY_ON:
63 63 try:
64 64 log = cls.get_logger()
65 65 except:
66 66 log = logging.getLogger(__name__)
67 67 else:
68 68 log = logging.getLogger(__name__)
69 69
70 70 return log
71 71
72 72
73 73 @task(ignore_result=True)
74 74 @locked_task
75 75 @dbsession
76 76 def whoosh_index(repo_location, full_index):
77 77 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
78 78 log = get_logger(whoosh_index)
79 79 DBS = get_session()
80 80
81 81 index_location = config['index_dir']
82 82 WhooshIndexingDaemon(index_location=index_location,
83 83 repo_location=repo_location, sa=DBS)\
84 84 .run(full_index=full_index)
85 85
86 86
87 87 @task(ignore_result=True)
88 88 @dbsession
89 89 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
90 90 log = get_logger(get_commits_stats)
91 91 DBS = get_session()
92 92 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
93 93 ts_max_y)
94 94 lockkey_path = config['here']
95 95
96 96 log.info('running task with lockkey %s' % lockkey)
97 97
98 98 try:
99 99 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
100 100
101 101 # for js data compatibility cleans the key for person from '
102 102 akc = lambda k: person(k).replace('"', "")
103 103
104 104 co_day_auth_aggr = {}
105 105 commits_by_day_aggregate = {}
106 106 repo = Repository.get_by_repo_name(repo_name)
107 107 if repo is None:
108 108 return True
109 109
110 110 repo = repo.scm_instance
111 111 repo_size = repo.count()
112 112 # return if repo have no revisions
113 113 if repo_size < 1:
114 114 lock.release()
115 115 return True
116 116
117 117 skip_date_limit = True
118 118 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
119 119 last_rev = None
120 120 last_cs = None
121 121 timegetter = itemgetter('time')
122 122
123 123 dbrepo = DBS.query(Repository)\
124 124 .filter(Repository.repo_name == repo_name).scalar()
125 125 cur_stats = DBS.query(Statistics)\
126 126 .filter(Statistics.repository == dbrepo).scalar()
127 127
128 128 if cur_stats is not None:
129 129 last_rev = cur_stats.stat_on_revision
130 130
131 131 if last_rev == repo.get_changeset().revision and repo_size > 1:
132 132 # pass silently without any work if we're not on first revision or
133 133 # current state of parsing revision(from db marker) is the
134 134 # last revision
135 135 lock.release()
136 136 return True
137 137
138 138 if cur_stats:
139 139 commits_by_day_aggregate = OrderedDict(json.loads(
140 140 cur_stats.commit_activity_combined))
141 141 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
142 142
143 143 log.debug('starting parsing %s' % parse_limit)
144 144 lmktime = mktime
145 145
146 146 last_rev = last_rev + 1 if last_rev >= 0 else 0
147 147 log.debug('Getting revisions from %s to %s' % (
148 148 last_rev, last_rev + parse_limit)
149 149 )
150 150 for cs in repo[last_rev:last_rev + parse_limit]:
151 151 log.debug('parsing %s' % cs)
152 152 last_cs = cs # remember last parsed changeset
153 153 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
154 154 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
155 155
156 156 if akc(cs.author) in co_day_auth_aggr:
157 157 try:
158 158 l = [timegetter(x) for x in
159 159 co_day_auth_aggr[akc(cs.author)]['data']]
160 160 time_pos = l.index(k)
161 161 except ValueError:
162 162 time_pos = False
163 163
164 164 if time_pos >= 0 and time_pos is not False:
165 165
166 166 datadict = \
167 167 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
168 168
169 169 datadict["commits"] += 1
170 170 datadict["added"] += len(cs.added)
171 171 datadict["changed"] += len(cs.changed)
172 172 datadict["removed"] += len(cs.removed)
173 173
174 174 else:
175 175 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
176 176
177 177 datadict = {"time": k,
178 178 "commits": 1,
179 179 "added": len(cs.added),
180 180 "changed": len(cs.changed),
181 181 "removed": len(cs.removed),
182 182 }
183 183 co_day_auth_aggr[akc(cs.author)]['data']\
184 184 .append(datadict)
185 185
186 186 else:
187 187 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
188 188 co_day_auth_aggr[akc(cs.author)] = {
189 189 "label": akc(cs.author),
190 190 "data": [{"time":k,
191 191 "commits":1,
192 192 "added":len(cs.added),
193 193 "changed":len(cs.changed),
194 194 "removed":len(cs.removed),
195 195 }],
196 196 "schema": ["commits"],
197 197 }
198 198
199 199 #gather all data by day
200 200 if k in commits_by_day_aggregate:
201 201 commits_by_day_aggregate[k] += 1
202 202 else:
203 203 commits_by_day_aggregate[k] = 1
204 204
205 205 overview_data = sorted(commits_by_day_aggregate.items(),
206 206 key=itemgetter(0))
207 207
208 208 if not co_day_auth_aggr:
209 209 co_day_auth_aggr[akc(repo.contact)] = {
210 210 "label": akc(repo.contact),
211 211 "data": [0, 1],
212 212 "schema": ["commits"],
213 213 }
214 214
215 215 stats = cur_stats if cur_stats else Statistics()
216 216 stats.commit_activity = json.dumps(co_day_auth_aggr)
217 217 stats.commit_activity_combined = json.dumps(overview_data)
218 218
219 219 log.debug('last revison %s' % last_rev)
220 220 leftovers = len(repo.revisions[last_rev:])
221 221 log.debug('revisions to parse %s' % leftovers)
222 222
223 223 if last_rev == 0 or leftovers < parse_limit:
224 224 log.debug('getting code trending stats')
225 225 stats.languages = json.dumps(__get_codes_stats(repo_name))
226 226
227 227 try:
228 228 stats.repository = dbrepo
229 229 stats.stat_on_revision = last_cs.revision if last_cs else 0
230 230 DBS.add(stats)
231 231 DBS.commit()
232 232 except:
233 233 log.error(traceback.format_exc())
234 234 DBS.rollback()
235 235 lock.release()
236 236 return False
237 237
238 238 # final release
239 239 lock.release()
240 240
241 241 # execute another task if celery is enabled
242 242 if len(repo.revisions) > 1 and CELERY_ON:
243 243 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
244 244 return True
245 245 except LockHeld:
246 246 log.info('LockHeld')
247 247 return 'Task with key %s already running' % lockkey
248 248
249 249 @task(ignore_result=True)
250 250 @dbsession
251 251 def send_password_link(user_email):
252 252 from rhodecode.model.notification import EmailNotificationModel
253 253
254 254 log = get_logger(send_password_link)
255 255 DBS = get_session()
256 256
257 257 try:
258 258 user = User.get_by_email(user_email)
259 259 if user:
260 260 log.debug('password reset user found %s' % user)
261 261 link = url('reset_password_confirmation', key=user.api_key,
262 262 qualified=True)
263 263 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
264 264 body = EmailNotificationModel().get_email_tmpl(reg_type,
265 265 **{'user':user.short_contact,
266 266 'reset_url':link})
267 267 log.debug('sending email')
268 268 run_task(send_email, user_email,
269 269 _("password reset link"), body)
270 270 log.info('send new password mail to %s' % user_email)
271 271 else:
272 272 log.debug("password reset email %s not found" % user_email)
273 273 except:
274 274 log.error(traceback.format_exc())
275 275 return False
276 276
277 277 return True
278 278
279 279 @task(ignore_result=True)
280 280 @dbsession
281 281 def reset_user_password(user_email):
282 282 from rhodecode.lib import auth
283 283
284 284 log = get_logger(reset_user_password)
285 285 DBS = get_session()
286 286
287 287 try:
288 288 try:
289 289 user = User.get_by_email(user_email)
290 290 new_passwd = auth.PasswordGenerator().gen_password(8,
291 291 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
292 292 if user:
293 293 user.password = auth.get_crypt_password(new_passwd)
294 294 user.api_key = auth.generate_api_key(user.username)
295 295 DBS.add(user)
296 296 DBS.commit()
297 297 log.info('change password for %s' % user_email)
298 298 if new_passwd is None:
299 299 raise Exception('unable to generate new password')
300 300 except:
301 301 log.error(traceback.format_exc())
302 302 DBS.rollback()
303 303
304 304 run_task(send_email, user_email,
305 305 'Your new password',
306 306 'Your new RhodeCode password:%s' % (new_passwd))
307 307 log.info('send new password mail to %s' % user_email)
308 308
309 309 except:
310 310 log.error('Failed to update user password')
311 311 log.error(traceback.format_exc())
312 312
313 313 return True
314 314
315 315
316 316 @task(ignore_result=True)
317 317 @dbsession
318 318 def send_email(recipients, subject, body, html_body=''):
319 319 """
320 320 Sends an email with defined parameters from the .ini files.
321 321
322 322 :param recipients: list of recipients, it this is empty the defined email
323 323 address from field 'email_to' is used instead
324 324 :param subject: subject of the mail
325 325 :param body: body of the mail
326 326 :param html_body: html version of body
327 327 """
328 328 log = get_logger(send_email)
329 329 DBS = get_session()
330 330
331 331 email_config = config
332 332 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
333 333 if not recipients:
334 334 # if recipients are not defined we send to email_config + all admins
335 335 admins = [u.email for u in User.query()
336 336 .filter(User.admin == True).all()]
337 337 recipients = [email_config.get('email_to')] + admins
338 338
339 339 mail_from = email_config.get('app_email_from', 'RhodeCode')
340 340 user = email_config.get('smtp_username')
341 341 passwd = email_config.get('smtp_password')
342 342 mail_server = email_config.get('smtp_server')
343 343 mail_port = email_config.get('smtp_port')
344 344 tls = str2bool(email_config.get('smtp_use_tls'))
345 345 ssl = str2bool(email_config.get('smtp_use_ssl'))
346 346 debug = str2bool(config.get('debug'))
347 347 smtp_auth = email_config.get('smtp_auth')
348 348
349 349 try:
350 350 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
351 351 mail_port, ssl, tls, debug=debug)
352 352 m.send(recipients, subject, body, html_body)
353 353 except:
354 354 log.error('Mail sending failed')
355 355 log.error(traceback.format_exc())
356 356 return False
357 357 return True
358 358
359 359
360 360 @task(ignore_result=True)
361 361 @dbsession
362 362 def create_repo_fork(form_data, cur_user):
363 363 """
364 364 Creates a fork of repository using interval VCS methods
365 365
366 366 :param form_data:
367 367 :param cur_user:
368 368 """
369 369 from rhodecode.model.repo import RepoModel
370 370 from rhodecode.model.user import UserModel
371 371
372 372 log = get_logger(create_repo_fork)
373 373 DBS = get_session()
374 374
375 375 base_path = Repository.base_path()
376 376 cur_user = UserModel(DBS)._get_user(cur_user)
377 377
378 378 fork_name = form_data['repo_name_full']
379 379 repo_type = form_data['repo_type']
380 380 description = form_data['description']
381 381 owner = cur_user
382 382 private = form_data['private']
383 383 clone_uri = form_data.get('clone_uri')
384 384 repos_group = form_data['repo_group']
385 385 landing_rev = form_data['landing_rev']
386 386 copy_fork_permissions = form_data.get('copy_permissions')
387 387 fork_of = RepoModel(DBS)._get_repo(form_data.get('fork_parent_id'))
388 388
389 389 fork_repo = RepoModel(DBS).create_repo(
390 390 fork_name, repo_type, description, owner, private, clone_uri,
391 391 repos_group, landing_rev, just_db=True, fork_of=fork_of,
392 392 copy_fork_permissions=copy_fork_permissions
393 393 )
394 394
395 395 update_after_clone = form_data['update_after_clone']
396 396
397 397 source_repo_path = os.path.join(base_path, fork_of.repo_name)
398 398 destination_fork_path = os.path.join(base_path, fork_name)
399 399
400 400 log.info('creating fork of %s as %s', source_repo_path,
401 401 destination_fork_path)
402 402 backend = get_backend(repo_type)
403 403 backend(safe_str(destination_fork_path), create=True,
404 404 src_url=safe_str(source_repo_path),
405 update_after_clone=update_after_clone)
405 update_after_clone=update_after_clone, bare=True)
406 406 log_create_repository(fork_repo.get_dict(), created_by=cur_user.username)
407 407
408 408 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
409 409 fork_of.repo_name, '', DBS)
410 410
411 411 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
412 412 fork_name, '', DBS)
413 413 # finally commit at latest possible stage
414 414 DBS.commit()
415 415
416 416
417 417 def __get_codes_stats(repo_name):
418 418 from rhodecode.config.conf import LANGUAGES_EXTENSIONS_MAP
419 419 repo = Repository.get_by_repo_name(repo_name).scm_instance
420 420
421 421 tip = repo.get_changeset()
422 422 code_stats = {}
423 423
424 424 def aggregate(cs):
425 425 for f in cs[2]:
426 426 ext = lower(f.extension)
427 427 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
428 428 if ext in code_stats:
429 429 code_stats[ext] += 1
430 430 else:
431 431 code_stats[ext] = 1
432 432
433 433 map(aggregate, tip.walk('/'))
434 434
435 435 return code_stats or {}
@@ -1,654 +1,654 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.backends.git
4 4 ~~~~~~~~~~~~~~~~
5 5
6 6 Git backend implementation.
7 7
8 8 :created_on: Apr 8, 2010
9 9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 10 """
11 11
12 12 import os
13 13 import re
14 14 import time
15 15 import posixpath
16 16 import logging
17 17 import traceback
18 18 import urllib
19 19 import urllib2
20 20 from dulwich.repo import Repo, NotGitRepository
21 21 #from dulwich.config import ConfigFile
22 22 from string import Template
23 23 from subprocess import Popen, PIPE
24 24 from rhodecode.lib.vcs.backends.base import BaseRepository
25 25 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
26 26 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
27 27 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
28 28 from rhodecode.lib.vcs.exceptions import RepositoryError
29 29 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
30 30 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
31 31 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
32 32 from rhodecode.lib.vcs.utils.lazy import LazyProperty
33 33 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
34 34 from rhodecode.lib.vcs.utils.paths import abspath
35 35 from rhodecode.lib.vcs.utils.paths import get_user_home
36 36 from .workdir import GitWorkdir
37 37 from .changeset import GitChangeset
38 38 from .inmemory import GitInMemoryChangeset
39 39 from .config import ConfigFile
40 40 from rhodecode.lib import subprocessio
41 41
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class GitRepository(BaseRepository):
47 47 """
48 48 Git repository backend.
49 49 """
50 50 DEFAULT_BRANCH_NAME = 'master'
51 51 scm = 'git'
52 52
53 53 def __init__(self, repo_path, create=False, src_url=None,
54 54 update_after_clone=False, bare=False):
55 55
56 56 self.path = abspath(repo_path)
57 57 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
58 58 #temporary set that to now at later we will move it to constructor
59 59 baseui = None
60 60 if baseui is None:
61 61 from mercurial.ui import ui
62 62 baseui = ui()
63 63 # patch the instance of GitRepo with an "FAKE" ui object to add
64 64 # compatibility layer with Mercurial
65 65 setattr(self._repo, 'ui', baseui)
66 66
67 67 try:
68 68 self.head = self._repo.head()
69 69 except KeyError:
70 70 self.head = None
71 71
72 72 self._config_files = [
73 73 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
74 74 'config'),
75 75 abspath(get_user_home(), '.gitconfig'),
76 76 ]
77 77 self.bare = self._repo.bare
78 78
79 79 @LazyProperty
80 80 def revisions(self):
81 81 """
82 82 Returns list of revisions' ids, in ascending order. Being lazy
83 83 attribute allows external tools to inject shas from cache.
84 84 """
85 85 return self._get_all_revisions()
86 86
87 87 def run_git_command(self, cmd):
88 88 """
89 89 Runs given ``cmd`` as git command and returns tuple
90 90 (returncode, stdout, stderr).
91 91
92 92 .. note::
93 93 This method exists only until log/blame functionality is implemented
94 94 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
95 95 os command's output is road to hell...
96 96
97 97 :param cmd: git command to be executed
98 98 """
99 99
100 100 _copts = ['-c', 'core.quotepath=false', ]
101 101 _str_cmd = False
102 102 if isinstance(cmd, basestring):
103 103 cmd = [cmd]
104 104 _str_cmd = True
105 105
106 106 gitenv = os.environ
107 107 # need to clean fix GIT_DIR !
108 108 if 'GIT_DIR' in gitenv:
109 109 del gitenv['GIT_DIR']
110 110 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
111 111
112 112 cmd = ['git'] + _copts + cmd
113 113 if _str_cmd:
114 114 cmd = ' '.join(cmd)
115 115 try:
116 116 opts = dict(
117 117 env=gitenv,
118 118 shell=False,
119 119 )
120 120 if os.path.isdir(self.path):
121 121 opts['cwd'] = self.path
122 122 p = subprocessio.SubprocessIOChunker(cmd, **opts)
123 123 except (EnvironmentError, OSError), err:
124 124 log.error(traceback.format_exc())
125 125 raise RepositoryError("Couldn't run git command (%s).\n"
126 126 "Original error was:%s" % (cmd, err))
127 127
128 128 return ''.join(p.output), ''.join(p.error)
129 129
130 130 @classmethod
131 131 def _check_url(cls, url):
132 132 """
133 133 Functon will check given url and try to verify if it's a valid
134 134 link. Sometimes it may happened that mercurial will issue basic
135 135 auth request that can cause whole API to hang when used from python
136 136 or other external calls.
137 137
138 138 On failures it'll raise urllib2.HTTPError
139 139 """
140 140 from mercurial.util import url as Url
141 141
142 142 # those authnadlers are patched for python 2.6.5 bug an
143 143 # infinit looping when given invalid resources
144 144 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
145 145
146 146 # check first if it's not an local url
147 147 if os.path.isdir(url) or url.startswith('file:'):
148 148 return True
149 149
150 150 if('+' in url[:url.find('://')]):
151 151 url = url[url.find('+') + 1:]
152 152
153 153 handlers = []
154 154 test_uri, authinfo = Url(url).authinfo()
155 155 if not test_uri.endswith('info/refs'):
156 156 test_uri = test_uri.rstrip('/') + '/info/refs'
157 157 if authinfo:
158 158 #create a password manager
159 159 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
160 160 passmgr.add_password(*authinfo)
161 161
162 162 handlers.extend((httpbasicauthhandler(passmgr),
163 163 httpdigestauthhandler(passmgr)))
164 164
165 165 o = urllib2.build_opener(*handlers)
166 166 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
167 167
168 168 q = {"service": 'git-upload-pack'}
169 169 qs = '?%s' % urllib.urlencode(q)
170 170 cu = "%s%s" % (test_uri, qs)
171 171 req = urllib2.Request(cu, None, {})
172 172
173 173 try:
174 174 resp = o.open(req)
175 175 return resp.code == 200
176 176 except Exception, e:
177 177 # means it cannot be cloned
178 178 raise urllib2.URLError("[%s] %s" % (url, e))
179 179
180 180 def _get_repo(self, create, src_url=None, update_after_clone=False,
181 bare=False):
181 bare=False):
182 182 if create and os.path.exists(self.path):
183 183 raise RepositoryError("Location already exist")
184 184 if src_url and not create:
185 185 raise RepositoryError("Create should be set to True if src_url is "
186 186 "given (clone operation creates repository)")
187 187 try:
188 188 if create and src_url:
189 189 GitRepository._check_url(src_url)
190 190 self.clone(src_url, update_after_clone, bare)
191 191 return Repo(self.path)
192 192 elif create:
193 193 os.mkdir(self.path)
194 194 if bare:
195 195 return Repo.init_bare(self.path)
196 196 else:
197 197 return Repo.init(self.path)
198 198 else:
199 199 return Repo(self.path)
200 200 except (NotGitRepository, OSError), err:
201 201 raise RepositoryError(err)
202 202
203 203 def _get_all_revisions(self):
204 204 # we must check if this repo is not empty, since later command
205 205 # fails if it is. And it's cheaper to ask than throw the subprocess
206 206 # errors
207 207 try:
208 208 self._repo.head()
209 209 except KeyError:
210 210 return []
211 211 cmd = 'rev-list --all --reverse --date-order'
212 212 try:
213 213 so, se = self.run_git_command(cmd)
214 214 except RepositoryError:
215 215 # Can be raised for empty repositories
216 216 return []
217 217 return so.splitlines()
218 218
219 219 def _get_all_revisions2(self):
220 220 #alternate implementation using dulwich
221 221 includes = [x[1][0] for x in self._parsed_refs.iteritems()
222 222 if x[1][1] != 'T']
223 223 return [c.commit.id for c in self._repo.get_walker(include=includes)]
224 224
225 225 def _get_revision(self, revision):
226 226 """
227 227 For git backend we always return integer here. This way we ensure
228 228 that changset's revision attribute would become integer.
229 229 """
230 230 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
231 231 is_bstr = lambda o: isinstance(o, (str, unicode))
232 232 is_null = lambda o: len(o) == revision.count('0')
233 233
234 234 if len(self.revisions) == 0:
235 235 raise EmptyRepositoryError("There are no changesets yet")
236 236
237 237 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
238 238 revision = self.revisions[-1]
239 239
240 240 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
241 241 or isinstance(revision, int) or is_null(revision)):
242 242 try:
243 243 revision = self.revisions[int(revision)]
244 244 except:
245 245 raise ChangesetDoesNotExistError("Revision %r does not exist "
246 246 "for this repository %s" % (revision, self))
247 247
248 248 elif is_bstr(revision):
249 249 # get by branch/tag name
250 250 _ref_revision = self._parsed_refs.get(revision)
251 251 _tags_shas = self.tags.values()
252 252 if _ref_revision: # and _ref_revision[1] in ['H', 'RH', 'T']:
253 253 return _ref_revision[0]
254 254
255 255 # maybe it's a tag ? we don't have them in self.revisions
256 256 elif revision in _tags_shas:
257 257 return _tags_shas[_tags_shas.index(revision)]
258 258
259 259 elif not pattern.match(revision) or revision not in self.revisions:
260 260 raise ChangesetDoesNotExistError("Revision %r does not exist "
261 261 "for this repository %s" % (revision, self))
262 262
263 263 # Ensure we return full id
264 264 if not pattern.match(str(revision)):
265 265 raise ChangesetDoesNotExistError("Given revision %r not recognized"
266 266 % revision)
267 267 return revision
268 268
269 269 def _get_archives(self, archive_name='tip'):
270 270
271 271 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
272 272 yield {"type": i[0], "extension": i[1], "node": archive_name}
273 273
274 274 def _get_url(self, url):
275 275 """
276 276 Returns normalized url. If schema is not given, would fall to
277 277 filesystem (``file:///``) schema.
278 278 """
279 279 url = str(url)
280 280 if url != 'default' and not '://' in url:
281 281 url = ':///'.join(('file', url))
282 282 return url
283 283
284 284 @LazyProperty
285 285 def name(self):
286 286 return os.path.basename(self.path)
287 287
288 288 @LazyProperty
289 289 def last_change(self):
290 290 """
291 291 Returns last change made on this repository as datetime object
292 292 """
293 293 return date_fromtimestamp(self._get_mtime(), makedate()[1])
294 294
295 295 def _get_mtime(self):
296 296 try:
297 297 return time.mktime(self.get_changeset().date.timetuple())
298 298 except RepositoryError:
299 299 idx_loc = '' if self.bare else '.git'
300 300 # fallback to filesystem
301 301 in_path = os.path.join(self.path, idx_loc, "index")
302 302 he_path = os.path.join(self.path, idx_loc, "HEAD")
303 303 if os.path.exists(in_path):
304 304 return os.stat(in_path).st_mtime
305 305 else:
306 306 return os.stat(he_path).st_mtime
307 307
308 308 @LazyProperty
309 309 def description(self):
310 310 idx_loc = '' if self.bare else '.git'
311 311 undefined_description = u'unknown'
312 312 description_path = os.path.join(self.path, idx_loc, 'description')
313 313 if os.path.isfile(description_path):
314 314 return safe_unicode(open(description_path).read())
315 315 else:
316 316 return undefined_description
317 317
318 318 @LazyProperty
319 319 def contact(self):
320 320 undefined_contact = u'Unknown'
321 321 return undefined_contact
322 322
323 323 @property
324 324 def branches(self):
325 325 if not self.revisions:
326 326 return {}
327 327 sortkey = lambda ctx: ctx[0]
328 328 _branches = [(x[0], x[1][0])
329 329 for x in self._parsed_refs.iteritems() if x[1][1] == 'H']
330 330 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
331 331
332 332 @LazyProperty
333 333 def tags(self):
334 334 return self._get_tags()
335 335
336 336 def _get_tags(self):
337 337 if not self.revisions:
338 338 return {}
339 339
340 340 sortkey = lambda ctx: ctx[0]
341 341 _tags = [(x[0], x[1][0])
342 342 for x in self._parsed_refs.iteritems() if x[1][1] == 'T']
343 343 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
344 344
345 345 def tag(self, name, user, revision=None, message=None, date=None,
346 346 **kwargs):
347 347 """
348 348 Creates and returns a tag for the given ``revision``.
349 349
350 350 :param name: name for new tag
351 351 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
352 352 :param revision: changeset id for which new tag would be created
353 353 :param message: message of the tag's commit
354 354 :param date: date of tag's commit
355 355
356 356 :raises TagAlreadyExistError: if tag with same name already exists
357 357 """
358 358 if name in self.tags:
359 359 raise TagAlreadyExistError("Tag %s already exists" % name)
360 360 changeset = self.get_changeset(revision)
361 361 message = message or "Added tag %s for commit %s" % (name,
362 362 changeset.raw_id)
363 363 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
364 364
365 365 self._parsed_refs = self._get_parsed_refs()
366 366 self.tags = self._get_tags()
367 367 return changeset
368 368
369 369 def remove_tag(self, name, user, message=None, date=None):
370 370 """
371 371 Removes tag with the given ``name``.
372 372
373 373 :param name: name of the tag to be removed
374 374 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
375 375 :param message: message of the tag's removal commit
376 376 :param date: date of tag's removal commit
377 377
378 378 :raises TagDoesNotExistError: if tag with given name does not exists
379 379 """
380 380 if name not in self.tags:
381 381 raise TagDoesNotExistError("Tag %s does not exist" % name)
382 382 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
383 383 try:
384 384 os.remove(tagpath)
385 385 self._parsed_refs = self._get_parsed_refs()
386 386 self.tags = self._get_tags()
387 387 except OSError, e:
388 388 raise RepositoryError(e.strerror)
389 389
390 390 @LazyProperty
391 391 def _parsed_refs(self):
392 392 return self._get_parsed_refs()
393 393
394 394 def _get_parsed_refs(self):
395 395 refs = self._repo.get_refs()
396 396 keys = [('refs/heads/', 'H'),
397 397 ('refs/remotes/origin/', 'RH'),
398 398 ('refs/tags/', 'T')]
399 399 _refs = {}
400 400 for ref, sha in refs.iteritems():
401 401 for k, type_ in keys:
402 402 if ref.startswith(k):
403 403 _key = ref[len(k):]
404 404 _refs[_key] = [sha, type_]
405 405 break
406 406 return _refs
407 407
408 408 def _heads(self, reverse=False):
409 409 refs = self._repo.get_refs()
410 410 heads = {}
411 411
412 412 for key, val in refs.items():
413 413 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
414 414 if key.startswith(ref_key):
415 415 n = key[len(ref_key):]
416 416 if n not in ['HEAD']:
417 417 heads[n] = val
418 418
419 419 return heads if reverse else dict((y, x) for x, y in heads.iteritems())
420 420
421 421 def get_changeset(self, revision=None):
422 422 """
423 423 Returns ``GitChangeset`` object representing commit from git repository
424 424 at the given revision or head (most recent commit) if None given.
425 425 """
426 426 if isinstance(revision, GitChangeset):
427 427 return revision
428 428 revision = self._get_revision(revision)
429 429 changeset = GitChangeset(repository=self, revision=revision)
430 430 return changeset
431 431
432 432 def get_changesets(self, start=None, end=None, start_date=None,
433 433 end_date=None, branch_name=None, reverse=False):
434 434 """
435 435 Returns iterator of ``GitChangeset`` objects from start to end (both
436 436 are inclusive), in ascending date order (unless ``reverse`` is set).
437 437
438 438 :param start: changeset ID, as str; first returned changeset
439 439 :param end: changeset ID, as str; last returned changeset
440 440 :param start_date: if specified, changesets with commit date less than
441 441 ``start_date`` would be filtered out from returned set
442 442 :param end_date: if specified, changesets with commit date greater than
443 443 ``end_date`` would be filtered out from returned set
444 444 :param branch_name: if specified, changesets not reachable from given
445 445 branch would be filtered out from returned set
446 446 :param reverse: if ``True``, returned generator would be reversed
447 447 (meaning that returned changesets would have descending date order)
448 448
449 449 :raise BranchDoesNotExistError: If given ``branch_name`` does not
450 450 exist.
451 451 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
452 452 ``end`` could not be found.
453 453
454 454 """
455 455 if branch_name and branch_name not in self.branches:
456 456 raise BranchDoesNotExistError("Branch '%s' not found" \
457 457 % branch_name)
458 458 # %H at format means (full) commit hash, initial hashes are retrieved
459 459 # in ascending date order
460 460 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
461 461 cmd_params = {}
462 462 if start_date:
463 463 cmd_template += ' --since "$since"'
464 464 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
465 465 if end_date:
466 466 cmd_template += ' --until "$until"'
467 467 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
468 468 if branch_name:
469 469 cmd_template += ' $branch_name'
470 470 cmd_params['branch_name'] = branch_name
471 471 else:
472 472 cmd_template += ' --all'
473 473
474 474 cmd = Template(cmd_template).safe_substitute(**cmd_params)
475 475 revs = self.run_git_command(cmd)[0].splitlines()
476 476 start_pos = 0
477 477 end_pos = len(revs)
478 478 if start:
479 479 _start = self._get_revision(start)
480 480 try:
481 481 start_pos = revs.index(_start)
482 482 except ValueError:
483 483 pass
484 484
485 485 if end is not None:
486 486 _end = self._get_revision(end)
487 487 try:
488 488 end_pos = revs.index(_end)
489 489 except ValueError:
490 490 pass
491 491
492 492 if None not in [start, end] and start_pos > end_pos:
493 493 raise RepositoryError('start cannot be after end')
494 494
495 495 if end_pos is not None:
496 496 end_pos += 1
497 497
498 498 revs = revs[start_pos:end_pos]
499 499 if reverse:
500 500 revs = reversed(revs)
501 501 for rev in revs:
502 502 yield self.get_changeset(rev)
503 503
504 504 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
505 505 context=3):
506 506 """
507 507 Returns (git like) *diff*, as plain text. Shows changes introduced by
508 508 ``rev2`` since ``rev1``.
509 509
510 510 :param rev1: Entry point from which diff is shown. Can be
511 511 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
512 512 the changes since empty state of the repository until ``rev2``
513 513 :param rev2: Until which revision changes should be shown.
514 514 :param ignore_whitespace: If set to ``True``, would not show whitespace
515 515 changes. Defaults to ``False``.
516 516 :param context: How many lines before/after changed lines should be
517 517 shown. Defaults to ``3``.
518 518 """
519 519 flags = ['-U%s' % context]
520 520 if ignore_whitespace:
521 521 flags.append('-w')
522 522
523 523 if hasattr(rev1, 'raw_id'):
524 524 rev1 = getattr(rev1, 'raw_id')
525 525
526 526 if hasattr(rev2, 'raw_id'):
527 527 rev2 = getattr(rev2, 'raw_id')
528 528
529 529 if rev1 == self.EMPTY_CHANGESET:
530 530 rev2 = self.get_changeset(rev2).raw_id
531 531 cmd = ' '.join(['show'] + flags + [rev2])
532 532 else:
533 533 rev1 = self.get_changeset(rev1).raw_id
534 534 rev2 = self.get_changeset(rev2).raw_id
535 535 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
536 536
537 537 if path:
538 538 cmd += ' -- "%s"' % path
539 539 stdout, stderr = self.run_git_command(cmd)
540 540 # If we used 'show' command, strip first few lines (until actual diff
541 541 # starts)
542 542 if rev1 == self.EMPTY_CHANGESET:
543 543 lines = stdout.splitlines()
544 544 x = 0
545 545 for line in lines:
546 546 if line.startswith('diff'):
547 547 break
548 548 x += 1
549 549 # Append new line just like 'diff' command do
550 550 stdout = '\n'.join(lines[x:]) + '\n'
551 551 return stdout
552 552
553 553 @LazyProperty
554 554 def in_memory_changeset(self):
555 555 """
556 556 Returns ``GitInMemoryChangeset`` object for this repository.
557 557 """
558 558 return GitInMemoryChangeset(self)
559 559
560 560 def clone(self, url, update_after_clone=True, bare=False):
561 561 """
562 562 Tries to clone changes from external location.
563 563
564 564 :param update_after_clone: If set to ``False``, git won't checkout
565 565 working directory
566 566 :param bare: If set to ``True``, repository would be cloned into
567 567 *bare* git repository (no working directory at all).
568 568 """
569 569 url = self._get_url(url)
570 570 cmd = ['clone']
571 571 if bare:
572 572 cmd.append('--bare')
573 573 elif not update_after_clone:
574 574 cmd.append('--no-checkout')
575 575 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
576 576 cmd = ' '.join(cmd)
577 577 # If error occurs run_git_command raises RepositoryError already
578 578 self.run_git_command(cmd)
579 579
580 580 def pull(self, url):
581 581 """
582 582 Tries to pull changes from external location.
583 583 """
584 584 url = self._get_url(url)
585 585 cmd = ['pull']
586 586 cmd.append("--ff-only")
587 587 cmd.append(url)
588 588 cmd = ' '.join(cmd)
589 589 # If error occurs run_git_command raises RepositoryError already
590 590 self.run_git_command(cmd)
591 591
592 592 def fetch(self, url):
593 593 """
594 594 Tries to pull changes from external location.
595 595 """
596 596 url = self._get_url(url)
597 597 cmd = ['fetch']
598 598 cmd.append(url)
599 599 cmd = ' '.join(cmd)
600 600 # If error occurs run_git_command raises RepositoryError already
601 601 self.run_git_command(cmd)
602 602
603 603 @LazyProperty
604 604 def workdir(self):
605 605 """
606 606 Returns ``Workdir`` instance for this repository.
607 607 """
608 608 return GitWorkdir(self)
609 609
610 610 def get_config_value(self, section, name, config_file=None):
611 611 """
612 612 Returns configuration value for a given [``section``] and ``name``.
613 613
614 614 :param section: Section we want to retrieve value from
615 615 :param name: Name of configuration we want to retrieve
616 616 :param config_file: A path to file which should be used to retrieve
617 617 configuration from (might also be a list of file paths)
618 618 """
619 619 if config_file is None:
620 620 config_file = []
621 621 elif isinstance(config_file, basestring):
622 622 config_file = [config_file]
623 623
624 624 def gen_configs():
625 625 for path in config_file + self._config_files:
626 626 try:
627 627 yield ConfigFile.from_path(path)
628 628 except (IOError, OSError, ValueError):
629 629 continue
630 630
631 631 for config in gen_configs():
632 632 try:
633 633 return config.get(section, name)
634 634 except KeyError:
635 635 continue
636 636 return None
637 637
638 638 def get_user_name(self, config_file=None):
639 639 """
640 640 Returns user's name from global configuration file.
641 641
642 642 :param config_file: A path to file which should be used to retrieve
643 643 configuration from (might also be a list of file paths)
644 644 """
645 645 return self.get_config_value('user', 'name', config_file)
646 646
647 647 def get_user_email(self, config_file=None):
648 648 """
649 649 Returns user's email from global configuration file.
650 650
651 651 :param config_file: A path to file which should be used to retrieve
652 652 configuration from (might also be a list of file paths)
653 653 """
654 654 return self.get_config_value('user', 'email', config_file)
General Comments 0
You need to be logged in to leave comments. Login now