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