##// END OF EJS Templates
#235 forking page repo group selection...
marcink -
r1722:e7eef7a1 beta
parent child Browse files
Show More
@@ -1,483 +1,484 b''
1 1 """
2 2 Routes configuration
3 3
4 4 The more specific and detailed routes should be defined first so they
5 5 may take precedent over the more generic routes. For more information
6 6 refer to the routes manual at http://routes.groovie.org/docs/
7 7 """
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10
11 11
12 12 # prefix for non repository related links needs to be prefixed with `/`
13 13 ADMIN_PREFIX = '/_admin'
14 14
15 15
16 16 def make_map(config):
17 17 """Create, configure and return the routes Mapper"""
18 18 rmap = Mapper(directory=config['pylons.paths']['controllers'],
19 19 always_scan=config['debug'])
20 20 rmap.minimization = False
21 21 rmap.explicit = False
22 22
23 23 from rhodecode.lib.utils import is_valid_repo
24 24 from rhodecode.lib.utils import is_valid_repos_group
25 25
26 26 def check_repo(environ, match_dict):
27 27 """
28 28 check for valid repository for proper 404 handling
29 29
30 30 :param environ:
31 31 :param match_dict:
32 32 """
33 33
34 34 repo_name = match_dict.get('repo_name')
35 35 return is_valid_repo(repo_name, config['base_path'])
36 36
37 37 def check_group(environ, match_dict):
38 38 """
39 39 check for valid repositories group for proper 404 handling
40 40
41 41 :param environ:
42 42 :param match_dict:
43 43 """
44 44 repos_group_name = match_dict.get('group_name')
45 45
46 46 return is_valid_repos_group(repos_group_name, config['base_path'])
47 47
48 48
49 49 def check_int(environ, match_dict):
50 50 return match_dict.get('id').isdigit()
51 51
52 52 # The ErrorController route (handles 404/500 error pages); it should
53 53 # likely stay at the top, ensuring it can always be resolved
54 54 rmap.connect('/error/{action}', controller='error')
55 55 rmap.connect('/error/{action}/{id}', controller='error')
56 56
57 57 #==========================================================================
58 58 # CUSTOM ROUTES HERE
59 59 #==========================================================================
60 60
61 61 #MAIN PAGE
62 62 rmap.connect('home', '/', controller='home', action='index')
63 63 rmap.connect('repo_switcher', '/repos', controller='home',
64 64 action='repo_switcher')
65 65 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}',
66 66 controller='home', action='branch_tag_switcher')
67 67 rmap.connect('bugtracker',
68 68 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
69 69 _static=True)
70 70 rmap.connect('rst_help',
71 71 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
72 72 _static=True)
73 73 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
74 74
75 75 #ADMIN REPOSITORY REST ROUTES
76 76 with rmap.submapper(path_prefix=ADMIN_PREFIX,
77 77 controller='admin/repos') as m:
78 78 m.connect("repos", "/repos",
79 79 action="create", conditions=dict(method=["POST"]))
80 80 m.connect("repos", "/repos",
81 81 action="index", conditions=dict(method=["GET"]))
82 82 m.connect("formatted_repos", "/repos.{format}",
83 83 action="index",
84 84 conditions=dict(method=["GET"]))
85 85 m.connect("new_repo", "/repos/new",
86 86 action="new", conditions=dict(method=["GET"]))
87 87 m.connect("formatted_new_repo", "/repos/new.{format}",
88 88 action="new", conditions=dict(method=["GET"]))
89 89 m.connect("/repos/{repo_name:.*}",
90 90 action="update", conditions=dict(method=["PUT"],
91 91 function=check_repo))
92 92 m.connect("/repos/{repo_name:.*}",
93 93 action="delete", conditions=dict(method=["DELETE"],
94 94 function=check_repo))
95 95 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
96 96 action="edit", conditions=dict(method=["GET"],
97 97 function=check_repo))
98 98 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
99 99 action="edit", conditions=dict(method=["GET"],
100 100 function=check_repo))
101 101 m.connect("repo", "/repos/{repo_name:.*}",
102 102 action="show", conditions=dict(method=["GET"],
103 103 function=check_repo))
104 104 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
105 105 action="show", conditions=dict(method=["GET"],
106 106 function=check_repo))
107 107 #ajax delete repo perm user
108 108 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
109 109 action="delete_perm_user", conditions=dict(method=["DELETE"],
110 110 function=check_repo))
111 111 #ajax delete repo perm users_group
112 112 m.connect('delete_repo_users_group',
113 113 "/repos_delete_users_group/{repo_name:.*}",
114 114 action="delete_perm_users_group",
115 115 conditions=dict(method=["DELETE"], function=check_repo))
116 116
117 117 #settings actions
118 118 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
119 119 action="repo_stats", conditions=dict(method=["DELETE"],
120 120 function=check_repo))
121 121 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
122 122 action="repo_cache", conditions=dict(method=["DELETE"],
123 123 function=check_repo))
124 124 m.connect('repo_public_journal',
125 125 "/repos_public_journal/{repo_name:.*}",
126 126 action="repo_public_journal", conditions=dict(method=["PUT"],
127 127 function=check_repo))
128 128 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
129 129 action="repo_pull", conditions=dict(method=["PUT"],
130 130 function=check_repo))
131 131
132 132 with rmap.submapper(path_prefix=ADMIN_PREFIX,
133 133 controller='admin/repos_groups') as m:
134 134 m.connect("repos_groups", "/repos_groups",
135 135 action="create", conditions=dict(method=["POST"]))
136 136 m.connect("repos_groups", "/repos_groups",
137 137 action="index", conditions=dict(method=["GET"]))
138 138 m.connect("formatted_repos_groups", "/repos_groups.{format}",
139 139 action="index", conditions=dict(method=["GET"]))
140 140 m.connect("new_repos_group", "/repos_groups/new",
141 141 action="new", conditions=dict(method=["GET"]))
142 142 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
143 143 action="new", conditions=dict(method=["GET"]))
144 144 m.connect("update_repos_group", "/repos_groups/{id}",
145 145 action="update", conditions=dict(method=["PUT"],
146 146 function=check_int))
147 147 m.connect("delete_repos_group", "/repos_groups/{id}",
148 148 action="delete", conditions=dict(method=["DELETE"],
149 149 function=check_int))
150 150 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
151 151 action="edit", conditions=dict(method=["GET"],
152 152 function=check_int))
153 153 m.connect("formatted_edit_repos_group",
154 154 "/repos_groups/{id}.{format}/edit",
155 155 action="edit", conditions=dict(method=["GET"],
156 156 function=check_int))
157 157 m.connect("repos_group", "/repos_groups/{id}",
158 158 action="show", conditions=dict(method=["GET"],
159 159 function=check_int))
160 160 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
161 161 action="show", conditions=dict(method=["GET"],
162 162 function=check_int))
163 163
164 164 #ADMIN USER REST ROUTES
165 165 with rmap.submapper(path_prefix=ADMIN_PREFIX,
166 166 controller='admin/users') as m:
167 167 m.connect("users", "/users",
168 168 action="create", conditions=dict(method=["POST"]))
169 169 m.connect("users", "/users",
170 170 action="index", conditions=dict(method=["GET"]))
171 171 m.connect("formatted_users", "/users.{format}",
172 172 action="index", conditions=dict(method=["GET"]))
173 173 m.connect("new_user", "/users/new",
174 174 action="new", conditions=dict(method=["GET"]))
175 175 m.connect("formatted_new_user", "/users/new.{format}",
176 176 action="new", conditions=dict(method=["GET"]))
177 177 m.connect("update_user", "/users/{id}",
178 178 action="update", conditions=dict(method=["PUT"]))
179 179 m.connect("delete_user", "/users/{id}",
180 180 action="delete", conditions=dict(method=["DELETE"]))
181 181 m.connect("edit_user", "/users/{id}/edit",
182 182 action="edit", conditions=dict(method=["GET"]))
183 183 m.connect("formatted_edit_user",
184 184 "/users/{id}.{format}/edit",
185 185 action="edit", conditions=dict(method=["GET"]))
186 186 m.connect("user", "/users/{id}",
187 187 action="show", conditions=dict(method=["GET"]))
188 188 m.connect("formatted_user", "/users/{id}.{format}",
189 189 action="show", conditions=dict(method=["GET"]))
190 190
191 191 #EXTRAS USER ROUTES
192 192 m.connect("user_perm", "/users_perm/{id}",
193 193 action="update_perm", conditions=dict(method=["PUT"]))
194 194
195 195 #ADMIN USERS REST ROUTES
196 196 with rmap.submapper(path_prefix=ADMIN_PREFIX,
197 197 controller='admin/users_groups') as m:
198 198 m.connect("users_groups", "/users_groups",
199 199 action="create", conditions=dict(method=["POST"]))
200 200 m.connect("users_groups", "/users_groups",
201 201 action="index", conditions=dict(method=["GET"]))
202 202 m.connect("formatted_users_groups", "/users_groups.{format}",
203 203 action="index", conditions=dict(method=["GET"]))
204 204 m.connect("new_users_group", "/users_groups/new",
205 205 action="new", conditions=dict(method=["GET"]))
206 206 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
207 207 action="new", conditions=dict(method=["GET"]))
208 208 m.connect("update_users_group", "/users_groups/{id}",
209 209 action="update", conditions=dict(method=["PUT"]))
210 210 m.connect("delete_users_group", "/users_groups/{id}",
211 211 action="delete", conditions=dict(method=["DELETE"]))
212 212 m.connect("edit_users_group", "/users_groups/{id}/edit",
213 213 action="edit", conditions=dict(method=["GET"]))
214 214 m.connect("formatted_edit_users_group",
215 215 "/users_groups/{id}.{format}/edit",
216 216 action="edit", conditions=dict(method=["GET"]))
217 217 m.connect("users_group", "/users_groups/{id}",
218 218 action="show", conditions=dict(method=["GET"]))
219 219 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
220 220 action="show", conditions=dict(method=["GET"]))
221 221
222 222 #EXTRAS USER ROUTES
223 223 m.connect("users_group_perm", "/users_groups_perm/{id}",
224 224 action="update_perm", conditions=dict(method=["PUT"]))
225 225
226 226 #ADMIN GROUP REST ROUTES
227 227 rmap.resource('group', 'groups',
228 228 controller='admin/groups', path_prefix=ADMIN_PREFIX)
229 229
230 230 #ADMIN PERMISSIONS REST ROUTES
231 231 rmap.resource('permission', 'permissions',
232 232 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
233 233
234 234 ##ADMIN LDAP SETTINGS
235 235 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
236 236 controller='admin/ldap_settings', action='ldap_settings',
237 237 conditions=dict(method=["POST"]))
238 238
239 239 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
240 240 controller='admin/ldap_settings')
241 241
242 242 #ADMIN SETTINGS REST ROUTES
243 243 with rmap.submapper(path_prefix=ADMIN_PREFIX,
244 244 controller='admin/settings') as m:
245 245 m.connect("admin_settings", "/settings",
246 246 action="create", conditions=dict(method=["POST"]))
247 247 m.connect("admin_settings", "/settings",
248 248 action="index", conditions=dict(method=["GET"]))
249 249 m.connect("formatted_admin_settings", "/settings.{format}",
250 250 action="index", conditions=dict(method=["GET"]))
251 251 m.connect("admin_new_setting", "/settings/new",
252 252 action="new", conditions=dict(method=["GET"]))
253 253 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
254 254 action="new", conditions=dict(method=["GET"]))
255 255 m.connect("/settings/{setting_id}",
256 256 action="update", conditions=dict(method=["PUT"]))
257 257 m.connect("/settings/{setting_id}",
258 258 action="delete", conditions=dict(method=["DELETE"]))
259 259 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
260 260 action="edit", conditions=dict(method=["GET"]))
261 261 m.connect("formatted_admin_edit_setting",
262 262 "/settings/{setting_id}.{format}/edit",
263 263 action="edit", conditions=dict(method=["GET"]))
264 264 m.connect("admin_setting", "/settings/{setting_id}",
265 265 action="show", conditions=dict(method=["GET"]))
266 266 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
267 267 action="show", conditions=dict(method=["GET"]))
268 268 m.connect("admin_settings_my_account", "/my_account",
269 269 action="my_account", conditions=dict(method=["GET"]))
270 270 m.connect("admin_settings_my_account_update", "/my_account_update",
271 271 action="my_account_update", conditions=dict(method=["PUT"]))
272 272 m.connect("admin_settings_create_repository", "/create_repository",
273 273 action="create_repository", conditions=dict(method=["GET"]))
274 274
275 275
276 276 #NOTIFICATION REST ROUTES
277 277 with rmap.submapper(path_prefix=ADMIN_PREFIX,
278 278 controller='admin/notifications') as m:
279 279 m.connect("notifications", "/notifications",
280 280 action="create", conditions=dict(method=["POST"]))
281 281 m.connect("notifications", "/notifications",
282 282 action="index", conditions=dict(method=["GET"]))
283 283 m.connect("formatted_notifications", "/notifications.{format}",
284 284 action="index", conditions=dict(method=["GET"]))
285 285 m.connect("new_notification", "/notifications/new",
286 286 action="new", conditions=dict(method=["GET"]))
287 287 m.connect("formatted_new_notification", "/notifications/new.{format}",
288 288 action="new", conditions=dict(method=["GET"]))
289 289 m.connect("/notification/{notification_id}",
290 290 action="update", conditions=dict(method=["PUT"]))
291 291 m.connect("/notification/{notification_id}",
292 292 action="delete", conditions=dict(method=["DELETE"]))
293 293 m.connect("edit_notification", "/notification/{notification_id}/edit",
294 294 action="edit", conditions=dict(method=["GET"]))
295 295 m.connect("formatted_edit_notification",
296 296 "/notification/{notification_id}.{format}/edit",
297 297 action="edit", conditions=dict(method=["GET"]))
298 298 m.connect("notification", "/notification/{notification_id}",
299 299 action="show", conditions=dict(method=["GET"]))
300 300 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
301 301 action="show", conditions=dict(method=["GET"]))
302 302
303 303
304 304
305 305 #ADMIN MAIN PAGES
306 306 with rmap.submapper(path_prefix=ADMIN_PREFIX,
307 307 controller='admin/admin') as m:
308 308 m.connect('admin_home', '', action='index')
309 309 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
310 310 action='add_repo')
311 311
312 312 #==========================================================================
313 313 # API V1
314 314 #==========================================================================
315 315 with rmap.submapper(path_prefix=ADMIN_PREFIX,
316 316 controller='api/api') as m:
317 317 m.connect('api', '/api')
318 318
319 319
320 320 #USER JOURNAL
321 321 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
322 322
323 323 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
324 324 controller='journal', action="public_journal")
325 325
326 326 rmap.connect('public_journal_rss', '%s/public_journal_rss' % ADMIN_PREFIX,
327 327 controller='journal', action="public_journal_rss")
328 328
329 329 rmap.connect('public_journal_atom',
330 330 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
331 331 action="public_journal_atom")
332 332
333 333 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
334 334 controller='journal', action='toggle_following',
335 335 conditions=dict(method=["POST"]))
336 336
337 337 #SEARCH
338 338 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
339 339 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
340 340 controller='search')
341 341
342 342 #LOGIN/LOGOUT/REGISTER/SIGN IN
343 343 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
344 344 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
345 345 action='logout')
346 346
347 347 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
348 348 action='register')
349 349
350 350 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
351 351 controller='login', action='password_reset')
352 352
353 353 rmap.connect('reset_password_confirmation',
354 354 '%s/password_reset_confirmation' % ADMIN_PREFIX,
355 355 controller='login', action='password_reset_confirmation')
356 356
357 357 #FEEDS
358 358 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
359 359 controller='feed', action='rss',
360 360 conditions=dict(function=check_repo))
361 361
362 362 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
363 363 controller='feed', action='atom',
364 364 conditions=dict(function=check_repo))
365 365
366 366 #==========================================================================
367 367 # REPOSITORY ROUTES
368 368 #==========================================================================
369 369 rmap.connect('summary_home', '/{repo_name:.*}',
370 370 controller='summary',
371 371 conditions=dict(function=check_repo))
372 372
373 373 rmap.connect('repos_group_home', '/{group_name:.*}',
374 374 controller='admin/repos_groups', action="show_by_name",
375 375 conditions=dict(function=check_group))
376 376
377 377 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
378 378 controller='changeset', revision='tip',
379 379 conditions=dict(function=check_repo))
380 380
381 381 rmap.connect('changeset_comment', '/{repo_name:.*}/changeset/{revision}/comment',
382 382 controller='changeset', revision='tip', action='comment',
383 383 conditions=dict(function=check_repo))
384 384
385 385 rmap.connect('changeset_comment_delete', '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
386 386 controller='changeset', action='delete_comment',
387 387 conditions=dict(function=check_repo, method=["DELETE"]))
388 388
389 389 rmap.connect('raw_changeset_home',
390 390 '/{repo_name:.*}/raw-changeset/{revision}',
391 391 controller='changeset', action='raw_changeset',
392 392 revision='tip', conditions=dict(function=check_repo))
393 393
394 394 rmap.connect('summary_home', '/{repo_name:.*}/summary',
395 395 controller='summary', conditions=dict(function=check_repo))
396 396
397 397 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
398 398 controller='shortlog', conditions=dict(function=check_repo))
399 399
400 400 rmap.connect('branches_home', '/{repo_name:.*}/branches',
401 401 controller='branches', conditions=dict(function=check_repo))
402 402
403 403 rmap.connect('tags_home', '/{repo_name:.*}/tags',
404 404 controller='tags', conditions=dict(function=check_repo))
405 405
406 406 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
407 407 controller='changelog', conditions=dict(function=check_repo))
408 408
409 409 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
410 410 controller='changelog', action='changelog_details',
411 411 conditions=dict(function=check_repo))
412 412
413 413 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
414 414 controller='files', revision='tip', f_path='',
415 415 conditions=dict(function=check_repo))
416 416
417 417 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
418 418 controller='files', action='diff', revision='tip', f_path='',
419 419 conditions=dict(function=check_repo))
420 420
421 421 rmap.connect('files_rawfile_home',
422 422 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
423 423 controller='files', action='rawfile', revision='tip',
424 424 f_path='', conditions=dict(function=check_repo))
425 425
426 426 rmap.connect('files_raw_home',
427 427 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
428 428 controller='files', action='raw', revision='tip', f_path='',
429 429 conditions=dict(function=check_repo))
430 430
431 431 rmap.connect('files_annotate_home',
432 432 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
433 433 controller='files', action='annotate', revision='tip',
434 434 f_path='', conditions=dict(function=check_repo))
435 435
436 436 rmap.connect('files_edit_home',
437 437 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
438 438 controller='files', action='edit', revision='tip',
439 439 f_path='', conditions=dict(function=check_repo))
440 440
441 441 rmap.connect('files_add_home',
442 442 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
443 443 controller='files', action='add', revision='tip',
444 444 f_path='', conditions=dict(function=check_repo))
445 445
446 446 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
447 447 controller='files', action='archivefile',
448 448 conditions=dict(function=check_repo))
449 449
450 450 rmap.connect('files_nodelist_home',
451 451 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
452 452 controller='files', action='nodelist',
453 453 conditions=dict(function=check_repo))
454 454
455 455 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
456 456 controller='settings', action="delete",
457 457 conditions=dict(method=["DELETE"], function=check_repo))
458 458
459 459 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
460 460 controller='settings', action="update",
461 461 conditions=dict(method=["PUT"], function=check_repo))
462 462
463 463 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
464 464 controller='settings', action='index',
465 465 conditions=dict(function=check_repo))
466 466
467 467 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
468 controller='settings', action='fork_create',
468 controller='forks', action='fork_create',
469 469 conditions=dict(function=check_repo, method=["POST"]))
470 470
471 471 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
472 controller='settings', action='fork',
472 controller='forks', action='fork',
473 473 conditions=dict(function=check_repo))
474 474
475 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
476 controller='forks', action='forks',
477 conditions=dict(function=check_repo))
478
475 479 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
476 480 controller='followers', action='followers',
477 481 conditions=dict(function=check_repo))
478 482
479 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
480 controller='forks', action='forks',
481 conditions=dict(function=check_repo))
482 483
483 484 return rmap
@@ -1,397 +1,398 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Admin controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from paste.httpexceptions import HTTPInternalServerError
32 from pylons import request, response, session, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
32 from pylons import request, session, tmpl_context as c, url
33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 from sqlalchemy.exc import IntegrityError
35 36
36 37 from rhodecode.lib import helpers as h
37 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 39 HasPermissionAnyDecorator
39 40 from rhodecode.lib.base import BaseController, render
40 41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
41 42 from rhodecode.lib.helpers import get_token
43 from rhodecode.model.meta import Session
42 44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
43 45 from rhodecode.model.forms import RepoForm
44 46 from rhodecode.model.scm import ScmModel
45 47 from rhodecode.model.repo import RepoModel
46 from sqlalchemy.exc import IntegrityError
47 48
48 49 log = logging.getLogger(__name__)
49 50
50 51
51 52 class ReposController(BaseController):
52 53 """
53 54 REST Controller styled on the Atom Publishing Protocol"""
54 55 # To properly map this controller, ensure your config/routing.py
55 56 # file has a resource setup:
56 57 # map.resource('repo', 'repos')
57 58
58 59 @LoginRequired()
59 60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
60 61 def __before__(self):
61 62 c.admin_user = session.get('admin_user')
62 63 c.admin_username = session.get('admin_username')
63 64 super(ReposController, self).__before__()
64 65
65 66 def __load_defaults(self):
66 67 c.repo_groups = RepoGroup.groups_choices()
67 68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
68
69
69 70 repo_model = RepoModel()
70 71 c.users_array = repo_model.get_users_js()
71 72 c.users_groups_array = repo_model.get_users_groups_js()
72 73
73 74 def __load_data(self, repo_name=None):
74 75 """
75 76 Load defaults settings for edit, and update
76 77
77 78 :param repo_name:
78 79 """
79 80 self.__load_defaults()
80 81
81 82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
82 83 repo = db_repo.scm_instance
83 84
84 85 if c.repo_info is None:
85 86 h.flash(_('%s repository is not mapped to db perhaps'
86 87 ' it was created or renamed from the filesystem'
87 88 ' please run the application again'
88 89 ' in order to rescan repositories') % repo_name,
89 90 category='error')
90 91
91 92 return redirect(url('repos'))
92 93
93 94 c.default_user_id = User.get_by_username('default').user_id
94 95 c.in_public_journal = UserFollowing.query()\
95 96 .filter(UserFollowing.user_id == c.default_user_id)\
96 97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
97 98
98 99 if c.repo_info.stats:
99 100 last_rev = c.repo_info.stats.stat_on_revision
100 101 else:
101 102 last_rev = 0
102 103 c.stats_revision = last_rev
103 104
104 105 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
105 106
106 107 if last_rev == 0 or c.repo_last_rev == 0:
107 108 c.stats_percentage = 0
108 109 else:
109 110 c.stats_percentage = '%.2f' % ((float((last_rev)) /
110 111 c.repo_last_rev) * 100)
111 112
112 113 defaults = RepoModel()._get_defaults(repo_name)
113 114 return defaults
114 115
115 116 @HasPermissionAllDecorator('hg.admin')
116 117 def index(self, format='html'):
117 118 """GET /repos: All items in the collection"""
118 119 # url('repos')
119 120
120 121 c.repos_list = ScmModel().get_repos(Repository.query()
121 122 .order_by(Repository.repo_name)
122 123 .all(), sort_key='name_sort')
123 124 return render('admin/repos/repos.html')
124 125
125 126 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
126 127 def create(self):
127 128 """
128 129 POST /repos: Create a new item"""
129 130 # url('repos')
130 repo_model = RepoModel()
131
131 132 self.__load_defaults()
132 133 form_result = {}
133 134 try:
134 135 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
135 136 .to_python(dict(request.POST))
136 repo_model.create(form_result, self.rhodecode_user)
137 RepoModel().create(form_result, self.rhodecode_user)
137 138 if form_result['clone_uri']:
138 139 h.flash(_('created repository %s from %s') \
139 140 % (form_result['repo_name'], form_result['clone_uri']),
140 141 category='success')
141 142 else:
142 143 h.flash(_('created repository %s') % form_result['repo_name'],
143 144 category='success')
144 145
145 146 if request.POST.get('user_created'):
146 #created by regular non admin user
147 # created by regular non admin user
147 148 action_logger(self.rhodecode_user, 'user_created_repo',
148 149 form_result['repo_name_full'], '', self.sa)
149 150 else:
150 151 action_logger(self.rhodecode_user, 'admin_created_repo',
151 152 form_result['repo_name_full'], '', self.sa)
152
153 Session().commit()
153 154 except formencode.Invalid, errors:
154 155
155 156 c.new_repo = errors.value['repo_name']
156 157
157 158 if request.POST.get('user_created'):
158 159 r = render('admin/repos/repo_add_create_repository.html')
159 160 else:
160 161 r = render('admin/repos/repo_add.html')
161 162
162 163 return htmlfill.render(
163 164 r,
164 165 defaults=errors.value,
165 166 errors=errors.error_dict or {},
166 167 prefix_error=False,
167 168 encoding="UTF-8")
168 169
169 170 except Exception:
170 171 log.error(traceback.format_exc())
171 172 msg = _('error occurred during creation of repository %s') \
172 173 % form_result.get('repo_name')
173 174 h.flash(msg, category='error')
174 175 if request.POST.get('user_created'):
175 176 return redirect(url('home'))
176 177 return redirect(url('repos'))
177 178
178 179 @HasPermissionAllDecorator('hg.admin')
179 180 def new(self, format='html'):
180 181 """GET /repos/new: Form to create a new item"""
181 182 new_repo = request.GET.get('repo', '')
182 183 c.new_repo = repo_name_slug(new_repo)
183 184 self.__load_defaults()
184 185 return render('admin/repos/repo_add.html')
185 186
186 187 @HasPermissionAllDecorator('hg.admin')
187 188 def update(self, repo_name):
188 189 """
189 190 PUT /repos/repo_name: Update an existing item"""
190 191 # Forms posted to this method should contain a hidden field:
191 192 # <input type="hidden" name="_method" value="PUT" />
192 193 # Or using helpers:
193 194 # h.form(url('repo', repo_name=ID),
194 195 # method='put')
195 196 # url('repo', repo_name=ID)
196 197 self.__load_defaults()
197 198 repo_model = RepoModel()
198 199 changed_name = repo_name
199 200 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
200 201 repo_groups=c.repo_groups_choices)()
201 202 try:
202 203 form_result = _form.to_python(dict(request.POST))
203 204 repo = repo_model.update(repo_name, form_result)
204 205 invalidate_cache('get_repo_cached_%s' % repo_name)
205 206 h.flash(_('Repository %s updated successfully' % repo_name),
206 207 category='success')
207 208 changed_name = repo.repo_name
208 209 action_logger(self.rhodecode_user, 'admin_updated_repo',
209 210 changed_name, '', self.sa)
210
211 Session().commit()
211 212 except formencode.Invalid, errors:
212 213 defaults = self.__load_data(repo_name)
213 214 defaults.update(errors.value)
214 215 return htmlfill.render(
215 216 render('admin/repos/repo_edit.html'),
216 217 defaults=defaults,
217 218 errors=errors.error_dict or {},
218 219 prefix_error=False,
219 220 encoding="UTF-8")
220 221
221 222 except Exception:
222 223 log.error(traceback.format_exc())
223 224 h.flash(_('error occurred during update of repository %s') \
224 225 % repo_name, category='error')
225 226 return redirect(url('edit_repo', repo_name=changed_name))
226 227
227 228 @HasPermissionAllDecorator('hg.admin')
228 229 def delete(self, repo_name):
229 230 """
230 231 DELETE /repos/repo_name: Delete an existing item"""
231 232 # Forms posted to this method should contain a hidden field:
232 233 # <input type="hidden" name="_method" value="DELETE" />
233 234 # Or using helpers:
234 235 # h.form(url('repo', repo_name=ID),
235 236 # method='delete')
236 237 # url('repo', repo_name=ID)
237 238
238 239 repo_model = RepoModel()
239 240 repo = repo_model.get_by_repo_name(repo_name)
240 241 if not repo:
241 242 h.flash(_('%s repository is not mapped to db perhaps'
242 243 ' it was moved or renamed from the filesystem'
243 244 ' please run the application again'
244 245 ' in order to rescan repositories') % repo_name,
245 246 category='error')
246 247
247 248 return redirect(url('repos'))
248 249 try:
249 250 action_logger(self.rhodecode_user, 'admin_deleted_repo',
250 251 repo_name, '', self.sa)
251 252 repo_model.delete(repo)
252 253 invalidate_cache('get_repo_cached_%s' % repo_name)
253 254 h.flash(_('deleted repository %s') % repo_name, category='success')
254
255 Session().commit()
255 256 except IntegrityError, e:
256 257 if e.message.find('repositories_fork_id_fkey'):
257 258 log.error(traceback.format_exc())
258 259 h.flash(_('Cannot delete %s it still contains attached '
259 260 'forks') % repo_name,
260 261 category='warning')
261 262 else:
262 263 log.error(traceback.format_exc())
263 264 h.flash(_('An error occurred during '
264 265 'deletion of %s') % repo_name,
265 266 category='error')
266 267
267 268 except Exception, e:
268 269 log.error(traceback.format_exc())
269 270 h.flash(_('An error occurred during deletion of %s') % repo_name,
270 271 category='error')
271 272
272 273 return redirect(url('repos'))
273 274
274 275 @HasPermissionAllDecorator('hg.admin')
275 276 def delete_perm_user(self, repo_name):
276 277 """
277 278 DELETE an existing repository permission user
278 279
279 280 :param repo_name:
280 281 """
281 282
282 283 try:
283 284 repo_model = RepoModel()
284 285 repo_model.delete_perm_user(request.POST, repo_name)
285 286 except Exception, e:
286 287 h.flash(_('An error occurred during deletion of repository user'),
287 288 category='error')
288 289 raise HTTPInternalServerError()
289 290
290 291 @HasPermissionAllDecorator('hg.admin')
291 292 def delete_perm_users_group(self, repo_name):
292 293 """
293 294 DELETE an existing repository permission users group
294 295
295 296 :param repo_name:
296 297 """
297 298 try:
298 299 repo_model = RepoModel()
299 300 repo_model.delete_perm_users_group(request.POST, repo_name)
300 301 except Exception, e:
301 302 h.flash(_('An error occurred during deletion of repository'
302 303 ' users groups'),
303 304 category='error')
304 305 raise HTTPInternalServerError()
305 306
306 307 @HasPermissionAllDecorator('hg.admin')
307 308 def repo_stats(self, repo_name):
308 309 """
309 310 DELETE an existing repository statistics
310 311
311 312 :param repo_name:
312 313 """
313 314
314 315 try:
315 316 repo_model = RepoModel()
316 317 repo_model.delete_stats(repo_name)
317 318 except Exception, e:
318 319 h.flash(_('An error occurred during deletion of repository stats'),
319 320 category='error')
320 321 return redirect(url('edit_repo', repo_name=repo_name))
321 322
322 323 @HasPermissionAllDecorator('hg.admin')
323 324 def repo_cache(self, repo_name):
324 325 """
325 326 INVALIDATE existing repository cache
326 327
327 328 :param repo_name:
328 329 """
329 330
330 331 try:
331 332 ScmModel().mark_for_invalidation(repo_name)
332 333 except Exception, e:
333 334 h.flash(_('An error occurred during cache invalidation'),
334 335 category='error')
335 336 return redirect(url('edit_repo', repo_name=repo_name))
336 337
337 338 @HasPermissionAllDecorator('hg.admin')
338 339 def repo_public_journal(self, repo_name):
339 340 """
340 341 Set's this repository to be visible in public journal,
341 342 in other words assing default user to follow this repo
342 343
343 344 :param repo_name:
344 345 """
345 346
346 347 cur_token = request.POST.get('auth_token')
347 348 token = get_token()
348 349 if cur_token == token:
349 350 try:
350 351 repo_id = Repository.get_by_repo_name(repo_name).repo_id
351 352 user_id = User.get_by_username('default').user_id
352 353 self.scm_model.toggle_following_repo(repo_id, user_id)
353 354 h.flash(_('Updated repository visibility in public journal'),
354 355 category='success')
355 356 except:
356 357 h.flash(_('An error occurred during setting this'
357 358 ' repository in public journal'),
358 359 category='error')
359 360
360 361 else:
361 362 h.flash(_('Token mismatch'), category='error')
362 363 return redirect(url('edit_repo', repo_name=repo_name))
363 364
364 365 @HasPermissionAllDecorator('hg.admin')
365 366 def repo_pull(self, repo_name):
366 367 """
367 368 Runs task to update given repository with remote changes,
368 369 ie. make pull on remote location
369 370
370 371 :param repo_name:
371 372 """
372 373 try:
373 374 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
374 375 h.flash(_('Pulled from remote location'), category='success')
375 376 except Exception, e:
376 377 h.flash(_('An error occurred during pull from remote location'),
377 378 category='error')
378 379
379 380 return redirect(url('edit_repo', repo_name=repo_name))
380 381
381 382 @HasPermissionAllDecorator('hg.admin')
382 383 def show(self, repo_name, format='html'):
383 384 """GET /repos/repo_name: Show a specific item"""
384 385 # url('repo', repo_name=ID)
385 386
386 387 @HasPermissionAllDecorator('hg.admin')
387 388 def edit(self, repo_name, format='html'):
388 389 """GET /repos/repo_name/edit: Form to edit an existing item"""
389 390 # url('edit_repo', repo_name=ID)
390 391 defaults = self.__load_data(repo_name)
391 392
392 393 return htmlfill.render(
393 394 render('admin/repos/repo_edit.html'),
394 395 defaults=defaults,
395 396 encoding="UTF-8",
396 397 force_defaults=False
397 398 )
@@ -1,56 +1,174 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.forks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 forks controller for rhodecode
7 7
8 8 :created_on: Apr 23, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 import formencode
27 import traceback
28 from formencode import htmlfill
26 29
27 from pylons import tmpl_context as c, request
30 from pylons import tmpl_context as c, request, url
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
33
34 import rhodecode.lib.helpers as h
28 35
29 36 from rhodecode.lib.helpers import Page
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
38 NotAnonymous
31 39 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.model.db import Repository, User, UserFollowing
40 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
41 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.forms import RepoForkForm
33 43
34 44 log = logging.getLogger(__name__)
35 45
36 46
37 47 class ForksController(BaseRepoController):
38 48
39 49 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 'repository.admin')
42 50 def __before__(self):
43 51 super(ForksController, self).__before__()
44 52
53 def __load_defaults(self):
54 c.repo_groups = RepoGroup.groups_choices()
55 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
56
57 def __load_data(self, repo_name=None):
58 """
59 Load defaults settings for edit, and update
60
61 :param repo_name:
62 """
63 self.__load_defaults()
64
65 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
66 repo = db_repo.scm_instance
67
68 if c.repo_info is None:
69 h.flash(_('%s repository is not mapped to db perhaps'
70 ' it was created or renamed from the filesystem'
71 ' please run the application again'
72 ' in order to rescan repositories') % repo_name,
73 category='error')
74
75 return redirect(url('repos'))
76
77 c.default_user_id = User.get_by_username('default').user_id
78 c.in_public_journal = UserFollowing.query()\
79 .filter(UserFollowing.user_id == c.default_user_id)\
80 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
81
82 if c.repo_info.stats:
83 last_rev = c.repo_info.stats.stat_on_revision
84 else:
85 last_rev = 0
86 c.stats_revision = last_rev
87
88 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
89
90 if last_rev == 0 or c.repo_last_rev == 0:
91 c.stats_percentage = 0
92 else:
93 c.stats_percentage = '%.2f' % ((float((last_rev)) /
94 c.repo_last_rev) * 100)
95
96 defaults = RepoModel()._get_defaults(repo_name)
97 # add prefix to fork
98 defaults['repo_name'] = 'fork-' + defaults['repo_name']
99 return defaults
100
101 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
102 'repository.admin')
45 103 def forks(self, repo_name):
46 104 p = int(request.params.get('page', 1))
47 105 repo_id = c.rhodecode_db_repo.repo_id
48 106 d = Repository.get_repo_forks(repo_id)
49 107 c.forks_pager = Page(d, page=p, items_per_page=20)
50 108
51 109 c.forks_data = render('/forks/forks_data.html')
52 110
53 111 if request.environ.get('HTTP_X_PARTIAL_XHR'):
54 112 return c.forks_data
55 113
56 114 return render('/forks/forks.html')
115
116 @NotAnonymous()
117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
118 'repository.admin')
119 def fork(self, repo_name):
120 c.repo_info = Repository.get_by_repo_name(repo_name)
121 if not c.repo_info:
122 h.flash(_('%s repository is not mapped to db perhaps'
123 ' it was created or renamed from the file system'
124 ' please run the application again'
125 ' in order to rescan repositories') % repo_name,
126 category='error')
127
128 return redirect(url('home'))
129
130 defaults = self.__load_data(repo_name)
131
132 return htmlfill.render(
133 render('forks/fork.html'),
134 defaults=defaults,
135 encoding="UTF-8",
136 force_defaults=False
137 )
138
139
140 @NotAnonymous()
141 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
142 'repository.admin')
143 def fork_create(self, repo_name):
144 self.__load_defaults()
145 c.repo_info = Repository.get_by_repo_name(repo_name)
146 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
147 repo_groups=c.repo_groups_choices,)()
148 form_result = {}
149 try:
150 form_result = _form.to_python(dict(request.POST))
151 # add org_path of repo so we can do a clone from it later
152 form_result['org_path'] = c.repo_info.repo_name
153
154 # create fork is done sometimes async on celery, db transaction
155 # management is handled there.
156 RepoModel().create_fork(form_result, self.rhodecode_user)
157 h.flash(_('forked %s repository as %s') \
158 % (repo_name, form_result['repo_name']),
159 category='success')
160 except formencode.Invalid, errors:
161 c.new_repo = errors.value['repo_name']
162
163 return htmlfill.render(
164 render('forks/fork.html'),
165 defaults=errors.value,
166 errors=errors.error_dict or {},
167 prefix_error=False,
168 encoding="UTF-8")
169 except Exception:
170 log.error(traceback.format_exc())
171 h.flash(_('An error occurred during repository forking %s') %
172 repo_name, category='error')
173
174 return redirect(url('home'))
@@ -1,230 +1,233 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.journal
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Journal controller for pylons
7 7
8 8 :created_on: Nov 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 from itertools import groupby
26 27
27 28 from sqlalchemy import or_
28 from sqlalchemy.orm import joinedload, make_transient
29 from sqlalchemy.orm import joinedload
29 30 from webhelpers.paginate import Page
30 from itertools import groupby
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
31 32
32 33 from paste.httpexceptions import HTTPBadRequest
33 34 from pylons import request, tmpl_context as c, response, url
34 35 from pylons.i18n.translation import _
35 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
36 36
37 37 import rhodecode.lib.helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
39 39 from rhodecode.lib.base import BaseController, render
40 40 from rhodecode.model.db import UserLog, UserFollowing
41 from rhodecode.model.meta import Session
41 42
42 43 log = logging.getLogger(__name__)
43 44
44 45
45 46 class JournalController(BaseController):
46 47
47 48 def __before__(self):
48 49 super(JournalController, self).__before__()
49 50 self.rhodecode_user = self.rhodecode_user
50 51 self.title = _('%s public journal %s feed') % (c.rhodecode_name, '%s')
51 52 self.language = 'en-us'
52 53 self.ttl = "5"
53 54 self.feed_nr = 20
54 55
55 56 @LoginRequired()
56 57 @NotAnonymous()
57 58 def index(self):
58 59 # Return a rendered template
59 60 p = int(request.params.get('page', 1))
60 61
61 62 c.following = self.sa.query(UserFollowing)\
62 63 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
63 64 .options(joinedload(UserFollowing.follows_repository))\
64 65 .all()
65 66
66 67 journal = self._get_journal_data(c.following)
67 68
68 69 c.journal_pager = Page(journal, page=p, items_per_page=20)
69 70
70 71 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
71 72
72 73 c.journal_data = render('journal/journal_data.html')
73 74 if request.environ.get('HTTP_X_PARTIAL_XHR'):
74 75 return c.journal_data
75 76 return render('journal/journal.html')
76 77
77 78 def _get_daily_aggregate(self, journal):
78 79 groups = []
79 80 for k, g in groupby(journal, lambda x: x.action_as_day):
80 81 user_group = []
81 82 for k2, g2 in groupby(list(g), lambda x: x.user.email):
82 83 l = list(g2)
83 84 user_group.append((l[0].user, l))
84 85
85 86 groups.append((k, user_group,))
86 87
87 88 return groups
88 89
89 90 def _get_journal_data(self, following_repos):
90 91 repo_ids = [x.follows_repository.repo_id for x in following_repos
91 92 if x.follows_repository is not None]
92 93 user_ids = [x.follows_user.user_id for x in following_repos
93 94 if x.follows_user is not None]
94 95
95 96 filtering_criterion = None
96 97
97 98 if repo_ids and user_ids:
98 99 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
99 100 UserLog.user_id.in_(user_ids))
100 101 if repo_ids and not user_ids:
101 102 filtering_criterion = UserLog.repository_id.in_(repo_ids)
102 103 if not repo_ids and user_ids:
103 104 filtering_criterion = UserLog.user_id.in_(user_ids)
104 105 if filtering_criterion is not None:
105 106 journal = self.sa.query(UserLog)\
106 107 .options(joinedload(UserLog.user))\
107 108 .options(joinedload(UserLog.repository))\
108 109 .filter(filtering_criterion)\
109 110 .order_by(UserLog.action_date.desc())
110 111 else:
111 112 journal = []
112 113
113 114 return journal
114 115
115 116 @LoginRequired()
116 117 @NotAnonymous()
117 118 def toggle_following(self):
118 119 cur_token = request.POST.get('auth_token')
119 120 token = h.get_token()
120 121 if cur_token == token:
121 122
122 123 user_id = request.POST.get('follows_user_id')
123 124 if user_id:
124 125 try:
125 126 self.scm_model.toggle_following_user(user_id,
126 127 self.rhodecode_user.user_id)
128 Session().commit()
127 129 return 'ok'
128 130 except:
129 131 raise HTTPBadRequest()
130 132
131 133 repo_id = request.POST.get('follows_repo_id')
132 134 if repo_id:
133 135 try:
134 136 self.scm_model.toggle_following_repo(repo_id,
135 137 self.rhodecode_user.user_id)
138 Session().commit()
136 139 return 'ok'
137 140 except:
138 141 raise HTTPBadRequest()
139 142
140 143 log.debug('token mismatch %s vs %s', cur_token, token)
141 144 raise HTTPBadRequest()
142 145
143 146 @LoginRequired()
144 147 def public_journal(self):
145 148 # Return a rendered template
146 149 p = int(request.params.get('page', 1))
147 150
148 151 c.following = self.sa.query(UserFollowing)\
149 152 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
150 153 .options(joinedload(UserFollowing.follows_repository))\
151 154 .all()
152 155
153 156 journal = self._get_journal_data(c.following)
154 157
155 158 c.journal_pager = Page(journal, page=p, items_per_page=20)
156 159
157 160 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
158 161
159 162 c.journal_data = render('journal/journal_data.html')
160 163 if request.environ.get('HTTP_X_PARTIAL_XHR'):
161 164 return c.journal_data
162 165 return render('journal/public_journal.html')
163 166
164 167 @LoginRequired(api_access=True)
165 168 def public_journal_atom(self):
166 169 """
167 170 Produce an atom-1.0 feed via feedgenerator module
168 171 """
169 172 c.following = self.sa.query(UserFollowing)\
170 173 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
171 174 .options(joinedload(UserFollowing.follows_repository))\
172 175 .all()
173 176
174 177 journal = self._get_journal_data(c.following)
175 178
176 179 feed = Atom1Feed(title=self.title % 'atom',
177 180 link=url('public_journal_atom', qualified=True),
178 181 description=_('Public journal'),
179 182 language=self.language,
180 183 ttl=self.ttl)
181 184
182 185 for entry in journal[:self.feed_nr]:
183 186 #tmpl = h.action_parser(entry)[0]
184 187 action, action_extra = h.action_parser(entry, feed=True)
185 188 title = "%s - %s %s" % (entry.user.short_contact, action,
186 189 entry.repository.repo_name)
187 190 desc = action_extra()
188 191 feed.add_item(title=title,
189 192 pubdate=entry.action_date,
190 193 link=url('', qualified=True),
191 194 author_email=entry.user.email,
192 195 author_name=entry.user.full_contact,
193 196 description=desc)
194 197
195 198 response.content_type = feed.mime_type
196 199 return feed.writeString('utf-8')
197 200
198 201 @LoginRequired(api_access=True)
199 202 def public_journal_rss(self):
200 203 """
201 204 Produce an rss2 feed via feedgenerator module
202 205 """
203 206 c.following = self.sa.query(UserFollowing)\
204 207 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
205 208 .options(joinedload(UserFollowing.follows_repository))\
206 209 .all()
207 210
208 211 journal = self._get_journal_data(c.following)
209 212
210 213 feed = Rss201rev2Feed(title=self.title % 'rss',
211 214 link=url('public_journal_rss', qualified=True),
212 215 description=_('Public journal'),
213 216 language=self.language,
214 217 ttl=self.ttl)
215 218
216 219 for entry in journal[:self.feed_nr]:
217 220 #tmpl = h.action_parser(entry)[0]
218 221 action, action_extra = h.action_parser(entry, feed=True)
219 222 title = "%s - %s %s" % (entry.user.short_contact, action,
220 223 entry.repository.repo_name)
221 224 desc = action_extra()
222 225 feed.add_item(title=title,
223 226 pubdate=entry.action_date,
224 227 link=url('', qualified=True),
225 228 author_email=entry.user.email,
226 229 author_name=entry.user.full_contact,
227 230 description=desc)
228 231
229 232 response.content_type = feed.mime_type
230 233 return feed.writeString('utf-8')
@@ -1,208 +1,158 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Settings controller for rhodecode
7 7
8 8 :created_on: Jun 30, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31
32 32 from pylons import tmpl_context as c, request, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 import rhodecode.lib.helpers as h
37 37
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
39 HasRepoPermissionAnyDecorator, NotAnonymous
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
40 39 from rhodecode.lib.base import BaseRepoController, render
41 40 from rhodecode.lib.utils import invalidate_cache, action_logger
42 41
43 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
42 from rhodecode.model.forms import RepoSettingsForm
44 43 from rhodecode.model.repo import RepoModel
45 44 from rhodecode.model.db import RepoGroup
45 from rhodecode.model.meta import Session
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class SettingsController(BaseRepoController):
51 51
52 52 @LoginRequired()
53 53 def __before__(self):
54 54 super(SettingsController, self).__before__()
55
55
56 56 def __load_defaults(self):
57 57 c.repo_groups = RepoGroup.groups_choices()
58 58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
59
59
60 60 repo_model = RepoModel()
61 61 c.users_array = repo_model.get_users_js()
62 62 c.users_groups_array = repo_model.get_users_groups_js()
63
63
64 64 @HasRepoPermissionAllDecorator('repository.admin')
65 65 def index(self, repo_name):
66 66 repo_model = RepoModel()
67 67 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
68 68 if not repo:
69 69 h.flash(_('%s repository is not mapped to db perhaps'
70 70 ' it was created or renamed from the file system'
71 71 ' please run the application again'
72 72 ' in order to rescan repositories') % repo_name,
73 73 category='error')
74 74
75 75 return redirect(url('home'))
76 76
77 77 self.__load_defaults()
78 78
79 79 defaults = RepoModel()._get_defaults(repo_name)
80 80
81 81 return htmlfill.render(
82 82 render('settings/repo_settings.html'),
83 83 defaults=defaults,
84 84 encoding="UTF-8",
85 85 force_defaults=False
86 86 )
87 87
88 88 @HasRepoPermissionAllDecorator('repository.admin')
89 89 def update(self, repo_name):
90 90 repo_model = RepoModel()
91 91 changed_name = repo_name
92
92
93 93 self.__load_defaults()
94
94
95 95 _form = RepoSettingsForm(edit=True,
96 96 old_data={'repo_name': repo_name},
97 97 repo_groups=c.repo_groups_choices)()
98 98 try:
99 99 form_result = _form.to_python(dict(request.POST))
100
100
101 101 repo_model.update(repo_name, form_result)
102 102 invalidate_cache('get_repo_cached_%s' % repo_name)
103 103 h.flash(_('Repository %s updated successfully' % repo_name),
104 104 category='success')
105 105 changed_name = form_result['repo_name_full']
106 106 action_logger(self.rhodecode_user, 'user_updated_repo',
107 107 changed_name, '', self.sa)
108 Session().commit()
108 109 except formencode.Invalid, errors:
109 110 c.repo_info = repo_model.get_by_repo_name(repo_name)
110 111 c.users_array = repo_model.get_users_js()
111 112 errors.value.update({'user': c.repo_info.user.username})
112 113 return htmlfill.render(
113 114 render('settings/repo_settings.html'),
114 115 defaults=errors.value,
115 116 errors=errors.error_dict or {},
116 117 prefix_error=False,
117 118 encoding="UTF-8")
118 119 except Exception:
119 120 log.error(traceback.format_exc())
120 121 h.flash(_('error occurred during update of repository %s') \
121 122 % repo_name, category='error')
122 123
123 124 return redirect(url('repo_settings_home', repo_name=changed_name))
124 125
125 126 @HasRepoPermissionAllDecorator('repository.admin')
126 127 def delete(self, repo_name):
127 128 """DELETE /repos/repo_name: Delete an existing item"""
128 129 # Forms posted to this method should contain a hidden field:
129 130 # <input type="hidden" name="_method" value="DELETE" />
130 131 # Or using helpers:
131 132 # h.form(url('repo_settings_delete', repo_name=ID),
132 133 # method='delete')
133 134 # url('repo_settings_delete', repo_name=ID)
134 135
135 136 repo_model = RepoModel()
136 137 repo = repo_model.get_by_repo_name(repo_name)
137 138 if not repo:
138 139 h.flash(_('%s repository is not mapped to db perhaps'
139 140 ' it was moved or renamed from the filesystem'
140 141 ' please run the application again'
141 142 ' in order to rescan repositories') % repo_name,
142 143 category='error')
143 144
144 145 return redirect(url('home'))
145 146 try:
146 147 action_logger(self.rhodecode_user, 'user_deleted_repo',
147 148 repo_name, '', self.sa)
148 149 repo_model.delete(repo)
149 150 invalidate_cache('get_repo_cached_%s' % repo_name)
150 151 h.flash(_('deleted repository %s') % repo_name, category='success')
152 Session().commit()
151 153 except Exception:
152 154 log.error(traceback.format_exc())
153 155 h.flash(_('An error occurred during deletion of %s') % repo_name,
154 156 category='error')
155 157
156 158 return redirect(url('home'))
157
158 @NotAnonymous()
159 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
160 'repository.admin')
161 def fork(self, repo_name):
162 repo_model = RepoModel()
163 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
164 if not repo:
165 h.flash(_('%s repository is not mapped to db perhaps'
166 ' it was created or renamed from the file system'
167 ' please run the application again'
168 ' in order to rescan repositories') % repo_name,
169 category='error')
170
171 return redirect(url('home'))
172
173 return render('settings/repo_fork.html')
174
175 @NotAnonymous()
176 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
177 'repository.admin')
178 def fork_create(self, repo_name):
179 repo_model = RepoModel()
180 c.repo_info = repo_model.get_by_repo_name(repo_name)
181 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type})()
182 form_result = {}
183 try:
184 form_result = _form.to_python(dict(request.POST))
185 form_result.update({'repo_name': repo_name})
186 repo_model.create_fork(form_result, self.rhodecode_user)
187 h.flash(_('forked %s repository as %s') \
188 % (repo_name, form_result['fork_name']),
189 category='success')
190 action_logger(self.rhodecode_user,
191 'user_forked_repo:%s' % form_result['fork_name'],
192 repo_name, '', self.sa)
193 except formencode.Invalid, errors:
194 c.new_repo = errors.value['fork_name']
195 r = render('settings/repo_fork.html')
196
197 return htmlfill.render(
198 r,
199 defaults=errors.value,
200 errors=errors.error_dict or {},
201 prefix_error=False,
202 encoding="UTF-8")
203 except Exception:
204 log.error(traceback.format_exc())
205 h.flash(_('An error occurred during repository forking %s') %
206 repo_name, category='error')
207
208 return redirect(url('home'))
@@ -1,399 +1,406 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.tasks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode task modules, containing all task that suppose to be run
7 7 by celery daemon
8 8
9 9 :created_on: Oct 6, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 from celery.decorators import task
27 27
28 28 import os
29 29 import traceback
30 30 import logging
31 31 from os.path import join as jn
32 32
33 33 from time import mktime
34 34 from operator import itemgetter
35 35 from string import lower
36 36
37 37 from pylons import config, url
38 38 from pylons.i18n.translation import _
39 39
40 from vcs import get_backend
40 41
41 42 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
42 43 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
43 44 __get_lockkey, LockHeld, DaemonLock
44 45 from rhodecode.lib.helpers import person
45 46 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
46 from rhodecode.lib.utils import add_cache
47 from rhodecode.lib.utils import add_cache, action_logger
47 48 from rhodecode.lib.compat import json, OrderedDict
48 49
49 50 from rhodecode.model import init_model
50 51 from rhodecode.model import meta
51 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository, User
52
53 from vcs.backends import get_repo
52 from rhodecode.model.db import Statistics, Repository, User
54 53
55 54 from sqlalchemy import engine_from_config
56 55
57
58 56 add_cache(config)
59 57
60 58 __all__ = ['whoosh_index', 'get_commits_stats',
61 59 'reset_user_password', 'send_email']
62 60
61
63 62 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
64 63
65 64
66 65 def get_session():
67 66 if CELERY_ON:
68 67 engine = engine_from_config(config, 'sqlalchemy.db1.')
69 68 init_model(engine)
70 69 sa = meta.Session()
71 70 return sa
72 71
73 72 def get_logger(cls):
74 73 if CELERY_ON:
75 74 try:
76 75 log = cls.get_logger()
77 76 except:
78 77 log = logging.getLogger(__name__)
79 78 else:
80 79 log = logging.getLogger(__name__)
81 80
82 81 return log
83 82
84 def get_repos_path():
85 sa = get_session()
86 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
87 return q.ui_value
88
89
90 83 @task(ignore_result=True)
91 84 @locked_task
92 85 def whoosh_index(repo_location, full_index):
86 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
87
93 88 #log = whoosh_index.get_logger()
94 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
89
95 90 index_location = config['index_dir']
96 91 WhooshIndexingDaemon(index_location=index_location,
97 92 repo_location=repo_location, sa=get_session())\
98 93 .run(full_index=full_index)
99 94
100 95
101 96 @task(ignore_result=True)
102 97 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
103 98 log = get_logger(get_commits_stats)
104 99
105 100 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
106 101 ts_max_y)
107 102 lockkey_path = config['here']
108 103
109 104 log.info('running task with lockkey %s', lockkey)
110 105 try:
111 106 sa = get_session()
112 107 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
113 108
114 #for js data compatibilty cleans the key for person from '
109 # for js data compatibilty cleans the key for person from '
115 110 akc = lambda k: person(k).replace('"', "")
116 111
117 112 co_day_auth_aggr = {}
118 113 commits_by_day_aggregate = {}
119 repos_path = get_repos_path()
120 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
114 repo = Repository.get_by_repo_name(repo_name).scm_instance
121 115 repo_size = len(repo.revisions)
122 116 #return if repo have no revisions
123 117 if repo_size < 1:
124 118 lock.release()
125 119 return True
126 120
127 121 skip_date_limit = True
128 122 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
129 123 last_rev = 0
130 124 last_cs = None
131 125 timegetter = itemgetter('time')
132 126
133 127 dbrepo = sa.query(Repository)\
134 128 .filter(Repository.repo_name == repo_name).scalar()
135 129 cur_stats = sa.query(Statistics)\
136 130 .filter(Statistics.repository == dbrepo).scalar()
137 131
138 132 if cur_stats is not None:
139 133 last_rev = cur_stats.stat_on_revision
140 134
141 135 if last_rev == repo.get_changeset().revision and repo_size > 1:
142 #pass silently without any work if we're not on first revision or
143 #current state of parsing revision(from db marker) is the
144 #last revision
136 # pass silently without any work if we're not on first revision or
137 # current state of parsing revision(from db marker) is the
138 # last revision
145 139 lock.release()
146 140 return True
147 141
148 142 if cur_stats:
149 143 commits_by_day_aggregate = OrderedDict(json.loads(
150 144 cur_stats.commit_activity_combined))
151 145 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
152 146
153 147 log.debug('starting parsing %s', parse_limit)
154 148 lmktime = mktime
155 149
156 150 last_rev = last_rev + 1 if last_rev > 0 else last_rev
157 151
158 152 for cs in repo[last_rev:last_rev + parse_limit]:
159 153 last_cs = cs # remember last parsed changeset
160 154 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
161 155 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
162 156
163 157 if akc(cs.author) in co_day_auth_aggr:
164 158 try:
165 159 l = [timegetter(x) for x in
166 160 co_day_auth_aggr[akc(cs.author)]['data']]
167 161 time_pos = l.index(k)
168 162 except ValueError:
169 163 time_pos = False
170 164
171 165 if time_pos >= 0 and time_pos is not False:
172 166
173 167 datadict = \
174 168 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
175 169
176 170 datadict["commits"] += 1
177 171 datadict["added"] += len(cs.added)
178 172 datadict["changed"] += len(cs.changed)
179 173 datadict["removed"] += len(cs.removed)
180 174
181 175 else:
182 176 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
183 177
184 178 datadict = {"time": k,
185 179 "commits": 1,
186 180 "added": len(cs.added),
187 181 "changed": len(cs.changed),
188 182 "removed": len(cs.removed),
189 183 }
190 184 co_day_auth_aggr[akc(cs.author)]['data']\
191 185 .append(datadict)
192 186
193 187 else:
194 188 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
195 189 co_day_auth_aggr[akc(cs.author)] = {
196 190 "label": akc(cs.author),
197 191 "data": [{"time":k,
198 192 "commits":1,
199 193 "added":len(cs.added),
200 194 "changed":len(cs.changed),
201 195 "removed":len(cs.removed),
202 196 }],
203 197 "schema": ["commits"],
204 198 }
205 199
206 200 #gather all data by day
207 201 if k in commits_by_day_aggregate:
208 202 commits_by_day_aggregate[k] += 1
209 203 else:
210 204 commits_by_day_aggregate[k] = 1
211 205
212 206 overview_data = sorted(commits_by_day_aggregate.items(),
213 207 key=itemgetter(0))
214 208
215 209 if not co_day_auth_aggr:
216 210 co_day_auth_aggr[akc(repo.contact)] = {
217 211 "label": akc(repo.contact),
218 212 "data": [0, 1],
219 213 "schema": ["commits"],
220 214 }
221 215
222 216 stats = cur_stats if cur_stats else Statistics()
223 217 stats.commit_activity = json.dumps(co_day_auth_aggr)
224 218 stats.commit_activity_combined = json.dumps(overview_data)
225 219
226 220 log.debug('last revison %s', last_rev)
227 221 leftovers = len(repo.revisions[last_rev:])
228 222 log.debug('revisions to parse %s', leftovers)
229 223
230 224 if last_rev == 0 or leftovers < parse_limit:
231 225 log.debug('getting code trending stats')
232 226 stats.languages = json.dumps(__get_codes_stats(repo_name))
233 227
234 228 try:
235 229 stats.repository = dbrepo
236 230 stats.stat_on_revision = last_cs.revision if last_cs else 0
237 231 sa.add(stats)
238 232 sa.commit()
239 233 except:
240 234 log.error(traceback.format_exc())
241 235 sa.rollback()
242 236 lock.release()
243 237 return False
244 238
245 239 #final release
246 240 lock.release()
247 241
248 242 #execute another task if celery is enabled
249 243 if len(repo.revisions) > 1 and CELERY_ON:
250 244 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
251 245 return True
252 246 except LockHeld:
253 247 log.info('LockHeld')
254 248 return 'Task with key %s already running' % lockkey
255 249
256 250 @task(ignore_result=True)
257 251 def send_password_link(user_email):
252 from rhodecode.model.notification import EmailNotificationModel
253
258 254 log = get_logger(send_password_link)
259 255
260 256 try:
261 from rhodecode.model.notification import EmailNotificationModel
262 257 sa = get_session()
263 258 user = User.get_by_email(user_email)
264 259 if user:
265 260 log.debug('password reset user found %s' % user)
266 261 link = url('reset_password_confirmation', key=user.api_key,
267 262 qualified=True)
268 263 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
269 264 body = EmailNotificationModel().get_email_tmpl(reg_type,
270 265 **{'user':user.short_contact,
271 266 'reset_url':link})
272 267 log.debug('sending email')
273 268 run_task(send_email, user_email,
274 269 _("password reset link"), body)
275 270 log.info('send new password mail to %s', user_email)
276 271 else:
277 272 log.debug("password reset email %s not found" % user_email)
278 273 except:
279 274 log.error(traceback.format_exc())
280 275 return False
281 276
282 277 return True
283 278
284 279 @task(ignore_result=True)
285 280 def reset_user_password(user_email):
286 log = get_logger(reset_user_password)
281 from rhodecode.lib import auth
287 282
288 from rhodecode.lib import auth
283 log = get_logger(reset_user_password)
289 284
290 285 try:
291 286 try:
292 287 sa = get_session()
293 288 user = User.get_by_email(user_email)
294 289 new_passwd = auth.PasswordGenerator().gen_password(8,
295 290 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
296 291 if user:
297 292 user.password = auth.get_crypt_password(new_passwd)
298 293 user.api_key = auth.generate_api_key(user.username)
299 294 sa.add(user)
300 295 sa.commit()
301 296 log.info('change password for %s', user_email)
302 297 if new_passwd is None:
303 298 raise Exception('unable to generate new password')
304 299 except:
305 300 log.error(traceback.format_exc())
306 301 sa.rollback()
307 302
308 303 run_task(send_email, user_email,
309 304 'Your new password',
310 305 'Your new RhodeCode password:%s' % (new_passwd))
311 306 log.info('send new password mail to %s', user_email)
312 307
313 308 except:
314 309 log.error('Failed to update user password')
315 310 log.error(traceback.format_exc())
316 311
317 312 return True
318 313
319 314
320 315 @task(ignore_result=True)
321 316 def send_email(recipients, subject, body, html_body=''):
322 317 """
323 318 Sends an email with defined parameters from the .ini files.
324 319
325 320 :param recipients: list of recipients, it this is empty the defined email
326 321 address from field 'email_to' is used instead
327 322 :param subject: subject of the mail
328 323 :param body: body of the mail
329 324 :param html_body: html version of body
330 325 """
331 326 log = get_logger(send_email)
332 327 email_config = config
333 328
334 329 subject = "%s %s" % (email_config.get('email_prefix'), subject)
335 330 if not recipients:
336 331 # if recipients are not defined we send to email_config + all admins
337 332 admins = [u.email for u in User.query()
338 333 .filter(User.admin == True).all()]
339 334 recipients = [email_config.get('email_to')] + admins
340 335
341 336 mail_from = email_config.get('app_email_from', 'RhodeCode')
342 337 user = email_config.get('smtp_username')
343 338 passwd = email_config.get('smtp_password')
344 339 mail_server = email_config.get('smtp_server')
345 340 mail_port = email_config.get('smtp_port')
346 341 tls = str2bool(email_config.get('smtp_use_tls'))
347 342 ssl = str2bool(email_config.get('smtp_use_ssl'))
348 343 debug = str2bool(config.get('debug'))
349 344 smtp_auth = email_config.get('smtp_auth')
350 345
351 346 try:
352 347 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
353 348 mail_port, ssl, tls, debug=debug)
354 349 m.send(recipients, subject, body, html_body)
355 350 except:
356 351 log.error('Mail sending failed')
357 352 log.error(traceback.format_exc())
358 353 return False
359 354 return True
360 355
361 356
362 357 @task(ignore_result=True)
363 358 def create_repo_fork(form_data, cur_user):
359 """
360 Creates a fork of repository using interval VCS methods
361
362 :param form_data:
363 :param cur_user:
364 """
365 from rhodecode.model.repo import RepoModel
366
364 367 log = get_logger(create_repo_fork)
365 368
366 from rhodecode.model.repo import RepoModel
367 from vcs import get_backend
369 Session = get_session()
370 base_path = Repository.base_path()
371
372 RepoModel(Session).create(form_data, cur_user, just_db=True, fork=True)
373
374 alias = form_data['repo_type']
375 org_repo_name = form_data['org_path']
376 source_repo_path = os.path.join(base_path, org_repo_name)
377 destination_fork_path = os.path.join(base_path, form_data['repo_name_full'])
368 378
369 repo_model = RepoModel(get_session())
370 repo_model.create(form_data, cur_user, just_db=True, fork=True)
371 repo_name = form_data['repo_name']
372 repos_path = get_repos_path()
373 repo_path = os.path.join(repos_path, repo_name)
374 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
375 alias = form_data['repo_type']
376
377 log.info('creating repo fork %s as %s', repo_name, repo_path)
379 log.info('creating fork of %s as %s', source_repo_path,
380 destination_fork_path)
378 381 backend = get_backend(alias)
379 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
380
382 backend(safe_str(destination_fork_path), create=True,
383 src_url=safe_str(source_repo_path))
384 action_logger(cur_user, 'user_forked_repo:%s' % org_repo_name,
385 org_repo_name, '', Session)
386 # finally commit at latest possible stage
387 Session.commit()
381 388
382 389 def __get_codes_stats(repo_name):
383 repos_path = get_repos_path()
384 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
390 repo = Repository.get_by_repo_name(repo_name).scm_instance
391
385 392 tip = repo.get_changeset()
386 393 code_stats = {}
387 394
388 395 def aggregate(cs):
389 396 for f in cs[2]:
390 397 ext = lower(f.extension)
391 398 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
392 399 if ext in code_stats:
393 400 code_stats[ext] += 1
394 401 else:
395 402 code_stats[ext] = 1
396 403
397 404 map(aggregate, tip.walk('/'))
398 405
399 406 return code_stats or {}
@@ -1,115 +1,120 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.hooks
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Hooks runned by rhodecode
7 7
8 8 :created_on: Aug 6, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import sys
27 27
28 28 from mercurial.scmutil import revrange
29 29 from mercurial.node import nullrev
30 30
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib.utils import action_logger
33 33
34 34
35 35 def repo_size(ui, repo, hooktype=None, **kwargs):
36 """Presents size of repository after push
36 """
37 Presents size of repository after push
37 38
38 39 :param ui:
39 40 :param repo:
40 41 :param hooktype:
41 42 """
42 43
43 44 if hooktype != 'changegroup':
44 45 return False
45 46 size_hg, size_root = 0, 0
46 47 for path, dirs, files in os.walk(repo.root):
47 48 if path.find('.hg') != -1:
48 49 for f in files:
49 50 try:
50 51 size_hg += os.path.getsize(os.path.join(path, f))
51 52 except OSError:
52 53 pass
53 54 else:
54 55 for f in files:
55 56 try:
56 57 size_root += os.path.getsize(os.path.join(path, f))
57 58 except OSError:
58 59 pass
59 60
60 61 size_hg_f = h.format_byte_size(size_hg)
61 62 size_root_f = h.format_byte_size(size_root)
62 63 size_total_f = h.format_byte_size(size_root + size_hg)
63 64 sys.stdout.write('Repository size .hg:%s repo:%s total:%s\n' \
64 65 % (size_hg_f, size_root_f, size_total_f))
65 66
66 67
67 68 def log_pull_action(ui, repo, **kwargs):
68 """Logs user last pull action
69 """
70 Logs user last pull action
69 71
70 72 :param ui:
71 73 :param repo:
72 74 """
73 75
74 76 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
75 77 username = extra_params['username']
76 78 repository = extra_params['repository']
77 79 action = 'pull'
78 80
79 action_logger(username, action, repository, extra_params['ip'])
81 action_logger(username, action, repository, extra_params['ip'],
82 commit=True)
80 83
81 84 return 0
82 85
83 86
84 87 def log_push_action(ui, repo, **kwargs):
85 """Maps user last push action to new changeset id, from mercurial
88 """
89 Maps user last push action to new changeset id, from mercurial
86 90
87 91 :param ui:
88 92 :param repo:
89 93 """
90 94
91 95 extra_params = dict(repo.ui.configitems('rhodecode_extras'))
92 96 username = extra_params['username']
93 97 repository = extra_params['repository']
94 98 action = extra_params['action'] + ':%s'
95 99 node = kwargs['node']
96 100
97 101 def get_revs(repo, rev_opt):
98 102 if rev_opt:
99 103 revs = revrange(repo, rev_opt)
100 104
101 105 if len(revs) == 0:
102 106 return (nullrev, nullrev)
103 107 return (max(revs), min(revs))
104 108 else:
105 109 return (len(repo) - 1, 0)
106 110
107 111 stop, start = get_revs(repo, [node + ':'])
108 112
109 113 revs = (str(repo[r]) for r in xrange(start, stop + 1))
110 114
111 115 action = action % ','.join(revs)
112 116
113 action_logger(username, action, repository, extra_params['ip'])
117 action_logger(username, action, repository, extra_params['ip'],
118 commit=True)
114 119
115 120 return 0
@@ -1,599 +1,600 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import paste
31 31 import beaker
32 32 import tarfile
33 33 import shutil
34 34 from os.path import abspath
35 35 from os.path import dirname as dn, join as jn
36 36
37 37 from paste.script.command import Command, BadCommand
38 38
39 39 from mercurial import ui, config
40 40
41 41 from webhelpers.text import collapse, remove_formatting, strip_tags
42 42
43 43 from vcs import get_backend
44 44 from vcs.backends.base import BaseChangeset
45 45 from vcs.utils.lazy import LazyProperty
46 46 from vcs.utils.helpers import get_scm
47 47 from vcs.exceptions import VCSError
48 48
49 49 from rhodecode.lib.caching_query import FromCache
50 50
51 51 from rhodecode.model import meta
52 52 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 53 UserLog, RepoGroup, RhodeCodeSetting
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 def recursive_replace(str_, replace=' '):
59 59 """Recursive replace of given sign to just one instance
60 60
61 61 :param str_: given string
62 62 :param replace: char to find and replace multiple instances
63 63
64 64 Examples::
65 65 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
66 66 'Mighty-Mighty-Bo-sstones'
67 67 """
68 68
69 69 if str_.find(replace * 2) == -1:
70 70 return str_
71 71 else:
72 72 str_ = str_.replace(replace * 2, replace)
73 73 return recursive_replace(str_, replace)
74 74
75 75
76 76 def repo_name_slug(value):
77 77 """Return slug of name of repository
78 78 This function is called on each creation/modification
79 79 of repository to prevent bad names in repo
80 80 """
81 81
82 82 slug = remove_formatting(value)
83 83 slug = strip_tags(slug)
84 84
85 85 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
86 86 slug = slug.replace(c, '-')
87 87 slug = recursive_replace(slug, '-')
88 88 slug = collapse(slug, '-')
89 89 return slug
90 90
91 91
92 92 def get_repo_slug(request):
93 93 return request.environ['pylons.routes_dict'].get('repo_name')
94 94
95 95
96 def action_logger(user, action, repo, ipaddr='', sa=None):
96 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
97 97 """
98 98 Action logger for various actions made by users
99 99
100 100 :param user: user that made this action, can be a unique username string or
101 101 object containing user_id attribute
102 102 :param action: action to log, should be on of predefined unique actions for
103 103 easy translations
104 104 :param repo: string name of repository or object containing repo_id,
105 105 that action was made on
106 106 :param ipaddr: optional ip address from what the action was made
107 107 :param sa: optional sqlalchemy session
108 108
109 109 """
110 110
111 111 if not sa:
112 112 sa = meta.Session()
113 113
114 114 try:
115 115 if hasattr(user, 'user_id'):
116 116 user_obj = user
117 117 elif isinstance(user, basestring):
118 118 user_obj = User.get_by_username(user)
119 119 else:
120 120 raise Exception('You have to provide user object or username')
121 121
122 122 if hasattr(repo, 'repo_id'):
123 123 repo_obj = Repository.get(repo.repo_id)
124 124 repo_name = repo_obj.repo_name
125 125 elif isinstance(repo, basestring):
126 126 repo_name = repo.lstrip('/')
127 127 repo_obj = Repository.get_by_repo_name(repo_name)
128 128 else:
129 129 raise Exception('You have to provide repository to action logger')
130 130
131 131 user_log = UserLog()
132 132 user_log.user_id = user_obj.user_id
133 133 user_log.action = action
134 134
135 135 user_log.repository_id = repo_obj.repo_id
136 136 user_log.repository_name = repo_name
137 137
138 138 user_log.action_date = datetime.datetime.now()
139 139 user_log.user_ip = ipaddr
140 140 sa.add(user_log)
141 sa.commit()
142 141
143 142 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
143 if commit:
144 sa.commit()
144 145 except:
145 146 log.error(traceback.format_exc())
146 sa.rollback()
147 raise
147 148
148 149
149 150 def get_repos(path, recursive=False):
150 151 """
151 152 Scans given path for repos and return (name,(type,path)) tuple
152 153
153 154 :param path: path to scann for repositories
154 155 :param recursive: recursive search and return names with subdirs in front
155 156 """
156 157
157 158 if path.endswith(os.sep):
158 159 #remove ending slash for better results
159 160 path = path[:-1]
160 161
161 162 def _get_repos(p):
162 163 if not os.access(p, os.W_OK):
163 164 return
164 165 for dirpath in os.listdir(p):
165 166 if os.path.isfile(os.path.join(p, dirpath)):
166 167 continue
167 168 cur_path = os.path.join(p, dirpath)
168 169 try:
169 170 scm_info = get_scm(cur_path)
170 171 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
171 172 except VCSError:
172 173 if not recursive:
173 174 continue
174 175 #check if this dir containts other repos for recursive scan
175 176 rec_path = os.path.join(p, dirpath)
176 177 if os.path.isdir(rec_path):
177 178 for inner_scm in _get_repos(rec_path):
178 179 yield inner_scm
179 180
180 181 return _get_repos(path)
181 182
182 183
183 184 def is_valid_repo(repo_name, base_path):
184 185 """
185 186 Returns True if given path is a valid repository False otherwise
186 187 :param repo_name:
187 188 :param base_path:
188 189
189 190 :return True: if given path is a valid repository
190 191 """
191 192 full_path = os.path.join(base_path, repo_name)
192 193
193 194 try:
194 195 get_scm(full_path)
195 196 return True
196 197 except VCSError:
197 198 return False
198 199
199 200 def is_valid_repos_group(repos_group_name, base_path):
200 201 """
201 202 Returns True if given path is a repos group False otherwise
202 203
203 204 :param repo_name:
204 205 :param base_path:
205 206 """
206 207 full_path = os.path.join(base_path, repos_group_name)
207 208
208 209 # check if it's not a repo
209 210 if is_valid_repo(repos_group_name, base_path):
210 211 return False
211 212
212 213 # check if it's a valid path
213 214 if os.path.isdir(full_path):
214 215 return True
215 216
216 217 return False
217 218
218 219 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
219 220 while True:
220 221 ok = raw_input(prompt)
221 222 if ok in ('y', 'ye', 'yes'):
222 223 return True
223 224 if ok in ('n', 'no', 'nop', 'nope'):
224 225 return False
225 226 retries = retries - 1
226 227 if retries < 0:
227 228 raise IOError
228 229 print complaint
229 230
230 231 #propagated from mercurial documentation
231 232 ui_sections = ['alias', 'auth',
232 233 'decode/encode', 'defaults',
233 234 'diff', 'email',
234 235 'extensions', 'format',
235 236 'merge-patterns', 'merge-tools',
236 237 'hooks', 'http_proxy',
237 238 'smtp', 'patch',
238 239 'paths', 'profiling',
239 240 'server', 'trusted',
240 241 'ui', 'web', ]
241 242
242 243
243 244 def make_ui(read_from='file', path=None, checkpaths=True):
244 245 """A function that will read python rc files or database
245 246 and make an mercurial ui object from read options
246 247
247 248 :param path: path to mercurial config file
248 249 :param checkpaths: check the path
249 250 :param read_from: read from 'file' or 'db'
250 251 """
251 252
252 253 baseui = ui.ui()
253 254
254 255 #clean the baseui object
255 256 baseui._ocfg = config.config()
256 257 baseui._ucfg = config.config()
257 258 baseui._tcfg = config.config()
258 259
259 260 if read_from == 'file':
260 261 if not os.path.isfile(path):
261 262 log.warning('Unable to read config file %s' % path)
262 263 return False
263 264 log.debug('reading hgrc from %s', path)
264 265 cfg = config.config()
265 266 cfg.read(path)
266 267 for section in ui_sections:
267 268 for k, v in cfg.items(section):
268 269 log.debug('settings ui from file[%s]%s:%s', section, k, v)
269 270 baseui.setconfig(section, k, v)
270 271
271 272 elif read_from == 'db':
272 273 sa = meta.Session()
273 274 ret = sa.query(RhodeCodeUi)\
274 275 .options(FromCache("sql_cache_short",
275 276 "get_hg_ui_settings")).all()
276 277
277 278 hg_ui = ret
278 279 for ui_ in hg_ui:
279 280 if ui_.ui_active:
280 281 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
281 282 ui_.ui_key, ui_.ui_value)
282 283 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
283 284
284 285 meta.Session.remove()
285 286 return baseui
286 287
287 288
288 289 def set_rhodecode_config(config):
289 290 """
290 291 Updates pylons config with new settings from database
291 292
292 293 :param config:
293 294 """
294 295 hgsettings = RhodeCodeSetting.get_app_settings()
295 296
296 297 for k, v in hgsettings.items():
297 298 config[k] = v
298 299
299 300
300 301 def invalidate_cache(cache_key, *args):
301 302 """
302 303 Puts cache invalidation task into db for
303 304 further global cache invalidation
304 305 """
305 306
306 307 from rhodecode.model.scm import ScmModel
307 308
308 309 if cache_key.startswith('get_repo_cached_'):
309 310 name = cache_key.split('get_repo_cached_')[-1]
310 311 ScmModel().mark_for_invalidation(name)
311 312
312 313
313 314 class EmptyChangeset(BaseChangeset):
314 315 """
315 316 An dummy empty changeset. It's possible to pass hash when creating
316 317 an EmptyChangeset
317 318 """
318 319
319 320 def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None):
320 321 self._empty_cs = cs
321 322 self.revision = -1
322 323 self.message = ''
323 324 self.author = ''
324 325 self.date = ''
325 326 self.repository = repo
326 327 self.requested_revision = requested_revision
327 328 self.alias = alias
328 329
329 330 @LazyProperty
330 331 def raw_id(self):
331 332 """
332 333 Returns raw string identifying this changeset, useful for web
333 334 representation.
334 335 """
335 336
336 337 return self._empty_cs
337 338
338 339 @LazyProperty
339 340 def branch(self):
340 341 return get_backend(self.alias).DEFAULT_BRANCH_NAME
341 342
342 343 @LazyProperty
343 344 def short_id(self):
344 345 return self.raw_id[:12]
345 346
346 347 def get_file_changeset(self, path):
347 348 return self
348 349
349 350 def get_file_content(self, path):
350 351 return u''
351 352
352 353 def get_file_size(self, path):
353 354 return 0
354 355
355 356
356 357 def map_groups(groups):
357 358 """
358 359 Checks for groups existence, and creates groups structures.
359 360 It returns last group in structure
360 361
361 362 :param groups: list of groups structure
362 363 """
363 364 sa = meta.Session()
364 365
365 366 parent = None
366 367 group = None
367 368
368 369 # last element is repo in nested groups structure
369 370 groups = groups[:-1]
370 371
371 372 for lvl, group_name in enumerate(groups):
372 373 group_name = '/'.join(groups[:lvl] + [group_name])
373 374 group = sa.query(RepoGroup).filter(RepoGroup.group_name == group_name).scalar()
374 375
375 376 if group is None:
376 377 group = RepoGroup(group_name, parent)
377 378 sa.add(group)
378 379 sa.commit()
379 380 parent = group
380 381 return group
381 382
382 383
383 384 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
384 385 """
385 386 maps all repos given in initial_repo_list, non existing repositories
386 387 are created, if remove_obsolete is True it also check for db entries
387 388 that are not in initial_repo_list and removes them.
388 389
389 390 :param initial_repo_list: list of repositories found by scanning methods
390 391 :param remove_obsolete: check for obsolete entries in database
391 392 """
392 393 from rhodecode.model.repo import RepoModel
393 394 sa = meta.Session()
394 395 rm = RepoModel()
395 396 user = sa.query(User).filter(User.admin == True).first()
396 397 if user is None:
397 398 raise Exception('Missing administrative account !')
398 399 added = []
399 400
400 401 for name, repo in initial_repo_list.items():
401 402 group = map_groups(name.split(Repository.url_sep()))
402 403 if not rm.get_by_repo_name(name, cache=False):
403 404 log.info('repository %s not found creating default', name)
404 405 added.append(name)
405 406 form_data = {
406 407 'repo_name': name,
407 408 'repo_name_full': name,
408 409 'repo_type': repo.alias,
409 410 'description': repo.description \
410 411 if repo.description != 'unknown' else \
411 412 '%s repository' % name,
412 413 'private': False,
413 414 'group_id': getattr(group, 'group_id', None)
414 415 }
415 416 rm.create(form_data, user, just_db=True)
416 417
417 418 removed = []
418 419 if remove_obsolete:
419 420 #remove from database those repositories that are not in the filesystem
420 421 for repo in sa.query(Repository).all():
421 422 if repo.repo_name not in initial_repo_list.keys():
422 423 removed.append(repo.repo_name)
423 424 sa.delete(repo)
424 425 sa.commit()
425 426
426 427 return added, removed
427 428
428 429 # set cache regions for beaker so celery can utilise it
429 430 def add_cache(settings):
430 431 cache_settings = {'regions': None}
431 432 for key in settings.keys():
432 433 for prefix in ['beaker.cache.', 'cache.']:
433 434 if key.startswith(prefix):
434 435 name = key.split(prefix)[1].strip()
435 436 cache_settings[name] = settings[key].strip()
436 437 if cache_settings['regions']:
437 438 for region in cache_settings['regions'].split(','):
438 439 region = region.strip()
439 440 region_settings = {}
440 441 for key, value in cache_settings.items():
441 442 if key.startswith(region):
442 443 region_settings[key.split('.')[1]] = value
443 444 region_settings['expire'] = int(region_settings.get('expire',
444 445 60))
445 446 region_settings.setdefault('lock_dir',
446 447 cache_settings.get('lock_dir'))
447 448 region_settings.setdefault('data_dir',
448 449 cache_settings.get('data_dir'))
449 450
450 451 if 'type' not in region_settings:
451 452 region_settings['type'] = cache_settings.get('type',
452 453 'memory')
453 454 beaker.cache.cache_regions[region] = region_settings
454 455
455 456
456 457 #==============================================================================
457 458 # TEST FUNCTIONS AND CREATORS
458 459 #==============================================================================
459 460 def create_test_index(repo_location, config, full_index):
460 461 """
461 462 Makes default test index
462 463
463 464 :param config: test config
464 465 :param full_index:
465 466 """
466 467
467 468 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
468 469 from rhodecode.lib.pidlock import DaemonLock, LockHeld
469 470
470 471 repo_location = repo_location
471 472
472 473 index_location = os.path.join(config['app_conf']['index_dir'])
473 474 if not os.path.exists(index_location):
474 475 os.makedirs(index_location)
475 476
476 477 try:
477 478 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
478 479 WhooshIndexingDaemon(index_location=index_location,
479 480 repo_location=repo_location)\
480 481 .run(full_index=full_index)
481 482 l.release()
482 483 except LockHeld:
483 484 pass
484 485
485 486
486 487 def create_test_env(repos_test_path, config):
487 488 """
488 489 Makes a fresh database and
489 490 install test repository into tmp dir
490 491 """
491 492 from rhodecode.lib.db_manage import DbManage
492 493 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
493 494
494 495 # PART ONE create db
495 496 dbconf = config['sqlalchemy.db1.url']
496 497 log.debug('making test db %s', dbconf)
497 498
498 499 # create test dir if it doesn't exist
499 500 if not os.path.isdir(repos_test_path):
500 501 log.debug('Creating testdir %s' % repos_test_path)
501 502 os.makedirs(repos_test_path)
502 503
503 504 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
504 505 tests=True)
505 506 dbmanage.create_tables(override=True)
506 507 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
507 508 dbmanage.create_default_user()
508 509 dbmanage.admin_prompt()
509 510 dbmanage.create_permissions()
510 511 dbmanage.populate_default_permissions()
511 512
512 513 # PART TWO make test repo
513 514 log.debug('making test vcs repositories')
514 515
515 516 idx_path = config['app_conf']['index_dir']
516 517 data_path = config['app_conf']['cache_dir']
517 518
518 519 #clean index and data
519 520 if idx_path and os.path.exists(idx_path):
520 521 log.debug('remove %s' % idx_path)
521 522 shutil.rmtree(idx_path)
522 523
523 524 if data_path and os.path.exists(data_path):
524 525 log.debug('remove %s' % data_path)
525 526 shutil.rmtree(data_path)
526 527
527 528 #CREATE DEFAULT HG REPOSITORY
528 529 cur_dir = dn(dn(abspath(__file__)))
529 530 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
530 531 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
531 532 tar.close()
532 533
533 534
534 535 #==============================================================================
535 536 # PASTER COMMANDS
536 537 #==============================================================================
537 538 class BasePasterCommand(Command):
538 539 """
539 540 Abstract Base Class for paster commands.
540 541
541 542 The celery commands are somewhat aggressive about loading
542 543 celery.conf, and since our module sets the `CELERY_LOADER`
543 544 environment variable to our loader, we have to bootstrap a bit and
544 545 make sure we've had a chance to load the pylons config off of the
545 546 command line, otherwise everything fails.
546 547 """
547 548 min_args = 1
548 549 min_args_error = "Please provide a paster config file as an argument."
549 550 takes_config_file = 1
550 551 requires_config_file = True
551 552
552 553 def notify_msg(self, msg, log=False):
553 554 """Make a notification to user, additionally if logger is passed
554 555 it logs this action using given logger
555 556
556 557 :param msg: message that will be printed to user
557 558 :param log: logging instance, to use to additionally log this message
558 559
559 560 """
560 561 if log and isinstance(log, logging):
561 562 log(msg)
562 563
563 564 def run(self, args):
564 565 """
565 566 Overrides Command.run
566 567
567 568 Checks for a config file argument and loads it.
568 569 """
569 570 if len(args) < self.min_args:
570 571 raise BadCommand(
571 572 self.min_args_error % {'min_args': self.min_args,
572 573 'actual_args': len(args)})
573 574
574 575 # Decrement because we're going to lob off the first argument.
575 576 # @@ This is hacky
576 577 self.min_args -= 1
577 578 self.bootstrap_config(args[0])
578 579 self.update_parser()
579 580 return super(BasePasterCommand, self).run(args[1:])
580 581
581 582 def update_parser(self):
582 583 """
583 584 Abstract method. Allows for the class's parser to be updated
584 585 before the superclass's `run` method is called. Necessary to
585 586 allow options/arguments to be passed through to the underlying
586 587 celery command.
587 588 """
588 589 raise NotImplementedError("Abstract Method.")
589 590
590 591 def bootstrap_config(self, conf):
591 592 """
592 593 Loads the pylons configuration.
593 594 """
594 595 from pylons import config as pylonsconfig
595 596
596 597 path_to_ini_file = os.path.realpath(conf)
597 598 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
598 599 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
599 600
@@ -1,694 +1,681 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25 import traceback
26 26
27 27 import formencode
28 28 from formencode import All
29 29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 30 Email, Bool, StringBoolean, Set
31 31
32 32 from pylons.i18n.translation import _
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34
35 35 from rhodecode.config.routing import ADMIN_PREFIX
36 36 from rhodecode.lib.utils import repo_name_slug
37 37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 38 from rhodecode.lib.exceptions import LdapImportError
39 39 from rhodecode.model.user import UserModel
40 40 from rhodecode.model.repo import RepoModel
41 41 from rhodecode.model.db import User, UsersGroup, RepoGroup
42 42 from rhodecode import BACKENDS
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46 #this is needed to translate the messages using _() in validators
47 47 class State_obj(object):
48 48 _ = staticmethod(_)
49 49
50 50 #==============================================================================
51 51 # VALIDATORS
52 52 #==============================================================================
53 53 class ValidAuthToken(formencode.validators.FancyValidator):
54 54 messages = {'invalid_token':_('Token mismatch')}
55 55
56 56 def validate_python(self, value, state):
57 57
58 58 if value != authentication_token():
59 59 raise formencode.Invalid(self.message('invalid_token', state,
60 60 search_number=value), value, state)
61 61
62 62 def ValidUsername(edit, old_data):
63 63 class _ValidUsername(formencode.validators.FancyValidator):
64 64
65 65 def validate_python(self, value, state):
66 66 if value in ['default', 'new_user']:
67 67 raise formencode.Invalid(_('Invalid username'), value, state)
68 68 #check if user is unique
69 69 old_un = None
70 70 if edit:
71 71 old_un = UserModel().get(old_data.get('user_id')).username
72 72
73 73 if old_un != value or not edit:
74 74 if User.get_by_username(value, case_insensitive=True):
75 75 raise formencode.Invalid(_('This username already '
76 76 'exists') , value, state)
77 77
78 78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 79 raise formencode.Invalid(_('Username may only contain '
80 80 'alphanumeric characters '
81 81 'underscores, periods or dashes '
82 82 'and must begin with alphanumeric '
83 83 'character'), value, state)
84 84
85 85 return _ValidUsername
86 86
87 87
88 88 def ValidUsersGroup(edit, old_data):
89 89
90 90 class _ValidUsersGroup(formencode.validators.FancyValidator):
91 91
92 92 def validate_python(self, value, state):
93 93 if value in ['default']:
94 94 raise formencode.Invalid(_('Invalid group name'), value, state)
95 95 #check if group is unique
96 96 old_ugname = None
97 97 if edit:
98 98 old_ugname = UsersGroup.get(
99 99 old_data.get('users_group_id')).users_group_name
100 100
101 101 if old_ugname != value or not edit:
102 102 if UsersGroup.get_by_group_name(value, cache=False,
103 103 case_insensitive=True):
104 104 raise formencode.Invalid(_('This users group '
105 105 'already exists') , value,
106 106 state)
107 107
108 108
109 109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 110 raise formencode.Invalid(_('RepoGroup name may only contain '
111 111 'alphanumeric characters '
112 112 'underscores, periods or dashes '
113 113 'and must begin with alphanumeric '
114 114 'character'), value, state)
115 115
116 116 return _ValidUsersGroup
117 117
118 118
119 119 def ValidReposGroup(edit, old_data):
120 120 class _ValidReposGroup(formencode.validators.FancyValidator):
121 121
122 122 def validate_python(self, value, state):
123 123 #TODO WRITE VALIDATIONS
124 124 group_name = value.get('group_name')
125 125 group_parent_id = int(value.get('group_parent_id') or -1)
126 126
127 127 # slugify repo group just in case :)
128 128 slug = repo_name_slug(group_name)
129 129
130 130 # check for parent of self
131 131 if edit and old_data['group_id'] == group_parent_id:
132 132 e_dict = {'group_parent_id':_('Cannot assign this group '
133 133 'as parent')}
134 134 raise formencode.Invalid('', value, state,
135 135 error_dict=e_dict)
136 136
137 137 old_gname = None
138 138 if edit:
139 139 old_gname = RepoGroup.get(
140 140 old_data.get('group_id')).group_name
141 141
142 142 if old_gname != group_name or not edit:
143 143 # check filesystem
144 144 gr = RepoGroup.query().filter(RepoGroup.group_name == slug)\
145 145 .filter(RepoGroup.group_parent_id == group_parent_id).scalar()
146 146
147 147 if gr:
148 148 e_dict = {'group_name':_('This group already exists')}
149 149 raise formencode.Invalid('', value, state,
150 150 error_dict=e_dict)
151 151
152 152 return _ValidReposGroup
153 153
154 154 class ValidPassword(formencode.validators.FancyValidator):
155 155
156 156 def to_python(self, value, state):
157 157
158 158 if value:
159 159
160 160 if value.get('password'):
161 161 try:
162 162 value['password'] = get_crypt_password(value['password'])
163 163 except UnicodeEncodeError:
164 164 e_dict = {'password':_('Invalid characters in password')}
165 165 raise formencode.Invalid('', value, state, error_dict=e_dict)
166 166
167 167 if value.get('password_confirmation'):
168 168 try:
169 169 value['password_confirmation'] = \
170 170 get_crypt_password(value['password_confirmation'])
171 171 except UnicodeEncodeError:
172 172 e_dict = {'password_confirmation':_('Invalid characters in password')}
173 173 raise formencode.Invalid('', value, state, error_dict=e_dict)
174 174
175 175 if value.get('new_password'):
176 176 try:
177 177 value['new_password'] = \
178 178 get_crypt_password(value['new_password'])
179 179 except UnicodeEncodeError:
180 180 e_dict = {'new_password':_('Invalid characters in password')}
181 181 raise formencode.Invalid('', value, state, error_dict=e_dict)
182 182
183 183 return value
184 184
185 185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
186 186
187 187 def validate_python(self, value, state):
188
188
189 189 pass_val = value.get('password') or value.get('new_password')
190 190 if pass_val != value['password_confirmation']:
191 191 e_dict = {'password_confirmation':
192 192 _('Passwords do not match')}
193 193 raise formencode.Invalid('', value, state, error_dict=e_dict)
194 194
195 195 class ValidAuth(formencode.validators.FancyValidator):
196 196 messages = {
197 197 'invalid_password':_('invalid password'),
198 198 'invalid_login':_('invalid user name'),
199 199 'disabled_account':_('Your account is disabled')
200 200 }
201
201
202 202 # error mapping
203 203 e_dict = {'username':messages['invalid_login'],
204 204 'password':messages['invalid_password']}
205 205 e_dict_disable = {'username':messages['disabled_account']}
206 206
207 207 def validate_python(self, value, state):
208 208 password = value['password']
209 209 username = value['username']
210 210 user = User.get_by_username(username)
211
211
212 212 if authenticate(username, password):
213 213 return value
214 214 else:
215 215 if user and user.active is False:
216 216 log.warning('user %s is disabled', username)
217 217 raise formencode.Invalid(self.message('disabled_account',
218 218 state=State_obj),
219 219 value, state,
220 220 error_dict=self.e_dict_disable)
221 221 else:
222 222 log.warning('user %s not authenticated', username)
223 223 raise formencode.Invalid(self.message('invalid_password',
224 224 state=State_obj), value, state,
225 225 error_dict=self.e_dict)
226 226
227 227 class ValidRepoUser(formencode.validators.FancyValidator):
228 228
229 229 def to_python(self, value, state):
230 230 try:
231 231 User.query().filter(User.active == True)\
232 232 .filter(User.username == value).one()
233 233 except Exception:
234 234 raise formencode.Invalid(_('This username is not valid'),
235 235 value, state)
236 236 return value
237 237
238 238 def ValidRepoName(edit, old_data):
239 239 class _ValidRepoName(formencode.validators.FancyValidator):
240 240 def to_python(self, value, state):
241 241
242 242 repo_name = value.get('repo_name')
243 243
244 244 slug = repo_name_slug(repo_name)
245 245 if slug in [ADMIN_PREFIX, '']:
246 246 e_dict = {'repo_name': _('This repository name is disallowed')}
247 247 raise formencode.Invalid('', value, state, error_dict=e_dict)
248 248
249 249
250 250 if value.get('repo_group'):
251 251 gr = RepoGroup.get(value.get('repo_group'))
252 252 group_path = gr.full_path
253 253 # value needs to be aware of group name in order to check
254 254 # db key This is an actual just the name to store in the
255 255 # database
256 256 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
257
257
258 258 else:
259 259 group_path = ''
260 260 repo_name_full = repo_name
261 261
262 262
263 263 value['repo_name_full'] = repo_name_full
264 264 rename = old_data.get('repo_name') != repo_name_full
265 265 create = not edit
266 266 if rename or create:
267 267
268 268 if group_path != '':
269 269 if RepoModel().get_by_repo_name(repo_name_full,):
270 270 e_dict = {'repo_name':_('This repository already '
271 271 'exists in a group "%s"') %
272 272 gr.group_name}
273 273 raise formencode.Invalid('', value, state,
274 274 error_dict=e_dict)
275 275 elif RepoGroup.get_by_group_name(repo_name_full):
276 276 e_dict = {'repo_name':_('There is a group with this'
277 277 ' name already "%s"') %
278 278 repo_name_full}
279 279 raise formencode.Invalid('', value, state,
280 280 error_dict=e_dict)
281 281
282 282 elif RepoModel().get_by_repo_name(repo_name_full):
283 283 e_dict = {'repo_name':_('This repository '
284 284 'already exists')}
285 285 raise formencode.Invalid('', value, state,
286 286 error_dict=e_dict)
287 287
288 288 return value
289 289
290 290 return _ValidRepoName
291 291
292 def ValidForkName():
293 class _ValidForkName(formencode.validators.FancyValidator):
294 def to_python(self, value, state):
295
296 repo_name = value.get('fork_name')
297
298 slug = repo_name_slug(repo_name)
299 if slug in [ADMIN_PREFIX, '']:
300 e_dict = {'repo_name': _('This repository name is disallowed')}
301 raise formencode.Invalid('', value, state, error_dict=e_dict)
302
303 if RepoModel().get_by_repo_name(repo_name):
304 e_dict = {'fork_name':_('This repository '
305 'already exists')}
306 raise formencode.Invalid('', value, state,
307 error_dict=e_dict)
308 return value
309 return _ValidForkName
292 def ValidForkName(*args, **kwargs):
293 return ValidRepoName(*args, **kwargs)
310 294
311 295
312 296 def SlugifyName():
313 297 class _SlugifyName(formencode.validators.FancyValidator):
314 298
315 299 def to_python(self, value, state):
316 300 return repo_name_slug(value)
317 301
318 302 return _SlugifyName
319 303
320 304 def ValidCloneUri():
321 305 from mercurial.httprepo import httprepository, httpsrepository
322 306 from rhodecode.lib.utils import make_ui
323 307
324 308 class _ValidCloneUri(formencode.validators.FancyValidator):
325 309
326 310 def to_python(self, value, state):
327 311 if not value:
328 312 pass
329 313 elif value.startswith('https'):
330 314 try:
331 315 httpsrepository(make_ui('db'), value).capabilities
332 316 except Exception, e:
333 317 log.error(traceback.format_exc())
334 318 raise formencode.Invalid(_('invalid clone url'), value,
335 319 state)
336 320 elif value.startswith('http'):
337 321 try:
338 322 httprepository(make_ui('db'), value).capabilities
339 323 except Exception, e:
340 324 log.error(traceback.format_exc())
341 325 raise formencode.Invalid(_('invalid clone url'), value,
342 326 state)
343 327 else:
344 328 raise formencode.Invalid(_('Invalid clone url, provide a '
345 329 'valid clone http\s url'), value,
346 330 state)
347 331 return value
348 332
349 333 return _ValidCloneUri
350 334
351 335 def ValidForkType(old_data):
352 336 class _ValidForkType(formencode.validators.FancyValidator):
353 337
354 338 def to_python(self, value, state):
355 339 if old_data['repo_type'] != value:
356 340 raise formencode.Invalid(_('Fork have to be the same '
357 341 'type as original'), value, state)
358 342
359 343 return value
360 344 return _ValidForkType
361 345
362 346 class ValidPerms(formencode.validators.FancyValidator):
363 347 messages = {'perm_new_member_name':_('This username or users group name'
364 348 ' is not valid')}
365 349
366 350 def to_python(self, value, state):
367 351 perms_update = []
368 352 perms_new = []
369 353 #build a list of permission to update and new permission to create
370 354 for k, v in value.items():
371 355 #means new added member to permissions
372 356 if k.startswith('perm_new_member'):
373 357 new_perm = value.get('perm_new_member', False)
374 358 new_member = value.get('perm_new_member_name', False)
375 359 new_type = value.get('perm_new_member_type')
376 360
377 361 if new_member and new_perm:
378 362 if (new_member, new_perm, new_type) not in perms_new:
379 363 perms_new.append((new_member, new_perm, new_type))
380 364 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
381 365 member = k[7:]
382 366 t = {'u':'user',
383 367 'g':'users_group'}[k[0]]
384 368 if member == 'default':
385 369 if value['private']:
386 370 #set none for default when updating to private repo
387 371 v = 'repository.none'
388 372 perms_update.append((member, v, t))
389 373
390 374 value['perms_updates'] = perms_update
391 375 value['perms_new'] = perms_new
392 376
393 377 #update permissions
394 378 for k, v, t in perms_new:
395 379 try:
396 380 if t is 'user':
397 381 self.user_db = User.query()\
398 382 .filter(User.active == True)\
399 383 .filter(User.username == k).one()
400 384 if t is 'users_group':
401 385 self.user_db = UsersGroup.query()\
402 386 .filter(UsersGroup.users_group_active == True)\
403 387 .filter(UsersGroup.users_group_name == k).one()
404 388
405 389 except Exception:
406 390 msg = self.message('perm_new_member_name',
407 391 state=State_obj)
408 392 raise formencode.Invalid(msg, value, state,
409 393 error_dict={'perm_new_member_name':msg})
410 394 return value
411 395
412 396 class ValidSettings(formencode.validators.FancyValidator):
413 397
414 398 def to_python(self, value, state):
415 399 #settings form can't edit user
416 400 if value.has_key('user'):
417 401 del['value']['user']
418 402
419 403 return value
420 404
421 405 class ValidPath(formencode.validators.FancyValidator):
422 406 def to_python(self, value, state):
423 407
424 408 if not os.path.isdir(value):
425 409 msg = _('This is not a valid path')
426 410 raise formencode.Invalid(msg, value, state,
427 411 error_dict={'paths_root_path':msg})
428 412 return value
429 413
430 414 def UniqSystemEmail(old_data):
431 415 class _UniqSystemEmail(formencode.validators.FancyValidator):
432 416 def to_python(self, value, state):
433 417 value = value.lower()
434 418 if old_data.get('email') != value:
435 419 user = User.query().filter(User.email == value).scalar()
436 420 if user:
437 421 raise formencode.Invalid(
438 422 _("This e-mail address is already taken"),
439 423 value, state)
440 424 return value
441 425
442 426 return _UniqSystemEmail
443 427
444 428 class ValidSystemEmail(formencode.validators.FancyValidator):
445 429 def to_python(self, value, state):
446 430 value = value.lower()
447 431 user = User.query().filter(User.email == value).scalar()
448 432 if user is None:
449 433 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
450 434 value, state)
451 435
452 436 return value
453 437
454 438 class LdapLibValidator(formencode.validators.FancyValidator):
455 439
456 440 def to_python(self, value, state):
457 441
458 442 try:
459 443 import ldap
460 444 except ImportError:
461 445 raise LdapImportError
462 446 return value
463 447
464 448 class AttrLoginValidator(formencode.validators.FancyValidator):
465 449
466 450 def to_python(self, value, state):
467 451
468 452 if not value or not isinstance(value, (str, unicode)):
469 453 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
470 454 "must be specified - this is the name "
471 455 "of the attribute that is equivalent "
472 456 "to 'username'"),
473 457 value, state)
474 458
475 459 return value
476 460
477 461 #===============================================================================
478 462 # FORMS
479 463 #===============================================================================
480 464 class LoginForm(formencode.Schema):
481 465 allow_extra_fields = True
482 466 filter_extra_fields = True
483 467 username = UnicodeString(
484 468 strip=True,
485 469 min=1,
486 470 not_empty=True,
487 471 messages={
488 472 'empty':_('Please enter a login'),
489 473 'tooShort':_('Enter a value %(min)i characters long or more')}
490 474 )
491 475
492 476 password = UnicodeString(
493 477 strip=True,
494 478 min=3,
495 479 not_empty=True,
496 480 messages={
497 481 'empty':_('Please enter a password'),
498 482 'tooShort':_('Enter %(min)i characters or more')}
499 483 )
500 484
501 485 chained_validators = [ValidAuth]
502 486
503 487 def UserForm(edit=False, old_data={}):
504 488 class _UserForm(formencode.Schema):
505 489 allow_extra_fields = True
506 490 filter_extra_fields = True
507 491 username = All(UnicodeString(strip=True, min=1, not_empty=True),
508 492 ValidUsername(edit, old_data))
509 493 if edit:
510 494 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
511 495 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
512 496 admin = StringBoolean(if_missing=False)
513 497 else:
514 498 password = All(UnicodeString(strip=True, min=6, not_empty=True))
515 499 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
516
500
517 501 active = StringBoolean(if_missing=False)
518 502 name = UnicodeString(strip=True, min=1, not_empty=True)
519 503 lastname = UnicodeString(strip=True, min=1, not_empty=True)
520 504 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
521 505
522 506 chained_validators = [ValidPasswordsMatch, ValidPassword]
523 507
524 508 return _UserForm
525 509
526 510
527 511 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
528 512 class _UsersGroupForm(formencode.Schema):
529 513 allow_extra_fields = True
530 514 filter_extra_fields = True
531 515
532 516 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
533 517 ValidUsersGroup(edit, old_data))
534 518
535 519 users_group_active = StringBoolean(if_missing=False)
536 520
537 521 if edit:
538 522 users_group_members = OneOf(available_members, hideList=False,
539 523 testValueList=True,
540 524 if_missing=None, not_empty=False)
541 525
542 526 return _UsersGroupForm
543 527
544 528 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
545 529 class _ReposGroupForm(formencode.Schema):
546 530 allow_extra_fields = True
547 531 filter_extra_fields = True
548 532
549 533 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
550 534 SlugifyName())
551 535 group_description = UnicodeString(strip=True, min=1,
552 536 not_empty=True)
553 537 group_parent_id = OneOf(available_groups, hideList=False,
554 538 testValueList=True,
555 539 if_missing=None, not_empty=False)
556 540
557 541 chained_validators = [ValidReposGroup(edit, old_data)]
558 542
559 543 return _ReposGroupForm
560 544
561 545 def RegisterForm(edit=False, old_data={}):
562 546 class _RegisterForm(formencode.Schema):
563 547 allow_extra_fields = True
564 548 filter_extra_fields = True
565 549 username = All(ValidUsername(edit, old_data),
566 550 UnicodeString(strip=True, min=1, not_empty=True))
567 551 password = All(UnicodeString(strip=True, min=6, not_empty=True))
568 552 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
569 553 active = StringBoolean(if_missing=False)
570 554 name = UnicodeString(strip=True, min=1, not_empty=True)
571 555 lastname = UnicodeString(strip=True, min=1, not_empty=True)
572 556 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
573 557
574 558 chained_validators = [ValidPasswordsMatch, ValidPassword]
575 559
576 560 return _RegisterForm
577 561
578 562 def PasswordResetForm():
579 563 class _PasswordResetForm(formencode.Schema):
580 564 allow_extra_fields = True
581 565 filter_extra_fields = True
582 566 email = All(ValidSystemEmail(), Email(not_empty=True))
583 567 return _PasswordResetForm
584 568
585 569 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
586 570 repo_groups=[]):
587 571 class _RepoForm(formencode.Schema):
588 572 allow_extra_fields = True
589 573 filter_extra_fields = False
590 574 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
591 575 SlugifyName())
592 576 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
593 577 ValidCloneUri()())
594 578 repo_group = OneOf(repo_groups, hideList=True)
595 579 repo_type = OneOf(supported_backends)
596 580 description = UnicodeString(strip=True, min=1, not_empty=True)
597 581 private = StringBoolean(if_missing=False)
598 582 enable_statistics = StringBoolean(if_missing=False)
599 583 enable_downloads = StringBoolean(if_missing=False)
600 584
601 585 if edit:
602 586 #this is repo owner
603 587 user = All(UnicodeString(not_empty=True), ValidRepoUser)
604 588
605 589 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
606 590 return _RepoForm
607 591
608 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
592 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
593 repo_groups=[]):
609 594 class _RepoForkForm(formencode.Schema):
610 595 allow_extra_fields = True
611 596 filter_extra_fields = False
612 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
597 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
613 598 SlugifyName())
599 repo_group = OneOf(repo_groups, hideList=True)
600 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
614 601 description = UnicodeString(strip=True, min=1, not_empty=True)
615 602 private = StringBoolean(if_missing=False)
616 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
617
618 chained_validators = [ValidForkName()]
603 copy_permissions = StringBoolean(if_missing=False)
604 fork_parent_id = UnicodeString()
605 chained_validators = [ValidForkName(edit, old_data)]
619 606
620 607 return _RepoForkForm
621 608
622 609 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
623 610 repo_groups=[]):
624 611 class _RepoForm(formencode.Schema):
625 612 allow_extra_fields = True
626 613 filter_extra_fields = False
627 614 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
628 615 SlugifyName())
629 616 description = UnicodeString(strip=True, min=1, not_empty=True)
630 617 repo_group = OneOf(repo_groups, hideList=True)
631 618 private = StringBoolean(if_missing=False)
632 619
633 chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
620 chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
634 621 ValidSettings]
635 622 return _RepoForm
636 623
637 624
638 625 def ApplicationSettingsForm():
639 626 class _ApplicationSettingsForm(formencode.Schema):
640 627 allow_extra_fields = True
641 628 filter_extra_fields = False
642 629 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
643 630 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
644 631 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
645 632
646 633 return _ApplicationSettingsForm
647 634
648 635 def ApplicationUiSettingsForm():
649 636 class _ApplicationUiSettingsForm(formencode.Schema):
650 637 allow_extra_fields = True
651 638 filter_extra_fields = False
652 639 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
653 640 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
654 641 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
655 642 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
656 643 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
657 644 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
658 645
659 646 return _ApplicationUiSettingsForm
660 647
661 648 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
662 649 class _DefaultPermissionsForm(formencode.Schema):
663 650 allow_extra_fields = True
664 651 filter_extra_fields = True
665 652 overwrite_default = StringBoolean(if_missing=False)
666 653 anonymous = OneOf(['True', 'False'], if_missing=False)
667 654 default_perm = OneOf(perms_choices)
668 655 default_register = OneOf(register_choices)
669 656 default_create = OneOf(create_choices)
670 657
671 658 return _DefaultPermissionsForm
672 659
673 660
674 661 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
675 662 class _LdapSettingsForm(formencode.Schema):
676 663 allow_extra_fields = True
677 664 filter_extra_fields = True
678 665 pre_validators = [LdapLibValidator]
679 666 ldap_active = StringBoolean(if_missing=False)
680 667 ldap_host = UnicodeString(strip=True,)
681 668 ldap_port = Number(strip=True,)
682 669 ldap_tls_kind = OneOf(tls_kind_choices)
683 670 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
684 671 ldap_dn_user = UnicodeString(strip=True,)
685 672 ldap_dn_pass = UnicodeString(strip=True,)
686 673 ldap_base_dn = UnicodeString(strip=True,)
687 674 ldap_filter = UnicodeString(strip=True,)
688 675 ldap_search_scope = OneOf(search_scope_choices)
689 676 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
690 677 ldap_attr_firstname = UnicodeString(strip=True,)
691 678 ldap_attr_lastname = UnicodeString(strip=True,)
692 679 ldap_attr_email = UnicodeString(strip=True,)
693 680
694 681 return _LdapSettingsForm
@@ -1,205 +1,204 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.notification
4 4 ~~~~~~~~~~~~~~
5 5
6 6 Model for notifications
7 7
8 8
9 9 :created_on: Nov 20, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30 import datetime
31 31
32 32 from pylons import config
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.model import BaseModel
37 37 from rhodecode.model.db import Notification, User, UserNotification
38 from rhodecode.lib.celerylib import run_task
39 from rhodecode.lib.celerylib.tasks import send_email
40 38
41 39 log = logging.getLogger(__name__)
42 40
43 41
44 42 class NotificationModel(BaseModel):
45 43
46 44
47 45 def __get_user(self, user):
48 46 if isinstance(user, basestring):
49 47 return User.get_by_username(username=user)
50 48 else:
51 49 return self._get_instance(User, user)
52 50
53 51 def __get_notification(self, notification):
54 52 if isinstance(notification, Notification):
55 53 return notification
56 54 elif isinstance(notification, int):
57 55 return Notification.get(notification)
58 56 else:
59 57 if notification:
60 58 raise Exception('notification must be int or Instance'
61 59 ' of Notification got %s' % type(notification))
62 60
63 61
64 62 def create(self, created_by, subject, body, recipients,
65 63 type_=Notification.TYPE_MESSAGE):
66 64 """
67 65
68 66 Creates notification of given type
69 67
70 68 :param created_by: int, str or User instance. User who created this
71 69 notification
72 70 :param subject:
73 71 :param body:
74 72 :param recipients: list of int, str or User objects
75 73 :param type_: type of notification
76 74 """
75 from rhodecode.lib.celerylib import tasks, run_task
77 76
78 77 if not getattr(recipients, '__iter__', False):
79 78 raise Exception('recipients must be a list of iterable')
80 79
81 80 created_by_obj = self.__get_user(created_by)
82 81
83 82 recipients_objs = []
84 83 for u in recipients:
85 84 obj = self.__get_user(u)
86 85 if obj:
87 86 recipients_objs.append(obj)
88 87 recipients_objs = set(recipients_objs)
89 88
90 89 notif = Notification.create(created_by=created_by_obj, subject=subject,
91 90 body=body, recipients=recipients_objs,
92 91 type_=type_)
93 92
94 93
95 94 # send email with notification
96 95 for rec in recipients_objs:
97 96 email_subject = NotificationModel().make_description(notif, False)
98 97 type_ = EmailNotificationModel.TYPE_CHANGESET_COMMENT
99 98 email_body = body
100 99 email_body_html = EmailNotificationModel()\
101 100 .get_email_tmpl(type_, **{'subject':subject,
102 101 'body':h.rst(body)})
103 run_task(send_email, rec.email, email_subject, email_body,
102 run_task(tasks.send_email, rec.email, email_subject, email_body,
104 103 email_body_html)
105 104
106 105 return notif
107 106
108 107 def delete(self, user, notification):
109 108 # we don't want to remove actual notification just the assignment
110 109 try:
111 110 notification = self.__get_notification(notification)
112 111 user = self.__get_user(user)
113 112 if notification and user:
114 113 obj = UserNotification.query()\
115 114 .filter(UserNotification.user == user)\
116 115 .filter(UserNotification.notification
117 116 == notification)\
118 117 .one()
119 118 self.sa.delete(obj)
120 119 return True
121 120 except Exception:
122 121 log.error(traceback.format_exc())
123 122 raise
124 123
125 124 def get_for_user(self, user):
126 125 user = self.__get_user(user)
127 126 return user.notifications
128 127
129 128 def get_unread_cnt_for_user(self, user):
130 129 user = self.__get_user(user)
131 130 return UserNotification.query()\
132 131 .filter(UserNotification.read == False)\
133 132 .filter(UserNotification.user == user).count()
134 133
135 134 def get_unread_for_user(self, user):
136 135 user = self.__get_user(user)
137 136 return [x.notification for x in UserNotification.query()\
138 137 .filter(UserNotification.read == False)\
139 138 .filter(UserNotification.user == user).all()]
140 139
141 140 def get_user_notification(self, user, notification):
142 141 user = self.__get_user(user)
143 142 notification = self.__get_notification(notification)
144 143
145 144 return UserNotification.query()\
146 145 .filter(UserNotification.notification == notification)\
147 146 .filter(UserNotification.user == user).scalar()
148 147
149 148 def make_description(self, notification, show_age=True):
150 149 """
151 150 Creates a human readable description based on properties
152 151 of notification object
153 152 """
154 153
155 154 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
156 155 notification.TYPE_MESSAGE:_('sent message'),
157 156 notification.TYPE_MENTION:_('mentioned you')}
158 157 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
159 158
160 159 tmpl = "%(user)s %(action)s %(when)s"
161 160 if show_age:
162 161 when = h.age(notification.created_on)
163 162 else:
164 163 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
165 164 when = DTF(notification.created_on)
166 165 data = dict(user=notification.created_by_user.username,
167 166 action=_map[notification.type_],
168 167 when=when)
169 168 return tmpl % data
170 169
171 170
172 171 class EmailNotificationModel(BaseModel):
173 172
174 173 TYPE_CHANGESET_COMMENT = 'changeset_comment'
175 174 TYPE_PASSWORD_RESET = 'passoword_link'
176 175 TYPE_REGISTRATION = 'registration'
177 176 TYPE_DEFAULT = 'default'
178 177
179 178 def __init__(self):
180 179 self._template_root = config['pylons.paths']['templates'][0]
181 180
182 181 self.email_types = {
183 182 self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
184 183 self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
185 184 self.TYPE_REGISTRATION:'email_templates/registration.html',
186 185 self.TYPE_DEFAULT:'email_templates/default.html'
187 186 }
188 187
189 188 def get_email_tmpl(self, type_, **kwargs):
190 189 """
191 190 return generated template for email based on given type
192 191
193 192 :param type_:
194 193 """
195 194 base = self.email_types.get(type_, self.TYPE_DEFAULT)
196 195
197 196 lookup = config['pylons.app_globals'].mako_lookup
198 197 email_template = lookup.get_template(base)
199 198 # translator inject
200 199 _kwargs = {'_':_}
201 200 _kwargs.update(kwargs)
202 201 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
203 202 return email_template.render(**_kwargs)
204 203
205 204
@@ -1,414 +1,418 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.repo
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository model for rhodecode
7 7
8 8 :created_on: Jun 5, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import shutil
27 27 import logging
28 28 import traceback
29 29 from datetime import datetime
30 30
31 31 from vcs.utils.lazy import LazyProperty
32 32 from vcs.backends import get_backend
33 33
34 34 from rhodecode.lib import safe_str
35 35 from rhodecode.lib.caching_query import FromCache
36 36
37 37 from rhodecode.model import BaseModel
38 38 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
39 39 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43
44 44 class RepoModel(BaseModel):
45 45
46 46 @LazyProperty
47 47 def repos_path(self):
48 48 """
49 49 Get's the repositories root path from database
50 50 """
51 51
52 52 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
53 53 return q.ui_value
54 54
55 55 def get(self, repo_id, cache=False):
56 56 repo = self.sa.query(Repository)\
57 57 .filter(Repository.repo_id == repo_id)
58 58
59 59 if cache:
60 60 repo = repo.options(FromCache("sql_cache_short",
61 61 "get_repo_%s" % repo_id))
62 62 return repo.scalar()
63 63
64 64 def get_by_repo_name(self, repo_name, cache=False):
65 65 repo = self.sa.query(Repository)\
66 66 .filter(Repository.repo_name == repo_name)
67 67
68 68 if cache:
69 69 repo = repo.options(FromCache("sql_cache_short",
70 70 "get_repo_%s" % repo_name))
71 71 return repo.scalar()
72 72
73 73
74 74 def get_users_js(self):
75 75
76 76 users = self.sa.query(User).filter(User.active == True).all()
77 77 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
78 78 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
79 79 u.lastname, u.username)
80 80 for u in users])
81 81 return users_array
82 82
83 83 def get_users_groups_js(self):
84 84 users_groups = self.sa.query(UsersGroup)\
85 85 .filter(UsersGroup.users_group_active == True).all()
86 86
87 87 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
88 88
89 89 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
90 90 (gr.users_group_id, gr.users_group_name,
91 91 len(gr.members))
92 92 for gr in users_groups])
93 93 return users_groups_array
94 94
95 95 def _get_defaults(self, repo_name):
96 96 """
97 97 Get's information about repository, and returns a dict for
98 98 usage in forms
99 99
100 100 :param repo_name:
101 101 """
102 102
103 103 repo_info = Repository.get_by_repo_name(repo_name)
104 104
105 105 if repo_info is None:
106 106 return None
107 107
108 108 defaults = repo_info.get_dict()
109 109 group, repo_name = repo_info.groups_and_repo
110 110 defaults['repo_name'] = repo_name
111 111 defaults['repo_group'] = getattr(group[-1] if group else None,
112 112 'group_id', None)
113 113
114 114 # fill owner
115 115 if repo_info.user:
116 116 defaults.update({'user': repo_info.user.username})
117 117 else:
118 118 replacement_user = User.query().filter(User.admin ==
119 119 True).first().username
120 120 defaults.update({'user': replacement_user})
121 121
122 122 # fill repository users
123 123 for p in repo_info.repo_to_perm:
124 124 defaults.update({'u_perm_%s' % p.user.username:
125 125 p.permission.permission_name})
126 126
127 127 # fill repository groups
128 128 for p in repo_info.users_group_to_perm:
129 129 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
130 130 p.permission.permission_name})
131 131
132 132 return defaults
133 133
134 134
135 135 def update(self, repo_name, form_data):
136 136 try:
137 137 cur_repo = self.get_by_repo_name(repo_name, cache=False)
138 138
139 139 # update permissions
140 140 for member, perm, member_type in form_data['perms_updates']:
141 141 if member_type == 'user':
142 142 _member = User.get_by_username(member)
143 143 r2p = self.sa.query(UserRepoToPerm)\
144 144 .filter(UserRepoToPerm.user == _member)\
145 145 .filter(UserRepoToPerm.repository == cur_repo)\
146 146 .one()
147 147
148 148 r2p.permission = self.sa.query(Permission)\
149 149 .filter(Permission.permission_name ==
150 150 perm).scalar()
151 151 self.sa.add(r2p)
152 152 else:
153 153 g2p = self.sa.query(UsersGroupRepoToPerm)\
154 154 .filter(UsersGroupRepoToPerm.users_group ==
155 155 UsersGroup.get_by_group_name(member))\
156 156 .filter(UsersGroupRepoToPerm.repository ==
157 157 cur_repo).one()
158 158
159 159 g2p.permission = self.sa.query(Permission)\
160 160 .filter(Permission.permission_name ==
161 161 perm).scalar()
162 162 self.sa.add(g2p)
163 163
164 164 # set new permissions
165 165 for member, perm, member_type in form_data['perms_new']:
166 166 if member_type == 'user':
167 167 r2p = UserRepoToPerm()
168 168 r2p.repository = cur_repo
169 169 r2p.user = User.get_by_username(member)
170 170
171 171 r2p.permission = self.sa.query(Permission)\
172 172 .filter(Permission.
173 173 permission_name == perm)\
174 174 .scalar()
175 175 self.sa.add(r2p)
176 176 else:
177 177 g2p = UsersGroupRepoToPerm()
178 178 g2p.repository = cur_repo
179 179 g2p.users_group = UsersGroup.get_by_group_name(member)
180 180 g2p.permission = self.sa.query(Permission)\
181 181 .filter(Permission.
182 182 permission_name == perm)\
183 183 .scalar()
184 184 self.sa.add(g2p)
185 185
186 186 # update current repo
187 187 for k, v in form_data.items():
188 188 if k == 'user':
189 189 cur_repo.user = User.get_by_username(v)
190 190 elif k == 'repo_name':
191 191 pass
192 192 elif k == 'repo_group':
193 193 cur_repo.group = RepoGroup.get(v)
194 194
195 195 else:
196 196 setattr(cur_repo, k, v)
197 197
198 198 new_name = cur_repo.get_new_name(form_data['repo_name'])
199 199 cur_repo.repo_name = new_name
200 200
201 201 self.sa.add(cur_repo)
202 202
203 203 if repo_name != new_name:
204 204 # rename repository
205 205 self.__rename_repo(old=repo_name, new=new_name)
206 206
207 207 self.sa.commit()
208 208 return cur_repo
209 209 except:
210 210 log.error(traceback.format_exc())
211 211 self.sa.rollback()
212 212 raise
213 213
214 214 def create(self, form_data, cur_user, just_db=False, fork=False):
215 from rhodecode.model.scm import ScmModel
215 216
216 217 try:
217 218 if fork:
218 repo_name = form_data['fork_name']
219 org_name = form_data['repo_name']
220 org_full_name = org_name
219 fork_parent_id = form_data['fork_parent_id']
221 220
222 else:
223 org_name = repo_name = form_data['repo_name']
224 repo_name_full = form_data['repo_name_full']
221 # repo name is just a name of repository
222 # while repo_name_full is a full qualified name that is combined
223 # with name and path of group
224 repo_name = form_data['repo_name']
225 repo_name_full = form_data['repo_name_full']
225 226
226 227 new_repo = Repository()
227 228 new_repo.enable_statistics = False
229
228 230 for k, v in form_data.items():
229 231 if k == 'repo_name':
230 if fork:
231 v = repo_name
232 else:
233 v = repo_name_full
232 v = repo_name_full
234 233 if k == 'repo_group':
235 234 k = 'group_id'
236
237 235 if k == 'description':
238 236 v = v or repo_name
239 237
240 238 setattr(new_repo, k, v)
241 239
242 240 if fork:
243 parent_repo = self.sa.query(Repository)\
244 .filter(Repository.repo_name == org_full_name).one()
241 parent_repo = Repository.get(fork_parent_id)
245 242 new_repo.fork = parent_repo
246 243
247 244 new_repo.user_id = cur_user.user_id
248 245 self.sa.add(new_repo)
249 246
250 247 #create default permission
251 248 repo_to_perm = UserRepoToPerm()
252 249 default = 'repository.read'
253 250 for p in User.get_by_username('default').user_perms:
254 251 if p.permission.permission_name.startswith('repository.'):
255 252 default = p.permission.permission_name
256 253 break
257 254
258 255 default_perm = 'repository.none' if form_data['private'] else default
259 256
260 257 repo_to_perm.permission_id = self.sa.query(Permission)\
261 258 .filter(Permission.permission_name == default_perm)\
262 259 .one().permission_id
263 260
264 261 repo_to_perm.repository = new_repo
265 262 repo_to_perm.user_id = User.get_by_username('default').user_id
266 263
267 264 self.sa.add(repo_to_perm)
268 265
269 266 if not just_db:
270 267 self.__create_repo(repo_name, form_data['repo_type'],
271 268 form_data['repo_group'],
272 269 form_data['clone_uri'])
273 270
274 self.sa.commit()
275
276 #now automatically start following this repository as owner
277 from rhodecode.model.scm import ScmModel
271 # now automatically start following this repository as owner
278 272 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
279 cur_user.user_id)
273 cur_user.user_id)
280 274 return new_repo
281 275 except:
282 276 log.error(traceback.format_exc())
283 self.sa.rollback()
284 277 raise
285 278
286 279 def create_fork(self, form_data, cur_user):
280 """
281 Simple wrapper into executing celery task for fork creation
282
283 :param form_data:
284 :param cur_user:
285 """
287 286 from rhodecode.lib.celerylib import tasks, run_task
288 287 run_task(tasks.create_repo_fork, form_data, cur_user)
289 288
290 289 def delete(self, repo):
291 290 try:
292 291 self.sa.delete(repo)
293 292 self.__delete_repo(repo)
294 293 self.sa.commit()
295 294 except:
296 295 log.error(traceback.format_exc())
297 296 self.sa.rollback()
298 297 raise
299 298
300 299 def delete_perm_user(self, form_data, repo_name):
301 300 try:
302 301 obj = self.sa.query(UserRepoToPerm)\
303 302 .filter(UserRepoToPerm.repository \
304 303 == self.get_by_repo_name(repo_name))\
305 304 .filter(UserRepoToPerm.user_id == form_data['user_id']).one()
306 305 self.sa.delete(obj)
307 306 self.sa.commit()
308 307 except:
309 308 log.error(traceback.format_exc())
310 309 self.sa.rollback()
311 310 raise
312 311
313 312 def delete_perm_users_group(self, form_data, repo_name):
314 313 try:
315 314 obj = self.sa.query(UsersGroupRepoToPerm)\
316 315 .filter(UsersGroupRepoToPerm.repository \
317 316 == self.get_by_repo_name(repo_name))\
318 317 .filter(UsersGroupRepoToPerm.users_group_id
319 318 == form_data['users_group_id']).one()
320 319 self.sa.delete(obj)
321 320 self.sa.commit()
322 321 except:
323 322 log.error(traceback.format_exc())
324 323 self.sa.rollback()
325 324 raise
326 325
327 326 def delete_stats(self, repo_name):
327 """
328 removes stats for given repo
329
330 :param repo_name:
331 """
328 332 try:
329 333 obj = self.sa.query(Statistics)\
330 334 .filter(Statistics.repository == \
331 335 self.get_by_repo_name(repo_name)).one()
332 336 self.sa.delete(obj)
333 337 self.sa.commit()
334 338 except:
335 339 log.error(traceback.format_exc())
336 340 self.sa.rollback()
337 341 raise
338 342
339 343 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
340 344 """
341 345 makes repository on filesystem. It's group aware means it'll create
342 346 a repository within a group, and alter the paths accordingly of
343 347 group location
344 348
345 349 :param repo_name:
346 350 :param alias:
347 351 :param parent_id:
348 352 :param clone_uri:
349 353 """
350 354 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
351 355
352 356 if new_parent_id:
353 357 paths = RepoGroup.get(new_parent_id)\
354 358 .full_path.split(RepoGroup.url_sep())
355 359 new_parent_path = os.sep.join(paths)
356 360 else:
357 361 new_parent_path = ''
358 362
359 363 repo_path = os.path.join(*map(lambda x:safe_str(x),
360 364 [self.repos_path, new_parent_path, repo_name]))
361 365
362 366
363 367 # check if this path is not a repository
364 368 if is_valid_repo(repo_path, self.repos_path):
365 369 raise Exception('This path %s is a valid repository' % repo_path)
366 370
367 371 # check if this path is a group
368 372 if is_valid_repos_group(repo_path, self.repos_path):
369 373 raise Exception('This path %s is a valid group' % repo_path)
370 374
371 375 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
372 376 clone_uri)
373 377 backend = get_backend(alias)
374 378
375 379 backend(repo_path, create=True, src_url=clone_uri)
376 380
377 381
378 382 def __rename_repo(self, old, new):
379 383 """
380 384 renames repository on filesystem
381 385
382 386 :param old: old name
383 387 :param new: new name
384 388 """
385 389 log.info('renaming repo from %s to %s', old, new)
386 390
387 391 old_path = os.path.join(self.repos_path, old)
388 392 new_path = os.path.join(self.repos_path, new)
389 393 if os.path.isdir(new_path):
390 394 raise Exception('Was trying to rename to already existing dir %s' \
391 395 % new_path)
392 396 shutil.move(old_path, new_path)
393 397
394 398 def __delete_repo(self, repo):
395 399 """
396 400 removes repo from filesystem, the removal is acctually made by
397 401 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
398 402 repository is no longer valid for rhodecode, can be undeleted later on
399 403 by reverting the renames on this repository
400 404
401 405 :param repo: repo object
402 406 """
403 407 rm_path = os.path.join(self.repos_path, repo.repo_name)
404 408 log.info("Removing %s", rm_path)
405 409 #disable hg/git
406 410 alias = repo.repo_type
407 411 shutil.move(os.path.join(rm_path, '.%s' % alias),
408 412 os.path.join(rm_path, 'rm__.%s' % alias))
409 413 #disable repo
410 414 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
411 415 % (datetime.today()\
412 416 .strftime('%Y%m%d_%H%M%S_%f'),
413 417 repo.repo_name)))
414 418
@@ -1,384 +1,376 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import time
27 27 import traceback
28 28 import logging
29 29
30 30 from vcs import get_backend
31 31 from vcs.exceptions import RepositoryError
32 32 from vcs.utils.lazy import LazyProperty
33 33 from vcs.nodes import FileNode
34 34
35 35 from rhodecode import BACKENDS
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib import safe_str
38 38 from rhodecode.lib.auth import HasRepoPermissionAny
39 39 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
40 40 action_logger, EmptyChangeset
41 41 from rhodecode.model import BaseModel
42 42 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
43 43 UserFollowing, UserLog, User
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class UserTemp(object):
49 49 def __init__(self, user_id):
50 50 self.user_id = user_id
51 51
52 52 def __repr__(self):
53 53 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
54 54
55 55
56 56 class RepoTemp(object):
57 57 def __init__(self, repo_id):
58 58 self.repo_id = repo_id
59 59
60 60 def __repr__(self):
61 61 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
62 62
63 63 class CachedRepoList(object):
64 64
65 65 def __init__(self, db_repo_list, repos_path, order_by=None):
66 66 self.db_repo_list = db_repo_list
67 67 self.repos_path = repos_path
68 68 self.order_by = order_by
69 69 self.reversed = (order_by or '').startswith('-')
70 70
71 71 def __len__(self):
72 72 return len(self.db_repo_list)
73 73
74 74 def __repr__(self):
75 75 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
76 76
77 77 def __iter__(self):
78 78 for dbr in self.db_repo_list:
79 79
80 80 scmr = dbr.scm_instance_cached
81 81
82 82 # check permission at this level
83 83 if not HasRepoPermissionAny('repository.read', 'repository.write',
84 84 'repository.admin')(dbr.repo_name,
85 85 'get repo check'):
86 86 continue
87 87
88 88 if scmr is None:
89 89 log.error('%s this repository is present in database but it '
90 90 'cannot be created as an scm instance',
91 91 dbr.repo_name)
92 92 continue
93 93
94 94 last_change = scmr.last_change
95 95 tip = h.get_changeset_safe(scmr, 'tip')
96 96
97 97 tmp_d = {}
98 98 tmp_d['name'] = dbr.repo_name
99 99 tmp_d['name_sort'] = tmp_d['name'].lower()
100 100 tmp_d['description'] = dbr.description
101 101 tmp_d['description_sort'] = tmp_d['description']
102 102 tmp_d['last_change'] = last_change
103 103 tmp_d['last_change_sort'] = time.mktime(last_change \
104 104 .timetuple())
105 105 tmp_d['tip'] = tip.raw_id
106 106 tmp_d['tip_sort'] = tip.revision
107 107 tmp_d['rev'] = tip.revision
108 108 tmp_d['contact'] = dbr.user.full_contact
109 109 tmp_d['contact_sort'] = tmp_d['contact']
110 110 tmp_d['owner_sort'] = tmp_d['contact']
111 111 tmp_d['repo_archives'] = list(scmr._get_archives())
112 112 tmp_d['last_msg'] = tip.message
113 113 tmp_d['author'] = tip.author
114 114 tmp_d['dbrepo'] = dbr.get_dict()
115 115 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
116 116 else {}
117 117 yield tmp_d
118 118
119 119 class ScmModel(BaseModel):
120 120 """
121 121 Generic Scm Model
122 122 """
123 123
124 124 @LazyProperty
125 125 def repos_path(self):
126 126 """
127 127 Get's the repositories root path from database
128 128 """
129 129
130 130 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
131 131
132 132 return q.ui_value
133 133
134 134 def repo_scan(self, repos_path=None):
135 135 """
136 136 Listing of repositories in given path. This path should not be a
137 137 repository itself. Return a dictionary of repository objects
138 138
139 139 :param repos_path: path to directory containing repositories
140 140 """
141 141
142 142 log.info('scanning for repositories in %s', repos_path)
143 143
144 144 if repos_path is None:
145 145 repos_path = self.repos_path
146 146
147 147 baseui = make_ui('db')
148 148 repos_list = {}
149 149
150 150 for name, path in get_filesystem_repos(repos_path, recursive=True):
151 151
152 152 # name need to be decomposed and put back together using the /
153 153 # since this is internal storage separator for rhodecode
154 154 name = Repository.url_sep().join(name.split(os.sep))
155 155
156 156 try:
157 157 if name in repos_list:
158 158 raise RepositoryError('Duplicate repository name %s '
159 159 'found in %s' % (name, path))
160 160 else:
161 161
162 162 klass = get_backend(path[0])
163 163
164 164 if path[0] == 'hg' and path[0] in BACKENDS.keys():
165 165
166 166 # for mercurial we need to have an str path
167 167 repos_list[name] = klass(safe_str(path[1]),
168 168 baseui=baseui)
169 169
170 170 if path[0] == 'git' and path[0] in BACKENDS.keys():
171 171 repos_list[name] = klass(path[1])
172 172 except OSError:
173 173 continue
174 174
175 175 return repos_list
176 176
177 177 def get_repos(self, all_repos=None, sort_key=None):
178 178 """
179 179 Get all repos from db and for each repo create it's
180 180 backend instance and fill that backed with information from database
181 181
182 182 :param all_repos: list of repository names as strings
183 183 give specific repositories list, good for filtering
184 184 """
185 185 if all_repos is None:
186 186 all_repos = self.sa.query(Repository)\
187 187 .filter(Repository.group_id == None)\
188 188 .order_by(Repository.repo_name).all()
189 189
190 190 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
191 191 order_by=sort_key)
192 192
193 193 return repo_iter
194 194
195 195 def mark_for_invalidation(self, repo_name):
196 196 """Puts cache invalidation task into db for
197 197 further global cache invalidation
198 198
199 199 :param repo_name: this repo that should invalidation take place
200 200 """
201 201 CacheInvalidation.set_invalidate(repo_name)
202 202 CacheInvalidation.set_invalidate(repo_name + "_README")
203 203
204 204 def toggle_following_repo(self, follow_repo_id, user_id):
205 205
206 206 f = self.sa.query(UserFollowing)\
207 207 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
208 208 .filter(UserFollowing.user_id == user_id).scalar()
209 209
210 210 if f is not None:
211
212 211 try:
213 212 self.sa.delete(f)
214 self.sa.commit()
215 213 action_logger(UserTemp(user_id),
216 214 'stopped_following_repo',
217 215 RepoTemp(follow_repo_id))
218 216 return
219 217 except:
220 218 log.error(traceback.format_exc())
221 self.sa.rollback()
222 219 raise
223 220
224 221 try:
225 222 f = UserFollowing()
226 223 f.user_id = user_id
227 224 f.follows_repo_id = follow_repo_id
228 225 self.sa.add(f)
229 self.sa.commit()
226
230 227 action_logger(UserTemp(user_id),
231 228 'started_following_repo',
232 229 RepoTemp(follow_repo_id))
233 230 except:
234 231 log.error(traceback.format_exc())
235 self.sa.rollback()
236 232 raise
237 233
238 234 def toggle_following_user(self, follow_user_id, user_id):
239 235 f = self.sa.query(UserFollowing)\
240 236 .filter(UserFollowing.follows_user_id == follow_user_id)\
241 237 .filter(UserFollowing.user_id == user_id).scalar()
242 238
243 239 if f is not None:
244 240 try:
245 241 self.sa.delete(f)
246 self.sa.commit()
247 242 return
248 243 except:
249 244 log.error(traceback.format_exc())
250 self.sa.rollback()
251 245 raise
252 246
253 247 try:
254 248 f = UserFollowing()
255 249 f.user_id = user_id
256 250 f.follows_user_id = follow_user_id
257 251 self.sa.add(f)
258 self.sa.commit()
259 252 except:
260 253 log.error(traceback.format_exc())
261 self.sa.rollback()
262 254 raise
263 255
264 256 def is_following_repo(self, repo_name, user_id, cache=False):
265 257 r = self.sa.query(Repository)\
266 258 .filter(Repository.repo_name == repo_name).scalar()
267 259
268 260 f = self.sa.query(UserFollowing)\
269 261 .filter(UserFollowing.follows_repository == r)\
270 262 .filter(UserFollowing.user_id == user_id).scalar()
271 263
272 264 return f is not None
273 265
274 266 def is_following_user(self, username, user_id, cache=False):
275 267 u = User.get_by_username(username)
276 268
277 269 f = self.sa.query(UserFollowing)\
278 270 .filter(UserFollowing.follows_user == u)\
279 271 .filter(UserFollowing.user_id == user_id).scalar()
280 272
281 273 return f is not None
282 274
283 275 def get_followers(self, repo_id):
284 276 if not isinstance(repo_id, int):
285 277 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
286 278
287 279 return self.sa.query(UserFollowing)\
288 280 .filter(UserFollowing.follows_repo_id == repo_id).count()
289 281
290 282 def get_forks(self, repo_id):
291 283 if not isinstance(repo_id, int):
292 284 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
293 285
294 286 return self.sa.query(Repository)\
295 287 .filter(Repository.fork_id == repo_id).count()
296 288
297 289 def pull_changes(self, repo_name, username):
298 290 dbrepo = Repository.get_by_repo_name(repo_name)
299 291 clone_uri = dbrepo.clone_uri
300 292 if not clone_uri:
301 293 raise Exception("This repository doesn't have a clone uri")
302 294
303 295 repo = dbrepo.scm_instance
304 296 try:
305 297 extras = {'ip': '',
306 298 'username': username,
307 299 'action': 'push_remote',
308 300 'repository': repo_name}
309 301
310 302 #inject ui extra param to log this action via push logger
311 303 for k, v in extras.items():
312 304 repo._repo.ui.setconfig('rhodecode_extras', k, v)
313 305
314 306 repo.pull(clone_uri)
315 307 self.mark_for_invalidation(repo_name)
316 308 except:
317 309 log.error(traceback.format_exc())
318 310 raise
319 311
320 def commit_change(self, repo, repo_name, cs, user, author, message, content,
321 f_path):
312 def commit_change(self, repo, repo_name, cs, user, author, message,
313 content, f_path):
322 314
323 315 if repo.alias == 'hg':
324 316 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
325 317 elif repo.alias == 'git':
326 318 from vcs.backends.git import GitInMemoryChangeset as IMC
327 319
328 320 # decoding here will force that we have proper encoded values
329 321 # in any other case this will throw exceptions and deny commit
330 322 content = safe_str(content)
331 323 message = safe_str(message)
332 324 path = safe_str(f_path)
333 325 author = safe_str(author)
334 326 m = IMC(repo)
335 327 m.change(FileNode(path, content))
336 328 tip = m.commit(message=message,
337 329 author=author,
338 330 parents=[cs], branch=cs.branch)
339 331
340 332 new_cs = tip.short_id
341 333 action = 'push_local:%s' % new_cs
342 334
343 335 action_logger(user, action, repo_name)
344 336
345 337 self.mark_for_invalidation(repo_name)
346 338
347 339 def create_node(self, repo, repo_name, cs, user, author, message, content,
348 340 f_path):
349 341 if repo.alias == 'hg':
350 342 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
351 343 elif repo.alias == 'git':
352 344 from vcs.backends.git import GitInMemoryChangeset as IMC
353 345 # decoding here will force that we have proper encoded values
354 346 # in any other case this will throw exceptions and deny commit
355 347
356 348 if isinstance(content, (basestring,)):
357 349 content = safe_str(content)
358 350 elif isinstance(content, file):
359 351 content = content.read()
360 352
361 353 message = safe_str(message)
362 354 path = safe_str(f_path)
363 355 author = safe_str(author)
364 356 m = IMC(repo)
365 357
366 358 if isinstance(cs, EmptyChangeset):
367 359 # Emptychangeset means we we're editing empty repository
368 360 parents = None
369 361 else:
370 362 parents = [cs]
371 363
372 364 m.add(FileNode(path, content=content))
373 365 tip = m.commit(message=message,
374 366 author=author,
375 367 parents=parents, branch=cs.branch)
376 368 new_cs = tip.short_id
377 369 action = 'push_local:%s' % new_cs
378 370
379 371 action_logger(user, action, repo_name)
380 372
381 373 self.mark_for_invalidation(repo_name)
382 374
383 375 def get_unread_journal(self):
384 376 return self.sa.query(UserLog).count()
@@ -1,3526 +1,3530 b''
1 1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td
2 2 {
3 3 border: 0;
4 4 outline: 0;
5 5 font-size: 100%;
6 6 vertical-align: baseline;
7 7 background: transparent;
8 8 margin: 0;
9 9 padding: 0;
10 10 }
11 11
12 12 body {
13 13 line-height: 1;
14 14 height: 100%;
15 15 background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
16 16 font-family: Lucida Grande, Verdana, Lucida Sans Regular,
17 17 Lucida Sans Unicode, Arial, sans-serif; font-size : 12px;
18 18 color: #000;
19 19 margin: 0;
20 20 padding: 0;
21 21 font-size: 12px;
22 22 }
23 23
24 24 ol,ul {
25 25 list-style: none;
26 26 }
27 27
28 28 blockquote,q {
29 29 quotes: none;
30 30 }
31 31
32 32 blockquote:before,blockquote:after,q:before,q:after {
33 33 content: none;
34 34 }
35 35
36 36 :focus {
37 37 outline: 0;
38 38 }
39 39
40 40 del {
41 41 text-decoration: line-through;
42 42 }
43 43
44 44 table {
45 45 border-collapse: collapse;
46 46 border-spacing: 0;
47 47 }
48 48
49 49 html {
50 50 height: 100%;
51 51 }
52 52
53 53 a {
54 54 color: #003367;
55 55 text-decoration: none;
56 56 cursor: pointer;
57 57 }
58 58
59 59 a:hover {
60 60 color: #316293;
61 61 text-decoration: underline;
62 62 }
63 63
64 64 h1,h2,h3,h4,h5,h6 {
65 65 color: #292929;
66 66 font-weight: 700;
67 67 }
68 68
69 69 h1 {
70 70 font-size: 22px;
71 71 }
72 72
73 73 h2 {
74 74 font-size: 20px;
75 75 }
76 76
77 77 h3 {
78 78 font-size: 18px;
79 79 }
80 80
81 81 h4 {
82 82 font-size: 16px;
83 83 }
84 84
85 85 h5 {
86 86 font-size: 14px;
87 87 }
88 88
89 89 h6 {
90 90 font-size: 11px;
91 91 }
92 92
93 93 ul.circle {
94 94 list-style-type: circle;
95 95 }
96 96
97 97 ul.disc {
98 98 list-style-type: disc;
99 99 }
100 100
101 101 ul.square {
102 102 list-style-type: square;
103 103 }
104 104
105 105 ol.lower-roman {
106 106 list-style-type: lower-roman;
107 107 }
108 108
109 109 ol.upper-roman {
110 110 list-style-type: upper-roman;
111 111 }
112 112
113 113 ol.lower-alpha {
114 114 list-style-type: lower-alpha;
115 115 }
116 116
117 117 ol.upper-alpha {
118 118 list-style-type: upper-alpha;
119 119 }
120 120
121 121 ol.decimal {
122 122 list-style-type: decimal;
123 123 }
124 124
125 125 div.color {
126 126 clear: both;
127 127 overflow: hidden;
128 128 position: absolute;
129 129 background: #FFF;
130 130 margin: 7px 0 0 60px;
131 131 padding: 1px 1px 1px 0;
132 132 }
133 133
134 134 div.color a {
135 135 width: 15px;
136 136 height: 15px;
137 137 display: block;
138 138 float: left;
139 139 margin: 0 0 0 1px;
140 140 padding: 0;
141 141 }
142 142
143 143 div.options {
144 144 clear: both;
145 145 overflow: hidden;
146 146 position: absolute;
147 147 background: #FFF;
148 148 margin: 7px 0 0 162px;
149 149 padding: 0;
150 150 }
151 151
152 152 div.options a {
153 153 height: 1%;
154 154 display: block;
155 155 text-decoration: none;
156 156 margin: 0;
157 157 padding: 3px 8px;
158 158 }
159 159
160 160 .top-left-rounded-corner {
161 161 -webkit-border-top-left-radius: 8px;
162 162 -khtml-border-radius-topleft: 8px;
163 163 -moz-border-radius-topleft: 8px;
164 164 border-top-left-radius: 8px;
165 165 }
166 166
167 167 .top-right-rounded-corner {
168 168 -webkit-border-top-right-radius: 8px;
169 169 -khtml-border-radius-topright: 8px;
170 170 -moz-border-radius-topright: 8px;
171 171 border-top-right-radius: 8px;
172 172 }
173 173
174 174 .bottom-left-rounded-corner {
175 175 -webkit-border-bottom-left-radius: 8px;
176 176 -khtml-border-radius-bottomleft: 8px;
177 177 -moz-border-radius-bottomleft: 8px;
178 178 border-bottom-left-radius: 8px;
179 179 }
180 180
181 181 .bottom-right-rounded-corner {
182 182 -webkit-border-bottom-right-radius: 8px;
183 183 -khtml-border-radius-bottomright: 8px;
184 184 -moz-border-radius-bottomright: 8px;
185 185 border-bottom-right-radius: 8px;
186 186 }
187 187
188 188 #header {
189 189 margin: 0;
190 190 padding: 0 10px;
191 191 }
192 192
193 193 #header ul#logged-user {
194 194 margin-bottom: 5px !important;
195 195 -webkit-border-radius: 0px 0px 8px 8px;
196 196 -khtml-border-radius: 0px 0px 8px 8px;
197 197 -moz-border-radius: 0px 0px 8px 8px;
198 198 border-radius: 0px 0px 8px 8px;
199 199 height: 37px;
200 200 background-color: #eedc94;
201 201 background-repeat: repeat-x;
202 202 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
203 203 to(#eedc94) );
204 204 background-image: -moz-linear-gradient(top, #003b76, #00376e);
205 205 background-image: -ms-linear-gradient(top, #003b76, #00376e);
206 206 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),
207 207 color-stop(100%, #00376e) );
208 208 background-image: -webkit-linear-gradient(top, #003b76, #00376e) );
209 209 background-image: -o-linear-gradient(top, #003b76, #00376e) );
210 210 background-image: linear-gradient(top, #003b76, #00376e);
211 211 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
212 212 endColorstr='#00376e', GradientType=0 );
213 213 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
214 214 }
215 215
216 216 #header ul#logged-user li {
217 217 list-style: none;
218 218 float: left;
219 219 margin: 8px 0 0;
220 220 padding: 4px 12px;
221 221 border-left: 1px solid #316293;
222 222 }
223 223
224 224 #header ul#logged-user li.first {
225 225 border-left: none;
226 226 margin: 4px;
227 227 }
228 228
229 229 #header ul#logged-user li.first div.gravatar {
230 230 margin-top: -2px;
231 231 }
232 232
233 233 #header ul#logged-user li.first div.account {
234 234 padding-top: 4px;
235 235 float: left;
236 236 }
237 237
238 238 #header ul#logged-user li.last {
239 239 border-right: none;
240 240 }
241 241
242 242 #header ul#logged-user li a {
243 243 color: #fff;
244 244 font-weight: 700;
245 245 text-decoration: none;
246 246 }
247 247
248 248 #header ul#logged-user li a:hover {
249 249 text-decoration: underline;
250 250 }
251 251
252 252 #header ul#logged-user li.highlight a {
253 253 color: #fff;
254 254 }
255 255
256 256 #header ul#logged-user li.highlight a:hover {
257 257 color: #FFF;
258 258 }
259 259
260 260 #header #header-inner {
261 261 min-height: 40px;
262 262 clear: both;
263 263 position: relative;
264 264 background-color: #eedc94;
265 265 background-repeat: repeat-x;
266 266 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
267 267 to(#eedc94) );
268 268 background-image: -moz-linear-gradient(top, #003b76, #00376e);
269 269 background-image: -ms-linear-gradient(top, #003b76, #00376e);
270 270 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),
271 271 color-stop(100%, #00376e) );
272 272 background-image: -webkit-linear-gradient(top, #003b76, #00376e) );
273 273 background-image: -o-linear-gradient(top, #003b76, #00376e) );
274 274 background-image: linear-gradient(top, #003b76, #00376e);
275 275 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
276 276 endColorstr='#00376e', GradientType=0 );
277 277 margin: 0;
278 278 padding: 0;
279 279 display: block;
280 280 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
281 281 -webkit-border-radius: 4px 4px 4px 4px;
282 282 -khtml-border-radius: 4px 4px 4px 4px;
283 283 -moz-border-radius: 4px 4px 4px 4px;
284 284 border-radius: 4px 4px 4px 4px;
285 285 }
286 286 #header #header-inner.hover{
287 287 position: fixed !important;
288 288 width: 100% !important;
289 289 margin-left: -10px !important;
290 290 z-index: 10000;
291 291 border-radius: 0px 0px 4px 4px;
292 292 }
293 293 #header #header-inner #home a {
294 294 height: 40px;
295 295 width: 46px;
296 296 display: block;
297 297 background: url("../images/button_home.png");
298 298 background-position: 0 0;
299 299 margin: 0;
300 300 padding: 0;
301 301 }
302 302
303 303 #header #header-inner #home a:hover {
304 304 background-position: 0 -40px;
305 305 }
306 306
307 307 #header #header-inner #logo {
308 308 float: left;
309 309 position: absolute;
310 310 }
311 311
312 312 #header #header-inner #logo h1 {
313 313 color: #FFF;
314 314 font-size: 18px;
315 315 margin: 10px 0 0 13px;
316 316 padding: 0;
317 317 }
318 318
319 319 #header #header-inner #logo a {
320 320 color: #fff;
321 321 text-decoration: none;
322 322 }
323 323
324 324 #header #header-inner #logo a:hover {
325 325 color: #bfe3ff;
326 326 }
327 327
328 328 #header #header-inner #quick,#header #header-inner #quick ul {
329 329 position: relative;
330 330 float: right;
331 331 list-style-type: none;
332 332 list-style-position: outside;
333 333 margin: 6px 5px 0 0;
334 334 padding: 0;
335 335 }
336 336
337 337 #header #header-inner #quick li {
338 338 position: relative;
339 339 float: left;
340 340 margin: 0 5px 0 0;
341 341 padding: 0;
342 342 }
343 343
344 344 #header #header-inner #quick li a {
345 345 top: 0;
346 346 left: 0;
347 347 height: 1%;
348 348 display: block;
349 349 clear: both;
350 350 overflow: hidden;
351 351 color: #FFF;
352 352 font-weight: 700;
353 353 text-decoration: none;
354 354 background: #369;
355 355 padding: 0;
356 356 -webkit-border-radius: 4px 4px 4px 4px;
357 357 -khtml-border-radius: 4px 4px 4px 4px;
358 358 -moz-border-radius: 4px 4px 4px 4px;
359 359 border-radius: 4px 4px 4px 4px;
360 360 }
361 361
362 362 #header #header-inner #quick li span.short {
363 363 padding: 9px 6px 8px 6px;
364 364 }
365 365
366 366 #header #header-inner #quick li span {
367 367 top: 0;
368 368 right: 0;
369 369 height: 1%;
370 370 display: block;
371 371 float: left;
372 372 border-left: 1px solid #3f6f9f;
373 373 margin: 0;
374 374 padding: 10px 12px 8px 10px;
375 375 }
376 376
377 377 #header #header-inner #quick li span.normal {
378 378 border: none;
379 379 padding: 10px 12px 8px;
380 380 }
381 381
382 382 #header #header-inner #quick li span.icon {
383 383 top: 0;
384 384 left: 0;
385 385 border-left: none;
386 386 border-right: 1px solid #2e5c89;
387 387 padding: 8px 6px 4px;
388 388 }
389 389
390 390 #header #header-inner #quick li span.icon_short {
391 391 top: 0;
392 392 left: 0;
393 393 border-left: none;
394 394 border-right: 1px solid #2e5c89;
395 395 padding: 8px 6px 4px;
396 396 }
397 397
398 398 #header #header-inner #quick li span.icon img,#header #header-inner #quick li span.icon_short img
399 399 {
400 400 margin: 0px -2px 0px 0px;
401 401 }
402 402
403 403 #header #header-inner #quick li a:hover {
404 404 background: #4e4e4e no-repeat top left;
405 405 }
406 406
407 407 #header #header-inner #quick li a:hover span {
408 408 border-left: 1px solid #545454;
409 409 }
410 410
411 411 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short
412 412 {
413 413 border-left: none;
414 414 border-right: 1px solid #464646;
415 415 }
416 416
417 417 #header #header-inner #quick ul {
418 418 top: 29px;
419 419 right: 0;
420 420 min-width: 200px;
421 421 display: none;
422 422 position: absolute;
423 423 background: #FFF;
424 424 border: 1px solid #666;
425 425 border-top: 1px solid #003367;
426 426 z-index: 100;
427 427 margin: 0;
428 428 padding: 0;
429 429 }
430 430
431 431 #header #header-inner #quick ul.repo_switcher {
432 432 max-height: 275px;
433 433 overflow-x: hidden;
434 434 overflow-y: auto;
435 435 }
436 436
437 437 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
438 438 float: none;
439 439 margin: 0;
440 440 border-bottom: 2px solid #003367;
441 441 }
442 442
443 443 #header #header-inner #quick .repo_switcher_type {
444 444 position: absolute;
445 445 left: 0;
446 446 top: 9px;
447 447 }
448 448
449 449 #header #header-inner #quick li ul li {
450 450 border-bottom: 1px solid #ddd;
451 451 }
452 452
453 453 #header #header-inner #quick li ul li a {
454 454 width: 182px;
455 455 height: auto;
456 456 display: block;
457 457 float: left;
458 458 background: #FFF;
459 459 color: #003367;
460 460 font-weight: 400;
461 461 margin: 0;
462 462 padding: 7px 9px;
463 463 }
464 464
465 465 #header #header-inner #quick li ul li a:hover {
466 466 color: #000;
467 467 background: #FFF;
468 468 }
469 469
470 470 #header #header-inner #quick ul ul {
471 471 top: auto;
472 472 }
473 473
474 474 #header #header-inner #quick li ul ul {
475 475 right: 200px;
476 476 max-height: 275px;
477 477 overflow: auto;
478 478 overflow-x: hidden;
479 479 white-space: normal;
480 480 }
481 481
482 482 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover
483 483 {
484 484 background: url("../images/icons/book.png") no-repeat scroll 4px 9px
485 485 #FFF;
486 486 width: 167px;
487 487 margin: 0;
488 488 padding: 12px 9px 7px 24px;
489 489 }
490 490
491 491 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover
492 492 {
493 493 background: url("../images/icons/lock.png") no-repeat scroll 4px 9px
494 494 #FFF;
495 495 min-width: 167px;
496 496 margin: 0;
497 497 padding: 12px 9px 7px 24px;
498 498 }
499 499
500 500 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover
501 501 {
502 502 background: url("../images/icons/lock_open.png") no-repeat scroll 4px
503 503 9px #FFF;
504 504 min-width: 167px;
505 505 margin: 0;
506 506 padding: 12px 9px 7px 24px;
507 507 }
508 508
509 509 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover
510 510 {
511 511 background: url("../images/icons/hgicon.png") no-repeat scroll 4px 9px
512 512 #FFF;
513 513 min-width: 167px;
514 514 margin: 0 0 0 14px;
515 515 padding: 12px 9px 7px 24px;
516 516 }
517 517
518 518 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover
519 519 {
520 520 background: url("../images/icons/giticon.png") no-repeat scroll 4px 9px
521 521 #FFF;
522 522 min-width: 167px;
523 523 margin: 0 0 0 14px;
524 524 padding: 12px 9px 7px 24px;
525 525 }
526 526
527 527 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover
528 528 {
529 529 background: url("../images/icons/database_edit.png") no-repeat scroll
530 530 4px 9px #FFF;
531 531 width: 167px;
532 532 margin: 0;
533 533 padding: 12px 9px 7px 24px;
534 534 }
535 535
536 536 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover
537 537 {
538 538 background: url("../images/icons/database_link.png") no-repeat scroll
539 539 4px 9px #FFF;
540 540 width: 167px;
541 541 margin: 0;
542 542 padding: 12px 9px 7px 24px;
543 543 }
544 544
545 545 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover
546 546 {
547 547 background: #FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
548 548 width: 167px;
549 549 margin: 0;
550 550 padding: 12px 9px 7px 24px;
551 551 }
552 552
553 553 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover
554 554 {
555 555 background: #FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
556 556 width: 167px;
557 557 margin: 0;
558 558 padding: 12px 9px 7px 24px;
559 559 }
560 560
561 561 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover
562 562 {
563 563 background: #FFF url("../images/icons/cog.png") no-repeat 4px 9px;
564 564 width: 167px;
565 565 margin: 0;
566 566 padding: 12px 9px 7px 24px;
567 567 }
568 568
569 569 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover
570 570 {
571 571 background: #FFF url("../images/icons/key.png") no-repeat 4px 9px;
572 572 width: 167px;
573 573 margin: 0;
574 574 padding: 12px 9px 7px 24px;
575 575 }
576 576
577 577 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover
578 578 {
579 579 background: #FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
580 580 width: 167px;
581 581 margin: 0;
582 582 padding: 12px 9px 7px 24px;
583 583 }
584 584
585 585 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover
586 586 {
587 587 background: #FFF url("../images/icons/arrow_divide.png") no-repeat 4px
588 588 9px;
589 589 width: 167px;
590 590 margin: 0;
591 591 padding: 12px 9px 7px 24px;
592 592 }
593 593
594 594 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover
595 595 {
596 596 background: #FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
597 597 width: 167px;
598 598 margin: 0;
599 599 padding: 12px 9px 7px 24px;
600 600 }
601 601
602 602 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover
603 603 {
604 604 background: #FFF url("../images/icons/delete.png") no-repeat 4px 9px;
605 605 width: 167px;
606 606 margin: 0;
607 607 padding: 12px 9px 7px 24px;
608 608 }
609 609
610 610 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover
611 611 {
612 612 background: #FFF url("../images/icons/arrow_branch.png") no-repeat 4px
613 613 9px;
614 614 width: 167px;
615 615 margin: 0;
616 616 padding: 12px 9px 7px 24px;
617 617 }
618 618
619 619 #header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover
620 620 {
621 621 background: #FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
622 622 width: 167px;
623 623 margin: 0;
624 624 padding: 12px 9px 7px 24px;
625 625 }
626 626
627 627 #header #header-inner #quick li ul li a.admin,#header #header-inner #quick li ul li a.admin:hover
628 628 {
629 629 background: #FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
630 630 width: 167px;
631 631 margin: 0;
632 632 padding: 12px 9px 7px 24px;
633 633 }
634 634
635 635 .groups_breadcrumbs a {
636 636 color: #fff;
637 637 }
638 638
639 639 .groups_breadcrumbs a:hover {
640 640 color: #bfe3ff;
641 641 text-decoration: none;
642 642 }
643 643
644 644 .quick_repo_menu {
645 645 background: #FFF url("../images/vertical-indicator.png") 8px 50%
646 646 no-repeat !important;
647 647 cursor: pointer;
648 648 width: 8px;
649 649 }
650 650
651 651 .quick_repo_menu.active {
652 652 background: #FFF url("../images/horizontal-indicator.png") 4px 50%
653 653 no-repeat !important;
654 654 cursor: pointer;
655 655 }
656 656
657 657 .quick_repo_menu .menu_items {
658 658 margin-top: 6px;
659 659 width: 150px;
660 660 position: absolute;
661 661 background-color: #FFF;
662 662 background: none repeat scroll 0 0 #FFFFFF;
663 663 border-color: #003367 #666666 #666666;
664 664 border-right: 1px solid #666666;
665 665 border-style: solid;
666 666 border-width: 1px;
667 667 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
668 668 }
669 669
670 670 .quick_repo_menu .menu_items li {
671 671 padding: 0 !important;
672 672 }
673 673
674 674 .quick_repo_menu .menu_items a {
675 675 display: block;
676 676 padding: 4px 12px 4px 8px;
677 677 }
678 678
679 679 .quick_repo_menu .menu_items a:hover {
680 680 background-color: #EEE;
681 681 text-decoration: none;
682 682 }
683 683
684 684 .quick_repo_menu .menu_items .icon img {
685 685 margin-bottom: -2px;
686 686 }
687 687
688 688 .quick_repo_menu .menu_items.hidden {
689 689 display: none;
690 690 }
691 691
692 692 #content #left {
693 693 left: 0;
694 694 width: 280px;
695 695 position: absolute;
696 696 }
697 697
698 698 #content #right {
699 699 margin: 0 60px 10px 290px;
700 700 }
701 701
702 702 #content div.box {
703 703 clear: both;
704 704 overflow: hidden;
705 705 background: #fff;
706 706 margin: 0 0 10px;
707 707 padding: 0 0 10px;
708 708 -webkit-border-radius: 4px 4px 4px 4px;
709 709 -khtml-border-radius: 4px 4px 4px 4px;
710 710 -moz-border-radius: 4px 4px 4px 4px;
711 711 border-radius: 4px 4px 4px 4px;
712 712 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
713 713 }
714 714
715 715 #content div.box-left {
716 716 width: 49%;
717 717 clear: none;
718 718 float: left;
719 719 margin: 0 0 10px;
720 720 }
721 721
722 722 #content div.box-right {
723 723 width: 49%;
724 724 clear: none;
725 725 float: right;
726 726 margin: 0 0 10px;
727 727 }
728 728
729 729 #content div.box div.title {
730 730 clear: both;
731 731 overflow: hidden;
732 732 background-color: #eedc94;
733 733 background-repeat: repeat-x;
734 734 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
735 735 to(#eedc94) );
736 736 background-image: -moz-linear-gradient(top, #003b76, #00376e);
737 737 background-image: -ms-linear-gradient(top, #003b76, #00376e);
738 738 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),
739 739 color-stop(100%, #00376e) );
740 740 background-image: -webkit-linear-gradient(top, #003b76, #00376e) );
741 741 background-image: -o-linear-gradient(top, #003b76, #00376e) );
742 742 background-image: linear-gradient(top, #003b76, #00376e);
743 743 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
744 744 endColorstr='#00376e', GradientType=0 );
745 745 margin: 0 0 20px;
746 746 padding: 0;
747 747 }
748 748
749 749 #content div.box div.title h5 {
750 750 float: left;
751 751 border: none;
752 752 color: #fff;
753 753 text-transform: uppercase;
754 754 margin: 0;
755 755 padding: 11px 0 11px 10px;
756 756 }
757 757
758 758 #content div.box div.title ul.links li {
759 759 list-style: none;
760 760 float: left;
761 761 margin: 0;
762 762 padding: 0;
763 763 }
764 764
765 765 #content div.box div.title ul.links li a {
766 766 border-left: 1px solid #316293;
767 767 color: #FFFFFF;
768 768 display: block;
769 769 float: left;
770 770 font-size: 13px;
771 771 font-weight: 700;
772 772 height: 1%;
773 773 margin: 0;
774 774 padding: 11px 22px 12px;
775 775 text-decoration: none;
776 776 }
777 777
778 778 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6
779 779 {
780 780 clear: both;
781 781 overflow: hidden;
782 782 border-bottom: 1px solid #DDD;
783 783 margin: 10px 20px;
784 784 padding: 0 0 15px;
785 785 }
786 786
787 787 #content div.box p {
788 788 color: #5f5f5f;
789 789 font-size: 12px;
790 790 line-height: 150%;
791 791 margin: 0 24px 10px;
792 792 padding: 0;
793 793 }
794 794
795 795 #content div.box blockquote {
796 796 border-left: 4px solid #DDD;
797 797 color: #5f5f5f;
798 798 font-size: 11px;
799 799 line-height: 150%;
800 800 margin: 0 34px;
801 801 padding: 0 0 0 14px;
802 802 }
803 803
804 804 #content div.box blockquote p {
805 805 margin: 10px 0;
806 806 padding: 0;
807 807 }
808 808
809 809 #content div.box dl {
810 810 margin: 10px 24px;
811 811 }
812 812
813 813 #content div.box dt {
814 814 font-size: 12px;
815 815 margin: 0;
816 816 }
817 817
818 818 #content div.box dd {
819 819 font-size: 12px;
820 820 margin: 0;
821 821 padding: 8px 0 8px 15px;
822 822 }
823 823
824 824 #content div.box li {
825 825 font-size: 12px;
826 826 padding: 4px 0;
827 827 }
828 828
829 829 #content div.box ul.disc,#content div.box ul.circle {
830 830 margin: 10px 24px 10px 38px;
831 831 }
832 832
833 833 #content div.box ul.square {
834 834 margin: 10px 24px 10px 40px;
835 835 }
836 836
837 837 #content div.box img.left {
838 838 border: none;
839 839 float: left;
840 840 margin: 10px 10px 10px 0;
841 841 }
842 842
843 843 #content div.box img.right {
844 844 border: none;
845 845 float: right;
846 846 margin: 10px 0 10px 10px;
847 847 }
848 848
849 849 #content div.box div.messages {
850 850 clear: both;
851 851 overflow: hidden;
852 852 margin: 0 20px;
853 853 padding: 0;
854 854 }
855 855
856 856 #content div.box div.message {
857 857 clear: both;
858 858 overflow: hidden;
859 859 margin: 0;
860 860 padding: 10px 0;
861 861 }
862 862
863 863 #content div.box div.message a {
864 864 font-weight: 400 !important;
865 865 }
866 866
867 867 #content div.box div.message div.image {
868 868 float: left;
869 869 margin: 9px 0 0 5px;
870 870 padding: 6px;
871 871 }
872 872
873 873 #content div.box div.message div.image img {
874 874 vertical-align: middle;
875 875 margin: 0;
876 876 }
877 877
878 878 #content div.box div.message div.text {
879 879 float: left;
880 880 margin: 0;
881 881 padding: 9px 6px;
882 882 }
883 883
884 884 #content div.box div.message div.dismiss a {
885 885 height: 16px;
886 886 width: 16px;
887 887 display: block;
888 888 background: url("../images/icons/cross.png") no-repeat;
889 889 margin: 15px 14px 0 0;
890 890 padding: 0;
891 891 }
892 892
893 893 #content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6
894 894 {
895 895 border: none;
896 896 margin: 0;
897 897 padding: 0;
898 898 }
899 899
900 900 #content div.box div.message div.text span {
901 901 height: 1%;
902 902 display: block;
903 903 margin: 0;
904 904 padding: 5px 0 0;
905 905 }
906 906
907 907 #content div.box div.message-error {
908 908 height: 1%;
909 909 clear: both;
910 910 overflow: hidden;
911 911 background: #FBE3E4;
912 912 border: 1px solid #FBC2C4;
913 913 color: #860006;
914 914 }
915 915
916 916 #content div.box div.message-error h6 {
917 917 color: #860006;
918 918 }
919 919
920 920 #content div.box div.message-warning {
921 921 height: 1%;
922 922 clear: both;
923 923 overflow: hidden;
924 924 background: #FFF6BF;
925 925 border: 1px solid #FFD324;
926 926 color: #5f5200;
927 927 }
928 928
929 929 #content div.box div.message-warning h6 {
930 930 color: #5f5200;
931 931 }
932 932
933 933 #content div.box div.message-notice {
934 934 height: 1%;
935 935 clear: both;
936 936 overflow: hidden;
937 937 background: #8FBDE0;
938 938 border: 1px solid #6BACDE;
939 939 color: #003863;
940 940 }
941 941
942 942 #content div.box div.message-notice h6 {
943 943 color: #003863;
944 944 }
945 945
946 946 #content div.box div.message-success {
947 947 height: 1%;
948 948 clear: both;
949 949 overflow: hidden;
950 950 background: #E6EFC2;
951 951 border: 1px solid #C6D880;
952 952 color: #4e6100;
953 953 }
954 954
955 955 #content div.box div.message-success h6 {
956 956 color: #4e6100;
957 957 }
958 958
959 959 #content div.box div.form div.fields div.field {
960 960 height: 1%;
961 961 border-bottom: 1px solid #DDD;
962 962 clear: both;
963 963 margin: 0;
964 964 padding: 10px 0;
965 965 }
966 966
967 967 #content div.box div.form div.fields div.field-first {
968 968 padding: 0 0 10px;
969 969 }
970 970
971 971 #content div.box div.form div.fields div.field-noborder {
972 972 border-bottom: 0 !important;
973 973 }
974 974
975 975 #content div.box div.form div.fields div.field span.error-message {
976 976 height: 1%;
977 977 display: inline-block;
978 978 color: red;
979 979 margin: 8px 0 0 4px;
980 980 padding: 0;
981 981 }
982 982
983 983 #content div.box div.form div.fields div.field span.success {
984 984 height: 1%;
985 985 display: block;
986 986 color: #316309;
987 987 margin: 8px 0 0;
988 988 padding: 0;
989 989 }
990 990
991 991 #content div.box div.form div.fields div.field div.label {
992 992 left: 70px;
993 993 width: 155px;
994 994 position: absolute;
995 995 margin: 0;
996 996 padding: 5px 0 0 0px;
997 997 }
998 998
999 999 #content div.box div.form div.fields div.field div.label-summary {
1000 1000 left: 30px;
1001 1001 width: 155px;
1002 1002 position: absolute;
1003 1003 margin: 0;
1004 1004 padding: 0px 0 0 0px;
1005 1005 }
1006 1006
1007 1007 #content div.box-left div.form div.fields div.field div.label,#content div.box-right div.form div.fields div.field div.label
1008 1008 {
1009 1009 clear: both;
1010 1010 overflow: hidden;
1011 1011 left: 0;
1012 1012 width: auto;
1013 1013 position: relative;
1014 1014 margin: 0;
1015 1015 padding: 0 0 8px;
1016 1016 }
1017 1017
1018 1018 #content div.box div.form div.fields div.field div.label-select {
1019 1019 padding: 5px 0 0 5px;
1020 1020 }
1021 1021
1022 1022 #content div.box-left div.form div.fields div.field div.label-select,#content div.box-right div.form div.fields div.field div.label-select
1023 1023 {
1024 1024 padding: 0 0 8px;
1025 1025 }
1026 1026
1027 1027 #content div.box-left div.form div.fields div.field div.label-textarea,#content div.box-right div.form div.fields div.field div.label-textarea
1028 1028 {
1029 1029 padding: 0 0 8px !important;
1030 1030 }
1031 1031
1032 1032 #content div.box div.form div.fields div.field div.label label,div.label label
1033 1033 {
1034 1034 color: #393939;
1035 1035 font-weight: 700;
1036 1036 }
1037 1037 #content div.box div.form div.fields div.field div.label label,div.label-summary label
1038 1038 {
1039 1039 color: #393939;
1040 1040 font-weight: 700;
1041 1041 }
1042 1042 #content div.box div.form div.fields div.field div.input {
1043 1043 margin: 0 0 0 200px;
1044 1044 }
1045 1045
1046 1046 #content div.box div.form div.fields div.field div.input.summary {
1047 1047 margin: 0 0 0 150px;
1048 1048 }
1049 1049
1050 1050 #content div.box div.form div.fields div.field div.file {
1051 1051 margin: 0 0 0 200px;
1052 1052 }
1053 1053
1054 1054 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input
1055 1055 {
1056 1056 margin: 0 0 0 0px;
1057 1057 }
1058 1058
1059 1059 #content div.box div.form div.fields div.field div.input input {
1060 1060 background: #FFF;
1061 1061 border-top: 1px solid #b3b3b3;
1062 1062 border-left: 1px solid #b3b3b3;
1063 1063 border-right: 1px solid #eaeaea;
1064 1064 border-bottom: 1px solid #eaeaea;
1065 1065 color: #000;
1066 1066 font-size: 11px;
1067 1067 margin: 0;
1068 1068 padding: 7px 7px 6px;
1069 1069 }
1070 1070
1071 1071 #content div.box div.form div.fields div.field div.file input {
1072 1072 background: none repeat scroll 0 0 #FFFFFF;
1073 1073 border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
1074 1074 border-style: solid;
1075 1075 border-width: 1px;
1076 1076 color: #000000;
1077 1077 font-size: 11px;
1078 1078 margin: 0;
1079 1079 padding: 7px 7px 6px;
1080 1080 }
1081 1081
1082 1082 #content div.box div.form div.fields div.field div.input input.small {
1083 1083 width: 30%;
1084 1084 }
1085 1085
1086 1086 #content div.box div.form div.fields div.field div.input input.medium {
1087 1087 width: 55%;
1088 1088 }
1089 1089
1090 1090 #content div.box div.form div.fields div.field div.input input.large {
1091 1091 width: 85%;
1092 1092 }
1093 1093
1094 1094 #content div.box div.form div.fields div.field div.input input.date {
1095 1095 width: 177px;
1096 1096 }
1097 1097
1098 1098 #content div.box div.form div.fields div.field div.input input.button {
1099 1099 background: #D4D0C8;
1100 1100 border-top: 1px solid #FFF;
1101 1101 border-left: 1px solid #FFF;
1102 1102 border-right: 1px solid #404040;
1103 1103 border-bottom: 1px solid #404040;
1104 1104 color: #000;
1105 1105 margin: 0;
1106 1106 padding: 4px 8px;
1107 1107 }
1108 1108
1109 1109 #content div.box div.form div.fields div.field div.textarea {
1110 1110 border-top: 1px solid #b3b3b3;
1111 1111 border-left: 1px solid #b3b3b3;
1112 1112 border-right: 1px solid #eaeaea;
1113 1113 border-bottom: 1px solid #eaeaea;
1114 1114 margin: 0 0 0 200px;
1115 1115 padding: 10px;
1116 1116 }
1117 1117
1118 1118 #content div.box div.form div.fields div.field div.textarea-editor {
1119 1119 border: 1px solid #ddd;
1120 1120 padding: 0;
1121 1121 }
1122 1122
1123 1123 #content div.box div.form div.fields div.field div.textarea textarea {
1124 1124 width: 100%;
1125 1125 height: 220px;
1126 1126 overflow: hidden;
1127 1127 background: #FFF;
1128 1128 color: #000;
1129 1129 font-size: 11px;
1130 1130 outline: none;
1131 1131 border-width: 0;
1132 1132 margin: 0;
1133 1133 padding: 0;
1134 1134 }
1135 1135
1136 1136 #content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea
1137 1137 {
1138 1138 width: 100%;
1139 1139 height: 100px;
1140 1140 }
1141 1141
1142 1142 #content div.box div.form div.fields div.field div.textarea table {
1143 1143 width: 100%;
1144 1144 border: none;
1145 1145 margin: 0;
1146 1146 padding: 0;
1147 1147 }
1148 1148
1149 1149 #content div.box div.form div.fields div.field div.textarea table td {
1150 1150 background: #DDD;
1151 1151 border: none;
1152 1152 padding: 0;
1153 1153 }
1154 1154
1155 1155 #content div.box div.form div.fields div.field div.textarea table td table
1156 1156 {
1157 1157 width: auto;
1158 1158 border: none;
1159 1159 margin: 0;
1160 1160 padding: 0;
1161 1161 }
1162 1162
1163 1163 #content div.box div.form div.fields div.field div.textarea table td table td
1164 1164 {
1165 1165 font-size: 11px;
1166 1166 padding: 5px 5px 5px 0;
1167 1167 }
1168 1168
1169 1169 #content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus
1170 1170 {
1171 1171 background: #f6f6f6;
1172 1172 border-color: #666;
1173 1173 }
1174 1174
1175 1175 div.form div.fields div.field div.button {
1176 1176 margin: 0;
1177 1177 padding: 0 0 0 8px;
1178 1178 }
1179 1179 #content div.box table.noborder {
1180 1180 border: 1px solid transparent;
1181 1181 }
1182 1182
1183 1183 #content div.box table {
1184 1184 width: 100%;
1185 1185 border-collapse: separate;
1186 1186 margin: 0;
1187 1187 padding: 0;
1188 1188 border: 1px solid #eee;
1189 1189 -webkit-border-radius: 4px;
1190 1190 -moz-border-radius: 4px;
1191 1191 border-radius: 4px;
1192 1192 }
1193 1193
1194 1194 #content div.box table th {
1195 1195 background: #eee;
1196 1196 border-bottom: 1px solid #ddd;
1197 1197 padding: 5px 0px 5px 5px;
1198 1198 }
1199 1199
1200 1200 #content div.box table th.left {
1201 1201 text-align: left;
1202 1202 }
1203 1203
1204 1204 #content div.box table th.right {
1205 1205 text-align: right;
1206 1206 }
1207 1207
1208 1208 #content div.box table th.center {
1209 1209 text-align: center;
1210 1210 }
1211 1211
1212 1212 #content div.box table th.selected {
1213 1213 vertical-align: middle;
1214 1214 padding: 0;
1215 1215 }
1216 1216
1217 1217 #content div.box table td {
1218 1218 background: #fff;
1219 1219 border-bottom: 1px solid #cdcdcd;
1220 1220 vertical-align: middle;
1221 1221 padding: 5px;
1222 1222 }
1223 1223
1224 1224 #content div.box table tr.selected td {
1225 1225 background: #FFC;
1226 1226 }
1227 1227
1228 1228 #content div.box table td.selected {
1229 1229 width: 3%;
1230 1230 text-align: center;
1231 1231 vertical-align: middle;
1232 1232 padding: 0;
1233 1233 }
1234 1234
1235 1235 #content div.box table td.action {
1236 1236 width: 45%;
1237 1237 text-align: left;
1238 1238 }
1239 1239
1240 1240 #content div.box table td.date {
1241 1241 width: 33%;
1242 1242 text-align: center;
1243 1243 }
1244 1244
1245 1245 #content div.box div.action {
1246 1246 float: right;
1247 1247 background: #FFF;
1248 1248 text-align: right;
1249 1249 margin: 10px 0 0;
1250 1250 padding: 0;
1251 1251 }
1252 1252
1253 1253 #content div.box div.action select {
1254 1254 font-size: 11px;
1255 1255 margin: 0;
1256 1256 }
1257 1257
1258 1258 #content div.box div.action .ui-selectmenu {
1259 1259 margin: 0;
1260 1260 padding: 0;
1261 1261 }
1262 1262
1263 1263 #content div.box div.pagination {
1264 1264 height: 1%;
1265 1265 clear: both;
1266 1266 overflow: hidden;
1267 1267 margin: 10px 0 0;
1268 1268 padding: 0;
1269 1269 }
1270 1270
1271 1271 #content div.box div.pagination ul.pager {
1272 1272 float: right;
1273 1273 text-align: right;
1274 1274 margin: 0;
1275 1275 padding: 0;
1276 1276 }
1277 1277
1278 1278 #content div.box div.pagination ul.pager li {
1279 1279 height: 1%;
1280 1280 float: left;
1281 1281 list-style: none;
1282 1282 background: #ebebeb url("../images/pager.png") repeat-x;
1283 1283 border-top: 1px solid #dedede;
1284 1284 border-left: 1px solid #cfcfcf;
1285 1285 border-right: 1px solid #c4c4c4;
1286 1286 border-bottom: 1px solid #c4c4c4;
1287 1287 color: #4A4A4A;
1288 1288 font-weight: 700;
1289 1289 margin: 0 0 0 4px;
1290 1290 padding: 0;
1291 1291 }
1292 1292
1293 1293 #content div.box div.pagination ul.pager li.separator {
1294 1294 padding: 6px;
1295 1295 }
1296 1296
1297 1297 #content div.box div.pagination ul.pager li.current {
1298 1298 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1299 1299 border-top: 1px solid #ccc;
1300 1300 border-left: 1px solid #bebebe;
1301 1301 border-right: 1px solid #b1b1b1;
1302 1302 border-bottom: 1px solid #afafaf;
1303 1303 color: #515151;
1304 1304 padding: 6px;
1305 1305 }
1306 1306
1307 1307 #content div.box div.pagination ul.pager li a {
1308 1308 height: 1%;
1309 1309 display: block;
1310 1310 float: left;
1311 1311 color: #515151;
1312 1312 text-decoration: none;
1313 1313 margin: 0;
1314 1314 padding: 6px;
1315 1315 }
1316 1316
1317 1317 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active
1318 1318 {
1319 1319 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1320 1320 border-top: 1px solid #ccc;
1321 1321 border-left: 1px solid #bebebe;
1322 1322 border-right: 1px solid #b1b1b1;
1323 1323 border-bottom: 1px solid #afafaf;
1324 1324 margin: -1px;
1325 1325 }
1326 1326
1327 1327 #content div.box div.pagination-wh {
1328 1328 height: 1%;
1329 1329 clear: both;
1330 1330 overflow: hidden;
1331 1331 text-align: right;
1332 1332 margin: 10px 0 0;
1333 1333 padding: 0;
1334 1334 }
1335 1335
1336 1336 #content div.box div.pagination-right {
1337 1337 float: right;
1338 1338 }
1339 1339
1340 1340 #content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot
1341 1341 {
1342 1342 height: 1%;
1343 1343 float: left;
1344 1344 background: #ebebeb url("../images/pager.png") repeat-x;
1345 1345 border-top: 1px solid #dedede;
1346 1346 border-left: 1px solid #cfcfcf;
1347 1347 border-right: 1px solid #c4c4c4;
1348 1348 border-bottom: 1px solid #c4c4c4;
1349 1349 color: #4A4A4A;
1350 1350 font-weight: 700;
1351 1351 margin: 0 0 0 4px;
1352 1352 padding: 6px;
1353 1353 }
1354 1354
1355 1355 #content div.box div.pagination-wh span.pager_curpage {
1356 1356 height: 1%;
1357 1357 float: left;
1358 1358 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1359 1359 border-top: 1px solid #ccc;
1360 1360 border-left: 1px solid #bebebe;
1361 1361 border-right: 1px solid #b1b1b1;
1362 1362 border-bottom: 1px solid #afafaf;
1363 1363 color: #515151;
1364 1364 font-weight: 700;
1365 1365 margin: 0 0 0 4px;
1366 1366 padding: 6px;
1367 1367 }
1368 1368
1369 1369 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active
1370 1370 {
1371 1371 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1372 1372 border-top: 1px solid #ccc;
1373 1373 border-left: 1px solid #bebebe;
1374 1374 border-right: 1px solid #b1b1b1;
1375 1375 border-bottom: 1px solid #afafaf;
1376 1376 text-decoration: none;
1377 1377 }
1378 1378
1379 1379 #content div.box div.traffic div.legend {
1380 1380 clear: both;
1381 1381 overflow: hidden;
1382 1382 border-bottom: 1px solid #ddd;
1383 1383 margin: 0 0 10px;
1384 1384 padding: 0 0 10px;
1385 1385 }
1386 1386
1387 1387 #content div.box div.traffic div.legend h6 {
1388 1388 float: left;
1389 1389 border: none;
1390 1390 margin: 0;
1391 1391 padding: 0;
1392 1392 }
1393 1393
1394 1394 #content div.box div.traffic div.legend li {
1395 1395 list-style: none;
1396 1396 float: left;
1397 1397 font-size: 11px;
1398 1398 margin: 0;
1399 1399 padding: 0 8px 0 4px;
1400 1400 }
1401 1401
1402 1402 #content div.box div.traffic div.legend li.visits {
1403 1403 border-left: 12px solid #edc240;
1404 1404 }
1405 1405
1406 1406 #content div.box div.traffic div.legend li.pageviews {
1407 1407 border-left: 12px solid #afd8f8;
1408 1408 }
1409 1409
1410 1410 #content div.box div.traffic table {
1411 1411 width: auto;
1412 1412 }
1413 1413
1414 1414 #content div.box div.traffic table td {
1415 1415 background: transparent;
1416 1416 border: none;
1417 1417 padding: 2px 3px 3px;
1418 1418 }
1419 1419
1420 1420 #content div.box div.traffic table td.legendLabel {
1421 1421 padding: 0 3px 2px;
1422 1422 }
1423 1423
1424 1424 #summary {
1425 1425
1426 1426 }
1427 1427
1428 1428 #summary .desc {
1429 1429 white-space: pre;
1430 1430 width: 100%;
1431 1431 }
1432 1432
1433 1433 #summary .repo_name {
1434 1434 font-size: 1.6em;
1435 1435 font-weight: bold;
1436 1436 vertical-align: baseline;
1437 1437 clear: right
1438 1438 }
1439 1439
1440 1440 #footer {
1441 1441 clear: both;
1442 1442 overflow: hidden;
1443 1443 text-align: right;
1444 1444 margin: 0;
1445 1445 padding: 0 10px 4px;
1446 1446 margin: -10px 0 0;
1447 1447 }
1448 1448
1449 1449 #footer div#footer-inner {
1450 1450 background-color: #eedc94; background-repeat : repeat-x;
1451 1451 background-image : -khtml-gradient( linear, left top, left bottom,
1452 1452 from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient(
1453 1453 top, #003b76, #00376e); background-image : -ms-linear-gradient( top,
1454 1454 #003b76, #00376e); background-image : -webkit-gradient( linear, left
1455 1455 top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1456 1456 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1457 1457 background-image : -o-linear-gradient( top, #003b76, #00376e));
1458 1458 background-image : linear-gradient( top, #003b76, #00376e); filter :
1459 1459 progid : DXImageTransform.Microsoft.gradient ( startColorstr =
1460 1460 '#003b76', endColorstr = '#00376e', GradientType = 0);
1461 1461 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1462 1462 -webkit-border-radius: 4px 4px 4px 4px;
1463 1463 -khtml-border-radius: 4px 4px 4px 4px;
1464 1464 -moz-border-radius: 4px 4px 4px 4px;
1465 1465 border-radius: 4px 4px 4px 4px;
1466 1466 background-repeat: repeat-x;
1467 1467 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1468 1468 to(#eedc94) );
1469 1469 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1470 1470 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1471 1471 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),
1472 1472 color-stop(100%, #00376e) );
1473 1473 background-image: -webkit-linear-gradient(top, #003b76, #00376e) );
1474 1474 background-image: -o-linear-gradient(top, #003b76, #00376e) );
1475 1475 background-image: linear-gradient(top, #003b76, #00376e);
1476 1476 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1477 1477 endColorstr='#00376e', GradientType=0 );
1478 1478 }
1479 1479
1480 1480 #footer div#footer-inner p {
1481 1481 padding: 15px 25px 15px 0;
1482 1482 color: #FFF;
1483 1483 font-weight: 700;
1484 1484 }
1485 1485
1486 1486 #footer div#footer-inner .footer-link {
1487 1487 float: left;
1488 1488 padding-left: 10px;
1489 1489 }
1490 1490
1491 1491 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a
1492 1492 {
1493 1493 color: #FFF;
1494 1494 }
1495 1495
1496 1496 #login div.title {
1497 1497 width: 420px;
1498 1498 clear: both;
1499 1499 overflow: hidden;
1500 1500 position: relative;
1501 1501 background-color: #eedc94; background-repeat : repeat-x;
1502 1502 background-image : -khtml-gradient( linear, left top, left bottom,
1503 1503 from( #fceec1), to( #eedc94)); background-image : -moz-linear-gradient(
1504 1504 top, #003b76, #00376e); background-image : -ms-linear-gradient( top,
1505 1505 #003b76, #00376e); background-image : -webkit-gradient( linear, left
1506 1506 top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1507 1507 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1508 1508 background-image : -o-linear-gradient( top, #003b76, #00376e));
1509 1509 background-image : linear-gradient( top, #003b76, #00376e); filter :
1510 1510 progid : DXImageTransform.Microsoft.gradient ( startColorstr =
1511 1511 '#003b76', endColorstr = '#00376e', GradientType = 0);
1512 1512 margin: 0 auto;
1513 1513 padding: 0;
1514 1514 background-repeat: repeat-x;
1515 1515 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1516 1516 to(#eedc94) );
1517 1517 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1518 1518 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1519 1519 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),
1520 1520 color-stop(100%, #00376e) );
1521 1521 background-image: -webkit-linear-gradient(top, #003b76, #00376e) );
1522 1522 background-image: -o-linear-gradient(top, #003b76, #00376e) );
1523 1523 background-image: linear-gradient(top, #003b76, #00376e);
1524 1524 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1525 1525 endColorstr='#00376e', GradientType=0 );
1526 1526 }
1527 1527
1528 1528 #login div.inner {
1529 1529 width: 380px;
1530 1530 background: #FFF url("../images/login.png") no-repeat top left;
1531 1531 border-top: none;
1532 1532 border-bottom: none;
1533 1533 margin: 0 auto;
1534 1534 padding: 20px;
1535 1535 }
1536 1536
1537 1537 #login div.form div.fields div.field div.label {
1538 1538 width: 173px;
1539 1539 float: left;
1540 1540 text-align: right;
1541 1541 margin: 2px 10px 0 0;
1542 1542 padding: 5px 0 0 5px;
1543 1543 }
1544 1544
1545 1545 #login div.form div.fields div.field div.input input {
1546 1546 width: 176px;
1547 1547 background: #FFF;
1548 1548 border-top: 1px solid #b3b3b3;
1549 1549 border-left: 1px solid #b3b3b3;
1550 1550 border-right: 1px solid #eaeaea;
1551 1551 border-bottom: 1px solid #eaeaea;
1552 1552 color: #000;
1553 1553 font-size: 11px;
1554 1554 margin: 0;
1555 1555 padding: 7px 7px 6px;
1556 1556 }
1557 1557
1558 1558 #login div.form div.fields div.buttons {
1559 1559 clear: both;
1560 1560 overflow: hidden;
1561 1561 border-top: 1px solid #DDD;
1562 1562 text-align: right;
1563 1563 margin: 0;
1564 1564 padding: 10px 0 0;
1565 1565 }
1566 1566
1567 1567 #login div.form div.links {
1568 1568 clear: both;
1569 1569 overflow: hidden;
1570 1570 margin: 10px 0 0;
1571 1571 padding: 0 0 2px;
1572 1572 }
1573 1573
1574 1574 #quick_login {
1575 1575 top: 31px;
1576 1576 background-color: rgb(0, 51, 103);
1577 1577 z-index: 999;
1578 1578 height: 150px;
1579 1579 position: absolute;
1580 1580 margin-left: -16px;
1581 1581 width: 281px;
1582 1582 -webkit-border-radius: 0px 0px 4px 4px;
1583 1583 -khtml-border-radius: 0px 0px 4px 4px;
1584 1584 -moz-border-radius: 0px 0px 4px 4px;
1585 1585 border-radius: 0px 0px 4px 4px;
1586 1586 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1587 1587 }
1588 1588
1589 1589 #quick_login .password_forgoten {
1590 1590 padding-right: 10px;
1591 1591 padding-top: 0px;
1592 1592 float: left;
1593 1593 }
1594 1594
1595 1595 #quick_login .password_forgoten a {
1596 1596 font-size: 10px
1597 1597 }
1598 1598
1599 1599 #quick_login .register {
1600 1600 padding-right: 10px;
1601 1601 padding-top: 5px;
1602 1602 float: left;
1603 1603 }
1604 1604
1605 1605 #quick_login .register a {
1606 1606 font-size: 10px
1607 1607 }
1608 1608
1609 1609 #quick_login div.form div.fields {
1610 1610 padding-top: 2px;
1611 1611 padding-left: 10px;
1612 1612 }
1613 1613
1614 1614 #quick_login div.form div.fields div.field {
1615 1615 padding: 5px;
1616 1616 }
1617 1617
1618 1618 #quick_login div.form div.fields div.field div.label label {
1619 1619 color: #fff;
1620 1620 padding-bottom: 3px;
1621 1621 }
1622 1622
1623 1623 #quick_login div.form div.fields div.field div.input input {
1624 1624 width: 236px;
1625 1625 background: #FFF;
1626 1626 border-top: 1px solid #b3b3b3;
1627 1627 border-left: 1px solid #b3b3b3;
1628 1628 border-right: 1px solid #eaeaea;
1629 1629 border-bottom: 1px solid #eaeaea;
1630 1630 color: #000;
1631 1631 font-size: 11px;
1632 1632 margin: 0;
1633 1633 padding: 5px 7px 4px;
1634 1634 }
1635 1635
1636 1636 #quick_login div.form div.fields div.buttons {
1637 1637 clear: both;
1638 1638 overflow: hidden;
1639 1639 text-align: right;
1640 1640 margin: 0;
1641 1641 padding: 10px 14px 0px 5px;
1642 1642 }
1643 1643
1644 1644 #quick_login div.form div.links {
1645 1645 clear: both;
1646 1646 overflow: hidden;
1647 1647 margin: 10px 0 0;
1648 1648 padding: 0 0 2px;
1649 1649 }
1650 1650
1651 1651 #register div.title {
1652 1652 clear: both;
1653 1653 overflow: hidden;
1654 1654 position: relative;
1655 1655 background-color: #eedc94;
1656 1656 background-repeat: repeat-x;
1657 1657 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
1658 1658 to(#eedc94) );
1659 1659 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1660 1660 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1661 1661 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),
1662 1662 color-stop(100%, #00376e) );
1663 1663 background-image: -webkit-linear-gradient(top, #003b76, #00376e) );
1664 1664 background-image: -o-linear-gradient(top, #003b76, #00376e) );
1665 1665 background-image: linear-gradient(top, #003b76, #00376e);
1666 1666 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
1667 1667 endColorstr='#00376e', GradientType=0 );
1668 1668 margin: 0 auto;
1669 1669 padding: 0;
1670 1670 }
1671 1671
1672 1672 #register div.inner {
1673 1673 background: #FFF;
1674 1674 border-top: none;
1675 1675 border-bottom: none;
1676 1676 margin: 0 auto;
1677 1677 padding: 20px;
1678 1678 }
1679 1679
1680 1680 #register div.form div.fields div.field div.label {
1681 1681 width: 135px;
1682 1682 float: left;
1683 1683 text-align: right;
1684 1684 margin: 2px 10px 0 0;
1685 1685 padding: 5px 0 0 5px;
1686 1686 }
1687 1687
1688 1688 #register div.form div.fields div.field div.input input {
1689 1689 width: 300px;
1690 1690 background: #FFF;
1691 1691 border-top: 1px solid #b3b3b3;
1692 1692 border-left: 1px solid #b3b3b3;
1693 1693 border-right: 1px solid #eaeaea;
1694 1694 border-bottom: 1px solid #eaeaea;
1695 1695 color: #000;
1696 1696 font-size: 11px;
1697 1697 margin: 0;
1698 1698 padding: 7px 7px 6px;
1699 1699 }
1700 1700
1701 1701 #register div.form div.fields div.buttons {
1702 1702 clear: both;
1703 1703 overflow: hidden;
1704 1704 border-top: 1px solid #DDD;
1705 1705 text-align: left;
1706 1706 margin: 0;
1707 1707 padding: 10px 0 0 150px;
1708 1708 }
1709 1709
1710 1710 #register div.form div.activation_msg {
1711 1711 padding-top: 4px;
1712 1712 padding-bottom: 4px;
1713 1713 }
1714 1714
1715 1715 #journal .journal_day {
1716 1716 font-size: 20px;
1717 1717 padding: 10px 0px;
1718 1718 border-bottom: 2px solid #DDD;
1719 1719 margin-left: 10px;
1720 1720 margin-right: 10px;
1721 1721 }
1722 1722
1723 1723 #journal .journal_container {
1724 1724 padding: 5px;
1725 1725 clear: both;
1726 1726 margin: 0px 5px 0px 10px;
1727 1727 }
1728 1728
1729 1729 #journal .journal_action_container {
1730 1730 padding-left: 38px;
1731 1731 }
1732 1732
1733 1733 #journal .journal_user {
1734 1734 color: #747474;
1735 1735 font-size: 14px;
1736 1736 font-weight: bold;
1737 1737 height: 30px;
1738 1738 }
1739 1739
1740 1740 #journal .journal_icon {
1741 1741 clear: both;
1742 1742 float: left;
1743 1743 padding-right: 4px;
1744 1744 padding-top: 3px;
1745 1745 }
1746 1746
1747 1747 #journal .journal_action {
1748 1748 padding-top: 4px;
1749 1749 min-height: 2px;
1750 1750 float: left
1751 1751 }
1752 1752
1753 1753 #journal .journal_action_params {
1754 1754 clear: left;
1755 1755 padding-left: 22px;
1756 1756 }
1757 1757
1758 1758 #journal .journal_repo {
1759 1759 float: left;
1760 1760 margin-left: 6px;
1761 1761 padding-top: 3px;
1762 1762 }
1763 1763
1764 1764 #journal .date {
1765 1765 clear: both;
1766 1766 color: #777777;
1767 1767 font-size: 11px;
1768 1768 padding-left: 22px;
1769 1769 }
1770 1770
1771 1771 #journal .journal_repo .journal_repo_name {
1772 1772 font-weight: bold;
1773 1773 font-size: 1.1em;
1774 1774 }
1775 1775
1776 1776 #journal .compare_view {
1777 1777 padding: 5px 0px 5px 0px;
1778 1778 width: 95px;
1779 1779 }
1780 1780
1781 1781 .journal_highlight {
1782 1782 font-weight: bold;
1783 1783 padding: 0 2px;
1784 1784 vertical-align: bottom;
1785 1785 }
1786 1786
1787 1787 .trending_language_tbl,.trending_language_tbl td {
1788 1788 border: 0 !important;
1789 1789 margin: 0 !important;
1790 1790 padding: 0 !important;
1791 1791 }
1792 1792
1793 .trending_language_tbl,.trending_language_tbl tr {
1794 border-spacing: 1px;
1795 }
1796
1793 1797 .trending_language {
1794 1798 background-color: #003367;
1795 1799 color: #FFF;
1796 1800 display: block;
1797 1801 min-width: 20px;
1798 1802 text-decoration: none;
1799 1803 height: 12px;
1800 margin-bottom: 4px;
1804 margin-bottom: 0px;
1801 1805 margin-left: 5px;
1802 1806 white-space: pre;
1803 1807 padding: 3px;
1804 1808 }
1805 1809
1806 1810 h3.files_location {
1807 1811 font-size: 1.8em;
1808 1812 font-weight: 700;
1809 1813 border-bottom: none !important;
1810 1814 margin: 10px 0 !important;
1811 1815 }
1812 1816
1813 1817 #files_data dl dt {
1814 1818 float: left;
1815 1819 width: 115px;
1816 1820 margin: 0 !important;
1817 1821 padding: 5px;
1818 1822 }
1819 1823
1820 1824 #files_data dl dd {
1821 1825 margin: 0 !important;
1822 1826 padding: 5px !important;
1823 1827 }
1824 1828
1825 1829 #changeset_content {
1826 1830 border: 1px solid #CCC;
1827 1831 padding: 5px;
1828 1832 }
1829 1833
1830 1834 #changeset_compare_view_content {
1831 1835 border: 1px solid #CCC;
1832 1836 padding: 5px;
1833 1837 }
1834 1838
1835 1839 #changeset_content .container {
1836 1840 min-height: 120px;
1837 1841 font-size: 1.2em;
1838 1842 overflow: hidden;
1839 1843 }
1840 1844
1841 1845 #changeset_compare_view_content .compare_view_commits {
1842 1846 width: auto !important;
1843 1847 }
1844 1848
1845 1849 #changeset_compare_view_content .compare_view_commits td {
1846 1850 padding: 0px 0px 0px 12px !important;
1847 1851 }
1848 1852
1849 1853 #changeset_content .container .right {
1850 1854 float: right;
1851 1855 width: 25%;
1852 1856 text-align: right;
1853 1857 }
1854 1858
1855 1859 #changeset_content .container .left .message {
1856 1860 font-style: italic;
1857 1861 color: #556CB5;
1858 1862 white-space: pre-wrap;
1859 1863 }
1860 1864
1861 1865 .cs_files .cur_cs {
1862 1866 margin: 10px 2px;
1863 1867 font-weight: bold;
1864 1868 }
1865 1869
1866 1870 .cs_files .node {
1867 1871 float: left;
1868 1872 }
1869 1873
1870 1874 .cs_files .changes {
1871 1875 float: right;
1872 1876 color:#003367;
1873 1877
1874 1878 }
1875 1879
1876 1880 .cs_files .changes .added {
1877 1881 background-color: #BBFFBB;
1878 1882 float: left;
1879 1883 text-align: center;
1880 1884 font-size: 9px;
1881 1885 padding: 2px 0px 2px 0px;
1882 1886 }
1883 1887
1884 1888 .cs_files .changes .deleted {
1885 1889 background-color: #FF8888;
1886 1890 float: left;
1887 1891 text-align: center;
1888 1892 font-size: 9px;
1889 1893 padding: 2px 0px 2px 0px;
1890 1894 }
1891 1895
1892 1896 .cs_files .cs_added {
1893 1897 background: url("../images/icons/page_white_add.png") no-repeat scroll
1894 1898 3px;
1895 1899 height: 16px;
1896 1900 padding-left: 20px;
1897 1901 margin-top: 7px;
1898 1902 text-align: left;
1899 1903 }
1900 1904
1901 1905 .cs_files .cs_changed {
1902 1906 background: url("../images/icons/page_white_edit.png") no-repeat scroll
1903 1907 3px;
1904 1908 height: 16px;
1905 1909 padding-left: 20px;
1906 1910 margin-top: 7px;
1907 1911 text-align: left;
1908 1912 }
1909 1913
1910 1914 .cs_files .cs_removed {
1911 1915 background: url("../images/icons/page_white_delete.png") no-repeat
1912 1916 scroll 3px;
1913 1917 height: 16px;
1914 1918 padding-left: 20px;
1915 1919 margin-top: 7px;
1916 1920 text-align: left;
1917 1921 }
1918 1922
1919 1923 #graph {
1920 1924 overflow: hidden;
1921 1925 }
1922 1926
1923 1927 #graph_nodes {
1924 1928 float: left;
1925 1929 margin-right: -6px;
1926 1930 margin-top: 0px;
1927 1931 }
1928 1932
1929 1933 #graph_content {
1930 1934 width: 800px;
1931 1935 float: left;
1932 1936 }
1933 1937
1934 1938 #graph_content .container_header {
1935 1939 border: 1px solid #CCC;
1936 1940 padding: 10px;
1937 1941 height: 45px;
1938 1942 -webkit-border-radius: 6px 6px 0px 0px;
1939 1943 -moz-border-radius: 6px 6px 0px 0px;
1940 1944 border-radius: 6px 6px 0px 0px;
1941 1945 }
1942 1946
1943 1947 #graph_content #rev_range_container {
1944 1948 padding: 10px 0px;
1945 1949 clear: both;
1946 1950 }
1947 1951
1948 1952 #graph_content .container {
1949 1953 border-bottom: 1px solid #CCC;
1950 1954 border-left: 1px solid #CCC;
1951 1955 border-right: 1px solid #CCC;
1952 1956 min-height: 70px;
1953 1957 overflow: hidden;
1954 1958 font-size: 1.2em;
1955 1959 }
1956 1960
1957 1961 #graph_content .container .right {
1958 1962 float: right;
1959 1963 width: 28%;
1960 1964 text-align: right;
1961 1965 padding-bottom: 5px;
1962 1966 }
1963 1967
1964 1968 #graph_content .container .left .date {
1965 1969 font-weight: 700;
1966 1970 padding-bottom: 5px;
1967 1971 }
1968 1972
1969 1973 #graph_content .container .left .date span {
1970 1974 vertical-align: text-top;
1971 1975 }
1972 1976
1973 1977 #graph_content .container .left .author {
1974 1978 height: 22px;
1975 1979 }
1976 1980
1977 1981 #graph_content .container .left .author .user {
1978 1982 color: #444444;
1979 1983 float: left;
1980 1984 font-size: 12px;
1981 1985 margin-left: -4px;
1982 1986 margin-top: 4px;
1983 1987 }
1984 1988
1985 1989 #graph_content .container .left .message {
1986 1990 font-size: 100%;
1987 1991 padding-top: 3px;
1988 1992 white-space: pre-wrap;
1989 1993 }
1990 1994
1991 1995 #graph_content .container .left .message a:hover{
1992 1996 text-decoration: none;
1993 1997 }
1994 1998
1995 1999 .right div {
1996 2000 clear: both;
1997 2001 }
1998 2002
1999 2003 .right .changes .changed_total {
2000 2004 border: 0px solid #DDD;
2001 2005 display: block;
2002 2006 float: right;
2003 2007 text-align: center;
2004 2008 min-width: 45px;
2005 2009 cursor: pointer;
2006 2010 background: #FD8;
2007 2011 font-weight: bold;
2008 2012 -webkit-border-radius: 0px 0px 0px 6px;
2009 2013 -moz-border-radius: 0px 0px 0px 6px;
2010 2014 border-radius: 0px 0px 0px 6px;
2011 2015 padding: 2px;
2012 2016 }
2013 2017
2014 2018 .right .changes .added,.changed,.removed {
2015 2019 border: 1px solid #DDD;
2016 2020 display: block;
2017 2021 float: right;
2018 2022 text-align: center;
2019 2023 min-width: 15px;
2020 2024 cursor: help;
2021 2025 }
2022 2026
2023 2027 .right .changes .large {
2024 2028 border: 1px solid #DDD;
2025 2029 display: block;
2026 2030 float: right;
2027 2031 text-align: center;
2028 2032 min-width: 45px;
2029 2033 cursor: help;
2030 2034 background: #54A9F7;
2031 2035 }
2032 2036
2033 2037 .right .changes .added {
2034 2038 background: #BFB;
2035 2039 }
2036 2040
2037 2041 .right .changes .changed {
2038 2042 background: #FD8;
2039 2043 }
2040 2044
2041 2045 .right .changes .removed {
2042 2046 background: #F88;
2043 2047 }
2044 2048
2045 2049 .right .merge {
2046 2050 vertical-align: top;
2047 2051 font-size: 0.75em;
2048 2052 font-weight: 700;
2049 2053 }
2050 2054
2051 2055 .right .parent {
2052 2056 font-size: 90%;
2053 2057 font-family: monospace;
2054 2058 padding: 2px 2px 2px 2px;
2055 2059 }
2056 2060 .right .logtags{
2057 2061 padding: 2px 2px 2px 2px;
2058 2062 }
2059 2063 .right .logtags .branchtag,.logtags .branchtag {
2060 2064 padding: 1px 3px 2px;
2061 2065 background-color: #bfbfbf;
2062 2066 font-size: 9.75px;
2063 2067 font-weight: bold;
2064 2068 color: #ffffff;
2065 2069 text-transform: uppercase;
2066 2070 white-space: nowrap;
2067 2071 -webkit-border-radius: 3px;
2068 2072 -moz-border-radius: 3px;
2069 2073 border-radius: 3px;
2070 2074 padding-left:4px;
2071 2075 }
2072 2076 .right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
2073 2077 text-decoration: none;
2074 2078 }
2075 2079 .right .logtags .tagtag,.logtags .tagtag {
2076 2080 padding: 1px 3px 2px;
2077 2081 background-color: #62cffc;
2078 2082 font-size: 9.75px;
2079 2083 font-weight: bold;
2080 2084 color: #ffffff;
2081 2085 text-transform: uppercase;
2082 2086 white-space: nowrap;
2083 2087 -webkit-border-radius: 3px;
2084 2088 -moz-border-radius: 3px;
2085 2089 border-radius: 3px;
2086 2090 }
2087 2091 .right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
2088 2092 text-decoration: none;
2089 2093 }
2090 2094 div.browserblock {
2091 2095 overflow: hidden;
2092 2096 border: 1px solid #ccc;
2093 2097 background: #f8f8f8;
2094 2098 font-size: 100%;
2095 2099 line-height: 125%;
2096 2100 padding: 0;
2097 2101 }
2098 2102
2099 2103 div.browserblock .browser-header {
2100 2104 background: #FFF;
2101 2105 padding: 10px 0px 15px 0px;
2102 2106 width: 100%;
2103 2107 }
2104 2108
2105 2109 div.browserblock .browser-nav {
2106 2110 float: left
2107 2111 }
2108 2112
2109 2113 div.browserblock .browser-branch {
2110 2114 float: left;
2111 2115 }
2112 2116
2113 2117 div.browserblock .browser-branch label {
2114 2118 color: #4A4A4A;
2115 2119 vertical-align: text-top;
2116 2120 }
2117 2121
2118 2122 div.browserblock .browser-header span {
2119 2123 margin-left: 5px;
2120 2124 font-weight: 700;
2121 2125 }
2122 2126
2123 2127 div.browserblock .browser-search {
2124 2128 clear: both;
2125 2129 padding: 8px 8px 0px 5px;
2126 2130 height: 20px;
2127 2131 }
2128 2132
2129 2133 div.browserblock #node_filter_box {
2130 2134
2131 2135 }
2132 2136
2133 2137 div.browserblock .search_activate {
2134 2138 float: left
2135 2139 }
2136 2140
2137 2141 div.browserblock .add_node {
2138 2142 float: left;
2139 2143 padding-left: 5px;
2140 2144 }
2141 2145
2142 2146 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
2143 2147 {
2144 2148 text-decoration: none !important;
2145 2149 }
2146 2150
2147 2151 div.browserblock .browser-body {
2148 2152 background: #EEE;
2149 2153 border-top: 1px solid #CCC;
2150 2154 }
2151 2155
2152 2156 table.code-browser {
2153 2157 border-collapse: collapse;
2154 2158 width: 100%;
2155 2159 }
2156 2160
2157 2161 table.code-browser tr {
2158 2162 margin: 3px;
2159 2163 }
2160 2164
2161 2165 table.code-browser thead th {
2162 2166 background-color: #EEE;
2163 2167 height: 20px;
2164 2168 font-size: 1.1em;
2165 2169 font-weight: 700;
2166 2170 text-align: left;
2167 2171 padding-left: 10px;
2168 2172 }
2169 2173
2170 2174 table.code-browser tbody td {
2171 2175 padding-left: 10px;
2172 2176 height: 20px;
2173 2177 }
2174 2178
2175 2179 table.code-browser .browser-file {
2176 2180 background: url("../images/icons/document_16.png") no-repeat scroll 3px;
2177 2181 height: 16px;
2178 2182 padding-left: 20px;
2179 2183 text-align: left;
2180 2184 }
2181 2185
2182 2186 .diffblock .changeset_file {
2183 2187 background: url("../images/icons/file.png") no-repeat scroll 3px;
2184 2188 height: 16px;
2185 2189 padding-left: 22px;
2186 2190 text-align: left;
2187 2191 font-size: 14px;
2188 2192 }
2189 2193
2190 2194 .diffblock .changeset_header {
2191 2195 margin-left: 6px !important;
2192 2196 }
2193 2197
2194 2198 table.code-browser .browser-dir {
2195 2199 background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
2196 2200 height: 16px;
2197 2201 padding-left: 20px;
2198 2202 text-align: left;
2199 2203 }
2200 2204
2201 2205 .box .search {
2202 2206 clear: both;
2203 2207 overflow: hidden;
2204 2208 margin: 0;
2205 2209 padding: 0 20px 10px;
2206 2210 }
2207 2211
2208 2212 .box .search div.search_path {
2209 2213 background: none repeat scroll 0 0 #EEE;
2210 2214 border: 1px solid #CCC;
2211 2215 color: blue;
2212 2216 margin-bottom: 10px;
2213 2217 padding: 10px 0;
2214 2218 }
2215 2219
2216 2220 .box .search div.search_path div.link {
2217 2221 font-weight: 700;
2218 2222 margin-left: 25px;
2219 2223 }
2220 2224
2221 2225 .box .search div.search_path div.link a {
2222 2226 color: #003367;
2223 2227 cursor: pointer;
2224 2228 text-decoration: none;
2225 2229 }
2226 2230
2227 2231 #path_unlock {
2228 2232 color: red;
2229 2233 font-size: 1.2em;
2230 2234 padding-left: 4px;
2231 2235 }
2232 2236
2233 2237 .info_box span {
2234 2238 margin-left: 3px;
2235 2239 margin-right: 3px;
2236 2240 }
2237 2241
2238 2242 .info_box .rev {
2239 2243 color: #003367;
2240 2244 font-size: 1.6em;
2241 2245 font-weight: bold;
2242 2246 vertical-align: sub;
2243 2247 }
2244 2248
2245 2249 .info_box input#at_rev,.info_box input#size {
2246 2250 background: #FFF;
2247 2251 border-top: 1px solid #b3b3b3;
2248 2252 border-left: 1px solid #b3b3b3;
2249 2253 border-right: 1px solid #eaeaea;
2250 2254 border-bottom: 1px solid #eaeaea;
2251 2255 color: #000;
2252 2256 font-size: 12px;
2253 2257 margin: 0;
2254 2258 padding: 1px 5px 1px;
2255 2259 }
2256 2260
2257 2261 .info_box input#view {
2258 2262 text-align: center;
2259 2263 padding: 4px 3px 2px 2px;
2260 2264 }
2261 2265
2262 2266 .yui-overlay,.yui-panel-container {
2263 2267 visibility: hidden;
2264 2268 position: absolute;
2265 2269 z-index: 2;
2266 2270 }
2267 2271
2268 2272 .yui-tt {
2269 2273 visibility: hidden;
2270 2274 position: absolute;
2271 2275 color: #666;
2272 2276 background-color: #FFF;
2273 2277 border: 2px solid #003367;
2274 2278 font: 100% sans-serif;
2275 2279 width: auto;
2276 2280 opacity: 1px;
2277 2281 padding: 8px;
2278 2282 white-space: pre-wrap;
2279 2283 -webkit-border-radius: 8px 8px 8px 8px;
2280 2284 -khtml-border-radius: 8px 8px 8px 8px;
2281 2285 -moz-border-radius: 8px 8px 8px 8px;
2282 2286 border-radius: 8px 8px 8px 8px;
2283 2287 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
2284 2288 }
2285 2289
2286 2290 .ac {
2287 2291 vertical-align: top;
2288 2292 }
2289 2293
2290 2294 .ac .yui-ac {
2291 2295 position: relative;
2292 2296 font-size: 100%;
2293 2297 }
2294 2298
2295 2299 .ac .perm_ac {
2296 2300 width: 15em;
2297 2301 }
2298 2302
2299 2303 .ac .yui-ac-input {
2300 2304 width: 100%;
2301 2305 }
2302 2306
2303 2307 .ac .yui-ac-container {
2304 2308 position: absolute;
2305 2309 top: 1.6em;
2306 2310 width: 100%;
2307 2311 }
2308 2312
2309 2313 .ac .yui-ac-content {
2310 2314 position: absolute;
2311 2315 width: 100%;
2312 2316 border: 1px solid gray;
2313 2317 background: #fff;
2314 2318 overflow: hidden;
2315 2319 z-index: 9050;
2316 2320 }
2317 2321
2318 2322 .ac .yui-ac-shadow {
2319 2323 position: absolute;
2320 2324 width: 100%;
2321 2325 background: #000;
2322 2326 -moz-opacity: 0.1px;
2323 2327 opacity: .10;
2324 2328 filter: alpha(opacity = 10);
2325 2329 z-index: 9049;
2326 2330 margin: .3em;
2327 2331 }
2328 2332
2329 2333 .ac .yui-ac-content ul {
2330 2334 width: 100%;
2331 2335 margin: 0;
2332 2336 padding: 0;
2333 2337 }
2334 2338
2335 2339 .ac .yui-ac-content li {
2336 2340 cursor: default;
2337 2341 white-space: nowrap;
2338 2342 margin: 0;
2339 2343 padding: 2px 5px;
2340 2344 }
2341 2345
2342 2346 .ac .yui-ac-content li.yui-ac-prehighlight {
2343 2347 background: #B3D4FF;
2344 2348 }
2345 2349
2346 2350 .ac .yui-ac-content li.yui-ac-highlight {
2347 2351 background: #556CB5;
2348 2352 color: #FFF;
2349 2353 }
2350 2354
2351 2355 .follow {
2352 2356 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
2353 2357 height: 16px;
2354 2358 width: 20px;
2355 2359 cursor: pointer;
2356 2360 display: block;
2357 2361 float: right;
2358 2362 margin-top: 2px;
2359 2363 }
2360 2364
2361 2365 .following {
2362 2366 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2363 2367 height: 16px;
2364 2368 width: 20px;
2365 2369 cursor: pointer;
2366 2370 display: block;
2367 2371 float: right;
2368 2372 margin-top: 2px;
2369 2373 }
2370 2374
2371 2375 .currently_following {
2372 2376 padding-left: 10px;
2373 2377 padding-bottom: 5px;
2374 2378 }
2375 2379
2376 2380 .add_icon {
2377 2381 background: url("../images/icons/add.png") no-repeat scroll 3px;
2378 2382 padding-left: 20px;
2379 2383 padding-top: 0px;
2380 2384 text-align: left;
2381 2385 }
2382 2386
2383 2387 .edit_icon {
2384 2388 background: url("../images/icons/folder_edit.png") no-repeat scroll 3px;
2385 2389 padding-left: 20px;
2386 2390 padding-top: 0px;
2387 2391 text-align: left;
2388 2392 }
2389 2393
2390 2394 .delete_icon {
2391 2395 background: url("../images/icons/delete.png") no-repeat scroll 3px;
2392 2396 padding-left: 20px;
2393 2397 padding-top: 0px;
2394 2398 text-align: left;
2395 2399 }
2396 2400
2397 2401 .refresh_icon {
2398 2402 background: url("../images/icons/arrow_refresh.png") no-repeat scroll
2399 2403 3px;
2400 2404 padding-left: 20px;
2401 2405 padding-top: 0px;
2402 2406 text-align: left;
2403 2407 }
2404 2408
2405 2409 .pull_icon {
2406 2410 background: url("../images/icons/connect.png") no-repeat scroll 3px;
2407 2411 padding-left: 20px;
2408 2412 padding-top: 0px;
2409 2413 text-align: left;
2410 2414 }
2411 2415
2412 2416 .rss_icon {
2413 2417 background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
2414 2418 padding-left: 20px;
2415 2419 padding-top: 0px;
2416 2420 text-align: left;
2417 2421 }
2418 2422
2419 2423 .atom_icon {
2420 2424 background: url("../images/icons/atom.png") no-repeat scroll 3px;
2421 2425 padding-left: 20px;
2422 2426 padding-top: 0px;
2423 2427 text-align: left;
2424 2428 }
2425 2429
2426 2430 .archive_icon {
2427 2431 background: url("../images/icons/compress.png") no-repeat scroll 3px;
2428 2432 padding-left: 20px;
2429 2433 text-align: left;
2430 2434 padding-top: 1px;
2431 2435 }
2432 2436
2433 2437 .start_following_icon {
2434 2438 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
2435 2439 padding-left: 20px;
2436 2440 text-align: left;
2437 2441 padding-top: 0px;
2438 2442 }
2439 2443
2440 2444 .stop_following_icon {
2441 2445 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2442 2446 padding-left: 20px;
2443 2447 text-align: left;
2444 2448 padding-top: 0px;
2445 2449 }
2446 2450
2447 2451 .action_button {
2448 2452 border: 0;
2449 2453 display: inline;
2450 2454 }
2451 2455
2452 2456 .action_button:hover {
2453 2457 border: 0;
2454 2458 text-decoration: underline;
2455 2459 cursor: pointer;
2456 2460 }
2457 2461
2458 2462 #switch_repos {
2459 2463 position: absolute;
2460 2464 height: 25px;
2461 2465 z-index: 1;
2462 2466 }
2463 2467
2464 2468 #switch_repos select {
2465 2469 min-width: 150px;
2466 2470 max-height: 250px;
2467 2471 z-index: 1;
2468 2472 }
2469 2473
2470 2474 .breadcrumbs {
2471 2475 border: medium none;
2472 2476 color: #FFF;
2473 2477 float: left;
2474 2478 text-transform: uppercase;
2475 2479 font-weight: 700;
2476 2480 font-size: 14px;
2477 2481 margin: 0;
2478 2482 padding: 11px 0 11px 10px;
2479 2483 }
2480 2484
2481 2485 .breadcrumbs a {
2482 2486 color: #FFF;
2483 2487 }
2484 2488
2485 2489 .flash_msg {
2486 2490
2487 2491 }
2488 2492
2489 2493 .flash_msg ul {
2490 2494
2491 2495 }
2492 2496
2493 2497 .error_msg {
2494 2498 background-color: #c43c35;
2495 2499 background-repeat: repeat-x;
2496 2500 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b),
2497 2501 to(#c43c35) );
2498 2502 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
2499 2503 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
2500 2504 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b),
2501 2505 color-stop(100%, #c43c35) );
2502 2506 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
2503 2507 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
2504 2508 background-image: linear-gradient(top, #ee5f5b, #c43c35);
2505 2509 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',
2506 2510 endColorstr='#c43c35', GradientType=0 );
2507 2511 border-color: #c43c35 #c43c35 #882a25;
2508 2512 }
2509 2513
2510 2514 .warning_msg {
2511 2515 color: #404040 !important;
2512 2516 background-color: #eedc94;
2513 2517 background-repeat: repeat-x;
2514 2518 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
2515 2519 to(#eedc94) );
2516 2520 background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
2517 2521 background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
2518 2522 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1),
2519 2523 color-stop(100%, #eedc94) );
2520 2524 background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
2521 2525 background-image: -o-linear-gradient(top, #fceec1, #eedc94);
2522 2526 background-image: linear-gradient(top, #fceec1, #eedc94);
2523 2527 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1',
2524 2528 endColorstr='#eedc94', GradientType=0 );
2525 2529 border-color: #eedc94 #eedc94 #e4c652;
2526 2530 }
2527 2531
2528 2532 .success_msg {
2529 2533 background-color: #57a957;
2530 2534 background-repeat: repeat-x !important;
2531 2535 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462),
2532 2536 to(#57a957) );
2533 2537 background-image: -moz-linear-gradient(top, #62c462, #57a957);
2534 2538 background-image: -ms-linear-gradient(top, #62c462, #57a957);
2535 2539 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462),
2536 2540 color-stop(100%, #57a957) );
2537 2541 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
2538 2542 background-image: -o-linear-gradient(top, #62c462, #57a957);
2539 2543 background-image: linear-gradient(top, #62c462, #57a957);
2540 2544 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462',
2541 2545 endColorstr='#57a957', GradientType=0 );
2542 2546 border-color: #57a957 #57a957 #3d773d;
2543 2547 }
2544 2548
2545 2549 .notice_msg {
2546 2550 background-color: #339bb9;
2547 2551 background-repeat: repeat-x;
2548 2552 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de),
2549 2553 to(#339bb9) );
2550 2554 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
2551 2555 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
2552 2556 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de),
2553 2557 color-stop(100%, #339bb9) );
2554 2558 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
2555 2559 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
2556 2560 background-image: linear-gradient(top, #5bc0de, #339bb9);
2557 2561 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de',
2558 2562 endColorstr='#339bb9', GradientType=0 );
2559 2563 border-color: #339bb9 #339bb9 #22697d;
2560 2564 }
2561 2565
2562 2566 .success_msg,.error_msg,.notice_msg,.warning_msg {
2563 2567 font-size: 12px;
2564 2568 font-weight: 700;
2565 2569 min-height: 14px;
2566 2570 line-height: 14px;
2567 2571 margin-bottom: 10px;
2568 2572 margin-top: 0;
2569 2573 display: block;
2570 2574 overflow: auto;
2571 2575 padding: 6px 10px 6px 10px;
2572 2576 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
2573 2577 position: relative;
2574 2578 color: #FFF;
2575 2579 border-width: 1px;
2576 2580 border-style: solid;
2577 2581 -webkit-border-radius: 4px;
2578 2582 -moz-border-radius: 4px;
2579 2583 border-radius: 4px;
2580 2584 -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
2581 2585 -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
2582 2586 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
2583 2587 }
2584 2588
2585 2589 #msg_close {
2586 2590 background: transparent url("../icons/cross_grey_small.png") no-repeat
2587 2591 scroll 0 0;
2588 2592 cursor: pointer;
2589 2593 height: 16px;
2590 2594 position: absolute;
2591 2595 right: 5px;
2592 2596 top: 5px;
2593 2597 width: 16px;
2594 2598 }
2595 2599
2596 2600 div#legend_container table,div#legend_choices table {
2597 2601 width: auto !important;
2598 2602 }
2599 2603
2600 2604 table#permissions_manage {
2601 2605 width: 0 !important;
2602 2606 }
2603 2607
2604 2608 table#permissions_manage span.private_repo_msg {
2605 2609 font-size: 0.8em;
2606 2610 opacity: 0.6px;
2607 2611 }
2608 2612
2609 2613 table#permissions_manage td.private_repo_msg {
2610 2614 font-size: 0.8em;
2611 2615 }
2612 2616
2613 2617 table#permissions_manage tr#add_perm_input td {
2614 2618 vertical-align: middle;
2615 2619 }
2616 2620
2617 2621 div.gravatar {
2618 2622 background-color: #FFF;
2619 2623 border: 0px solid #D0D0D0;
2620 2624 float: left;
2621 2625 margin-right: 0.7em;
2622 2626 padding: 2px 2px 2px 2px;
2623 2627 line-height:0;
2624 2628 -webkit-border-radius: 6px;
2625 2629 -khtml-border-radius: 6px;
2626 2630 -moz-border-radius: 6px;
2627 2631 border-radius: 6px;
2628 2632 }
2629 2633
2630 2634 div.gravatar img {
2631 2635 -webkit-border-radius: 4px;
2632 2636 -khtml-border-radius: 4px;
2633 2637 -moz-border-radius: 4px;
2634 2638 border-radius: 4px;
2635 2639 }
2636 2640
2637 2641 #header,#content,#footer {
2638 2642 min-width: 978px;
2639 2643 }
2640 2644
2641 2645 #content {
2642 2646 clear: both;
2643 2647 overflow: hidden;
2644 2648 padding: 14px 10px;
2645 2649 }
2646 2650
2647 2651 #content div.box div.title div.search {
2648 2652
2649 2653 border-left: 1px solid #316293;
2650 2654 }
2651 2655
2652 2656 #content div.box div.title div.search div.input input {
2653 2657 border: 1px solid #316293;
2654 2658 }
2655 2659
2656 2660 .ui-button-small a:hover {
2657 2661
2658 2662 }
2659 2663
2660 2664 input.ui-button-small,.ui-button-small {
2661 2665 background: #e5e3e3 url("../images/button.png") repeat-x !important;
2662 2666 border-top: 1px solid #DDD !important;
2663 2667 border-left: 1px solid #c6c6c6 !important;
2664 2668 border-right: 1px solid #DDD !important;
2665 2669 border-bottom: 1px solid #c6c6c6 !important;
2666 2670 color: #515151 !important;
2667 2671 outline: none !important;
2668 2672 margin: 0 !important;
2669 2673 -webkit-border-radius: 4px 4px 4px 4px !important;
2670 2674 -khtml-border-radius: 4px 4px 4px 4px !important;
2671 2675 -moz-border-radius: 4px 4px 4px 4px !important;
2672 2676 border-radius: 4px 4px 4px 4px !important;
2673 2677 box-shadow: 0 1px 0 #ececec !important;
2674 2678 cursor: pointer !important;
2675 2679 padding: 3px 3px 3px 3px;
2676 2680 }
2677 2681
2678 2682 input.ui-button-small.xsmall,.ui-button-small.xsmall{
2679 2683 padding: 1px 2px 1px 1px;
2680 2684 }
2681 2685
2682 2686 input.ui-button-small:hover,.ui-button-small:hover {
2683 2687 background: #b4b4b4 url("../images/button_selected.png") repeat-x
2684 2688 !important;
2685 2689 border-top: 1px solid #ccc !important;
2686 2690 border-left: 1px solid #bebebe !important;
2687 2691 border-right: 1px solid #b1b1b1 !important;
2688 2692 border-bottom: 1px solid #afafaf !important;
2689 2693 text-decoration: none;
2690 2694 }
2691 2695
2692 2696 input.ui-button-small-blue,.ui-button-small-blue {
2693 2697 background: #4e85bb url("../images/button_highlight.png") repeat-x;
2694 2698 border-top: 1px solid #5c91a4;
2695 2699 border-left: 1px solid #2a6f89;
2696 2700 border-right: 1px solid #2b7089;
2697 2701 border-bottom: 1px solid #1a6480;
2698 2702 color: #fff;
2699 2703 -webkit-border-radius: 4px 4px 4px 4px;
2700 2704 -khtml-border-radius: 4px 4px 4px 4px;
2701 2705 -moz-border-radius: 4px 4px 4px 4px;
2702 2706 border-radius: 4px 4px 4px 4px;
2703 2707 box-shadow: 0 1px 0 #ececec;
2704 2708 cursor: pointer;
2705 2709 padding: 0px 2px 1px 2px;
2706 2710 }
2707 2711
2708 2712 input.ui-button-small-blue:hover {
2709 2713
2710 2714 }
2711 2715
2712 2716 ins,div.options a:hover {
2713 2717 text-decoration: none;
2714 2718 }
2715 2719
2716 2720 img,#header #header-inner #quick li a:hover span.normal,#header #header-inner #quick li ul li.last,#content div.box div.form div.fields div.field div.textarea table td table td a,#clone_url
2717 2721 {
2718 2722 border: none;
2719 2723 }
2720 2724
2721 2725 img.icon,.right .merge img {
2722 2726 vertical-align: bottom;
2723 2727 }
2724 2728
2725 2729 #header ul#logged-user,#content div.box div.title ul.links,#content div.box div.message div.dismiss,#content div.box div.traffic div.legend ul
2726 2730 {
2727 2731 float: right;
2728 2732 margin: 0;
2729 2733 padding: 0;
2730 2734 }
2731 2735
2732 2736 #header #header-inner #home,#header #header-inner #logo,#content div.box ul.left,#content div.box ol.left,#content div.box div.pagination-left,div#commit_history,div#legend_data,div#legend_container,div#legend_choices
2733 2737 {
2734 2738 float: left;
2735 2739 }
2736 2740
2737 2741 #header #header-inner #quick li:hover ul ul,#header #header-inner #quick li:hover ul ul ul,#header #header-inner #quick li:hover ul ul ul ul,#content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow
2738 2742 {
2739 2743 display: none;
2740 2744 }
2741 2745
2742 2746 #header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded
2743 2747 {
2744 2748 display: block;
2745 2749 }
2746 2750
2747 2751 #content div.graph {
2748 2752 padding: 0 10px 10px;
2749 2753 }
2750 2754
2751 2755 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a
2752 2756 {
2753 2757 color: #bfe3ff;
2754 2758 }
2755 2759
2756 2760 #content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal
2757 2761 {
2758 2762 margin: 10px 24px 10px 44px;
2759 2763 }
2760 2764
2761 2765 #content div.box div.form,#content div.box div.table,#content div.box div.traffic
2762 2766 {
2763 2767 clear: both;
2764 2768 overflow: hidden;
2765 2769 margin: 0;
2766 2770 padding: 0 20px 10px;
2767 2771 }
2768 2772
2769 2773 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields
2770 2774 {
2771 2775 clear: both;
2772 2776 overflow: hidden;
2773 2777 margin: 0;
2774 2778 padding: 0;
2775 2779 }
2776 2780
2777 2781 #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span
2778 2782 {
2779 2783 height: 1%;
2780 2784 display: block;
2781 2785 color: #363636;
2782 2786 margin: 0;
2783 2787 padding: 2px 0 0;
2784 2788 }
2785 2789
2786 2790 #content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error
2787 2791 {
2788 2792 background: #FBE3E4;
2789 2793 border-top: 1px solid #e1b2b3;
2790 2794 border-left: 1px solid #e1b2b3;
2791 2795 border-right: 1px solid #FBC2C4;
2792 2796 border-bottom: 1px solid #FBC2C4;
2793 2797 }
2794 2798
2795 2799 #content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success
2796 2800 {
2797 2801 background: #E6EFC2;
2798 2802 border-top: 1px solid #cebb98;
2799 2803 border-left: 1px solid #cebb98;
2800 2804 border-right: 1px solid #c6d880;
2801 2805 border-bottom: 1px solid #c6d880;
2802 2806 }
2803 2807
2804 2808 #content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input
2805 2809 {
2806 2810 margin: 0;
2807 2811 }
2808 2812
2809 2813 #content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios
2810 2814 {
2811 2815 margin: 0 0 0 0px !important;
2812 2816 padding: 0;
2813 2817 }
2814 2818
2815 2819 #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios
2816 2820 {
2817 2821 margin: 0 0 0 200px;
2818 2822 padding: 0;
2819 2823 }
2820 2824
2821 2825 #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover
2822 2826 {
2823 2827 color: #000;
2824 2828 text-decoration: none;
2825 2829 }
2826 2830
2827 2831 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus
2828 2832 {
2829 2833 border: 1px solid #666;
2830 2834 }
2831 2835
2832 2836 #content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio
2833 2837 {
2834 2838 clear: both;
2835 2839 overflow: hidden;
2836 2840 margin: 0;
2837 2841 padding: 8px 0 2px;
2838 2842 }
2839 2843
2840 2844 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input
2841 2845 {
2842 2846 float: left;
2843 2847 margin: 0;
2844 2848 }
2845 2849
2846 2850 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label
2847 2851 {
2848 2852 height: 1%;
2849 2853 display: block;
2850 2854 float: left;
2851 2855 margin: 2px 0 0 4px;
2852 2856 }
2853 2857
2854 2858 div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input
2855 2859 {
2856 2860 color: #000;
2857 2861 font-size: 11px;
2858 2862 font-weight: 700;
2859 2863 margin: 0;
2860 2864 }
2861 2865
2862 2866 input.ui-button {
2863 2867 background: #e5e3e3 url("../images/button.png") repeat-x;
2864 2868 border-top: 1px solid #DDD;
2865 2869 border-left: 1px solid #c6c6c6;
2866 2870 border-right: 1px solid #DDD;
2867 2871 border-bottom: 1px solid #c6c6c6;
2868 2872 color: #515151 !important;
2869 2873 outline: none;
2870 2874 margin: 0;
2871 2875 padding: 6px 12px;
2872 2876 -webkit-border-radius: 4px 4px 4px 4px;
2873 2877 -khtml-border-radius: 4px 4px 4px 4px;
2874 2878 -moz-border-radius: 4px 4px 4px 4px;
2875 2879 border-radius: 4px 4px 4px 4px;
2876 2880 box-shadow: 0 1px 0 #ececec;
2877 2881 cursor: pointer;
2878 2882 }
2879 2883
2880 2884 input.ui-button:hover {
2881 2885 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
2882 2886 border-top: 1px solid #ccc;
2883 2887 border-left: 1px solid #bebebe;
2884 2888 border-right: 1px solid #b1b1b1;
2885 2889 border-bottom: 1px solid #afafaf;
2886 2890 }
2887 2891
2888 2892 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight
2889 2893 {
2890 2894 display: inline;
2891 2895 }
2892 2896
2893 2897 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons
2894 2898 {
2895 2899 margin: 10px 0 0 200px;
2896 2900 padding: 0;
2897 2901 }
2898 2902
2899 2903 #content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons
2900 2904 {
2901 2905 margin: 10px 0 0;
2902 2906 }
2903 2907
2904 2908 #content div.box table td.user,#content div.box table td.address {
2905 2909 width: 10%;
2906 2910 text-align: center;
2907 2911 }
2908 2912
2909 2913 #content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link
2910 2914 {
2911 2915 text-align: right;
2912 2916 margin: 6px 0 0;
2913 2917 padding: 0;
2914 2918 }
2915 2919
2916 2920 #content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover
2917 2921 {
2918 2922 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
2919 2923 border-top: 1px solid #ccc;
2920 2924 border-left: 1px solid #bebebe;
2921 2925 border-right: 1px solid #b1b1b1;
2922 2926 border-bottom: 1px solid #afafaf;
2923 2927 color: #515151;
2924 2928 margin: 0;
2925 2929 padding: 6px 12px;
2926 2930 }
2927 2931
2928 2932 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results
2929 2933 {
2930 2934 text-align: left;
2931 2935 float: left;
2932 2936 margin: 0;
2933 2937 padding: 0;
2934 2938 }
2935 2939
2936 2940 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span
2937 2941 {
2938 2942 height: 1%;
2939 2943 display: block;
2940 2944 float: left;
2941 2945 background: #ebebeb url("../images/pager.png") repeat-x;
2942 2946 border-top: 1px solid #dedede;
2943 2947 border-left: 1px solid #cfcfcf;
2944 2948 border-right: 1px solid #c4c4c4;
2945 2949 border-bottom: 1px solid #c4c4c4;
2946 2950 color: #4A4A4A;
2947 2951 font-weight: 700;
2948 2952 margin: 0;
2949 2953 padding: 6px 8px;
2950 2954 }
2951 2955
2952 2956 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled
2953 2957 {
2954 2958 color: #B4B4B4;
2955 2959 padding: 6px;
2956 2960 }
2957 2961
2958 2962 #login,#register {
2959 2963 width: 520px;
2960 2964 margin: 10% auto 0;
2961 2965 padding: 0;
2962 2966 }
2963 2967
2964 2968 #login div.color,#register div.color {
2965 2969 clear: both;
2966 2970 overflow: hidden;
2967 2971 background: #FFF;
2968 2972 margin: 10px auto 0;
2969 2973 padding: 3px 3px 3px 0;
2970 2974 }
2971 2975
2972 2976 #login div.color a,#register div.color a {
2973 2977 width: 20px;
2974 2978 height: 20px;
2975 2979 display: block;
2976 2980 float: left;
2977 2981 margin: 0 0 0 3px;
2978 2982 padding: 0;
2979 2983 }
2980 2984
2981 2985 #login div.title h5,#register div.title h5 {
2982 2986 color: #fff;
2983 2987 margin: 10px;
2984 2988 padding: 0;
2985 2989 }
2986 2990
2987 2991 #login div.form div.fields div.field,#register div.form div.fields div.field
2988 2992 {
2989 2993 clear: both;
2990 2994 overflow: hidden;
2991 2995 margin: 0;
2992 2996 padding: 0 0 10px;
2993 2997 }
2994 2998
2995 2999 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message
2996 3000 {
2997 3001 height: 1%;
2998 3002 display: block;
2999 3003 color: red;
3000 3004 margin: 8px 0 0;
3001 3005 padding: 0;
3002 3006 max-width: 320px;
3003 3007 }
3004 3008
3005 3009 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label
3006 3010 {
3007 3011 color: #000;
3008 3012 font-weight: 700;
3009 3013 }
3010 3014
3011 3015 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input
3012 3016 {
3013 3017 float: left;
3014 3018 margin: 0;
3015 3019 padding: 0;
3016 3020 }
3017 3021
3018 3022 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox
3019 3023 {
3020 3024 margin: 0 0 0 184px;
3021 3025 padding: 0;
3022 3026 }
3023 3027
3024 3028 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label
3025 3029 {
3026 3030 color: #565656;
3027 3031 font-weight: 700;
3028 3032 }
3029 3033
3030 3034 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input
3031 3035 {
3032 3036 color: #000;
3033 3037 font-size: 1em;
3034 3038 font-weight: 700;
3035 3039 margin: 0;
3036 3040 }
3037 3041
3038 3042 #changeset_content .container .wrapper,#graph_content .container .wrapper
3039 3043 {
3040 3044 width: 600px;
3041 3045 }
3042 3046
3043 3047 #changeset_content .container .left,#graph_content .container .left {
3044 3048 float: left;
3045 3049 width: 70%;
3046 3050 padding-left: 5px;
3047 3051 }
3048 3052
3049 3053 #changeset_content .container .left .date,.ac .match {
3050 3054 font-weight: 700;
3051 3055 padding-top: 5px;
3052 3056 padding-bottom: 5px;
3053 3057 }
3054 3058
3055 3059 div#legend_container table td,div#legend_choices table td {
3056 3060 border: none !important;
3057 3061 height: 20px !important;
3058 3062 padding: 0 !important;
3059 3063 }
3060 3064
3061 3065 .q_filter_box {
3062 3066 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
3063 3067 -webkit-border-radius: 4px;
3064 3068 -moz-border-radius: 4px;
3065 3069 border-radius: 4px;
3066 3070 border: 0 none;
3067 3071 color: #AAAAAA;
3068 3072 margin-bottom: -4px;
3069 3073 margin-top: -4px;
3070 3074 padding-left: 3px;
3071 3075 }
3072 3076
3073 3077 #node_filter {
3074 3078 border: 0px solid #545454;
3075 3079 color: #AAAAAA;
3076 3080 padding-left: 3px;
3077 3081 }
3078 3082
3079 3083 /*README STYLE*/
3080 3084
3081 3085 div.readme {
3082 3086 padding:0px;
3083 3087 }
3084 3088
3085 3089 div.readme h2 {
3086 3090 font-weight: normal;
3087 3091 }
3088 3092
3089 3093 div.readme .readme_box {
3090 3094 background-color: #fafafa;
3091 3095 }
3092 3096
3093 3097 div.readme .readme_box {
3094 3098 clear:both;
3095 3099 overflow:hidden;
3096 3100 margin:0;
3097 3101 padding:0 20px 10px;
3098 3102 }
3099 3103
3100 3104 div.readme .readme_box h1, div.readme .readme_box h2, div.readme .readme_box h3, div.readme .readme_box h4, div.readme .readme_box h5, div.readme .readme_box h6 {
3101 3105 border-bottom: 0 !important;
3102 3106 margin: 0 !important;
3103 3107 padding: 0 !important;
3104 3108 line-height: 1.5em !important;
3105 3109 }
3106 3110
3107 3111
3108 3112 div.readme .readme_box h1:first-child {
3109 3113 padding-top: .25em !important;
3110 3114 }
3111 3115
3112 3116 div.readme .readme_box h2, div.readme .readme_box h3 {
3113 3117 margin: 1em 0 !important;
3114 3118 }
3115 3119
3116 3120 div.readme .readme_box h2 {
3117 3121 margin-top: 1.5em !important;
3118 3122 border-top: 4px solid #e0e0e0 !important;
3119 3123 padding-top: .5em !important;
3120 3124 }
3121 3125
3122 3126 div.readme .readme_box p {
3123 3127 color: black !important;
3124 3128 margin: 1em 0 !important;
3125 3129 line-height: 1.5em !important;
3126 3130 }
3127 3131
3128 3132 div.readme .readme_box ul {
3129 3133 list-style: disc !important;
3130 3134 margin: 1em 0 1em 2em !important;
3131 3135 }
3132 3136
3133 3137 div.readme .readme_box ol {
3134 3138 list-style: decimal;
3135 3139 margin: 1em 0 1em 2em !important;
3136 3140 }
3137 3141
3138 3142 div.readme .readme_box pre, code {
3139 3143 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
3140 3144 }
3141 3145
3142 3146 div.readme .readme_box code {
3143 3147 font-size: 12px !important;
3144 3148 background-color: ghostWhite !important;
3145 3149 color: #444 !important;
3146 3150 padding: 0 .2em !important;
3147 3151 border: 1px solid #dedede !important;
3148 3152 }
3149 3153
3150 3154 div.readme .readme_box pre code {
3151 3155 padding: 0 !important;
3152 3156 font-size: 12px !important;
3153 3157 background-color: #eee !important;
3154 3158 border: none !important;
3155 3159 }
3156 3160
3157 3161 div.readme .readme_box pre {
3158 3162 margin: 1em 0;
3159 3163 font-size: 12px;
3160 3164 background-color: #eee;
3161 3165 border: 1px solid #ddd;
3162 3166 padding: 5px;
3163 3167 color: #444;
3164 3168 overflow: auto;
3165 3169 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
3166 3170 -webkit-border-radius: 3px;
3167 3171 -moz-border-radius: 3px;
3168 3172 border-radius: 3px;
3169 3173 }
3170 3174
3171 3175
3172 3176 /** RST STYLE **/
3173 3177
3174 3178
3175 3179 div.rst-block {
3176 3180 padding:0px;
3177 3181 }
3178 3182
3179 3183 div.rst-block h2 {
3180 3184 font-weight: normal;
3181 3185 }
3182 3186
3183 3187 div.rst-block {
3184 3188 background-color: #fafafa;
3185 3189 }
3186 3190
3187 3191 div.rst-block {
3188 3192 clear:both;
3189 3193 overflow:hidden;
3190 3194 margin:0;
3191 3195 padding:0 20px 10px;
3192 3196 }
3193 3197
3194 3198 div.rst-block h1, div.rst-block h2, div.rst-block h3, div.rst-block h4, div.rst-block h5, div.rst-block h6 {
3195 3199 border-bottom: 0 !important;
3196 3200 margin: 0 !important;
3197 3201 padding: 0 !important;
3198 3202 line-height: 1.5em !important;
3199 3203 }
3200 3204
3201 3205
3202 3206 div.rst-block h1:first-child {
3203 3207 padding-top: .25em !important;
3204 3208 }
3205 3209
3206 3210 div.rst-block h2, div.rst-block h3 {
3207 3211 margin: 1em 0 !important;
3208 3212 }
3209 3213
3210 3214 div.rst-block h2 {
3211 3215 margin-top: 1.5em !important;
3212 3216 border-top: 4px solid #e0e0e0 !important;
3213 3217 padding-top: .5em !important;
3214 3218 }
3215 3219
3216 3220 div.rst-block p {
3217 3221 color: black !important;
3218 3222 margin: 1em 0 !important;
3219 3223 line-height: 1.5em !important;
3220 3224 }
3221 3225
3222 3226 div.rst-block ul {
3223 3227 list-style: disc !important;
3224 3228 margin: 1em 0 1em 2em !important;
3225 3229 }
3226 3230
3227 3231 div.rst-block ol {
3228 3232 list-style: decimal;
3229 3233 margin: 1em 0 1em 2em !important;
3230 3234 }
3231 3235
3232 3236 div.rst-block pre, code {
3233 3237 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
3234 3238 }
3235 3239
3236 3240 div.rst-block code {
3237 3241 font-size: 12px !important;
3238 3242 background-color: ghostWhite !important;
3239 3243 color: #444 !important;
3240 3244 padding: 0 .2em !important;
3241 3245 border: 1px solid #dedede !important;
3242 3246 }
3243 3247
3244 3248 div.rst-block pre code {
3245 3249 padding: 0 !important;
3246 3250 font-size: 12px !important;
3247 3251 background-color: #eee !important;
3248 3252 border: none !important;
3249 3253 }
3250 3254
3251 3255 div.rst-block pre {
3252 3256 margin: 1em 0;
3253 3257 font-size: 12px;
3254 3258 background-color: #eee;
3255 3259 border: 1px solid #ddd;
3256 3260 padding: 5px;
3257 3261 color: #444;
3258 3262 overflow: auto;
3259 3263 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
3260 3264 -webkit-border-radius: 3px;
3261 3265 -moz-border-radius: 3px;
3262 3266 border-radius: 3px;
3263 3267 }
3264 3268
3265 3269
3266 3270 /** comment main **/
3267 3271 .comments {
3268 3272 padding:10px 20px;
3269 3273 }
3270 3274
3271 3275 .comments .comment {
3272 3276 border: 1px solid #ddd;
3273 3277 margin-top: 10px;
3274 3278 -webkit-border-radius: 4px;
3275 3279 -moz-border-radius: 4px;
3276 3280 border-radius: 4px;
3277 3281 }
3278 3282
3279 3283 .comments .comment .meta {
3280 3284 background: #f8f8f8;
3281 3285 padding: 6px;
3282 3286 border-bottom: 1px solid #ddd;
3283 3287 }
3284 3288
3285 3289 .comments .comment .meta img {
3286 3290 vertical-align: middle;
3287 3291 }
3288 3292
3289 3293 .comments .comment .meta .user {
3290 3294 font-weight: bold;
3291 3295 }
3292 3296
3293 3297 .comments .comment .meta .date {
3294 3298 float: right;
3295 3299 }
3296 3300
3297 3301 .comments .comment .text {
3298 3302 padding: 8px 6px 6px 14px;
3299 3303 background-color: #FAFAFA;
3300 3304 }
3301 3305
3302 3306 .comments .comments-number{
3303 3307 padding:0px 0px 10px 0px;
3304 3308 font-weight: bold;
3305 3309 color: #666;
3306 3310 font-size: 16px;
3307 3311 }
3308 3312
3309 3313 /** comment form **/
3310 3314
3311 3315 .comment-form .clearfix{
3312 3316 background: #EEE;
3313 3317 -webkit-border-radius: 4px;
3314 3318 -moz-border-radius: 4px;
3315 3319 border-radius: 4px;
3316 3320 padding: 10px;
3317 3321 }
3318 3322
3319 3323 div.comment-form {
3320 3324 margin-top: 20px;
3321 3325 }
3322 3326
3323 3327 .comment-form strong {
3324 3328 display: block;
3325 3329 margin-bottom: 15px;
3326 3330 }
3327 3331
3328 3332 .comment-form textarea {
3329 3333 width: 100%;
3330 3334 height: 100px;
3331 3335 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
3332 3336 }
3333 3337
3334 3338 form.comment-form {
3335 3339 margin-top: 10px;
3336 3340 margin-left: 10px;
3337 3341 }
3338 3342
3339 3343 .comment-form-submit {
3340 3344 margin-top: 5px;
3341 3345 margin-left: 525px;
3342 3346 }
3343 3347
3344 3348 .file-comments {
3345 3349 display: none;
3346 3350 }
3347 3351
3348 3352 .comment-form .comment {
3349 3353 margin-left: 10px;
3350 3354 }
3351 3355
3352 3356 .comment-form .comment-help{
3353 3357 padding: 0px 0px 5px 0px;
3354 3358 color: #666;
3355 3359 }
3356 3360
3357 3361 .comment-form .comment-button{
3358 3362 padding-top:5px;
3359 3363 }
3360 3364
3361 3365 .add-another-button {
3362 3366 margin-left: 10px;
3363 3367 margin-top: 10px;
3364 3368 margin-bottom: 10px;
3365 3369 }
3366 3370
3367 3371 .comment .buttons {
3368 3372 position: absolute;
3369 3373 right:40px;
3370 3374 }
3371 3375
3372 3376
3373 3377 .show-inline-comments{
3374 3378 position: relative;
3375 3379 top:1px
3376 3380 }
3377 3381
3378 3382 /** comment inline form **/
3379 3383
3380 3384 .comment-inline-form .clearfix{
3381 3385 background: #EEE;
3382 3386 -webkit-border-radius: 4px;
3383 3387 -moz-border-radius: 4px;
3384 3388 border-radius: 4px;
3385 3389 padding: 5px;
3386 3390 }
3387 3391
3388 3392 div.comment-inline-form {
3389 3393 margin-top: 5px;
3390 3394 padding:2px 6px 8px 6px;
3391 3395 }
3392 3396
3393 3397 .comment-inline-form strong {
3394 3398 display: block;
3395 3399 margin-bottom: 15px;
3396 3400 }
3397 3401
3398 3402 .comment-inline-form textarea {
3399 3403 width: 100%;
3400 3404 height: 100px;
3401 3405 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
3402 3406 }
3403 3407
3404 3408 form.comment-inline-form {
3405 3409 margin-top: 10px;
3406 3410 margin-left: 10px;
3407 3411 }
3408 3412
3409 3413 .comment-inline-form-submit {
3410 3414 margin-top: 5px;
3411 3415 margin-left: 525px;
3412 3416 }
3413 3417
3414 3418 .file-comments {
3415 3419 display: none;
3416 3420 }
3417 3421
3418 3422 .comment-inline-form .comment {
3419 3423 margin-left: 10px;
3420 3424 }
3421 3425
3422 3426 .comment-inline-form .comment-help{
3423 3427 padding: 0px 0px 2px 0px;
3424 3428 color: #666666;
3425 3429 font-size: 10px;
3426 3430 }
3427 3431
3428 3432 .comment-inline-form .comment-button{
3429 3433 padding-top:5px;
3430 3434 }
3431 3435
3432 3436 /** comment inline **/
3433 3437 .inline-comments {
3434 3438 padding:10px 20px;
3435 3439 }
3436 3440
3437 3441 .inline-comments div.rst-block {
3438 3442 clear:both;
3439 3443 overflow:hidden;
3440 3444 margin:0;
3441 3445 padding:0 20px 0px;
3442 3446 }
3443 3447 .inline-comments .comment {
3444 3448 border: 1px solid #ddd;
3445 3449 -webkit-border-radius: 4px;
3446 3450 -moz-border-radius: 4px;
3447 3451 border-radius: 4px;
3448 3452 margin: 3px 3px 5px 5px;
3449 3453 }
3450 3454
3451 3455 .inline-comments .comment .meta {
3452 3456 background: #f8f8f8;
3453 3457 padding: 6px;
3454 3458 border-bottom: 1px solid #ddd;
3455 3459 }
3456 3460
3457 3461 .inline-comments .comment .meta img {
3458 3462 vertical-align: middle;
3459 3463 }
3460 3464
3461 3465 .inline-comments .comment .meta .user {
3462 3466 font-weight: bold;
3463 3467 }
3464 3468
3465 3469 .inline-comments .comment .meta .date {
3466 3470 float: right;
3467 3471 }
3468 3472
3469 3473 .inline-comments .comment .text {
3470 3474 padding: 8px 6px 6px 14px;
3471 3475 background-color: #FAFAFA;
3472 3476 }
3473 3477
3474 3478 .inline-comments .comments-number{
3475 3479 padding:0px 0px 10px 0px;
3476 3480 font-weight: bold;
3477 3481 color: #666;
3478 3482 font-size: 16px;
3479 3483 }
3480 3484 .inline-comments-button .add-comment{
3481 3485 margin:10px 5px !important;
3482 3486 }
3483 3487 .notifications{
3484 3488 width:22px;
3485 3489 padding:2px;
3486 3490 float:right;
3487 3491 -webkit-border-radius: 4px;
3488 3492 -moz-border-radius: 4px;
3489 3493 border-radius: 4px;
3490 3494 text-align: center;
3491 3495 margin: 0px -10px 0px 5px;
3492 3496 background-color: #DEDEDE;
3493 3497 }
3494 3498 .notifications a{
3495 3499 color:#888 !important;
3496 3500 display: block;
3497 3501 font-size: 10px
3498 3502 }
3499 3503 .notifications a:hover{
3500 3504 text-decoration: none !important;
3501 3505 }
3502 3506 .notification-header{
3503 3507
3504 3508 }
3505 3509 .notification-header .desc{
3506 3510 font-size: 16px;
3507 3511 height: 24px;
3508 3512 padding-top: 6px;
3509 3513 float: left
3510 3514 }
3511 3515
3512 3516 .notification-header .desc.unread{
3513 3517 font-weight: bold;
3514 3518 font-size: 17px;
3515 3519 }
3516 3520
3517 3521 .notification-header .delete-notifications{
3518 3522 float: right;
3519 3523 padding-top: 8px;
3520 3524 cursor: pointer;
3521 3525 }
3522 3526 .notification-subject{
3523 3527 clear:both;
3524 3528 border-bottom: 1px solid #eee;
3525 3529 padding:5px 0px 5px 38px;
3526 3530 } No newline at end of file
@@ -1,61 +1,78 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${c.repo_name} ${_('Fork')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(u'Home',h.url('/'))}
10 10 &raquo;
11 11 ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
12 12 &raquo;
13 13 ${_('fork')}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('')}
18 18 </%def>
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 ${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
26 26 <div class="form">
27 27 <!-- fields -->
28 28 <div class="fields">
29 29 <div class="field">
30 <div class="label">
31 <label for="repo_name">${_('Fork name')}:</label>
32 </div>
33 <div class="input">
34 ${h.text('fork_name',class_="small")}
35 ${h.hidden('repo_type',c.repo_info.repo_type)}
36 </div>
37 </div>
30 <div class="label">
31 <label for="repo_name">${_('Fork name')}:</label>
32 </div>
33 <div class="input">
34 ${h.text('repo_name',class_="small")}
35 ${h.hidden('repo_type',c.repo_info.repo_type)}
36 ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
37 </div>
38 </div>
39 <div class="field">
40 <div class="label">
41 <label for="repo_group">${_('Repository group')}:</label>
42 </div>
43 <div class="input">
44 ${h.select('repo_group','',c.repo_groups,class_="medium")}
45 </div>
46 </div>
38 47 <div class="field">
39 48 <div class="label label-textarea">
40 49 <label for="description">${_('Description')}:</label>
41 50 </div>
42 51 <div class="textarea text-area editor">
43 52 ${h.textarea('description',cols=23,rows=5)}
44 53 </div>
45 54 </div>
46 55 <div class="field">
47 56 <div class="label label-checkbox">
48 57 <label for="private">${_('Private')}:</label>
49 58 </div>
50 59 <div class="checkboxes">
51 60 ${h.checkbox('private',value="True")}
52 61 </div>
53 </div>
62 </div>
63 <div class="field">
64 <div class="label label-checkbox">
65 <label for="private">${_('Copy permissions')}:</label>
66 </div>
67 <div class="checkboxes">
68 ${h.checkbox('copy_permissions',value="True")}
69 </div>
70 </div>
54 71 <div class="buttons">
55 72 ${h.submit('',_('fork this repository'),class_="ui-button")}
56 73 </div>
57 74 </div>
58 75 </div>
59 76 ${h.end_form()}
60 77 </div>
61 78 </%def>
General Comments 0
You need to be logged in to leave comments. Login now