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