##// END OF EJS Templates
fixed issues with removed repos was accidentally added as groups, after...
marcink -
r2069:003c504d beta
parent child Browse files
Show More
@@ -1,550 +1,552 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 Changelog
3 Changelog
4 =========
4 =========
5
5
6
6
7 1.3.3 (**2012-XX-XX**)
7 1.3.3 (**2012-XX-XX**)
8 ----------------------
8 ----------------------
9
9
10 :status: in-progress
10 :status: in-progress
11 :branch: beta
11 :branch: beta
12
12
13 news
13 news
14 ++++
14 ++++
15
15
16
16
17 fixes
17 fixes
18 +++++
18 +++++
19
19
20 - fixed some python2.5 compatibility issues
20 - fixed some python2.5 compatibility issues
21 - fixed issues with removed repos was accidentally added as groups, after
22 full rescan of paths
21
23
22 1.3.2 (**2012-02-28**)
24 1.3.2 (**2012-02-28**)
23 ----------------------
25 ----------------------
24
26
25 news
27 news
26 ++++
28 ++++
27
29
28
30
29 fixes
31 fixes
30 +++++
32 +++++
31
33
32 - fixed git protocol issues with repos-groups
34 - fixed git protocol issues with repos-groups
33 - fixed git remote repos validator that prevented from cloning remote git repos
35 - fixed git remote repos validator that prevented from cloning remote git repos
34 - fixes #370 ending slashes fixes for repo and groups
36 - fixes #370 ending slashes fixes for repo and groups
35 - fixes #368 improved git-protocol detection to handle other clients
37 - fixes #368 improved git-protocol detection to handle other clients
36 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
38 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
37 Moved To Root
39 Moved To Root
38 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
40 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
39 - fixed #373 missing cascade drop on user_group_to_perm table
41 - fixed #373 missing cascade drop on user_group_to_perm table
40
42
41 1.3.1 (**2012-02-27**)
43 1.3.1 (**2012-02-27**)
42 ----------------------
44 ----------------------
43
45
44 news
46 news
45 ++++
47 ++++
46
48
47
49
48 fixes
50 fixes
49 +++++
51 +++++
50
52
51 - redirection loop occurs when remember-me wasn't checked during login
53 - redirection loop occurs when remember-me wasn't checked during login
52 - fixes issues with git blob history generation
54 - fixes issues with git blob history generation
53 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
55 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
54
56
55 1.3.0 (**2012-02-26**)
57 1.3.0 (**2012-02-26**)
56 ----------------------
58 ----------------------
57
59
58 news
60 news
59 ++++
61 ++++
60
62
61 - code review, inspired by github code-comments
63 - code review, inspired by github code-comments
62 - #215 rst and markdown README files support
64 - #215 rst and markdown README files support
63 - #252 Container-based and proxy pass-through authentication support
65 - #252 Container-based and proxy pass-through authentication support
64 - #44 branch browser. Filtering of changelog by branches
66 - #44 branch browser. Filtering of changelog by branches
65 - mercurial bookmarks support
67 - mercurial bookmarks support
66 - new hover top menu, optimized to add maximum size for important views
68 - new hover top menu, optimized to add maximum size for important views
67 - configurable clone url template with possibility to specify protocol like
69 - configurable clone url template with possibility to specify protocol like
68 ssh:// or http:// and also manually alter other parts of clone_url.
70 ssh:// or http:// and also manually alter other parts of clone_url.
69 - enabled largefiles extension by default
71 - enabled largefiles extension by default
70 - optimized summary file pages and saved a lot of unused space in them
72 - optimized summary file pages and saved a lot of unused space in them
71 - #239 option to manually mark repository as fork
73 - #239 option to manually mark repository as fork
72 - #320 mapping of commit authors to RhodeCode users
74 - #320 mapping of commit authors to RhodeCode users
73 - #304 hashes are displayed using monospace font
75 - #304 hashes are displayed using monospace font
74 - diff configuration, toggle white lines and context lines
76 - diff configuration, toggle white lines and context lines
75 - #307 configurable diffs, whitespace toggle, increasing context lines
77 - #307 configurable diffs, whitespace toggle, increasing context lines
76 - sorting on branches, tags and bookmarks using YUI datatable
78 - sorting on branches, tags and bookmarks using YUI datatable
77 - improved file filter on files page
79 - improved file filter on files page
78 - implements #330 api method for listing nodes ar particular revision
80 - implements #330 api method for listing nodes ar particular revision
79 - #73 added linking issues in commit messages to chosen issue tracker url
81 - #73 added linking issues in commit messages to chosen issue tracker url
80 based on user defined regular expression
82 based on user defined regular expression
81 - added linking of changesets in commit messages
83 - added linking of changesets in commit messages
82 - new compact changelog with expandable commit messages
84 - new compact changelog with expandable commit messages
83 - firstname and lastname are optional in user creation
85 - firstname and lastname are optional in user creation
84 - #348 added post-create repository hook
86 - #348 added post-create repository hook
85 - #212 global encoding settings is now configurable from .ini files
87 - #212 global encoding settings is now configurable from .ini files
86 - #227 added repository groups permissions
88 - #227 added repository groups permissions
87 - markdown gets codehilite extensions
89 - markdown gets codehilite extensions
88 - new API methods, delete_repositories, grante/revoke permissions for groups
90 - new API methods, delete_repositories, grante/revoke permissions for groups
89 and repos
91 and repos
90
92
91
93
92 fixes
94 fixes
93 +++++
95 +++++
94
96
95 - rewrote dbsession management for atomic operations, and better error handling
97 - rewrote dbsession management for atomic operations, and better error handling
96 - fixed sorting of repo tables
98 - fixed sorting of repo tables
97 - #326 escape of special html entities in diffs
99 - #326 escape of special html entities in diffs
98 - normalized user_name => username in api attributes
100 - normalized user_name => username in api attributes
99 - fixes #298 ldap created users with mixed case emails created conflicts
101 - fixes #298 ldap created users with mixed case emails created conflicts
100 on saving a form
102 on saving a form
101 - fixes issue when owner of a repo couldn't revoke permissions for users
103 - fixes issue when owner of a repo couldn't revoke permissions for users
102 and groups
104 and groups
103 - fixes #271 rare JSON serialization problem with statistics
105 - fixes #271 rare JSON serialization problem with statistics
104 - fixes #337 missing validation check for conflicting names of a group with a
106 - fixes #337 missing validation check for conflicting names of a group with a
105 repositories group
107 repositories group
106 - #340 fixed session problem for mysql and celery tasks
108 - #340 fixed session problem for mysql and celery tasks
107 - fixed #331 RhodeCode mangles repository names if the a repository group
109 - fixed #331 RhodeCode mangles repository names if the a repository group
108 contains the "full path" to the repositories
110 contains the "full path" to the repositories
109 - #355 RhodeCode doesn't store encrypted LDAP passwords
111 - #355 RhodeCode doesn't store encrypted LDAP passwords
110
112
111 1.2.5 (**2012-01-28**)
113 1.2.5 (**2012-01-28**)
112 ----------------------
114 ----------------------
113
115
114 news
116 news
115 ++++
117 ++++
116
118
117 fixes
119 fixes
118 +++++
120 +++++
119
121
120 - #340 Celery complains about MySQL server gone away, added session cleanup
122 - #340 Celery complains about MySQL server gone away, added session cleanup
121 for celery tasks
123 for celery tasks
122 - #341 "scanning for repositories in None" log message during Rescan was missing
124 - #341 "scanning for repositories in None" log message during Rescan was missing
123 a parameter
125 a parameter
124 - fixed creating archives with subrepos. Some hooks were triggered during that
126 - fixed creating archives with subrepos. Some hooks were triggered during that
125 operation leading to crash.
127 operation leading to crash.
126 - fixed missing email in account page.
128 - fixed missing email in account page.
127 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
129 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
128 forking on windows impossible
130 forking on windows impossible
129
131
130 1.2.4 (**2012-01-19**)
132 1.2.4 (**2012-01-19**)
131 ----------------------
133 ----------------------
132
134
133 news
135 news
134 ++++
136 ++++
135
137
136 - RhodeCode is bundled with mercurial series 2.0.X by default, with
138 - RhodeCode is bundled with mercurial series 2.0.X by default, with
137 full support to largefiles extension. Enabled by default in new installations
139 full support to largefiles extension. Enabled by default in new installations
138 - #329 Ability to Add/Remove Groups to/from a Repository via AP
140 - #329 Ability to Add/Remove Groups to/from a Repository via AP
139 - added requires.txt file with requirements
141 - added requires.txt file with requirements
140
142
141 fixes
143 fixes
142 +++++
144 +++++
143
145
144 - fixes db session issues with celery when emailing admins
146 - fixes db session issues with celery when emailing admins
145 - #331 RhodeCode mangles repository names if the a repository group
147 - #331 RhodeCode mangles repository names if the a repository group
146 contains the "full path" to the repositories
148 contains the "full path" to the repositories
147 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
149 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
148 - DB session cleanup after hg protocol operations, fixes issues with
150 - DB session cleanup after hg protocol operations, fixes issues with
149 `mysql has gone away` errors
151 `mysql has gone away` errors
150 - #333 doc fixes for get_repo api function
152 - #333 doc fixes for get_repo api function
151 - #271 rare JSON serialization problem with statistics enabled
153 - #271 rare JSON serialization problem with statistics enabled
152 - #337 Fixes issues with validation of repository name conflicting with
154 - #337 Fixes issues with validation of repository name conflicting with
153 a group name. A proper message is now displayed.
155 a group name. A proper message is now displayed.
154 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
156 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
155 doesn't work
157 doesn't work
156 - #316 fixes issues with web description in hgrc files
158 - #316 fixes issues with web description in hgrc files
157
159
158 1.2.3 (**2011-11-02**)
160 1.2.3 (**2011-11-02**)
159 ----------------------
161 ----------------------
160
162
161 news
163 news
162 ++++
164 ++++
163
165
164 - added option to manage repos group for non admin users
166 - added option to manage repos group for non admin users
165 - added following API methods for get_users, create_user, get_users_groups,
167 - added following API methods for get_users, create_user, get_users_groups,
166 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
168 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
167 get_repo, create_repo, add_user_to_repo
169 get_repo, create_repo, add_user_to_repo
168 - implements #237 added password confirmation for my account
170 - implements #237 added password confirmation for my account
169 and admin edit user.
171 and admin edit user.
170 - implements #291 email notification for global events are now sent to all
172 - implements #291 email notification for global events are now sent to all
171 administrator users, and global config email.
173 administrator users, and global config email.
172
174
173 fixes
175 fixes
174 +++++
176 +++++
175
177
176 - added option for passing auth method for smtp mailer
178 - added option for passing auth method for smtp mailer
177 - #276 issue with adding a single user with id>10 to usergroups
179 - #276 issue with adding a single user with id>10 to usergroups
178 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
180 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
179 - #288 fixes managing of repos in a group for non admin user
181 - #288 fixes managing of repos in a group for non admin user
180
182
181 1.2.2 (**2011-10-17**)
183 1.2.2 (**2011-10-17**)
182 ----------------------
184 ----------------------
183
185
184 news
186 news
185 ++++
187 ++++
186
188
187 - #226 repo groups are available by path instead of numerical id
189 - #226 repo groups are available by path instead of numerical id
188
190
189 fixes
191 fixes
190 +++++
192 +++++
191
193
192 - #259 Groups with the same name but with different parent group
194 - #259 Groups with the same name but with different parent group
193 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
195 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
194 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
196 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
195 - #265 ldap save fails sometimes on converting attributes to booleans,
197 - #265 ldap save fails sometimes on converting attributes to booleans,
196 added getter and setter into model that will prevent from this on db model level
198 added getter and setter into model that will prevent from this on db model level
197 - fixed problems with timestamps issues #251 and #213
199 - fixed problems with timestamps issues #251 and #213
198 - fixes #266 RhodeCode allows to create repo with the same name and in
200 - fixes #266 RhodeCode allows to create repo with the same name and in
199 the same parent as group
201 the same parent as group
200 - fixes #245 Rescan of the repositories on Windows
202 - fixes #245 Rescan of the repositories on Windows
201 - fixes #248 cannot edit repos inside a group on windows
203 - fixes #248 cannot edit repos inside a group on windows
202 - fixes #219 forking problems on windows
204 - fixes #219 forking problems on windows
203
205
204 1.2.1 (**2011-10-08**)
206 1.2.1 (**2011-10-08**)
205 ----------------------
207 ----------------------
206
208
207 news
209 news
208 ++++
210 ++++
209
211
210
212
211 fixes
213 fixes
212 +++++
214 +++++
213
215
214 - fixed problems with basic auth and push problems
216 - fixed problems with basic auth and push problems
215 - gui fixes
217 - gui fixes
216 - fixed logger
218 - fixed logger
217
219
218 1.2.0 (**2011-10-07**)
220 1.2.0 (**2011-10-07**)
219 ----------------------
221 ----------------------
220
222
221 news
223 news
222 ++++
224 ++++
223
225
224 - implemented #47 repository groups
226 - implemented #47 repository groups
225 - implemented #89 Can setup google analytics code from settings menu
227 - implemented #89 Can setup google analytics code from settings menu
226 - implemented #91 added nicer looking archive urls with more download options
228 - implemented #91 added nicer looking archive urls with more download options
227 like tags, branches
229 like tags, branches
228 - implemented #44 into file browsing, and added follow branch option
230 - implemented #44 into file browsing, and added follow branch option
229 - implemented #84 downloads can be enabled/disabled for each repository
231 - implemented #84 downloads can be enabled/disabled for each repository
230 - anonymous repository can be cloned without having to pass default:default
232 - anonymous repository can be cloned without having to pass default:default
231 into clone url
233 into clone url
232 - fixed #90 whoosh indexer can index chooses repositories passed in command
234 - fixed #90 whoosh indexer can index chooses repositories passed in command
233 line
235 line
234 - extended journal with day aggregates and paging
236 - extended journal with day aggregates and paging
235 - implemented #107 source code lines highlight ranges
237 - implemented #107 source code lines highlight ranges
236 - implemented #93 customizable changelog on combined revision ranges -
238 - implemented #93 customizable changelog on combined revision ranges -
237 equivalent of githubs compare view
239 equivalent of githubs compare view
238 - implemented #108 extended and more powerful LDAP configuration
240 - implemented #108 extended and more powerful LDAP configuration
239 - implemented #56 users groups
241 - implemented #56 users groups
240 - major code rewrites optimized codes for speed and memory usage
242 - major code rewrites optimized codes for speed and memory usage
241 - raw and diff downloads are now in git format
243 - raw and diff downloads are now in git format
242 - setup command checks for write access to given path
244 - setup command checks for write access to given path
243 - fixed many issues with international characters and unicode. It uses utf8
245 - fixed many issues with international characters and unicode. It uses utf8
244 decode with replace to provide less errors even with non utf8 encoded strings
246 decode with replace to provide less errors even with non utf8 encoded strings
245 - #125 added API KEY access to feeds
247 - #125 added API KEY access to feeds
246 - #109 Repository can be created from external Mercurial link (aka. remote
248 - #109 Repository can be created from external Mercurial link (aka. remote
247 repository, and manually updated (via pull) from admin panel
249 repository, and manually updated (via pull) from admin panel
248 - beta git support - push/pull server + basic view for git repos
250 - beta git support - push/pull server + basic view for git repos
249 - added followers page and forks page
251 - added followers page and forks page
250 - server side file creation (with binary file upload interface)
252 - server side file creation (with binary file upload interface)
251 and edition with commits powered by codemirror
253 and edition with commits powered by codemirror
252 - #111 file browser file finder, quick lookup files on whole file tree
254 - #111 file browser file finder, quick lookup files on whole file tree
253 - added quick login sliding menu into main page
255 - added quick login sliding menu into main page
254 - changelog uses lazy loading of affected files details, in some scenarios
256 - changelog uses lazy loading of affected files details, in some scenarios
255 this can improve speed of changelog page dramatically especially for
257 this can improve speed of changelog page dramatically especially for
256 larger repositories.
258 larger repositories.
257 - implements #214 added support for downloading subrepos in download menu.
259 - implements #214 added support for downloading subrepos in download menu.
258 - Added basic API for direct operations on rhodecode via JSON
260 - Added basic API for direct operations on rhodecode via JSON
259 - Implemented advanced hook management
261 - Implemented advanced hook management
260
262
261 fixes
263 fixes
262 +++++
264 +++++
263
265
264 - fixed file browser bug, when switching into given form revision the url was
266 - fixed file browser bug, when switching into given form revision the url was
265 not changing
267 not changing
266 - fixed propagation to error controller on simplehg and simplegit middlewares
268 - fixed propagation to error controller on simplehg and simplegit middlewares
267 - fixed error when trying to make a download on empty repository
269 - fixed error when trying to make a download on empty repository
268 - fixed problem with '[' chars in commit messages in journal
270 - fixed problem with '[' chars in commit messages in journal
269 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
271 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
270 - journal fork fixes
272 - journal fork fixes
271 - removed issue with space inside renamed repository after deletion
273 - removed issue with space inside renamed repository after deletion
272 - fixed strange issue on formencode imports
274 - fixed strange issue on formencode imports
273 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
275 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
274 - #150 fixes for errors on repositories mapped in db but corrupted in
276 - #150 fixes for errors on repositories mapped in db but corrupted in
275 filesystem
277 filesystem
276 - fixed problem with ascendant characters in realm #181
278 - fixed problem with ascendant characters in realm #181
277 - fixed problem with sqlite file based database connection pool
279 - fixed problem with sqlite file based database connection pool
278 - whoosh indexer and code stats share the same dynamic extensions map
280 - whoosh indexer and code stats share the same dynamic extensions map
279 - fixes #188 - relationship delete of repo_to_perm entry on user removal
281 - fixes #188 - relationship delete of repo_to_perm entry on user removal
280 - fixes issue #189 Trending source files shows "show more" when no more exist
282 - fixes issue #189 Trending source files shows "show more" when no more exist
281 - fixes issue #197 Relative paths for pidlocks
283 - fixes issue #197 Relative paths for pidlocks
282 - fixes issue #198 password will require only 3 chars now for login form
284 - fixes issue #198 password will require only 3 chars now for login form
283 - fixes issue #199 wrong redirection for non admin users after creating a repository
285 - fixes issue #199 wrong redirection for non admin users after creating a repository
284 - fixes issues #202, bad db constraint made impossible to attach same group
286 - fixes issues #202, bad db constraint made impossible to attach same group
285 more than one time. Affects only mysql/postgres
287 more than one time. Affects only mysql/postgres
286 - fixes #218 os.kill patch for windows was missing sig param
288 - fixes #218 os.kill patch for windows was missing sig param
287 - improved rendering of dag (they are not trimmed anymore when number of
289 - improved rendering of dag (they are not trimmed anymore when number of
288 heads exceeds 5)
290 heads exceeds 5)
289
291
290 1.1.8 (**2011-04-12**)
292 1.1.8 (**2011-04-12**)
291 ----------------------
293 ----------------------
292
294
293 news
295 news
294 ++++
296 ++++
295
297
296 - improved windows support
298 - improved windows support
297
299
298 fixes
300 fixes
299 +++++
301 +++++
300
302
301 - fixed #140 freeze of python dateutil library, since new version is python2.x
303 - fixed #140 freeze of python dateutil library, since new version is python2.x
302 incompatible
304 incompatible
303 - setup-app will check for write permission in given path
305 - setup-app will check for write permission in given path
304 - cleaned up license info issue #149
306 - cleaned up license info issue #149
305 - fixes for issues #137,#116 and problems with unicode and accented characters.
307 - fixes for issues #137,#116 and problems with unicode and accented characters.
306 - fixes crashes on gravatar, when passed in email as unicode
308 - fixes crashes on gravatar, when passed in email as unicode
307 - fixed tooltip flickering problems
309 - fixed tooltip flickering problems
308 - fixed came_from redirection on windows
310 - fixed came_from redirection on windows
309 - fixed logging modules, and sql formatters
311 - fixed logging modules, and sql formatters
310 - windows fixes for os.kill issue #133
312 - windows fixes for os.kill issue #133
311 - fixes path splitting for windows issues #148
313 - fixes path splitting for windows issues #148
312 - fixed issue #143 wrong import on migration to 1.1.X
314 - fixed issue #143 wrong import on migration to 1.1.X
313 - fixed problems with displaying binary files, thanks to Thomas Waldmann
315 - fixed problems with displaying binary files, thanks to Thomas Waldmann
314 - removed name from archive files since it's breaking ui for long repo names
316 - removed name from archive files since it's breaking ui for long repo names
315 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
317 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
316 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
318 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
317 Thomas Waldmann
319 Thomas Waldmann
318 - fixed issue #166 summary pager was skipping 10 revisions on second page
320 - fixed issue #166 summary pager was skipping 10 revisions on second page
319
321
320
322
321 1.1.7 (**2011-03-23**)
323 1.1.7 (**2011-03-23**)
322 ----------------------
324 ----------------------
323
325
324 news
326 news
325 ++++
327 ++++
326
328
327 fixes
329 fixes
328 +++++
330 +++++
329
331
330 - fixed (again) #136 installation support for FreeBSD
332 - fixed (again) #136 installation support for FreeBSD
331
333
332
334
333 1.1.6 (**2011-03-21**)
335 1.1.6 (**2011-03-21**)
334 ----------------------
336 ----------------------
335
337
336 news
338 news
337 ++++
339 ++++
338
340
339 fixes
341 fixes
340 +++++
342 +++++
341
343
342 - fixed #136 installation support for FreeBSD
344 - fixed #136 installation support for FreeBSD
343 - RhodeCode will check for python version during installation
345 - RhodeCode will check for python version during installation
344
346
345 1.1.5 (**2011-03-17**)
347 1.1.5 (**2011-03-17**)
346 ----------------------
348 ----------------------
347
349
348 news
350 news
349 ++++
351 ++++
350
352
351 - basic windows support, by exchanging pybcrypt into sha256 for windows only
353 - basic windows support, by exchanging pybcrypt into sha256 for windows only
352 highly inspired by idea of mantis406
354 highly inspired by idea of mantis406
353
355
354 fixes
356 fixes
355 +++++
357 +++++
356
358
357 - fixed sorting by author in main page
359 - fixed sorting by author in main page
358 - fixed crashes with diffs on binary files
360 - fixed crashes with diffs on binary files
359 - fixed #131 problem with boolean values for LDAP
361 - fixed #131 problem with boolean values for LDAP
360 - fixed #122 mysql problems thanks to striker69
362 - fixed #122 mysql problems thanks to striker69
361 - fixed problem with errors on calling raw/raw_files/annotate functions
363 - fixed problem with errors on calling raw/raw_files/annotate functions
362 with unknown revisions
364 with unknown revisions
363 - fixed returned rawfiles attachment names with international character
365 - fixed returned rawfiles attachment names with international character
364 - cleaned out docs, big thanks to Jason Harris
366 - cleaned out docs, big thanks to Jason Harris
365
367
366 1.1.4 (**2011-02-19**)
368 1.1.4 (**2011-02-19**)
367 ----------------------
369 ----------------------
368
370
369 news
371 news
370 ++++
372 ++++
371
373
372 fixes
374 fixes
373 +++++
375 +++++
374
376
375 - fixed formencode import problem on settings page, that caused server crash
377 - fixed formencode import problem on settings page, that caused server crash
376 when that page was accessed as first after server start
378 when that page was accessed as first after server start
377 - journal fixes
379 - journal fixes
378 - fixed option to access repository just by entering http://server/<repo_name>
380 - fixed option to access repository just by entering http://server/<repo_name>
379
381
380 1.1.3 (**2011-02-16**)
382 1.1.3 (**2011-02-16**)
381 ----------------------
383 ----------------------
382
384
383 news
385 news
384 ++++
386 ++++
385
387
386 - implemented #102 allowing the '.' character in username
388 - implemented #102 allowing the '.' character in username
387 - added option to access repository just by entering http://server/<repo_name>
389 - added option to access repository just by entering http://server/<repo_name>
388 - celery task ignores result for better performance
390 - celery task ignores result for better performance
389
391
390 fixes
392 fixes
391 +++++
393 +++++
392
394
393 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
395 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
394 apollo13 and Johan Walles
396 apollo13 and Johan Walles
395 - small fixes in journal
397 - small fixes in journal
396 - fixed problems with getting setting for celery from .ini files
398 - fixed problems with getting setting for celery from .ini files
397 - registration, password reset and login boxes share the same title as main
399 - registration, password reset and login boxes share the same title as main
398 application now
400 application now
399 - fixed #113: to high permissions to fork repository
401 - fixed #113: to high permissions to fork repository
400 - fixed problem with '[' chars in commit messages in journal
402 - fixed problem with '[' chars in commit messages in journal
401 - removed issue with space inside renamed repository after deletion
403 - removed issue with space inside renamed repository after deletion
402 - db transaction fixes when filesystem repository creation failed
404 - db transaction fixes when filesystem repository creation failed
403 - fixed #106 relation issues on databases different than sqlite
405 - fixed #106 relation issues on databases different than sqlite
404 - fixed static files paths links to use of url() method
406 - fixed static files paths links to use of url() method
405
407
406 1.1.2 (**2011-01-12**)
408 1.1.2 (**2011-01-12**)
407 ----------------------
409 ----------------------
408
410
409 news
411 news
410 ++++
412 ++++
411
413
412
414
413 fixes
415 fixes
414 +++++
416 +++++
415
417
416 - fixes #98 protection against float division of percentage stats
418 - fixes #98 protection against float division of percentage stats
417 - fixed graph bug
419 - fixed graph bug
418 - forced webhelpers version since it was making troubles during installation
420 - forced webhelpers version since it was making troubles during installation
419
421
420 1.1.1 (**2011-01-06**)
422 1.1.1 (**2011-01-06**)
421 ----------------------
423 ----------------------
422
424
423 news
425 news
424 ++++
426 ++++
425
427
426 - added force https option into ini files for easier https usage (no need to
428 - added force https option into ini files for easier https usage (no need to
427 set server headers with this options)
429 set server headers with this options)
428 - small css updates
430 - small css updates
429
431
430 fixes
432 fixes
431 +++++
433 +++++
432
434
433 - fixed #96 redirect loop on files view on repositories without changesets
435 - fixed #96 redirect loop on files view on repositories without changesets
434 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
436 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
435 and server crashed with errors
437 and server crashed with errors
436 - fixed large tooltips problems on main page
438 - fixed large tooltips problems on main page
437 - fixed #92 whoosh indexer is more error proof
439 - fixed #92 whoosh indexer is more error proof
438
440
439 1.1.0 (**2010-12-18**)
441 1.1.0 (**2010-12-18**)
440 ----------------------
442 ----------------------
441
443
442 news
444 news
443 ++++
445 ++++
444
446
445 - rewrite of internals for vcs >=0.1.10
447 - rewrite of internals for vcs >=0.1.10
446 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
448 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
447 with older clients
449 with older clients
448 - anonymous access, authentication via ldap
450 - anonymous access, authentication via ldap
449 - performance upgrade for cached repos list - each repository has its own
451 - performance upgrade for cached repos list - each repository has its own
450 cache that's invalidated when needed.
452 cache that's invalidated when needed.
451 - performance upgrades on repositories with large amount of commits (20K+)
453 - performance upgrades on repositories with large amount of commits (20K+)
452 - main page quick filter for filtering repositories
454 - main page quick filter for filtering repositories
453 - user dashboards with ability to follow chosen repositories actions
455 - user dashboards with ability to follow chosen repositories actions
454 - sends email to admin on new user registration
456 - sends email to admin on new user registration
455 - added cache/statistics reset options into repository settings
457 - added cache/statistics reset options into repository settings
456 - more detailed action logger (based on hooks) with pushed changesets lists
458 - more detailed action logger (based on hooks) with pushed changesets lists
457 and options to disable those hooks from admin panel
459 and options to disable those hooks from admin panel
458 - introduced new enhanced changelog for merges that shows more accurate results
460 - introduced new enhanced changelog for merges that shows more accurate results
459 - new improved and faster code stats (based on pygments lexers mapping tables,
461 - new improved and faster code stats (based on pygments lexers mapping tables,
460 showing up to 10 trending sources for each repository. Additionally stats
462 showing up to 10 trending sources for each repository. Additionally stats
461 can be disabled in repository settings.
463 can be disabled in repository settings.
462 - gui optimizations, fixed application width to 1024px
464 - gui optimizations, fixed application width to 1024px
463 - added cut off (for large files/changesets) limit into config files
465 - added cut off (for large files/changesets) limit into config files
464 - whoosh, celeryd, upgrade moved to paster command
466 - whoosh, celeryd, upgrade moved to paster command
465 - other than sqlite database backends can be used
467 - other than sqlite database backends can be used
466
468
467 fixes
469 fixes
468 +++++
470 +++++
469
471
470 - fixes #61 forked repo was showing only after cache expired
472 - fixes #61 forked repo was showing only after cache expired
471 - fixes #76 no confirmation on user deletes
473 - fixes #76 no confirmation on user deletes
472 - fixes #66 Name field misspelled
474 - fixes #66 Name field misspelled
473 - fixes #72 block user removal when he owns repositories
475 - fixes #72 block user removal when he owns repositories
474 - fixes #69 added password confirmation fields
476 - fixes #69 added password confirmation fields
475 - fixes #87 RhodeCode crashes occasionally on updating repository owner
477 - fixes #87 RhodeCode crashes occasionally on updating repository owner
476 - fixes #82 broken annotations on files with more than 1 blank line at the end
478 - fixes #82 broken annotations on files with more than 1 blank line at the end
477 - a lot of fixes and tweaks for file browser
479 - a lot of fixes and tweaks for file browser
478 - fixed detached session issues
480 - fixed detached session issues
479 - fixed when user had no repos he would see all repos listed in my account
481 - fixed when user had no repos he would see all repos listed in my account
480 - fixed ui() instance bug when global hgrc settings was loaded for server
482 - fixed ui() instance bug when global hgrc settings was loaded for server
481 instance and all hgrc options were merged with our db ui() object
483 instance and all hgrc options were merged with our db ui() object
482 - numerous small bugfixes
484 - numerous small bugfixes
483
485
484 (special thanks for TkSoh for detailed feedback)
486 (special thanks for TkSoh for detailed feedback)
485
487
486
488
487 1.0.2 (**2010-11-12**)
489 1.0.2 (**2010-11-12**)
488 ----------------------
490 ----------------------
489
491
490 news
492 news
491 ++++
493 ++++
492
494
493 - tested under python2.7
495 - tested under python2.7
494 - bumped sqlalchemy and celery versions
496 - bumped sqlalchemy and celery versions
495
497
496 fixes
498 fixes
497 +++++
499 +++++
498
500
499 - fixed #59 missing graph.js
501 - fixed #59 missing graph.js
500 - fixed repo_size crash when repository had broken symlinks
502 - fixed repo_size crash when repository had broken symlinks
501 - fixed python2.5 crashes.
503 - fixed python2.5 crashes.
502
504
503
505
504 1.0.1 (**2010-11-10**)
506 1.0.1 (**2010-11-10**)
505 ----------------------
507 ----------------------
506
508
507 news
509 news
508 ++++
510 ++++
509
511
510 - small css updated
512 - small css updated
511
513
512 fixes
514 fixes
513 +++++
515 +++++
514
516
515 - fixed #53 python2.5 incompatible enumerate calls
517 - fixed #53 python2.5 incompatible enumerate calls
516 - fixed #52 disable mercurial extension for web
518 - fixed #52 disable mercurial extension for web
517 - fixed #51 deleting repositories don't delete it's dependent objects
519 - fixed #51 deleting repositories don't delete it's dependent objects
518
520
519
521
520 1.0.0 (**2010-11-02**)
522 1.0.0 (**2010-11-02**)
521 ----------------------
523 ----------------------
522
524
523 - security bugfix simplehg wasn't checking for permissions on commands
525 - security bugfix simplehg wasn't checking for permissions on commands
524 other than pull or push.
526 other than pull or push.
525 - fixed doubled messages after push or pull in admin journal
527 - fixed doubled messages after push or pull in admin journal
526 - templating and css corrections, fixed repo switcher on chrome, updated titles
528 - templating and css corrections, fixed repo switcher on chrome, updated titles
527 - admin menu accessible from options menu on repository view
529 - admin menu accessible from options menu on repository view
528 - permissions cached queries
530 - permissions cached queries
529
531
530 1.0.0rc4 (**2010-10-12**)
532 1.0.0rc4 (**2010-10-12**)
531 --------------------------
533 --------------------------
532
534
533 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
535 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
534 - removed cache_manager settings from sqlalchemy meta
536 - removed cache_manager settings from sqlalchemy meta
535 - added sqlalchemy cache settings to ini files
537 - added sqlalchemy cache settings to ini files
536 - validated password length and added second try of failure on paster setup-app
538 - validated password length and added second try of failure on paster setup-app
537 - fixed setup database destroy prompt even when there was no db
539 - fixed setup database destroy prompt even when there was no db
538
540
539
541
540 1.0.0rc3 (**2010-10-11**)
542 1.0.0rc3 (**2010-10-11**)
541 -------------------------
543 -------------------------
542
544
543 - fixed i18n during installation.
545 - fixed i18n during installation.
544
546
545 1.0.0rc2 (**2010-10-11**)
547 1.0.0rc2 (**2010-10-11**)
546 -------------------------
548 -------------------------
547
549
548 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
550 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
549 occure. After vcs is fixed it'll be put back again.
551 occure. After vcs is fixed it'll be put back again.
550 - templating/css rewrites, optimized css. No newline at end of file
552 - templating/css rewrites, optimized css.
@@ -1,622 +1,629 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import re
27 import logging
28 import logging
28 import datetime
29 import datetime
29 import traceback
30 import traceback
30 import paste
31 import paste
31 import beaker
32 import beaker
32 import tarfile
33 import tarfile
33 import shutil
34 import shutil
34 from os.path import abspath
35 from os.path import abspath
35 from os.path import dirname as dn, join as jn
36 from os.path import dirname as dn, join as jn
36
37
37 from paste.script.command import Command, BadCommand
38 from paste.script.command import Command, BadCommand
38
39
39 from mercurial import ui, config
40 from mercurial import ui, config
40
41
41 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42
43
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.backends.base import BaseChangeset
45 from rhodecode.lib.vcs.backends.base import BaseChangeset
45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.helpers import get_scm
47 from rhodecode.lib.vcs.utils.helpers import get_scm
47 from rhodecode.lib.vcs.exceptions import VCSError
48 from rhodecode.lib.vcs.exceptions import VCSError
48
49
49 from rhodecode.lib.caching_query import FromCache
50 from rhodecode.lib.caching_query import FromCache
50
51
51 from rhodecode.model import meta
52 from rhodecode.model import meta
52 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
54 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
55 from rhodecode.model.repos_group import ReposGroupModel
56 from rhodecode.model.repos_group import ReposGroupModel
56
57
57 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
58
59
60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61
59
62
60 def recursive_replace(str_, replace=' '):
63 def recursive_replace(str_, replace=' '):
61 """Recursive replace of given sign to just one instance
64 """Recursive replace of given sign to just one instance
62
65
63 :param str_: given string
66 :param str_: given string
64 :param replace: char to find and replace multiple instances
67 :param replace: char to find and replace multiple instances
65
68
66 Examples::
69 Examples::
67 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
70 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
68 'Mighty-Mighty-Bo-sstones'
71 'Mighty-Mighty-Bo-sstones'
69 """
72 """
70
73
71 if str_.find(replace * 2) == -1:
74 if str_.find(replace * 2) == -1:
72 return str_
75 return str_
73 else:
76 else:
74 str_ = str_.replace(replace * 2, replace)
77 str_ = str_.replace(replace * 2, replace)
75 return recursive_replace(str_, replace)
78 return recursive_replace(str_, replace)
76
79
77
80
78 def repo_name_slug(value):
81 def repo_name_slug(value):
79 """Return slug of name of repository
82 """Return slug of name of repository
80 This function is called on each creation/modification
83 This function is called on each creation/modification
81 of repository to prevent bad names in repo
84 of repository to prevent bad names in repo
82 """
85 """
83
86
84 slug = remove_formatting(value)
87 slug = remove_formatting(value)
85 slug = strip_tags(slug)
88 slug = strip_tags(slug)
86
89
87 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
90 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
88 slug = slug.replace(c, '-')
91 slug = slug.replace(c, '-')
89 slug = recursive_replace(slug, '-')
92 slug = recursive_replace(slug, '-')
90 slug = collapse(slug, '-')
93 slug = collapse(slug, '-')
91 return slug
94 return slug
92
95
93
96
94 def get_repo_slug(request):
97 def get_repo_slug(request):
95 _repo = request.environ['pylons.routes_dict'].get('repo_name')
98 _repo = request.environ['pylons.routes_dict'].get('repo_name')
96 if _repo:
99 if _repo:
97 _repo = _repo.rstrip('/')
100 _repo = _repo.rstrip('/')
98 return _repo
101 return _repo
99
102
100
103
101 def get_repos_group_slug(request):
104 def get_repos_group_slug(request):
102 _group = request.environ['pylons.routes_dict'].get('group_name')
105 _group = request.environ['pylons.routes_dict'].get('group_name')
103 if _group:
106 if _group:
104 _group = _group.rstrip('/')
107 _group = _group.rstrip('/')
105 return _group
108 return _group
106
109
107
110
108 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
111 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
109 """
112 """
110 Action logger for various actions made by users
113 Action logger for various actions made by users
111
114
112 :param user: user that made this action, can be a unique username string or
115 :param user: user that made this action, can be a unique username string or
113 object containing user_id attribute
116 object containing user_id attribute
114 :param action: action to log, should be on of predefined unique actions for
117 :param action: action to log, should be on of predefined unique actions for
115 easy translations
118 easy translations
116 :param repo: string name of repository or object containing repo_id,
119 :param repo: string name of repository or object containing repo_id,
117 that action was made on
120 that action was made on
118 :param ipaddr: optional ip address from what the action was made
121 :param ipaddr: optional ip address from what the action was made
119 :param sa: optional sqlalchemy session
122 :param sa: optional sqlalchemy session
120
123
121 """
124 """
122
125
123 if not sa:
126 if not sa:
124 sa = meta.Session
127 sa = meta.Session
125
128
126 try:
129 try:
127 if hasattr(user, 'user_id'):
130 if hasattr(user, 'user_id'):
128 user_obj = user
131 user_obj = user
129 elif isinstance(user, basestring):
132 elif isinstance(user, basestring):
130 user_obj = User.get_by_username(user)
133 user_obj = User.get_by_username(user)
131 else:
134 else:
132 raise Exception('You have to provide user object or username')
135 raise Exception('You have to provide user object or username')
133
136
134 if hasattr(repo, 'repo_id'):
137 if hasattr(repo, 'repo_id'):
135 repo_obj = Repository.get(repo.repo_id)
138 repo_obj = Repository.get(repo.repo_id)
136 repo_name = repo_obj.repo_name
139 repo_name = repo_obj.repo_name
137 elif isinstance(repo, basestring):
140 elif isinstance(repo, basestring):
138 repo_name = repo.lstrip('/')
141 repo_name = repo.lstrip('/')
139 repo_obj = Repository.get_by_repo_name(repo_name)
142 repo_obj = Repository.get_by_repo_name(repo_name)
140 else:
143 else:
141 raise Exception('You have to provide repository to action logger')
144 raise Exception('You have to provide repository to action logger')
142
145
143 user_log = UserLog()
146 user_log = UserLog()
144 user_log.user_id = user_obj.user_id
147 user_log.user_id = user_obj.user_id
145 user_log.action = action
148 user_log.action = action
146
149
147 user_log.repository_id = repo_obj.repo_id
150 user_log.repository_id = repo_obj.repo_id
148 user_log.repository_name = repo_name
151 user_log.repository_name = repo_name
149
152
150 user_log.action_date = datetime.datetime.now()
153 user_log.action_date = datetime.datetime.now()
151 user_log.user_ip = ipaddr
154 user_log.user_ip = ipaddr
152 sa.add(user_log)
155 sa.add(user_log)
153
156
154 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
157 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
155 if commit:
158 if commit:
156 sa.commit()
159 sa.commit()
157 except:
160 except:
158 log.error(traceback.format_exc())
161 log.error(traceback.format_exc())
159 raise
162 raise
160
163
161
164
162 def get_repos(path, recursive=False):
165 def get_repos(path, recursive=False):
163 """
166 """
164 Scans given path for repos and return (name,(type,path)) tuple
167 Scans given path for repos and return (name,(type,path)) tuple
165
168
166 :param path: path to scan for repositories
169 :param path: path to scan for repositories
167 :param recursive: recursive search and return names with subdirs in front
170 :param recursive: recursive search and return names with subdirs in front
168 """
171 """
169
172
170 # remove ending slash for better results
173 # remove ending slash for better results
171 path = path.rstrip(os.sep)
174 path = path.rstrip(os.sep)
172
175
173 def _get_repos(p):
176 def _get_repos(p):
174 if not os.access(p, os.W_OK):
177 if not os.access(p, os.W_OK):
175 return
178 return
176 for dirpath in os.listdir(p):
179 for dirpath in os.listdir(p):
177 if os.path.isfile(os.path.join(p, dirpath)):
180 if os.path.isfile(os.path.join(p, dirpath)):
178 continue
181 continue
179 cur_path = os.path.join(p, dirpath)
182 cur_path = os.path.join(p, dirpath)
180 try:
183 try:
181 scm_info = get_scm(cur_path)
184 scm_info = get_scm(cur_path)
182 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
185 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
183 except VCSError:
186 except VCSError:
184 if not recursive:
187 if not recursive:
185 continue
188 continue
186 #check if this dir containts other repos for recursive scan
189 #check if this dir containts other repos for recursive scan
187 rec_path = os.path.join(p, dirpath)
190 rec_path = os.path.join(p, dirpath)
188 if os.path.isdir(rec_path):
191 if os.path.isdir(rec_path):
189 for inner_scm in _get_repos(rec_path):
192 for inner_scm in _get_repos(rec_path):
190 yield inner_scm
193 yield inner_scm
191
194
192 return _get_repos(path)
195 return _get_repos(path)
193
196
194
197
195 def is_valid_repo(repo_name, base_path):
198 def is_valid_repo(repo_name, base_path):
196 """
199 """
197 Returns True if given path is a valid repository False otherwise
200 Returns True if given path is a valid repository False otherwise
198 :param repo_name:
201 :param repo_name:
199 :param base_path:
202 :param base_path:
200
203
201 :return True: if given path is a valid repository
204 :return True: if given path is a valid repository
202 """
205 """
203 full_path = os.path.join(base_path, repo_name)
206 full_path = os.path.join(base_path, repo_name)
204
207
205 try:
208 try:
206 get_scm(full_path)
209 get_scm(full_path)
207 return True
210 return True
208 except VCSError:
211 except VCSError:
209 return False
212 return False
210
213
211
214
212 def is_valid_repos_group(repos_group_name, base_path):
215 def is_valid_repos_group(repos_group_name, base_path):
213 """
216 """
214 Returns True if given path is a repos group False otherwise
217 Returns True if given path is a repos group False otherwise
215
218
216 :param repo_name:
219 :param repo_name:
217 :param base_path:
220 :param base_path:
218 """
221 """
219 full_path = os.path.join(base_path, repos_group_name)
222 full_path = os.path.join(base_path, repos_group_name)
220
223
221 # check if it's not a repo
224 # check if it's not a repo
222 if is_valid_repo(repos_group_name, base_path):
225 if is_valid_repo(repos_group_name, base_path):
223 return False
226 return False
224
227
225 # check if it's a valid path
228 # check if it's a valid path
226 if os.path.isdir(full_path):
229 if os.path.isdir(full_path):
227 return True
230 return True
228
231
229 return False
232 return False
230
233
231
234
232 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
235 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
233 while True:
236 while True:
234 ok = raw_input(prompt)
237 ok = raw_input(prompt)
235 if ok in ('y', 'ye', 'yes'):
238 if ok in ('y', 'ye', 'yes'):
236 return True
239 return True
237 if ok in ('n', 'no', 'nop', 'nope'):
240 if ok in ('n', 'no', 'nop', 'nope'):
238 return False
241 return False
239 retries = retries - 1
242 retries = retries - 1
240 if retries < 0:
243 if retries < 0:
241 raise IOError
244 raise IOError
242 print complaint
245 print complaint
243
246
244 #propagated from mercurial documentation
247 #propagated from mercurial documentation
245 ui_sections = ['alias', 'auth',
248 ui_sections = ['alias', 'auth',
246 'decode/encode', 'defaults',
249 'decode/encode', 'defaults',
247 'diff', 'email',
250 'diff', 'email',
248 'extensions', 'format',
251 'extensions', 'format',
249 'merge-patterns', 'merge-tools',
252 'merge-patterns', 'merge-tools',
250 'hooks', 'http_proxy',
253 'hooks', 'http_proxy',
251 'smtp', 'patch',
254 'smtp', 'patch',
252 'paths', 'profiling',
255 'paths', 'profiling',
253 'server', 'trusted',
256 'server', 'trusted',
254 'ui', 'web', ]
257 'ui', 'web', ]
255
258
256
259
257 def make_ui(read_from='file', path=None, checkpaths=True):
260 def make_ui(read_from='file', path=None, checkpaths=True):
258 """A function that will read python rc files or database
261 """A function that will read python rc files or database
259 and make an mercurial ui object from read options
262 and make an mercurial ui object from read options
260
263
261 :param path: path to mercurial config file
264 :param path: path to mercurial config file
262 :param checkpaths: check the path
265 :param checkpaths: check the path
263 :param read_from: read from 'file' or 'db'
266 :param read_from: read from 'file' or 'db'
264 """
267 """
265
268
266 baseui = ui.ui()
269 baseui = ui.ui()
267
270
268 # clean the baseui object
271 # clean the baseui object
269 baseui._ocfg = config.config()
272 baseui._ocfg = config.config()
270 baseui._ucfg = config.config()
273 baseui._ucfg = config.config()
271 baseui._tcfg = config.config()
274 baseui._tcfg = config.config()
272
275
273 if read_from == 'file':
276 if read_from == 'file':
274 if not os.path.isfile(path):
277 if not os.path.isfile(path):
275 log.debug('hgrc file is not present at %s skipping...' % path)
278 log.debug('hgrc file is not present at %s skipping...' % path)
276 return False
279 return False
277 log.debug('reading hgrc from %s' % path)
280 log.debug('reading hgrc from %s' % path)
278 cfg = config.config()
281 cfg = config.config()
279 cfg.read(path)
282 cfg.read(path)
280 for section in ui_sections:
283 for section in ui_sections:
281 for k, v in cfg.items(section):
284 for k, v in cfg.items(section):
282 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
285 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
283 baseui.setconfig(section, k, v)
286 baseui.setconfig(section, k, v)
284
287
285 elif read_from == 'db':
288 elif read_from == 'db':
286 sa = meta.Session
289 sa = meta.Session
287 ret = sa.query(RhodeCodeUi)\
290 ret = sa.query(RhodeCodeUi)\
288 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
291 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
289 .all()
292 .all()
290
293
291 hg_ui = ret
294 hg_ui = ret
292 for ui_ in hg_ui:
295 for ui_ in hg_ui:
293 if ui_.ui_active:
296 if ui_.ui_active:
294 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
297 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
295 ui_.ui_key, ui_.ui_value)
298 ui_.ui_key, ui_.ui_value)
296 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
299 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
297
300
298 meta.Session.remove()
301 meta.Session.remove()
299 return baseui
302 return baseui
300
303
301
304
302 def set_rhodecode_config(config):
305 def set_rhodecode_config(config):
303 """
306 """
304 Updates pylons config with new settings from database
307 Updates pylons config with new settings from database
305
308
306 :param config:
309 :param config:
307 """
310 """
308 hgsettings = RhodeCodeSetting.get_app_settings()
311 hgsettings = RhodeCodeSetting.get_app_settings()
309
312
310 for k, v in hgsettings.items():
313 for k, v in hgsettings.items():
311 config[k] = v
314 config[k] = v
312
315
313
316
314 def invalidate_cache(cache_key, *args):
317 def invalidate_cache(cache_key, *args):
315 """
318 """
316 Puts cache invalidation task into db for
319 Puts cache invalidation task into db for
317 further global cache invalidation
320 further global cache invalidation
318 """
321 """
319
322
320 from rhodecode.model.scm import ScmModel
323 from rhodecode.model.scm import ScmModel
321
324
322 if cache_key.startswith('get_repo_cached_'):
325 if cache_key.startswith('get_repo_cached_'):
323 name = cache_key.split('get_repo_cached_')[-1]
326 name = cache_key.split('get_repo_cached_')[-1]
324 ScmModel().mark_for_invalidation(name)
327 ScmModel().mark_for_invalidation(name)
325
328
326
329
327 class EmptyChangeset(BaseChangeset):
330 class EmptyChangeset(BaseChangeset):
328 """
331 """
329 An dummy empty changeset. It's possible to pass hash when creating
332 An dummy empty changeset. It's possible to pass hash when creating
330 an EmptyChangeset
333 an EmptyChangeset
331 """
334 """
332
335
333 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
336 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
334 alias=None):
337 alias=None):
335 self._empty_cs = cs
338 self._empty_cs = cs
336 self.revision = -1
339 self.revision = -1
337 self.message = ''
340 self.message = ''
338 self.author = ''
341 self.author = ''
339 self.date = ''
342 self.date = ''
340 self.repository = repo
343 self.repository = repo
341 self.requested_revision = requested_revision
344 self.requested_revision = requested_revision
342 self.alias = alias
345 self.alias = alias
343
346
344 @LazyProperty
347 @LazyProperty
345 def raw_id(self):
348 def raw_id(self):
346 """
349 """
347 Returns raw string identifying this changeset, useful for web
350 Returns raw string identifying this changeset, useful for web
348 representation.
351 representation.
349 """
352 """
350
353
351 return self._empty_cs
354 return self._empty_cs
352
355
353 @LazyProperty
356 @LazyProperty
354 def branch(self):
357 def branch(self):
355 return get_backend(self.alias).DEFAULT_BRANCH_NAME
358 return get_backend(self.alias).DEFAULT_BRANCH_NAME
356
359
357 @LazyProperty
360 @LazyProperty
358 def short_id(self):
361 def short_id(self):
359 return self.raw_id[:12]
362 return self.raw_id[:12]
360
363
361 def get_file_changeset(self, path):
364 def get_file_changeset(self, path):
362 return self
365 return self
363
366
364 def get_file_content(self, path):
367 def get_file_content(self, path):
365 return u''
368 return u''
366
369
367 def get_file_size(self, path):
370 def get_file_size(self, path):
368 return 0
371 return 0
369
372
370
373
371 def map_groups(groups):
374 def map_groups(groups):
372 """
375 """
373 Checks for groups existence, and creates groups structures.
376 Checks for groups existence, and creates groups structures.
374 It returns last group in structure
377 It returns last group in structure
375
378
376 :param groups: list of groups structure
379 :param groups: list of groups structure
377 """
380 """
378 sa = meta.Session
381 sa = meta.Session
379
382
380 parent = None
383 parent = None
381 group = None
384 group = None
382
385
383 # last element is repo in nested groups structure
386 # last element is repo in nested groups structure
384 groups = groups[:-1]
387 groups = groups[:-1]
385 rgm = ReposGroupModel(sa)
388 rgm = ReposGroupModel(sa)
386 for lvl, group_name in enumerate(groups):
389 for lvl, group_name in enumerate(groups):
387 group_name = '/'.join(groups[:lvl] + [group_name])
390 group_name = '/'.join(groups[:lvl] + [group_name])
388 group = RepoGroup.get_by_group_name(group_name)
391 group = RepoGroup.get_by_group_name(group_name)
389 desc = '%s group' % group_name
392 desc = '%s group' % group_name
390
393
391 # # WTF that doesn't work !?
394 # # WTF that doesn't work !?
392 # if group is None:
395 # if group is None:
393 # group = rgm.create(group_name, desc, parent, just_db=True)
396 # group = rgm.create(group_name, desc, parent, just_db=True)
394 # sa.commit()
397 # sa.commit()
395
398
399 # skip folders that are now removed repos
400 if REMOVED_REPO_PAT.match(group_name):
401 break
402
396 if group is None:
403 if group is None:
397 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
404 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
398 group = RepoGroup(group_name, parent)
405 group = RepoGroup(group_name, parent)
399 group.group_description = desc
406 group.group_description = desc
400 sa.add(group)
407 sa.add(group)
401 rgm._create_default_perms(group)
408 rgm._create_default_perms(group)
402 sa.commit()
409 sa.commit()
403 parent = group
410 parent = group
404 return group
411 return group
405
412
406
413
407 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
414 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
408 """
415 """
409 maps all repos given in initial_repo_list, non existing repositories
416 maps all repos given in initial_repo_list, non existing repositories
410 are created, if remove_obsolete is True it also check for db entries
417 are created, if remove_obsolete is True it also check for db entries
411 that are not in initial_repo_list and removes them.
418 that are not in initial_repo_list and removes them.
412
419
413 :param initial_repo_list: list of repositories found by scanning methods
420 :param initial_repo_list: list of repositories found by scanning methods
414 :param remove_obsolete: check for obsolete entries in database
421 :param remove_obsolete: check for obsolete entries in database
415 """
422 """
416 from rhodecode.model.repo import RepoModel
423 from rhodecode.model.repo import RepoModel
417 sa = meta.Session
424 sa = meta.Session
418 rm = RepoModel()
425 rm = RepoModel()
419 user = sa.query(User).filter(User.admin == True).first()
426 user = sa.query(User).filter(User.admin == True).first()
420 if user is None:
427 if user is None:
421 raise Exception('Missing administrative account !')
428 raise Exception('Missing administrative account !')
422 added = []
429 added = []
423
430
424 for name, repo in initial_repo_list.items():
431 for name, repo in initial_repo_list.items():
425 group = map_groups(name.split(Repository.url_sep()))
432 group = map_groups(name.split(Repository.url_sep()))
426 if not rm.get_by_repo_name(name, cache=False):
433 if not rm.get_by_repo_name(name, cache=False):
427 log.info('repository %s not found creating default' % name)
434 log.info('repository %s not found creating default' % name)
428 added.append(name)
435 added.append(name)
429 form_data = {
436 form_data = {
430 'repo_name': name,
437 'repo_name': name,
431 'repo_name_full': name,
438 'repo_name_full': name,
432 'repo_type': repo.alias,
439 'repo_type': repo.alias,
433 'description': repo.description \
440 'description': repo.description \
434 if repo.description != 'unknown' else '%s repository' % name,
441 if repo.description != 'unknown' else '%s repository' % name,
435 'private': False,
442 'private': False,
436 'group_id': getattr(group, 'group_id', None)
443 'group_id': getattr(group, 'group_id', None)
437 }
444 }
438 rm.create(form_data, user, just_db=True)
445 rm.create(form_data, user, just_db=True)
439 sa.commit()
446 sa.commit()
440 removed = []
447 removed = []
441 if remove_obsolete:
448 if remove_obsolete:
442 #remove from database those repositories that are not in the filesystem
449 #remove from database those repositories that are not in the filesystem
443 for repo in sa.query(Repository).all():
450 for repo in sa.query(Repository).all():
444 if repo.repo_name not in initial_repo_list.keys():
451 if repo.repo_name not in initial_repo_list.keys():
445 removed.append(repo.repo_name)
452 removed.append(repo.repo_name)
446 sa.delete(repo)
453 sa.delete(repo)
447 sa.commit()
454 sa.commit()
448
455
449 return added, removed
456 return added, removed
450
457
451
458
452 # set cache regions for beaker so celery can utilise it
459 # set cache regions for beaker so celery can utilise it
453 def add_cache(settings):
460 def add_cache(settings):
454 cache_settings = {'regions': None}
461 cache_settings = {'regions': None}
455 for key in settings.keys():
462 for key in settings.keys():
456 for prefix in ['beaker.cache.', 'cache.']:
463 for prefix in ['beaker.cache.', 'cache.']:
457 if key.startswith(prefix):
464 if key.startswith(prefix):
458 name = key.split(prefix)[1].strip()
465 name = key.split(prefix)[1].strip()
459 cache_settings[name] = settings[key].strip()
466 cache_settings[name] = settings[key].strip()
460 if cache_settings['regions']:
467 if cache_settings['regions']:
461 for region in cache_settings['regions'].split(','):
468 for region in cache_settings['regions'].split(','):
462 region = region.strip()
469 region = region.strip()
463 region_settings = {}
470 region_settings = {}
464 for key, value in cache_settings.items():
471 for key, value in cache_settings.items():
465 if key.startswith(region):
472 if key.startswith(region):
466 region_settings[key.split('.')[1]] = value
473 region_settings[key.split('.')[1]] = value
467 region_settings['expire'] = int(region_settings.get('expire',
474 region_settings['expire'] = int(region_settings.get('expire',
468 60))
475 60))
469 region_settings.setdefault('lock_dir',
476 region_settings.setdefault('lock_dir',
470 cache_settings.get('lock_dir'))
477 cache_settings.get('lock_dir'))
471 region_settings.setdefault('data_dir',
478 region_settings.setdefault('data_dir',
472 cache_settings.get('data_dir'))
479 cache_settings.get('data_dir'))
473
480
474 if 'type' not in region_settings:
481 if 'type' not in region_settings:
475 region_settings['type'] = cache_settings.get('type',
482 region_settings['type'] = cache_settings.get('type',
476 'memory')
483 'memory')
477 beaker.cache.cache_regions[region] = region_settings
484 beaker.cache.cache_regions[region] = region_settings
478
485
479
486
480 #==============================================================================
487 #==============================================================================
481 # TEST FUNCTIONS AND CREATORS
488 # TEST FUNCTIONS AND CREATORS
482 #==============================================================================
489 #==============================================================================
483 def create_test_index(repo_location, config, full_index):
490 def create_test_index(repo_location, config, full_index):
484 """
491 """
485 Makes default test index
492 Makes default test index
486
493
487 :param config: test config
494 :param config: test config
488 :param full_index:
495 :param full_index:
489 """
496 """
490
497
491 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
498 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
492 from rhodecode.lib.pidlock import DaemonLock, LockHeld
499 from rhodecode.lib.pidlock import DaemonLock, LockHeld
493
500
494 repo_location = repo_location
501 repo_location = repo_location
495
502
496 index_location = os.path.join(config['app_conf']['index_dir'])
503 index_location = os.path.join(config['app_conf']['index_dir'])
497 if not os.path.exists(index_location):
504 if not os.path.exists(index_location):
498 os.makedirs(index_location)
505 os.makedirs(index_location)
499
506
500 try:
507 try:
501 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
508 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
502 WhooshIndexingDaemon(index_location=index_location,
509 WhooshIndexingDaemon(index_location=index_location,
503 repo_location=repo_location)\
510 repo_location=repo_location)\
504 .run(full_index=full_index)
511 .run(full_index=full_index)
505 l.release()
512 l.release()
506 except LockHeld:
513 except LockHeld:
507 pass
514 pass
508
515
509
516
510 def create_test_env(repos_test_path, config):
517 def create_test_env(repos_test_path, config):
511 """
518 """
512 Makes a fresh database and
519 Makes a fresh database and
513 install test repository into tmp dir
520 install test repository into tmp dir
514 """
521 """
515 from rhodecode.lib.db_manage import DbManage
522 from rhodecode.lib.db_manage import DbManage
516 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
523 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
517
524
518 # PART ONE create db
525 # PART ONE create db
519 dbconf = config['sqlalchemy.db1.url']
526 dbconf = config['sqlalchemy.db1.url']
520 log.debug('making test db %s' % dbconf)
527 log.debug('making test db %s' % dbconf)
521
528
522 # create test dir if it doesn't exist
529 # create test dir if it doesn't exist
523 if not os.path.isdir(repos_test_path):
530 if not os.path.isdir(repos_test_path):
524 log.debug('Creating testdir %s' % repos_test_path)
531 log.debug('Creating testdir %s' % repos_test_path)
525 os.makedirs(repos_test_path)
532 os.makedirs(repos_test_path)
526
533
527 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
534 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
528 tests=True)
535 tests=True)
529 dbmanage.create_tables(override=True)
536 dbmanage.create_tables(override=True)
530 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
537 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
531 dbmanage.create_default_user()
538 dbmanage.create_default_user()
532 dbmanage.admin_prompt()
539 dbmanage.admin_prompt()
533 dbmanage.create_permissions()
540 dbmanage.create_permissions()
534 dbmanage.populate_default_permissions()
541 dbmanage.populate_default_permissions()
535 Session.commit()
542 Session.commit()
536 # PART TWO make test repo
543 # PART TWO make test repo
537 log.debug('making test vcs repositories')
544 log.debug('making test vcs repositories')
538
545
539 idx_path = config['app_conf']['index_dir']
546 idx_path = config['app_conf']['index_dir']
540 data_path = config['app_conf']['cache_dir']
547 data_path = config['app_conf']['cache_dir']
541
548
542 #clean index and data
549 #clean index and data
543 if idx_path and os.path.exists(idx_path):
550 if idx_path and os.path.exists(idx_path):
544 log.debug('remove %s' % idx_path)
551 log.debug('remove %s' % idx_path)
545 shutil.rmtree(idx_path)
552 shutil.rmtree(idx_path)
546
553
547 if data_path and os.path.exists(data_path):
554 if data_path and os.path.exists(data_path):
548 log.debug('remove %s' % data_path)
555 log.debug('remove %s' % data_path)
549 shutil.rmtree(data_path)
556 shutil.rmtree(data_path)
550
557
551 #CREATE DEFAULT HG REPOSITORY
558 #CREATE DEFAULT HG REPOSITORY
552 cur_dir = dn(dn(abspath(__file__)))
559 cur_dir = dn(dn(abspath(__file__)))
553 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
560 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
554 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
561 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
555 tar.close()
562 tar.close()
556
563
557
564
558 #==============================================================================
565 #==============================================================================
559 # PASTER COMMANDS
566 # PASTER COMMANDS
560 #==============================================================================
567 #==============================================================================
561 class BasePasterCommand(Command):
568 class BasePasterCommand(Command):
562 """
569 """
563 Abstract Base Class for paster commands.
570 Abstract Base Class for paster commands.
564
571
565 The celery commands are somewhat aggressive about loading
572 The celery commands are somewhat aggressive about loading
566 celery.conf, and since our module sets the `CELERY_LOADER`
573 celery.conf, and since our module sets the `CELERY_LOADER`
567 environment variable to our loader, we have to bootstrap a bit and
574 environment variable to our loader, we have to bootstrap a bit and
568 make sure we've had a chance to load the pylons config off of the
575 make sure we've had a chance to load the pylons config off of the
569 command line, otherwise everything fails.
576 command line, otherwise everything fails.
570 """
577 """
571 min_args = 1
578 min_args = 1
572 min_args_error = "Please provide a paster config file as an argument."
579 min_args_error = "Please provide a paster config file as an argument."
573 takes_config_file = 1
580 takes_config_file = 1
574 requires_config_file = True
581 requires_config_file = True
575
582
576 def notify_msg(self, msg, log=False):
583 def notify_msg(self, msg, log=False):
577 """Make a notification to user, additionally if logger is passed
584 """Make a notification to user, additionally if logger is passed
578 it logs this action using given logger
585 it logs this action using given logger
579
586
580 :param msg: message that will be printed to user
587 :param msg: message that will be printed to user
581 :param log: logging instance, to use to additionally log this message
588 :param log: logging instance, to use to additionally log this message
582
589
583 """
590 """
584 if log and isinstance(log, logging):
591 if log and isinstance(log, logging):
585 log(msg)
592 log(msg)
586
593
587 def run(self, args):
594 def run(self, args):
588 """
595 """
589 Overrides Command.run
596 Overrides Command.run
590
597
591 Checks for a config file argument and loads it.
598 Checks for a config file argument and loads it.
592 """
599 """
593 if len(args) < self.min_args:
600 if len(args) < self.min_args:
594 raise BadCommand(
601 raise BadCommand(
595 self.min_args_error % {'min_args': self.min_args,
602 self.min_args_error % {'min_args': self.min_args,
596 'actual_args': len(args)})
603 'actual_args': len(args)})
597
604
598 # Decrement because we're going to lob off the first argument.
605 # Decrement because we're going to lob off the first argument.
599 # @@ This is hacky
606 # @@ This is hacky
600 self.min_args -= 1
607 self.min_args -= 1
601 self.bootstrap_config(args[0])
608 self.bootstrap_config(args[0])
602 self.update_parser()
609 self.update_parser()
603 return super(BasePasterCommand, self).run(args[1:])
610 return super(BasePasterCommand, self).run(args[1:])
604
611
605 def update_parser(self):
612 def update_parser(self):
606 """
613 """
607 Abstract method. Allows for the class's parser to be updated
614 Abstract method. Allows for the class's parser to be updated
608 before the superclass's `run` method is called. Necessary to
615 before the superclass's `run` method is called. Necessary to
609 allow options/arguments to be passed through to the underlying
616 allow options/arguments to be passed through to the underlying
610 celery command.
617 celery command.
611 """
618 """
612 raise NotImplementedError("Abstract Method.")
619 raise NotImplementedError("Abstract Method.")
613
620
614 def bootstrap_config(self, conf):
621 def bootstrap_config(self, conf):
615 """
622 """
616 Loads the pylons configuration.
623 Loads the pylons configuration.
617 """
624 """
618 from pylons import config as pylonsconfig
625 from pylons import config as pylonsconfig
619
626
620 path_to_ini_file = os.path.realpath(conf)
627 path_to_ini_file = os.path.realpath(conf)
621 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
628 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
622 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
629 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,456 +1,459 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import time
26 import time
27 import traceback
27 import traceback
28 import logging
28 import logging
29 import cStringIO
29 import cStringIO
30
30
31 from rhodecode.lib.vcs import get_backend
31 from rhodecode.lib.vcs import get_backend
32 from rhodecode.lib.vcs.exceptions import RepositoryError
32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 from rhodecode.lib.vcs.utils.lazy import LazyProperty
33 from rhodecode.lib.vcs.utils.lazy import LazyProperty
34 from rhodecode.lib.vcs.nodes import FileNode
34 from rhodecode.lib.vcs.nodes import FileNode
35
35
36 from rhodecode import BACKENDS
36 from rhodecode import BACKENDS
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import safe_str
38 from rhodecode.lib import safe_str
39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
41 action_logger, EmptyChangeset
41 action_logger, EmptyChangeset, REMOVED_REPO_PAT
42 from rhodecode.model import BaseModel
42 from rhodecode.model import BaseModel
43 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
43 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
44 UserFollowing, UserLog, User, RepoGroup
44 UserFollowing, UserLog, User, RepoGroup
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class UserTemp(object):
49 class UserTemp(object):
50 def __init__(self, user_id):
50 def __init__(self, user_id):
51 self.user_id = user_id
51 self.user_id = user_id
52
52
53 def __repr__(self):
53 def __repr__(self):
54 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
54 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
55
55
56
56
57 class RepoTemp(object):
57 class RepoTemp(object):
58 def __init__(self, repo_id):
58 def __init__(self, repo_id):
59 self.repo_id = repo_id
59 self.repo_id = repo_id
60
60
61 def __repr__(self):
61 def __repr__(self):
62 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
62 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
63
63
64
64
65 class CachedRepoList(object):
65 class CachedRepoList(object):
66
66
67 def __init__(self, db_repo_list, repos_path, order_by=None):
67 def __init__(self, db_repo_list, repos_path, order_by=None):
68 self.db_repo_list = db_repo_list
68 self.db_repo_list = db_repo_list
69 self.repos_path = repos_path
69 self.repos_path = repos_path
70 self.order_by = order_by
70 self.order_by = order_by
71 self.reversed = (order_by or '').startswith('-')
71 self.reversed = (order_by or '').startswith('-')
72
72
73 def __len__(self):
73 def __len__(self):
74 return len(self.db_repo_list)
74 return len(self.db_repo_list)
75
75
76 def __repr__(self):
76 def __repr__(self):
77 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
77 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
78
78
79 def __iter__(self):
79 def __iter__(self):
80 for dbr in self.db_repo_list:
80 for dbr in self.db_repo_list:
81 scmr = dbr.scm_instance_cached
81 scmr = dbr.scm_instance_cached
82 # check permission at this level
82 # check permission at this level
83 if not HasRepoPermissionAny(
83 if not HasRepoPermissionAny(
84 'repository.read', 'repository.write', 'repository.admin'
84 'repository.read', 'repository.write', 'repository.admin'
85 )(dbr.repo_name, 'get repo check'):
85 )(dbr.repo_name, 'get repo check'):
86 continue
86 continue
87
87
88 if scmr is None:
88 if scmr is None:
89 log.error(
89 log.error(
90 '%s this repository is present in database but it '
90 '%s this repository is present in database but it '
91 'cannot be created as an scm instance' % dbr.repo_name
91 'cannot be created as an scm instance' % dbr.repo_name
92 )
92 )
93 continue
93 continue
94
94
95 last_change = scmr.last_change
95 last_change = scmr.last_change
96 tip = h.get_changeset_safe(scmr, 'tip')
96 tip = h.get_changeset_safe(scmr, 'tip')
97
97
98 tmp_d = {}
98 tmp_d = {}
99 tmp_d['name'] = dbr.repo_name
99 tmp_d['name'] = dbr.repo_name
100 tmp_d['name_sort'] = tmp_d['name'].lower()
100 tmp_d['name_sort'] = tmp_d['name'].lower()
101 tmp_d['description'] = dbr.description
101 tmp_d['description'] = dbr.description
102 tmp_d['description_sort'] = tmp_d['description']
102 tmp_d['description_sort'] = tmp_d['description']
103 tmp_d['last_change'] = last_change
103 tmp_d['last_change'] = last_change
104 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
104 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
105 tmp_d['tip'] = tip.raw_id
105 tmp_d['tip'] = tip.raw_id
106 tmp_d['tip_sort'] = tip.revision
106 tmp_d['tip_sort'] = tip.revision
107 tmp_d['rev'] = tip.revision
107 tmp_d['rev'] = tip.revision
108 tmp_d['contact'] = dbr.user.full_contact
108 tmp_d['contact'] = dbr.user.full_contact
109 tmp_d['contact_sort'] = tmp_d['contact']
109 tmp_d['contact_sort'] = tmp_d['contact']
110 tmp_d['owner_sort'] = tmp_d['contact']
110 tmp_d['owner_sort'] = tmp_d['contact']
111 tmp_d['repo_archives'] = list(scmr._get_archives())
111 tmp_d['repo_archives'] = list(scmr._get_archives())
112 tmp_d['last_msg'] = tip.message
112 tmp_d['last_msg'] = tip.message
113 tmp_d['author'] = tip.author
113 tmp_d['author'] = tip.author
114 tmp_d['dbrepo'] = dbr.get_dict()
114 tmp_d['dbrepo'] = dbr.get_dict()
115 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
115 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
116 yield tmp_d
116 yield tmp_d
117
117
118
118
119 class GroupList(object):
119 class GroupList(object):
120
120
121 def __init__(self, db_repo_group_list):
121 def __init__(self, db_repo_group_list):
122 self.db_repo_group_list = db_repo_group_list
122 self.db_repo_group_list = db_repo_group_list
123
123
124 def __len__(self):
124 def __len__(self):
125 return len(self.db_repo_group_list)
125 return len(self.db_repo_group_list)
126
126
127 def __repr__(self):
127 def __repr__(self):
128 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
128 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
129
129
130 def __iter__(self):
130 def __iter__(self):
131 for dbgr in self.db_repo_group_list:
131 for dbgr in self.db_repo_group_list:
132 # check permission at this level
132 # check permission at this level
133 if not HasReposGroupPermissionAny(
133 if not HasReposGroupPermissionAny(
134 'group.read', 'group.write', 'group.admin'
134 'group.read', 'group.write', 'group.admin'
135 )(dbgr.group_name, 'get group repo check'):
135 )(dbgr.group_name, 'get group repo check'):
136 continue
136 continue
137
137
138 yield dbgr
138 yield dbgr
139
139
140
140
141 class ScmModel(BaseModel):
141 class ScmModel(BaseModel):
142 """
142 """
143 Generic Scm Model
143 Generic Scm Model
144 """
144 """
145
145
146 def __get_repo(self, instance):
146 def __get_repo(self, instance):
147 cls = Repository
147 cls = Repository
148 if isinstance(instance, cls):
148 if isinstance(instance, cls):
149 return instance
149 return instance
150 elif isinstance(instance, int) or str(instance).isdigit():
150 elif isinstance(instance, int) or str(instance).isdigit():
151 return cls.get(instance)
151 return cls.get(instance)
152 elif isinstance(instance, basestring):
152 elif isinstance(instance, basestring):
153 return cls.get_by_repo_name(instance)
153 return cls.get_by_repo_name(instance)
154 elif instance:
154 elif instance:
155 raise Exception('given object must be int, basestr or Instance'
155 raise Exception('given object must be int, basestr or Instance'
156 ' of %s got %s' % (type(cls), type(instance)))
156 ' of %s got %s' % (type(cls), type(instance)))
157
157
158 @LazyProperty
158 @LazyProperty
159 def repos_path(self):
159 def repos_path(self):
160 """
160 """
161 Get's the repositories root path from database
161 Get's the repositories root path from database
162 """
162 """
163
163
164 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
164 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
165
165
166 return q.ui_value
166 return q.ui_value
167
167
168 def repo_scan(self, repos_path=None):
168 def repo_scan(self, repos_path=None):
169 """
169 """
170 Listing of repositories in given path. This path should not be a
170 Listing of repositories in given path. This path should not be a
171 repository itself. Return a dictionary of repository objects
171 repository itself. Return a dictionary of repository objects
172
172
173 :param repos_path: path to directory containing repositories
173 :param repos_path: path to directory containing repositories
174 """
174 """
175
175
176 if repos_path is None:
176 if repos_path is None:
177 repos_path = self.repos_path
177 repos_path = self.repos_path
178
178
179 log.info('scanning for repositories in %s' % repos_path)
179 log.info('scanning for repositories in %s' % repos_path)
180
180
181 baseui = make_ui('db')
181 baseui = make_ui('db')
182 repos = {}
182 repos = {}
183
183
184 for name, path in get_filesystem_repos(repos_path, recursive=True):
184 for name, path in get_filesystem_repos(repos_path, recursive=True):
185 # skip removed repos
186 if REMOVED_REPO_PAT.match(name):
187 continue
185
188
186 # name need to be decomposed and put back together using the /
189 # name need to be decomposed and put back together using the /
187 # since this is internal storage separator for rhodecode
190 # since this is internal storage separator for rhodecode
188 name = Repository.url_sep().join(name.split(os.sep))
191 name = Repository.url_sep().join(name.split(os.sep))
189
192
190 try:
193 try:
191 if name in repos:
194 if name in repos:
192 raise RepositoryError('Duplicate repository name %s '
195 raise RepositoryError('Duplicate repository name %s '
193 'found in %s' % (name, path))
196 'found in %s' % (name, path))
194 else:
197 else:
195
198
196 klass = get_backend(path[0])
199 klass = get_backend(path[0])
197
200
198 if path[0] == 'hg' and path[0] in BACKENDS.keys():
201 if path[0] == 'hg' and path[0] in BACKENDS.keys():
199 repos[name] = klass(safe_str(path[1]), baseui=baseui)
202 repos[name] = klass(safe_str(path[1]), baseui=baseui)
200
203
201 if path[0] == 'git' and path[0] in BACKENDS.keys():
204 if path[0] == 'git' and path[0] in BACKENDS.keys():
202 repos[name] = klass(path[1])
205 repos[name] = klass(path[1])
203 except OSError:
206 except OSError:
204 continue
207 continue
205
208
206 return repos
209 return repos
207
210
208 def get_repos(self, all_repos=None, sort_key=None):
211 def get_repos(self, all_repos=None, sort_key=None):
209 """
212 """
210 Get all repos from db and for each repo create it's
213 Get all repos from db and for each repo create it's
211 backend instance and fill that backed with information from database
214 backend instance and fill that backed with information from database
212
215
213 :param all_repos: list of repository names as strings
216 :param all_repos: list of repository names as strings
214 give specific repositories list, good for filtering
217 give specific repositories list, good for filtering
215 """
218 """
216 if all_repos is None:
219 if all_repos is None:
217 all_repos = self.sa.query(Repository)\
220 all_repos = self.sa.query(Repository)\
218 .filter(Repository.group_id == None)\
221 .filter(Repository.group_id == None)\
219 .order_by(Repository.repo_name).all()
222 .order_by(Repository.repo_name).all()
220
223
221 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
224 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
222 order_by=sort_key)
225 order_by=sort_key)
223
226
224 return repo_iter
227 return repo_iter
225
228
226 def get_repos_groups(self, all_groups=None):
229 def get_repos_groups(self, all_groups=None):
227 if all_groups is None:
230 if all_groups is None:
228 all_groups = RepoGroup.query()\
231 all_groups = RepoGroup.query()\
229 .filter(RepoGroup.group_parent_id == None).all()
232 .filter(RepoGroup.group_parent_id == None).all()
230 group_iter = GroupList(all_groups)
233 group_iter = GroupList(all_groups)
231
234
232 return group_iter
235 return group_iter
233
236
234 def mark_for_invalidation(self, repo_name):
237 def mark_for_invalidation(self, repo_name):
235 """Puts cache invalidation task into db for
238 """Puts cache invalidation task into db for
236 further global cache invalidation
239 further global cache invalidation
237
240
238 :param repo_name: this repo that should invalidation take place
241 :param repo_name: this repo that should invalidation take place
239 """
242 """
240 CacheInvalidation.set_invalidate(repo_name)
243 CacheInvalidation.set_invalidate(repo_name)
241 CacheInvalidation.set_invalidate(repo_name + "_README")
244 CacheInvalidation.set_invalidate(repo_name + "_README")
242
245
243 def toggle_following_repo(self, follow_repo_id, user_id):
246 def toggle_following_repo(self, follow_repo_id, user_id):
244
247
245 f = self.sa.query(UserFollowing)\
248 f = self.sa.query(UserFollowing)\
246 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
249 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
247 .filter(UserFollowing.user_id == user_id).scalar()
250 .filter(UserFollowing.user_id == user_id).scalar()
248
251
249 if f is not None:
252 if f is not None:
250 try:
253 try:
251 self.sa.delete(f)
254 self.sa.delete(f)
252 action_logger(UserTemp(user_id),
255 action_logger(UserTemp(user_id),
253 'stopped_following_repo',
256 'stopped_following_repo',
254 RepoTemp(follow_repo_id))
257 RepoTemp(follow_repo_id))
255 return
258 return
256 except:
259 except:
257 log.error(traceback.format_exc())
260 log.error(traceback.format_exc())
258 raise
261 raise
259
262
260 try:
263 try:
261 f = UserFollowing()
264 f = UserFollowing()
262 f.user_id = user_id
265 f.user_id = user_id
263 f.follows_repo_id = follow_repo_id
266 f.follows_repo_id = follow_repo_id
264 self.sa.add(f)
267 self.sa.add(f)
265
268
266 action_logger(UserTemp(user_id),
269 action_logger(UserTemp(user_id),
267 'started_following_repo',
270 'started_following_repo',
268 RepoTemp(follow_repo_id))
271 RepoTemp(follow_repo_id))
269 except:
272 except:
270 log.error(traceback.format_exc())
273 log.error(traceback.format_exc())
271 raise
274 raise
272
275
273 def toggle_following_user(self, follow_user_id, user_id):
276 def toggle_following_user(self, follow_user_id, user_id):
274 f = self.sa.query(UserFollowing)\
277 f = self.sa.query(UserFollowing)\
275 .filter(UserFollowing.follows_user_id == follow_user_id)\
278 .filter(UserFollowing.follows_user_id == follow_user_id)\
276 .filter(UserFollowing.user_id == user_id).scalar()
279 .filter(UserFollowing.user_id == user_id).scalar()
277
280
278 if f is not None:
281 if f is not None:
279 try:
282 try:
280 self.sa.delete(f)
283 self.sa.delete(f)
281 return
284 return
282 except:
285 except:
283 log.error(traceback.format_exc())
286 log.error(traceback.format_exc())
284 raise
287 raise
285
288
286 try:
289 try:
287 f = UserFollowing()
290 f = UserFollowing()
288 f.user_id = user_id
291 f.user_id = user_id
289 f.follows_user_id = follow_user_id
292 f.follows_user_id = follow_user_id
290 self.sa.add(f)
293 self.sa.add(f)
291 except:
294 except:
292 log.error(traceback.format_exc())
295 log.error(traceback.format_exc())
293 raise
296 raise
294
297
295 def is_following_repo(self, repo_name, user_id, cache=False):
298 def is_following_repo(self, repo_name, user_id, cache=False):
296 r = self.sa.query(Repository)\
299 r = self.sa.query(Repository)\
297 .filter(Repository.repo_name == repo_name).scalar()
300 .filter(Repository.repo_name == repo_name).scalar()
298
301
299 f = self.sa.query(UserFollowing)\
302 f = self.sa.query(UserFollowing)\
300 .filter(UserFollowing.follows_repository == r)\
303 .filter(UserFollowing.follows_repository == r)\
301 .filter(UserFollowing.user_id == user_id).scalar()
304 .filter(UserFollowing.user_id == user_id).scalar()
302
305
303 return f is not None
306 return f is not None
304
307
305 def is_following_user(self, username, user_id, cache=False):
308 def is_following_user(self, username, user_id, cache=False):
306 u = User.get_by_username(username)
309 u = User.get_by_username(username)
307
310
308 f = self.sa.query(UserFollowing)\
311 f = self.sa.query(UserFollowing)\
309 .filter(UserFollowing.follows_user == u)\
312 .filter(UserFollowing.follows_user == u)\
310 .filter(UserFollowing.user_id == user_id).scalar()
313 .filter(UserFollowing.user_id == user_id).scalar()
311
314
312 return f is not None
315 return f is not None
313
316
314 def get_followers(self, repo_id):
317 def get_followers(self, repo_id):
315 if not isinstance(repo_id, int):
318 if not isinstance(repo_id, int):
316 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
319 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
317
320
318 return self.sa.query(UserFollowing)\
321 return self.sa.query(UserFollowing)\
319 .filter(UserFollowing.follows_repo_id == repo_id).count()
322 .filter(UserFollowing.follows_repo_id == repo_id).count()
320
323
321 def get_forks(self, repo_id):
324 def get_forks(self, repo_id):
322 if not isinstance(repo_id, int):
325 if not isinstance(repo_id, int):
323 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
326 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
324
327
325 return self.sa.query(Repository)\
328 return self.sa.query(Repository)\
326 .filter(Repository.fork_id == repo_id).count()
329 .filter(Repository.fork_id == repo_id).count()
327
330
328 def mark_as_fork(self, repo, fork, user):
331 def mark_as_fork(self, repo, fork, user):
329 repo = self.__get_repo(repo)
332 repo = self.__get_repo(repo)
330 fork = self.__get_repo(fork)
333 fork = self.__get_repo(fork)
331 repo.fork = fork
334 repo.fork = fork
332 self.sa.add(repo)
335 self.sa.add(repo)
333 return repo
336 return repo
334
337
335 def pull_changes(self, repo_name, username):
338 def pull_changes(self, repo_name, username):
336 dbrepo = Repository.get_by_repo_name(repo_name)
339 dbrepo = Repository.get_by_repo_name(repo_name)
337 clone_uri = dbrepo.clone_uri
340 clone_uri = dbrepo.clone_uri
338 if not clone_uri:
341 if not clone_uri:
339 raise Exception("This repository doesn't have a clone uri")
342 raise Exception("This repository doesn't have a clone uri")
340
343
341 repo = dbrepo.scm_instance
344 repo = dbrepo.scm_instance
342 try:
345 try:
343 extras = {'ip': '',
346 extras = {'ip': '',
344 'username': username,
347 'username': username,
345 'action': 'push_remote',
348 'action': 'push_remote',
346 'repository': repo_name}
349 'repository': repo_name}
347
350
348 #inject ui extra param to log this action via push logger
351 #inject ui extra param to log this action via push logger
349 for k, v in extras.items():
352 for k, v in extras.items():
350 repo._repo.ui.setconfig('rhodecode_extras', k, v)
353 repo._repo.ui.setconfig('rhodecode_extras', k, v)
351
354
352 repo.pull(clone_uri)
355 repo.pull(clone_uri)
353 self.mark_for_invalidation(repo_name)
356 self.mark_for_invalidation(repo_name)
354 except:
357 except:
355 log.error(traceback.format_exc())
358 log.error(traceback.format_exc())
356 raise
359 raise
357
360
358 def commit_change(self, repo, repo_name, cs, user, author, message,
361 def commit_change(self, repo, repo_name, cs, user, author, message,
359 content, f_path):
362 content, f_path):
360
363
361 if repo.alias == 'hg':
364 if repo.alias == 'hg':
362 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
365 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
363 elif repo.alias == 'git':
366 elif repo.alias == 'git':
364 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
367 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
365
368
366 # decoding here will force that we have proper encoded values
369 # decoding here will force that we have proper encoded values
367 # in any other case this will throw exceptions and deny commit
370 # in any other case this will throw exceptions and deny commit
368 content = safe_str(content)
371 content = safe_str(content)
369 message = safe_str(message)
372 message = safe_str(message)
370 path = safe_str(f_path)
373 path = safe_str(f_path)
371 author = safe_str(author)
374 author = safe_str(author)
372 m = IMC(repo)
375 m = IMC(repo)
373 m.change(FileNode(path, content))
376 m.change(FileNode(path, content))
374 tip = m.commit(message=message,
377 tip = m.commit(message=message,
375 author=author,
378 author=author,
376 parents=[cs], branch=cs.branch)
379 parents=[cs], branch=cs.branch)
377
380
378 new_cs = tip.short_id
381 new_cs = tip.short_id
379 action = 'push_local:%s' % new_cs
382 action = 'push_local:%s' % new_cs
380
383
381 action_logger(user, action, repo_name)
384 action_logger(user, action, repo_name)
382
385
383 self.mark_for_invalidation(repo_name)
386 self.mark_for_invalidation(repo_name)
384
387
385 def create_node(self, repo, repo_name, cs, user, author, message, content,
388 def create_node(self, repo, repo_name, cs, user, author, message, content,
386 f_path):
389 f_path):
387 if repo.alias == 'hg':
390 if repo.alias == 'hg':
388 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
391 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
389 elif repo.alias == 'git':
392 elif repo.alias == 'git':
390 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
393 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
391 # decoding here will force that we have proper encoded values
394 # decoding here will force that we have proper encoded values
392 # in any other case this will throw exceptions and deny commit
395 # in any other case this will throw exceptions and deny commit
393
396
394 if isinstance(content, (basestring,)):
397 if isinstance(content, (basestring,)):
395 content = safe_str(content)
398 content = safe_str(content)
396 elif isinstance(content, (file, cStringIO.OutputType,)):
399 elif isinstance(content, (file, cStringIO.OutputType,)):
397 content = content.read()
400 content = content.read()
398 else:
401 else:
399 raise Exception('Content is of unrecognized type %s' % (
402 raise Exception('Content is of unrecognized type %s' % (
400 type(content)
403 type(content)
401 ))
404 ))
402
405
403 message = safe_str(message)
406 message = safe_str(message)
404 path = safe_str(f_path)
407 path = safe_str(f_path)
405 author = safe_str(author)
408 author = safe_str(author)
406 m = IMC(repo)
409 m = IMC(repo)
407
410
408 if isinstance(cs, EmptyChangeset):
411 if isinstance(cs, EmptyChangeset):
409 # Emptychangeset means we we're editing empty repository
412 # Emptychangeset means we we're editing empty repository
410 parents = None
413 parents = None
411 else:
414 else:
412 parents = [cs]
415 parents = [cs]
413
416
414 m.add(FileNode(path, content=content))
417 m.add(FileNode(path, content=content))
415 tip = m.commit(message=message,
418 tip = m.commit(message=message,
416 author=author,
419 author=author,
417 parents=parents, branch=cs.branch)
420 parents=parents, branch=cs.branch)
418 new_cs = tip.short_id
421 new_cs = tip.short_id
419 action = 'push_local:%s' % new_cs
422 action = 'push_local:%s' % new_cs
420
423
421 action_logger(user, action, repo_name)
424 action_logger(user, action, repo_name)
422
425
423 self.mark_for_invalidation(repo_name)
426 self.mark_for_invalidation(repo_name)
424
427
425 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
428 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
426 """
429 """
427 recursive walk in root dir and return a set of all path in that dir
430 recursive walk in root dir and return a set of all path in that dir
428 based on repository walk function
431 based on repository walk function
429
432
430 :param repo_name: name of repository
433 :param repo_name: name of repository
431 :param revision: revision for which to list nodes
434 :param revision: revision for which to list nodes
432 :param root_path: root path to list
435 :param root_path: root path to list
433 :param flat: return as a list, if False returns a dict with decription
436 :param flat: return as a list, if False returns a dict with decription
434
437
435 """
438 """
436 _files = list()
439 _files = list()
437 _dirs = list()
440 _dirs = list()
438 try:
441 try:
439 _repo = self.__get_repo(repo_name)
442 _repo = self.__get_repo(repo_name)
440 changeset = _repo.scm_instance.get_changeset(revision)
443 changeset = _repo.scm_instance.get_changeset(revision)
441 root_path = root_path.lstrip('/')
444 root_path = root_path.lstrip('/')
442 for topnode, dirs, files in changeset.walk(root_path):
445 for topnode, dirs, files in changeset.walk(root_path):
443 for f in files:
446 for f in files:
444 _files.append(f.path if flat else {"name": f.path,
447 _files.append(f.path if flat else {"name": f.path,
445 "type": "file"})
448 "type": "file"})
446 for d in dirs:
449 for d in dirs:
447 _dirs.append(d.path if flat else {"name": d.path,
450 _dirs.append(d.path if flat else {"name": d.path,
448 "type": "dir"})
451 "type": "dir"})
449 except RepositoryError:
452 except RepositoryError:
450 log.debug(traceback.format_exc())
453 log.debug(traceback.format_exc())
451 raise
454 raise
452
455
453 return _dirs, _files
456 return _dirs, _files
454
457
455 def get_unread_journal(self):
458 def get_unread_journal(self):
456 return self.sa.query(UserLog).count()
459 return self.sa.query(UserLog).count()
General Comments 0
You need to be logged in to leave comments. Login now