PageRenderTime 79ms CodeModel.GetById 39ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/galaxy/webapps/community/controllers/repository.py

https://bitbucket.org/cistrome/cistrome-harvard/
Python | 1144 lines | 1081 code | 7 blank | 56 comment | 52 complexity | aa433671131278cfec4260685f873f4f MD5 | raw file
  1. import os, logging, urllib, ConfigParser, tempfile, shutil
  2. from time import strftime
  3. from datetime import date, datetime
  4. from galaxy import util
  5. from galaxy.datatypes.checkers import *
  6. from galaxy.web.base.controller import *
  7. from galaxy.web.form_builder import CheckboxField
  8. from galaxy.webapps.community import model
  9. from galaxy.webapps.community.model import directory_hash_id
  10. from galaxy.web.framework.helpers import time_ago, iff, grids
  11. from galaxy.util.json import from_json_string, to_json_string
  12. from galaxy.model.orm import *
  13. from common import *
  14. from mercurial import hg, ui, patch, commands
  15. log = logging.getLogger( __name__ )
  16. # Characters that must be html escaped
  17. MAPPED_CHARS = { '>' :'>',
  18. '<' :'&lt;',
  19. '"' : '&quot;',
  20. '&' : '&amp;',
  21. '\'' : '&apos;' }
  22. MAX_CONTENT_SIZE = 32768
  23. VALID_CHARS = set( string.letters + string.digits + "'\"-=_.()/+*^,:?!#[]%\\$@;{}" )
  24. VALID_REPOSITORYNAME_RE = re.compile( "^[a-z0-9\_]+$" )
  25. class CategoryListGrid( grids.Grid ):
  26. class NameColumn( grids.TextColumn ):
  27. def get_value( self, trans, grid, category ):
  28. return category.name
  29. class DescriptionColumn( grids.TextColumn ):
  30. def get_value( self, trans, grid, category ):
  31. return category.description
  32. class RepositoriesColumn( grids.TextColumn ):
  33. def get_value( self, trans, grid, category ):
  34. if category.repositories:
  35. viewable_repositories = 0
  36. for rca in category.repositories:
  37. viewable_repositories += 1
  38. return viewable_repositories
  39. return 0
  40. # Grid definition
  41. webapp = "community"
  42. title = "Categories"
  43. model_class = model.Category
  44. template='/webapps/community/category/grid.mako'
  45. default_sort_key = "name"
  46. columns = [
  47. NameColumn( "Name",
  48. key="Category.name",
  49. link=( lambda item: dict( operation="repositories_by_category", id=item.id, webapp="community" ) ),
  50. attach_popup=False ),
  51. DescriptionColumn( "Description",
  52. key="Category.description",
  53. attach_popup=False ),
  54. # Columns that are valid for filtering but are not visible.
  55. RepositoriesColumn( "Repositories",
  56. model_class=model.Repository,
  57. attach_popup=False )
  58. ]
  59. # Override these
  60. default_filter = {}
  61. global_actions = []
  62. operations = []
  63. standard_filters = []
  64. num_rows_per_page = 50
  65. preserve_state = False
  66. use_paging = True
  67. class RepositoryListGrid( grids.Grid ):
  68. class NameColumn( grids.TextColumn ):
  69. def get_value( self, trans, grid, repository ):
  70. return repository.name
  71. class RevisionColumn( grids.GridColumn ):
  72. def __init__( self, col_name ):
  73. grids.GridColumn.__init__( self, col_name )
  74. def get_value( self, trans, grid, repository ):
  75. """
  76. Display a SelectField whose options are the changeset_revision
  77. strings of all downloadable_revisions of this repository.
  78. """
  79. select_field = build_changeset_revision_select_field( trans, repository )
  80. if len( select_field.options ) > 1:
  81. return select_field.get_html()
  82. return repository.revision
  83. class DescriptionColumn( grids.TextColumn ):
  84. def get_value( self, trans, grid, repository ):
  85. return repository.description
  86. class CategoryColumn( grids.TextColumn ):
  87. def get_value( self, trans, grid, repository ):
  88. rval = '<ul>'
  89. if repository.categories:
  90. for rca in repository.categories:
  91. rval += '<li><a href="browse_repositories?operation=repositories_by_category&id=%s&webapp=community">%s</a></li>' \
  92. % ( trans.security.encode_id( rca.category.id ), rca.category.name )
  93. else:
  94. rval += '<li>not set</li>'
  95. rval += '</ul>'
  96. return rval
  97. class RepositoryCategoryColumn( grids.GridColumn ):
  98. def filter( self, trans, user, query, column_filter ):
  99. """Modify query to filter by category."""
  100. if column_filter == "All":
  101. return query
  102. return query.filter( model.Category.name == column_filter )
  103. class UserColumn( grids.TextColumn ):
  104. def get_value( self, trans, grid, repository ):
  105. if repository.user:
  106. return repository.user.username
  107. return 'no user'
  108. class EmailColumn( grids.TextColumn ):
  109. def filter( self, trans, user, query, column_filter ):
  110. if column_filter == 'All':
  111. return query
  112. return query.filter( and_( model.Repository.table.c.user_id == model.User.table.c.id,
  113. model.User.table.c.email == column_filter ) )
  114. class EmailAlertsColumn( grids.TextColumn ):
  115. def get_value( self, trans, grid, repository ):
  116. if trans.user and repository.email_alerts and trans.user.email in from_json_string( repository.email_alerts ):
  117. return 'yes'
  118. return ''
  119. # Grid definition
  120. title = "Repositories"
  121. model_class = model.Repository
  122. template='/webapps/community/repository/grid.mako'
  123. default_sort_key = "name"
  124. columns = [
  125. NameColumn( "Name",
  126. key="name",
  127. link=( lambda item: dict( operation="view_or_manage_repository",
  128. id=item.id,
  129. webapp="community" ) ),
  130. attach_popup=True ),
  131. DescriptionColumn( "Synopsis",
  132. key="description",
  133. attach_popup=False ),
  134. RevisionColumn( "Revision" ),
  135. CategoryColumn( "Category",
  136. model_class=model.Category,
  137. key="Category.name",
  138. attach_popup=False ),
  139. UserColumn( "Owner",
  140. model_class=model.User,
  141. link=( lambda item: dict( operation="repositories_by_user", id=item.id, webapp="community" ) ),
  142. attach_popup=False,
  143. key="User.username" ),
  144. grids.CommunityRatingColumn( "Average Rating", key="rating" ),
  145. EmailAlertsColumn( "Alert", attach_popup=False ),
  146. # Columns that are valid for filtering but are not visible.
  147. EmailColumn( "Email",
  148. model_class=model.User,
  149. key="email",
  150. visible=False ),
  151. RepositoryCategoryColumn( "Category",
  152. model_class=model.Category,
  153. key="Category.name",
  154. visible=False ),
  155. grids.DeletedColumn( "Deleted",
  156. key="deleted",
  157. visible=False,
  158. filterable="advanced" )
  159. ]
  160. columns.append( grids.MulticolFilterColumn( "Search repository name, description",
  161. cols_to_filter=[ columns[0], columns[1] ],
  162. key="free-text-search",
  163. visible=False,
  164. filterable="standard" ) )
  165. operations = [ grids.GridOperation( "Receive email alerts",
  166. allow_multiple=False,
  167. condition=( lambda item: not item.deleted ),
  168. async_compatible=False ) ]
  169. standard_filters = []
  170. default_filter = dict( deleted="False" )
  171. num_rows_per_page = 50
  172. preserve_state = False
  173. use_paging = True
  174. def build_initial_query( self, trans, **kwd ):
  175. return trans.sa_session.query( self.model_class ) \
  176. .join( model.User.table ) \
  177. .outerjoin( model.RepositoryCategoryAssociation.table ) \
  178. .outerjoin( model.Category.table )
  179. class DownloadableRepositoryListGrid( RepositoryListGrid ):
  180. class RevisionColumn( grids.GridColumn ):
  181. def __init__( self, col_name ):
  182. grids.GridColumn.__init__( self, col_name )
  183. def get_value( self, trans, grid, repository ):
  184. """
  185. Display a SelectField whose options are the changeset_revision
  186. strings of all downloadable_revisions of this repository.
  187. """
  188. select_field = build_changeset_revision_select_field( trans, repository )
  189. if len( select_field.options ) > 1:
  190. return select_field.get_html()
  191. return repository.revision
  192. title = "Downloadable repositories"
  193. columns = [
  194. RepositoryListGrid.NameColumn( "Name",
  195. key="name",
  196. attach_popup=True ),
  197. RepositoryListGrid.DescriptionColumn( "Synopsis",
  198. key="description",
  199. attach_popup=False ),
  200. RevisionColumn( "Revision" ),
  201. RepositoryListGrid.UserColumn( "Owner",
  202. model_class=model.User,
  203. attach_popup=False,
  204. key="User.username" )
  205. ]
  206. columns.append( grids.MulticolFilterColumn( "Search repository name, description",
  207. cols_to_filter=[ columns[0], columns[1] ],
  208. key="free-text-search",
  209. visible=False,
  210. filterable="standard" ) )
  211. operations = []
  212. def build_initial_query( self, trans, **kwd ):
  213. return trans.sa_session.query( self.model_class ) \
  214. .join( model.RepositoryMetadata.table ) \
  215. .join( model.User.table )
  216. class RepositoryController( BaseUIController, ItemRatings ):
  217. downloadable_repository_list_grid = DownloadableRepositoryListGrid()
  218. repository_list_grid = RepositoryListGrid()
  219. category_list_grid = CategoryListGrid()
  220. @web.expose
  221. def index( self, trans, **kwd ):
  222. params = util.Params( kwd )
  223. message = util.restore_text( params.get( 'message', '' ) )
  224. status = params.get( 'status', 'done' )
  225. return trans.fill_template( '/webapps/community/index.mako', message=message, status=status )
  226. @web.expose
  227. def browse_categories( self, trans, **kwd ):
  228. if 'f-free-text-search' in kwd:
  229. # Trick to enable searching repository name, description from the CategoryListGrid.
  230. # What we've done is rendered the search box for the RepositoryListGrid on the grid.mako
  231. # template for the CategoryListGrid. See ~/templates/webapps/community/category/grid.mako.
  232. # Since we are searching repositories and not categories, redirect to browse_repositories().
  233. if 'id' in kwd and 'f-free-text-search' in kwd and kwd[ 'id' ] == kwd[ 'f-free-text-search' ]:
  234. # The value of 'id' has been set to the search string, which is a repository name.
  235. # We'll try to get the desired encoded repository id to pass on.
  236. try:
  237. repository = get_repository_by_name( trans, kwd[ 'id' ] )
  238. kwd[ 'id' ] = trans.security.encode_id( repository.id )
  239. except:
  240. pass
  241. return self.browse_repositories( trans, **kwd )
  242. if 'operation' in kwd:
  243. operation = kwd['operation'].lower()
  244. if operation in [ "repositories_by_category", "repositories_by_user" ]:
  245. # Eliminate the current filters if any exist.
  246. for k, v in kwd.items():
  247. if k.startswith( 'f-' ):
  248. del kwd[ k ]
  249. return trans.response.send_redirect( web.url_for( controller='repository',
  250. action='browse_repositories',
  251. **kwd ) )
  252. # Render the list view
  253. return self.category_list_grid( trans, **kwd )
  254. @web.expose
  255. def browse_downloadable_repositories( self, trans, **kwd ):
  256. # Set the toolshedgalaxyurl cookie so we can get back
  257. # to the calling local Galaxy instance.
  258. galaxy_url = kwd.get( 'galaxy_url', None )
  259. if galaxy_url:
  260. trans.set_cookie( galaxy_url, name='toolshedgalaxyurl' )
  261. repository_id = kwd.get( 'id', None )
  262. if 'operation' in kwd:
  263. operation = kwd[ 'operation' ].lower()
  264. if operation == "preview_tools_in_changeset":
  265. repository = get_repository( trans, repository_id )
  266. return trans.response.send_redirect( web.url_for( controller='repository',
  267. action='preview_tools_in_changeset',
  268. repository_id=repository_id,
  269. changeset_revision=repository.tip ) )
  270. # The changeset_revision_select_field in the RepositoryListGrid performs a refresh_on_change
  271. # which sends in request parameters like changeset_revison_1, changeset_revision_2, etc. One
  272. # of the many select fields on the grid performed the refresh_on_change, so we loop through
  273. # all of the received values to see which value is not the repository tip. If we find it, we
  274. # know the refresh_on_change occurred, and we have the necessary repository id and change set
  275. # revision to pass on.
  276. for k, v in kwd.items():
  277. changset_revision_str = 'changeset_revision_'
  278. if k.startswith( changset_revision_str ):
  279. repository_id = trans.security.encode_id( int( k.lstrip( changset_revision_str ) ) )
  280. repository = get_repository( trans, repository_id )
  281. if repository.tip != v:
  282. return trans.response.send_redirect( web.url_for( controller='repository',
  283. action='preview_tools_in_changeset',
  284. repository_id=trans.security.encode_id( repository.id ),
  285. changeset_revision=v ) )
  286. url_args = dict( action='browse_downloadable_repositories',
  287. operation='preview_tools_in_changeset',
  288. repository_id=repository_id )
  289. self.downloadable_repository_list_grid.operations = [ grids.GridOperation( "Preview and install tools",
  290. url_args=url_args,
  291. allow_multiple=False,
  292. async_compatible=False ) ]
  293. # Render the list view
  294. return self.downloadable_repository_list_grid( trans, **kwd )
  295. @web.expose
  296. def preview_tools_in_changeset( self, trans, repository_id, **kwd ):
  297. params = util.Params( kwd )
  298. message = util.restore_text( params.get( 'message', '' ) )
  299. status = params.get( 'status', 'done' )
  300. repository = get_repository( trans, repository_id )
  301. changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip ) )
  302. repository_metadata = get_repository_metadata_by_changeset_revision( trans, repository_id, changeset_revision )
  303. if repository_metadata:
  304. metadata = repository_metadata.metadata
  305. else:
  306. metadata = None
  307. revision_label = get_revision_label( trans, repository, changeset_revision )
  308. changeset_revision_select_field = build_changeset_revision_select_field( trans,
  309. repository,
  310. selected_value=changeset_revision,
  311. add_id_to_name=False )
  312. return trans.fill_template( '/webapps/community/repository/preview_tools_in_changeset.mako',
  313. repository=repository,
  314. changeset_revision=changeset_revision,
  315. revision_label=revision_label,
  316. changeset_revision_select_field=changeset_revision_select_field,
  317. metadata=metadata,
  318. display_for_install=True,
  319. message=message,
  320. status=status )
  321. @web.expose
  322. def install_repository_revision( self, trans, repository_id, **kwd ):
  323. params = util.Params( kwd )
  324. message = util.restore_text( params.get( 'message', '' ) )
  325. status = params.get( 'status', 'done' )
  326. galaxy_url = trans.get_cookie( name='toolshedgalaxyurl' )
  327. repository = get_repository( trans, repository_id )
  328. changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip ) )
  329. # Redirect back to local Galaxy to perform install.
  330. tool_shed_url = trans.request.host
  331. repository_clone_url = generate_clone_url( trans, repository_id )
  332. # TODO: support https in the following url.
  333. url = 'http://%s/admin/install_tool_shed_repository?tool_shed_url=%s&repository_name=%s&repository_clone_url=%s&changeset_revision=%s' % \
  334. ( galaxy_url, tool_shed_url, repository.name, repository_clone_url, changeset_revision )
  335. return trans.response.send_redirect( url )
  336. @web.expose
  337. def browse_repositories( self, trans, **kwd ):
  338. # We add params to the keyword dict in this method in order to rename the param
  339. # with an "f-" prefix, simulating filtering by clicking a search link. We have
  340. # to take this approach because the "-" character is illegal in HTTP requests.
  341. if 'operation' in kwd:
  342. operation = kwd['operation'].lower()
  343. if operation == "view_or_manage_repository":
  344. repository_id = kwd[ 'id' ]
  345. repository = get_repository( trans, repository_id )
  346. is_admin = trans.user_is_admin()
  347. if is_admin or repository.user == trans.user:
  348. return trans.response.send_redirect( web.url_for( controller='repository',
  349. action='manage_repository',
  350. **kwd ) )
  351. else:
  352. return trans.response.send_redirect( web.url_for( controller='repository',
  353. action='view_repository',
  354. **kwd ) )
  355. elif operation == "edit_repository":
  356. return trans.response.send_redirect( web.url_for( controller='repository',
  357. action='edit_repository',
  358. **kwd ) )
  359. elif operation == "repositories_by_user":
  360. # Eliminate the current filters if any exist.
  361. for k, v in kwd.items():
  362. if k.startswith( 'f-' ):
  363. del kwd[ k ]
  364. if 'user_id' in kwd:
  365. user = get_user( trans, kwd[ 'user_id' ] )
  366. kwd[ 'f-email' ] = user.email
  367. del kwd[ 'user_id' ]
  368. else:
  369. # The received id is the repository id, so we need to get the id of the user
  370. # that uploaded the repository.
  371. repository_id = kwd.get( 'id', None )
  372. repository = get_repository( trans, repository_id )
  373. kwd[ 'f-email' ] = repository.user.email
  374. elif operation == "my_repositories":
  375. # Eliminate the current filters if any exist.
  376. for k, v in kwd.items():
  377. if k.startswith( 'f-' ):
  378. del kwd[ k ]
  379. kwd[ 'f-email' ] = trans.user.email
  380. elif operation == "repositories_by_category":
  381. # Eliminate the current filters if any exist.
  382. for k, v in kwd.items():
  383. if k.startswith( 'f-' ):
  384. del kwd[ k ]
  385. category_id = kwd.get( 'id', None )
  386. category = get_category( trans, category_id )
  387. kwd[ 'f-Category.name' ] = category.name
  388. elif operation == "receive email alerts":
  389. if trans.user:
  390. if kwd[ 'id' ]:
  391. return trans.response.send_redirect( web.url_for( controller='repository',
  392. action='set_email_alerts',
  393. **kwd ) )
  394. else:
  395. kwd[ 'message' ] = 'You must be logged in to set email alerts.'
  396. kwd[ 'status' ] = 'error'
  397. del kwd[ 'operation' ]
  398. # The changeset_revision_select_field in the RepositoryListGrid performs a refresh_on_change
  399. # which sends in request parameters like changeset_revison_1, changeset_revision_2, etc. One
  400. # of the many select fields on the grid performed the refresh_on_change, so we loop through
  401. # all of the received values to see which value is not the repository tip. If we find it, we
  402. # know the refresh_on_change occurred, and we have the necessary repository id and change set
  403. # revision to pass on.
  404. for k, v in kwd.items():
  405. changset_revision_str = 'changeset_revision_'
  406. if k.startswith( changset_revision_str ):
  407. repository_id = trans.security.encode_id( int( k.lstrip( changset_revision_str ) ) )
  408. repository = get_repository( trans, repository_id )
  409. if repository.tip != v:
  410. return trans.response.send_redirect( web.url_for( controller='repository',
  411. action='browse_repositories',
  412. operation='view_or_manage_repository',
  413. id=trans.security.encode_id( repository.id ),
  414. changeset_revision=v ) )
  415. # Render the list view
  416. return self.repository_list_grid( trans, **kwd )
  417. @web.expose
  418. def create_repository( self, trans, **kwd ):
  419. params = util.Params( kwd )
  420. message = util.restore_text( params.get( 'message', '' ) )
  421. status = params.get( 'status', 'done' )
  422. categories = get_categories( trans )
  423. if not categories:
  424. message = 'No categories have been configured in this instance of the Galaxy Tool Shed. ' + \
  425. 'An administrator needs to create some via the Administrator control panel before creating repositories.',
  426. status = 'error'
  427. return trans.response.send_redirect( web.url_for( controller='repository',
  428. action='browse_repositories',
  429. message=message,
  430. status=status ) )
  431. name = util.restore_text( params.get( 'name', '' ) )
  432. description = util.restore_text( params.get( 'description', '' ) )
  433. long_description = util.restore_text( params.get( 'long_description', '' ) )
  434. category_ids = util.listify( params.get( 'category_id', '' ) )
  435. selected_categories = [ trans.security.decode_id( id ) for id in category_ids ]
  436. if params.get( 'create_repository_button', False ):
  437. error = False
  438. message = self.__validate_repository_name( name, trans.user )
  439. if message:
  440. error = True
  441. if not description:
  442. message = 'Enter a description.'
  443. error = True
  444. if not error:
  445. # Add the repository record to the db
  446. repository = trans.app.model.Repository( name=name,
  447. description=description,
  448. long_description=long_description,
  449. user_id=trans.user.id )
  450. # Flush to get the id
  451. trans.sa_session.add( repository )
  452. trans.sa_session.flush()
  453. # Determine the repository's repo_path on disk
  454. dir = os.path.join( trans.app.config.file_path, *directory_hash_id( repository.id ) )
  455. # Create directory if it does not exist
  456. if not os.path.exists( dir ):
  457. os.makedirs( dir )
  458. # Define repo name inside hashed directory
  459. repository_path = os.path.join( dir, "repo_%d" % repository.id )
  460. # Create local repository directory
  461. if not os.path.exists( repository_path ):
  462. os.makedirs( repository_path )
  463. # Create the local repository
  464. repo = hg.repository( get_configured_ui(), repository_path, create=True )
  465. # Add an entry in the hgweb.config file for the local repository
  466. # This enables calls to repository.repo_path
  467. self.__add_hgweb_config_entry( trans, repository, repository_path )
  468. # Create a .hg/hgrc file for the local repository
  469. self.__create_hgrc_file( repository )
  470. flush_needed = False
  471. if category_ids:
  472. # Create category associations
  473. for category_id in category_ids:
  474. category = trans.app.model.Category.get( trans.security.decode_id( category_id ) )
  475. rca = trans.app.model.RepositoryCategoryAssociation( repository, category )
  476. trans.sa_session.add( rca )
  477. flush_needed = True
  478. if flush_needed:
  479. trans.sa_session.flush()
  480. message = "Repository '%s' has been created." % repository.name
  481. trans.response.send_redirect( web.url_for( controller='repository',
  482. action='view_repository',
  483. message=message,
  484. id=trans.security.encode_id( repository.id ) ) )
  485. return trans.fill_template( '/webapps/community/repository/create_repository.mako',
  486. name=name,
  487. description=description,
  488. long_description=long_description,
  489. selected_categories=selected_categories,
  490. categories=categories,
  491. message=message,
  492. status=status )
  493. def __validate_repository_name( self, name, user ):
  494. # Repository names must be unique for each user, must be at least four characters
  495. # in length and must contain only lower-case letters, numbers, and the '_' character.
  496. if name in [ 'None', None, '' ]:
  497. return 'Enter the required repository name.'
  498. for repository in user.active_repositories:
  499. if repository.name == name:
  500. return "You already have a repository named '%s', so choose a different name." % name
  501. if len( name ) < 4:
  502. return "Repository names must be at least 4 characters in length."
  503. if len( name ) > 80:
  504. return "Repository names cannot be more than 80 characters in length."
  505. if not( VALID_REPOSITORYNAME_RE.match( name ) ):
  506. return "Repository names must contain only lower-case letters, numbers and underscore '_'."
  507. return ''
  508. def __make_hgweb_config_copy( self, trans, hgweb_config ):
  509. # Make a backup of the hgweb.config file
  510. today = date.today()
  511. backup_date = today.strftime( "%Y_%m_%d" )
  512. hgweb_config_copy = '%s/hgweb.config_%s_backup' % ( trans.app.config.root, backup_date )
  513. shutil.copy( os.path.abspath( hgweb_config ), os.path.abspath( hgweb_config_copy ) )
  514. def __add_hgweb_config_entry( self, trans, repository, repository_path ):
  515. # Add an entry in the hgweb.config file for a new repository.
  516. # An entry looks something like:
  517. # repos/test/mira_assembler = database/community_files/000/repo_123.
  518. hgweb_config = "%s/hgweb.config" % trans.app.config.root
  519. # Make a backup of the hgweb.config file since we're going to be changing it.
  520. self.__make_hgweb_config_copy( trans, hgweb_config )
  521. entry = "repos/%s/%s = %s" % ( repository.user.username, repository.name, repository_path.lstrip( './' ) )
  522. if os.path.exists( hgweb_config ):
  523. output = open( hgweb_config, 'a' )
  524. else:
  525. output = open( hgweb_config, 'w' )
  526. output.write( '[paths]\n' )
  527. output.write( "%s\n" % entry )
  528. output.close()
  529. def __change_hgweb_config_entry( self, trans, repository, old_repository_name, new_repository_name ):
  530. # Change an entry in the hgweb.config file for a repository. This only happens when
  531. # the owner changes the name of the repository. An entry looks something like:
  532. # repos/test/mira_assembler = database/community_files/000/repo_123.
  533. hgweb_config = "%s/hgweb.config" % trans.app.config.root
  534. # Make a backup of the hgweb.config file since we're going to be changing it.
  535. self.__make_hgweb_config_copy( trans, hgweb_config )
  536. repo_dir = repository.repo_path
  537. old_lhs = "repos/%s/%s" % ( repository.user.username, old_repository_name )
  538. old_entry = "%s = %s" % ( old_lhs, repo_dir )
  539. new_entry = "repos/%s/%s = %s\n" % ( repository.user.username, new_repository_name, repo_dir )
  540. tmp_fd, tmp_fname = tempfile.mkstemp()
  541. new_hgweb_config = open( tmp_fname, 'wb' )
  542. for i, line in enumerate( open( hgweb_config ) ):
  543. if line.startswith( old_lhs ):
  544. new_hgweb_config.write( new_entry )
  545. else:
  546. new_hgweb_config.write( line )
  547. shutil.move( tmp_fname, os.path.abspath( hgweb_config ) )
  548. def __create_hgrc_file( self, repository ):
  549. # At this point, an entry for the repository is required to be in the hgweb.config
  550. # file so we can call repository.repo_path.
  551. # Create a .hg/hgrc file that looks something like this:
  552. # [web]
  553. # allow_push = test
  554. # name = convert_characters1
  555. # push_ssl = False
  556. # Since we support both http and https, we set push_ssl to False to override
  557. # the default (which is True) in the mercurial api.
  558. repo = hg.repository( get_configured_ui(), path=repository.repo_path )
  559. fp = repo.opener( 'hgrc', 'wb' )
  560. fp.write( '[paths]\n' )
  561. fp.write( 'default = .\n' )
  562. fp.write( 'default-push = .\n' )
  563. fp.write( '[web]\n' )
  564. fp.write( 'allow_push = %s\n' % repository.user.username )
  565. fp.write( 'name = %s\n' % repository.name )
  566. fp.write( 'push_ssl = false\n' )
  567. fp.close()
  568. @web.expose
  569. def browse_repository( self, trans, id, **kwd ):
  570. params = util.Params( kwd )
  571. message = util.restore_text( params.get( 'message', '' ) )
  572. status = params.get( 'status', 'done' )
  573. commit_message = util.restore_text( params.get( 'commit_message', 'Deleted selected files' ) )
  574. repository = get_repository( trans, id )
  575. repo = hg.repository( get_configured_ui(), repository.repo_path )
  576. current_working_dir = os.getcwd()
  577. # Update repository files for browsing.
  578. update_for_browsing( trans, repository, current_working_dir, commit_message=commit_message )
  579. is_malicious = change_set_is_malicious( trans, id, repository.tip )
  580. return trans.fill_template( '/webapps/community/repository/browse_repository.mako',
  581. repo=repo,
  582. repository=repository,
  583. commit_message=commit_message,
  584. is_malicious=is_malicious,
  585. message=message,
  586. status=status )
  587. @web.expose
  588. def contact_owner( self, trans, id, **kwd ):
  589. params = util.Params( kwd )
  590. message = util.restore_text( params.get( 'message', '' ) )
  591. status = params.get( 'status', 'done' )
  592. repository = get_repository( trans, id )
  593. if trans.user and trans.user.email:
  594. return trans.fill_template( "/webapps/community/repository/contact_owner.mako",
  595. repository=repository,
  596. message=message,
  597. status=status )
  598. else:
  599. # Do all we can to eliminate spam.
  600. return trans.show_error_message( "You must be logged in to contact the owner of a repository." )
  601. @web.expose
  602. def send_to_owner( self, trans, id, message='' ):
  603. repository = get_repository( trans, id )
  604. if not message:
  605. message = 'Enter a message'
  606. status = 'error'
  607. elif trans.user and trans.user.email:
  608. smtp_server = trans.app.config.smtp_server
  609. from_address = trans.app.config.email_from
  610. if smtp_server is None or from_address is None:
  611. return trans.show_error_message( "Mail is not configured for this Galaxy tool shed instance" )
  612. to_address = repository.user.email
  613. # Get the name of the server hosting the tool shed instance.
  614. host = trans.request.host
  615. # Build the email message
  616. body = string.Template( contact_owner_template ) \
  617. .safe_substitute( username=trans.user.username,
  618. repository_name=repository.name,
  619. email=trans.user.email,
  620. message=message,
  621. host=host )
  622. subject = "Regarding your tool shed repository named %s" % repository.name
  623. # Send it
  624. try:
  625. util.send_mail( from_address, to_address, subject, body, trans.app.config )
  626. message = "Your message has been sent"
  627. status = "done"
  628. except Exception, e:
  629. message = "An error occurred sending your message by email: %s" % str( e )
  630. status = "error"
  631. else:
  632. # Do all we can to eliminate spam.
  633. return trans.show_error_message( "You must be logged in to contact the owner of a repository." )
  634. return trans.response.send_redirect( web.url_for( controller='repository',
  635. action='contact_owner',
  636. id=id,
  637. message=message,
  638. status=status ) )
  639. @web.expose
  640. def select_files_to_delete( self, trans, id, **kwd ):
  641. params = util.Params( kwd )
  642. message = util.restore_text( params.get( 'message', '' ) )
  643. status = params.get( 'status', 'done' )
  644. commit_message = util.restore_text( params.get( 'commit_message', 'Deleted selected files' ) )
  645. repository = get_repository( trans, id )
  646. repo_dir = repository.repo_path
  647. repo = hg.repository( get_configured_ui(), repo_dir )
  648. selected_files_to_delete = util.restore_text( params.get( 'selected_files_to_delete', '' ) )
  649. if params.get( 'select_files_to_delete_button', False ):
  650. if selected_files_to_delete:
  651. selected_files_to_delete = selected_files_to_delete.split( ',' )
  652. current_working_dir = os.getcwd()
  653. # Get the current repository tip.
  654. tip = repository.tip
  655. for selected_file in selected_files_to_delete:
  656. try:
  657. commands.remove( repo.ui, repo, repo_file, force=True )
  658. except Exception, e:
  659. # I never have a problem with commands.remove on a Mac, but in the test/production
  660. # tool shed environment, it throws an exception whenever I delete all files from a
  661. # repository. If this happens, we'll try the following.
  662. relative_selected_file = selected_file.split( 'repo_%d' % repository.id )[1].lstrip( '/' )
  663. repo.dirstate.remove( relative_selected_file )
  664. repo.dirstate.write()
  665. absolute_selected_file = os.path.abspath( selected_file )
  666. if os.path.isdir( absolute_selected_file ):
  667. try:
  668. os.rmdir( absolute_selected_file )
  669. except OSError, e:
  670. # The directory is not empty
  671. pass
  672. elif os.path.isfile( absolute_selected_file ):
  673. os.remove( absolute_selected_file )
  674. dir = os.path.split( absolute_selected_file )[0]
  675. try:
  676. os.rmdir( dir )
  677. except OSError, e:
  678. # The directory is not empty
  679. pass
  680. # Commit the change set.
  681. if not commit_message:
  682. commit_message = 'Deleted selected files'
  683. try:
  684. commands.commit( repo.ui, repo, repo_dir, user=trans.user.username, message=commit_message )
  685. except Exception, e:
  686. # I never have a problem with commands.commit on a Mac, but in the test/production
  687. # tool shed environment, it occasionally throws a "TypeError: array item must be char"
  688. # exception. If this happens, we'll try the following.
  689. repo.dirstate.write()
  690. repo.commit( user=trans.user.username, text=commit_message )
  691. handle_email_alerts( trans, repository )
  692. # Update the repository files for browsing.
  693. update_for_browsing( trans, repository, current_working_dir, commit_message=commit_message )
  694. # Get the new repository tip.
  695. repo = hg.repository( get_configured_ui(), repo_dir )
  696. if tip != repository.tip:
  697. message = "The selected files were deleted from the repository."
  698. else:
  699. message = 'No changes to repository.'
  700. # Set metadata on the repository tip
  701. error_message, status = set_repository_metadata( trans, id, repository.tip, **kwd )
  702. if error_message:
  703. message = '%s<br/>%s' % ( message, error_message )
  704. return trans.response.send_redirect( web.url_for( controller='repository',
  705. action='manage_repository',
  706. id=id,
  707. message=message,
  708. status=status ) )
  709. else:
  710. message = "Select at least 1 file to delete from the repository before clicking <b>Delete selected files</b>."
  711. status = "error"
  712. is_malicious = change_set_is_malicious( trans, id, repository.tip )
  713. return trans.fill_template( '/webapps/community/repository/browse_repository.mako',
  714. repo=repo,
  715. repository=repository,
  716. commit_message=commit_message,
  717. is_malicious=is_malicious,
  718. message=message,
  719. status=status )
  720. @web.expose
  721. def view_repository( self, trans, id, **kwd ):
  722. params = util.Params( kwd )
  723. message = util.restore_text( params.get( 'message', '' ) )
  724. status = params.get( 'status', 'done' )
  725. repository = get_repository( trans, id )
  726. repo = hg.repository( get_configured_ui(), repository.repo_path )
  727. avg_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, repository, webapp_model=trans.model )
  728. changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip ) )
  729. display_reviews = util.string_as_bool( params.get( 'display_reviews', False ) )
  730. alerts = params.get( 'alerts', '' )
  731. alerts_checked = CheckboxField.is_checked( alerts )
  732. if repository.email_alerts:
  733. email_alerts = from_json_string( repository.email_alerts )
  734. else:
  735. email_alerts = []
  736. user = trans.user
  737. if user and params.get( 'receive_email_alerts_button', False ):
  738. flush_needed = False
  739. if alerts_checked:
  740. if user.email not in email_alerts:
  741. email_alerts.append( user.email )
  742. repository.email_alerts = to_json_string( email_alerts )
  743. flush_needed = True
  744. else:
  745. if user.email in email_alerts:
  746. email_alerts.remove( user.email )
  747. repository.email_alerts = to_json_string( email_alerts )
  748. flush_needed = True
  749. if flush_needed:
  750. trans.sa_session.add( repository )
  751. trans.sa_session.flush()
  752. checked = alerts_checked or ( user and user.email in email_alerts )
  753. alerts_check_box = CheckboxField( 'alerts', checked=checked )
  754. changeset_revision_select_field = build_changeset_revision_select_field( trans,
  755. repository,
  756. selected_value=changeset_revision,
  757. add_id_to_name=False )
  758. revision_label = get_revision_label( trans, repository, changeset_revision )
  759. repository_metadata = get_repository_metadata_by_changeset_revision( trans, id, changeset_revision )
  760. if repository_metadata:
  761. metadata = repository_metadata.metadata
  762. else:
  763. metadata = None
  764. is_malicious = change_set_is_malicious( trans, id, repository.tip )
  765. if is_malicious:
  766. if trans.app.security_agent.can_push( trans.user, repository ):
  767. message += malicious_error_can_push
  768. else:
  769. message += malicious_error
  770. status = 'error'
  771. return trans.fill_template( '/webapps/community/repository/view_repository.mako',
  772. repo=repo,
  773. repository=repository,
  774. metadata=metadata,
  775. avg_rating=avg_rating,
  776. display_reviews=display_reviews,
  777. num_ratings=num_ratings,
  778. alerts_check_box=alerts_check_box,
  779. changeset_revision=changeset_revision,
  780. changeset_revision_select_field=changeset_revision_select_field,
  781. revision_label=revision_label,
  782. is_malicious=is_malicious,
  783. message=message,
  784. status=status )
  785. @web.expose
  786. @web.require_login( "manage repository" )
  787. def manage_repository( self, trans, id, **kwd ):
  788. params = util.Params( kwd )
  789. message = util.restore_text( params.get( 'message', '' ) )
  790. status = params.get( 'status', 'done' )
  791. repository = get_repository( trans, id )
  792. repo_dir = repository.repo_path
  793. repo = hg.repository( get_configured_ui(), repo_dir )
  794. repo_name = util.restore_text( params.get( 'repo_name', repository.name ) )
  795. changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip ) )
  796. description = util.restore_text( params.get( 'description', repository.description ) )
  797. long_description = util.restore_text( params.get( 'long_description', repository.long_description ) )
  798. avg_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, repository, webapp_model=trans.model )
  799. display_reviews = util.string_as_bool( params.get( 'display_reviews', False ) )
  800. alerts = params.get( 'alerts', '' )
  801. alerts_checked = CheckboxField.is_checked( alerts )
  802. category_ids = util.listify( params.get( 'category_id', '' ) )
  803. if repository.email_alerts:
  804. email_alerts = from_json_string( repository.email_alerts )
  805. else:
  806. email_alerts = []
  807. allow_push = params.get( 'allow_push', '' )
  808. error = False
  809. user = trans.user
  810. if params.get( 'edit_repository_button', False ):
  811. flush_needed = False
  812. # TODO: add a can_manage in the security agent.
  813. if user != repository.user:
  814. message = "You are not the owner of this repository, so you cannot manage it."
  815. status = error
  816. return trans.response.send_redirect( web.url_for( controller='repository',
  817. action='view_repository',
  818. id=id,
  819. message=message,
  820. status=status ) )
  821. if repo_name != repository.name:
  822. message = self.__validate_repository_name( repo_name, user )
  823. if message:
  824. error = True
  825. else:
  826. self.__change_hgweb_config_entry( trans, repository, repository.name, repo_name )
  827. repository.name = repo_name
  828. flush_needed = True
  829. if description != repository.description:
  830. repository.description = description
  831. flush_needed = True
  832. if long_description != repository.long_description:
  833. repository.long_description = long_description
  834. flush_needed = True
  835. if flush_needed:
  836. trans.sa_session.add( repository )
  837. trans.sa_session.flush()
  838. message = "The repository information has been updated."
  839. elif params.get( 'manage_categories_button', False ):
  840. flush_needed = False
  841. # Delete all currently existing categories.
  842. for rca in repository.categories:
  843. trans.sa_session.delete( rca )
  844. trans.sa_session.flush()
  845. if category_ids:
  846. # Create category associations
  847. for category_id in category_ids:
  848. category = trans.app.model.Category.get( trans.security.decode_id( category_id ) )
  849. rca = trans.app.model.RepositoryCategoryAssociation( repository, category )
  850. trans.sa_session.add( rca )
  851. trans.sa_session.flush()
  852. message = "The repository information has been updated."
  853. elif params.get( 'user_access_button', False ):
  854. if allow_push not in [ 'none' ]:
  855. remove_auth = params.get( 'remove_auth', '' )
  856. if remove_auth:
  857. usernames = ''
  858. else:
  859. user_ids = util.listify( allow_push )
  860. usernames = []
  861. for user_id in user_ids:
  862. user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
  863. usernames.append( user.username )
  864. usernames = ','.join( usernames )
  865. repository.set_allow_push( usernames, remove_auth=remove_auth )
  866. message = "The repository information has been updated."
  867. elif params.get( 'receive_email_alerts_button', False ):
  868. flush_needed = False
  869. if alerts_checked:
  870. if user.email not in email_alerts:
  871. email_alerts.append( user.email )
  872. repository.email_alerts = to_json_string( email_alerts )
  873. flush_needed = True
  874. else:
  875. if user.email in email_alerts:
  876. email_alerts.remove( user.email )
  877. repository.email_alerts = to_json_string( email_alerts )
  878. flush_needed = True
  879. if flush_needed:
  880. trans.sa_session.add( repository )
  881. trans.sa_session.flush()
  882. message = "The repository information has been updated."
  883. if error:
  884. status = 'error'
  885. if repository.allow_push:
  886. current_allow_push_list = repository.allow_push.split( ',' )
  887. else:
  888. current_allow_push_list = []
  889. allow_push_select_field = self.__build_allow_push_select_field( trans, current_allow_push_list )
  890. checked = alerts_checked or user.email in email_alerts
  891. alerts_check_box = CheckboxField( 'alerts', checked=checked )
  892. changeset_revision_select_field = build_changeset_revision_select_field( trans,
  893. repository,
  894. selected_value=changeset_revision,
  895. add_id_to_name=False )
  896. revision_label = get_revision_label( trans, repository, changeset_revision )
  897. repository_metadata = get_repository_metadata_by_changeset_revision( trans, id, changeset_revision )
  898. if repository_metadata:
  899. metadata = repository_metadata.metadata
  900. is_malicious = repository_metadata.malicious
  901. else:
  902. metadata = None
  903. is_malicious = False
  904. if is_malicious:
  905. if trans.app.security_agent.can_push( trans.user, repository ):
  906. message += malicious_error_can_push
  907. else:
  908. message += malicious_error
  909. status = 'error'
  910. malicious_check_box = CheckboxField( 'malicious', checked=is_malicious )
  911. categories = get_categories( trans )
  912. selected_categories = [ rca.category_id for rca in repository.categories ]
  913. return trans.fill_template( '/webapps/community/repository/manage_repository.mako',
  914. repo_name=repo_name,
  915. description=description,
  916. long_description=long_description,
  917. current_allow_push_list=current_allow_push_list,
  918. allow_push_select_field=allow_push_select_field,
  919. repo=repo,
  920. repository=repository,
  921. changeset_revision=changeset_revision,
  922. changeset_revision_select_field=changeset_revision_select_field,
  923. revision_label=revision_label,
  924. selected_categories=selected_categories,
  925. categories=categories,
  926. metadata=metadata,
  927. avg_rating=avg_rating,
  928. display_reviews=display_reviews,
  929. num_ratings=num_ratings,
  930. alerts_check_box=alerts_check_box,
  931. malicious_check_box=malicious_check_box,
  932. is_malicious=is_malicious,
  933. message=message,
  934. status=status )
  935. @web.expose
  936. def view_changelog( self, trans, id, **kwd ):
  937. params = util.Params( kwd )
  938. message = util.restore_text( params.get( 'message', '' ) )
  939. status = params.get( 'status', 'done' )
  940. repository = get_repository( trans, id )
  941. repo = hg.repository( get_configured_ui(), repository.repo_path )
  942. changesets = []
  943. for changeset in repo.changelog:
  944. ctx = repo.changectx( changeset )
  945. t, tz = ctx.date()
  946. date = datetime( *time.gmtime( float( t ) - tz )[:6] )
  947. display_date = date.strftime( "%Y-%m-%d" )
  948. change_dict = { 'ctx' : ctx,
  949. 'rev' : str( ctx.rev() ),
  950. 'date' : date,
  951. 'display_date' : display_date,
  952. 'description' : ctx.description(),
  953. 'files' : ctx.files(),
  954. 'user' : ctx.user(),
  955. 'parent' : ctx.parents()[0] }
  956. # Make sure we'll view latest changeset first.
  957. changesets.insert( 0, change_dict )
  958. is_malicious = change_set_is_malicious( trans, id, repository.tip )
  959. return trans.fill_template( '/webapps/community/repository/view_changelog.mako',
  960. repository=repository,
  961. changesets=changesets,
  962. is_malicious=is_malicious,
  963. message=message,
  964. status=status )
  965. @web.expose
  966. def view_changeset( self, trans, id, ctx_str, **kwd ):
  967. params = util.Params( kwd )
  968. message = util.restore_text( params.get( 'message', '' ) )
  969. status = params.get( 'status', 'done' )
  970. repository = get_repository( trans, id )
  971. repo = hg.repository( get_configured_ui(), repository.repo_path )
  972. ctx = get_changectx_for_changeset( trans, repo, ctx_str )
  973. if ctx is None:
  974. message = "Repository does not include changeset revision '%s'." % str( ctx_str )
  975. status = 'error'
  976. return trans.response.send_redirect( web.url_for( controller='repository',
  977. action='view_changelog',
  978. id=id,
  979. message=message,
  980. status=status ) )
  981. ctx_parent = ctx.parents()[0]
  982. modified, added, removed, deleted, unknown, ignored, clean = repo.status( node1=ctx_parent.node(), node2=ctx.node() )
  983. anchors = modified + added + removed + deleted + unknown + ignored + clean
  984. diffs = []
  985. for diff in patch.diff( repo, node1=ctx_parent.node(), node2=ctx.node() ):
  986. diffs.append( self.to_html_escaped( diff ) )
  987. is_malicious = change_set_is_malicious( trans, id, repository.tip )
  988. return trans.fill_template( '/webapps/community/repository/view_changeset.mako',
  989. repository=repository,
  990. ctx=ctx,
  991. anchors=anchors,
  992. modified=modified,
  993. added=added,
  994. removed=removed,
  995. deleted=deleted,
  996. unknown=unknown,
  997. ignored=ignored,
  998. clean=clean,
  999. diffs=diffs,
  1000. is_malicious=is_malicious,
  1001. message=message,
  1002. status=status )
  1003. @web.expose
  1004. @web.require_login( "rate repositories" )
  1005. def rate_repository( self, trans, **kwd ):
  1006. """ Rate a repository and return updated rating data. """
  1007. params = util.Params( kwd )
  1008. message = util.restore_text( params.get( 'message', '' ) )
  1009. status = params.get( 'status', 'done' )
  1010. id = params.get( 'id', None )
  1011. if not id:
  1012. return trans.response.send_redirect( web.url_for( controller='repository',
  1013. action='browse_repositories',
  1014. message='Select a repository to rate',
  1015. status='error' ) )
  1016. repository = get_repository( trans, id )
  1017. repo = hg.repository( get_configured_ui(), repository.repo_path )
  1018. if repository.user == trans.user:
  1019. return trans.response.send_redirect( web.url_for( controller='repository',
  1020. action='browse_repositories',
  1021. message="You are not allowed to rate your own repository",
  1022. status='error' ) )
  1023. if params.get( 'rate_button', False ):
  1024. rating = int( params.get( 'rating', '0' ) )
  1025. comment = util.restore_text( params.get( 'comment', '' ) )
  1026. rating = self.rate_item( trans, trans.user, repository, rating, comment )
  1027. avg_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, repository, webapp_model=trans.model )
  1028. display_reviews = util.string_as_bool( params.get( 'display_reviews', False ) )
  1029. rra = self.get_user_item_rating( trans.sa_session, trans.user, repository, webapp_model=trans.model )
  1030. is_malicious = change_set_is_malicious( trans, id, repository.tip )
  1031. return trans.fill_template( '/webapps/community/repository/rate_repository.mako',
  1032. repository=repository,
  1033. avg_rating=avg_rating,
  1034. display_reviews=display_reviews,
  1035. num_ratings=num_ratings,
  1036. rra=rra,
  1037. is_malicious=is_malicious,
  1038. message=message,
  1039. status=status )
  1040. @web.expose
  1041. @web.require_login( "set email alerts" )
  1042. def set_email_alerts( self, trans, **kwd ):
  1043. # Set email alerts for selected repositories
  1044. params = util.Params( kwd )
  1045. user = trans.user
  1046. if user:
  1047. repository_ids = util.listify( kwd.get( 'id', '' ) )
  1048. total_alerts_added = 0
  1049. total_alerts_removed = 0
  1050. flush_needed = False
  1051. for repository_id in repository_ids:
  1052. repository = get_repository( trans, repository_id )
  1053. if repository.email_alerts:
  1054. email_alerts = from_json_string( repository.email_alerts )
  1055. else:
  1056. email_alerts = []
  1057. if user.email in email_alerts:
  1058. email_alerts.remove( user.email )
  1059. repository.email_alerts = to_json_string( email_alerts )
  1060. trans.sa_session.add( repository )
  1061. flush_needed = True
  1062. total_alerts_removed += 1
  1063. else:
  1064. email_alerts.append( user.email )
  1065. repository.email_alerts = to_json_string( email_alerts )
  1066. trans.sa_session.add( repository )
  1067. flush_needed = True
  1068. total_alerts_added += 1
  1069. if flush_needed:
  1070. trans.sa_session.flush()
  1071. message = 'Total alerts added: %d, total alerts removed: %d' % ( total_alerts_added, total_alerts_removed )
  1072. kwd[ 'message' ] = message
  1073. kwd[ 'status' ] = 'done'
  1074. del kwd[ 'operation' ]
  1075. return trans.response.send_redirect( web.url_for( controller='repository',
  1076. action='browse_repositories',
  1077. **kwd ) )
  1078. @web.expose
  1079. @web.require_login( "set repository metadata" )
  1080. def set_metadata( self, trans, id, ctx_str, **kwd ):
  1081. malicious = kwd.get( 'malicious', '' )
  1082. if kwd.get( 'malicious_button', False ):
  1083. repository_metadata = get_repository_metadata_by_changeset_revision( trans, id, ctx_str )
  1084. malicious_checked = CheckboxField.is_checked( malicious )
  1085. repository_metadata.malicious = malicious_checked
  1086. trans.sa_session.add( repository_metadata )
  1087. trans.sa_session.flush()
  1088. if malicious_checked:
  1089. message = "The repository tip has been defined as malicious."
  1090. else:
  1091. message = "The repository tip has been defined as <b>not</b> malicious."
  1092. status = 'done'
  1093. else:
  1094. # The set_metadata_button was clicked
  1095. message, status = set_repository_metadata( trans, id, ctx_str, **kwd )
  1096. if not message:
  1097. message = "Metadata for change set revision '%s' has been reset." % str( ctx_str )
  1098. return trans.response.send_redirect( web.url_for( controller='repository',
  1099. action='manage_repository',
  1100. id=id,
  1101. changeset_revision=ctx_str,
  1102. malicious=malicious,
  1103. message=message,
  1104. status=status ) )
  1105. @web.expose
  1106. def display_tool( self, trans, repository_id, tool_config, changeset_revision, **kwd ):
  1107. params = util.Params( kwd )
  1108. message = util.restore_text( params.get( 'message', '' ) )
  1109. status = params.get( 'status', 'done' )
  1110. display_for_install = util.string_as_bool( params.get( 'display_for_install', False ) )
  1111. repository = get_repository( trans, repository_id )
  1112. repo = hg.repository( get_configured_ui(), repository.repo_path )
  1113. try:
  1114. if changeset_revision == repository.tip:
  1115. # Get the tool config from the file system we use for browsing.
  1116. tool = load_tool( trans, os.path.abspath( tool_config ) )
  1117. else:
  1118. # Get the tool config file name from the hgweb url, something like:
  1119. # /repos/test/convert_chars1/file/e58dcf0026c7/convert_characters.xml
  1120. old_tool_config_file_name = tool_config.split( '/' )[ -1 ]
  1121. ctx = get_changectx_for_changeset( trans, repo, changeset_revision )
  1122. fctx = None
  1123. for filename in ctx:
  1124. filename_head, filename_tail = os.path.split( filename )
  1125. if filename_tail == old_tool_config_file_name:
  1126. fctx = ctx[ filename ]
  1127. break
  1128. if fctx:
  1129. # Write the contents of the old tool config to a temporary file.
  1130. fh = tempfile.NamedTemporaryFile( 'w' )
  1131. tmp_filename = fh.name
  1132. fh.close()
  1133. fh = open( tmp_fi