PageRenderTime 55ms CodeModel.GetById 12ms app.highlight 36ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://bitbucket.org/cistrome/cistrome-harvard/
Python | 610 lines | 608 code | 2 blank | 0 comment | 12 complexity | cc695534265982bd809e9a07df3dcb00 MD5 | raw file
  1import os, string, socket, logging
  2from time import strftime
  3from datetime import *
  4from galaxy.tools import *
  5from galaxy.util.json import from_json_string, to_json_string
  6from galaxy.web.base.controller import *
  7from galaxy.webapps.community import model
  8from galaxy.model.orm import *
  9from galaxy.model.item_attrs import UsesItemRatings
 10from mercurial import hg, ui, commands
 11
 12log = logging.getLogger( __name__ )
 13
 14email_alert_template = """
 15GALAXY TOOL SHED REPOSITORY UPDATE ALERT
 16-----------------------------------------------------------------------------
 17You received this alert because you registered to receive email whenever
 18changes were made to the repository named "${repository_name}".
 19-----------------------------------------------------------------------------
 20
 21Date of change: ${display_date}
 22Changed by:     ${username}
 23
 24Revision: ${revision}
 25Change description:
 26${description}
 27
 28-----------------------------------------------------------------------------
 29This change alert was sent from the Galaxy tool shed hosted on the server
 30"${host}"
 31"""
 32
 33contact_owner_template = """
 34GALAXY TOOL SHED REPOSITORY MESSAGE
 35------------------------
 36
 37The user '${username}' sent you the following message regarding your tool shed
 38repository named '${repository_name}'.  You can respond by sending a reply to
 39the user's email address: ${email}.
 40-----------------------------------------------------------------------------
 41${message}
 42-----------------------------------------------------------------------------
 43This message was sent from the Galaxy Tool Shed instance hosted on the server
 44'${host}'
 45"""
 46
 47# States for passing messages
 48SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error"
 49
 50malicious_error = "  This changeset cannot be downloaded because it potentially produces malicious behavior or contains inappropriate content."
 51malicious_error_can_push = "  Correct this changeset as soon as possible, it potentially produces malicious behavior or contains inappropriate content."
 52
 53class ItemRatings( UsesItemRatings ):
 54    """Overrides rate_item method since we also allow for comments"""
 55    def rate_item( self, trans, user, item, rating, comment='' ):
 56        """ Rate an item. Return type is <item_class>RatingAssociation. """
 57        item_rating = self.get_user_item_rating( trans.sa_session, user, item, webapp_model=trans.model )
 58        if not item_rating:
 59            # User has not yet rated item; create rating.
 60            item_rating_assoc_class = self._get_item_rating_assoc_class( item, webapp_model=trans.model )
 61            item_rating = item_rating_assoc_class()
 62            item_rating.user = trans.user
 63            item_rating.set_item( item )
 64            item_rating.rating = rating
 65            item_rating.comment = comment
 66            trans.sa_session.add( item_rating )
 67            trans.sa_session.flush()
 68        elif item_rating.rating != rating or item_rating.comment != comment:
 69            # User has previously rated item; update rating.
 70            item_rating.rating = rating
 71            item_rating.comment = comment
 72            trans.sa_session.add( item_rating )
 73            trans.sa_session.flush()
 74        return item_rating
 75
 76## ---- Utility methods -------------------------------------------------------
 77
 78def get_categories( trans ):
 79    """Get all categories from the database"""
 80    return trans.sa_session.query( trans.model.Category ) \
 81                           .filter( trans.model.Category.table.c.deleted==False ) \
 82                           .order_by( trans.model.Category.table.c.name ).all()
 83def get_category( trans, id ):
 84    """Get a category from the database"""
 85    return trans.sa_session.query( trans.model.Category ).get( trans.security.decode_id( id ) )
 86def get_repository( trans, id ):
 87    """Get a repository from the database via id"""
 88    return trans.sa_session.query( trans.model.Repository ).get( trans.security.decode_id( id ) )
 89def get_repository_metadata_by_changeset_revision( trans, id, changeset_revision ):
 90    """Get metadata for a specified repository change set from the database"""
 91    return trans.sa_session.query( trans.model.RepositoryMetadata ) \
 92                           .filter( and_( trans.model.RepositoryMetadata.table.c.repository_id == trans.security.decode_id( id ),
 93                                          trans.model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) ) \
 94                           .first()
 95def get_repository_metadata_by_id( trans, id ):
 96    """Get repository metadata from the database"""
 97    return trans.sa_session.query( trans.model.RepositoryMetadata ).get( trans.security.decode_id( id ) )
 98def get_revision_label( trans, repository, changeset_revision ):
 99    """
100    Return a string consisting of the human read-able 
101    changeset rev and the changeset revision string.
102    """
103    repo = hg.repository( get_configured_ui(), repository.repo_path )
104    ctx = get_changectx_for_changeset( trans, repo, changeset_revision )
105    if ctx:
106        return "%s:%s" % ( str( ctx.rev() ), changeset_revision )
107    else:
108        return "-1:%s" % changeset_revision
109def get_latest_repository_metadata( trans, id ):
110    """Get last metadata defined for a specified repository from the database"""
111    return trans.sa_session.query( trans.model.RepositoryMetadata ) \
112                           .filter( trans.model.RepositoryMetadata.table.c.repository_id == trans.security.decode_id( id ) ) \
113                           .order_by( trans.model.RepositoryMetadata.table.c.id.desc() ) \
114                           .first()
115def generate_workflow_metadata( trans, id, changeset_revision, exported_workflow_dict, metadata_dict ):
116    """
117    Update the received metadata_dict with changes that have been applied
118    to the received exported_workflow_dict.  Store everything except the
119    workflow steps in the database.
120    """
121    workflow_dict = { 'a_galaxy_workflow' : exported_workflow_dict[ 'a_galaxy_workflow' ],
122                      'name' :exported_workflow_dict[ 'name' ],
123                      'annotation' : exported_workflow_dict[ 'annotation' ],
124                      'format-version' : exported_workflow_dict[ 'format-version' ] }
125    if 'workflows' in metadata_dict:
126        metadata_dict[ 'workflows' ].append( workflow_dict )
127    else:
128        metadata_dict[ 'workflows' ] = [ workflow_dict ]
129    return metadata_dict
130def new_workflow_metadata_required( trans, id, metadata_dict ):
131    """
132    TODO: Currently everything about an exported workflow except the name is hard-coded, so
133    there's no real way to differentiate versions of exported workflows.  If this changes at
134    some future time, this method should be enhanced accordingly...
135    """
136    if 'workflows' in metadata_dict:
137        repository_metadata = get_latest_repository_metadata( trans, id )
138        if repository_metadata:
139            if repository_metadata.metadata:
140                # The repository has metadata, so update the workflows value - no new record is needed.
141                return False
142        else:
143            # There is no saved repository metadata, so we need to create a new repository_metadata table record.
144            return True
145    # The received metadata_dict includes no metadata for workflows, so a new repository_metadata table
146    # record is not needed.
147    return False
148def generate_clone_url( trans, repository_id ):
149    repository = get_repository( trans, repository_id )
150    protocol, base = trans.request.base.split( '://' )
151    if trans.user:
152        username = '%s@' % trans.user.username
153    else:
154        username = ''
155    return '%s://%s%s/repos/%s/%s' % ( protocol, username, base, repository.user.username, repository.name )
156def generate_tool_guid( trans, repository, tool ):
157    """
158    Generate a guid for the received tool.  The form of the guid is    
159    <tool shed host>/repos/<tool shed username>/<tool shed repo name>/<tool id>/<tool version>
160    """
161    return '%s/repos/%s/%s/%s/%s' % ( trans.request.host,
162                                      repository.user.username,
163                                      repository.name,
164                                      tool.id,
165                                      tool.version )
166def check_tool_input_params( trans, name, tool, sample_files, invalid_files ):
167    """
168    Check all of the tool's input parameters, looking for any that are dynamically generated
169    using external data files to make sure the files exist.
170    """
171    can_set_metadata = True
172    correction_msg = ''
173    for input_param in tool.input_params:
174        if isinstance( input_param, galaxy.tools.parameters.basic.SelectToolParameter ) and input_param.is_dynamic:
175            # If the tool refers to .loc files or requires an entry in the
176            # tool_data_table_conf.xml, make sure all requirements exist.
177            options = input_param.dynamic_options or input_param.options
178            if options:
179                if options.tool_data_table or options.missing_tool_data_table_name:
180                    # Make sure the repository contains a tool_data_table_conf.xml.sample file.
181                    sample_found = False
182                    for sample_file in sample_files:
183                        head, tail = os.path.split( sample_file )
184                        if tail == 'tool_data_table_conf.xml.sample':
185                            sample_found = True
186                            error, correction_msg = handle_sample_tool_data_table_conf_file( trans, sample_file )
187                            if error:
188                                can_set_metadata = False
189                                invalid_files.append( ( tail, correction_msg ) ) 
190                            else:
191                                options.missing_tool_data_table_name = None
192                            break
193                    if not sample_found:
194                        can_set_metadata = False
195                        correction_msg = "This file requires an entry in the tool_data_table_conf.xml file.  "
196                        correction_msg += "Upload a file named tool_data_table_conf.xml.sample to the repository "
197                        correction_msg += "that includes the required entry to resolve this issue.<br/>"
198                        invalid_files.append( ( name, correction_msg ) )
199                if options.index_file or options.missing_index_file:
200                    # Make sure the repository contains the required xxx.loc.sample file.
201                    index_file = options.index_file or options.missing_index_file
202                    index_head, index_tail = os.path.split( index_file )
203                    sample_found = False
204                    for sample_file in sample_files:
205                        sample_head, sample_tail = os.path.split( sample_file )
206                        if sample_tail == '%s.sample' % index_tail:
207                            copy_sample_loc_file( trans, sample_file )
208                            options.index_file = index_tail
209                            options.missing_index_file = None
210                            options.tool_data_table.missing_index_file = None
211                            sample_found = True
212                            break
213                    if not sample_found:
214                        can_set_metadata = False
215                        correction_msg = "This file refers to a file named <b>%s</b>.  " % str( index_file )
216                        correction_msg += "Upload a file named <b>%s.sample</b> to the repository to correct this error." % str( index_tail )
217                        invalid_files.append( ( name, correction_msg ) )
218    return can_set_metadata, invalid_files
219def generate_tool_metadata( trans, id, changeset_revision, tool_config, tool, metadata_dict ):
220    """
221    Update the received metadata_dict with changes that have been
222    applied to the received tool.
223    """
224    repository = get_repository( trans, id )
225    # Handle tool.requirements.
226    tool_requirements = []
227    for tr in tool.requirements:
228        name=tr.name
229        type=tr.type
230        if type == 'fabfile':
231            version = None
232            fabfile = tr.fabfile
233            method = tr.method
234        else:
235            version = tr.version
236            fabfile = None
237            method = None
238        requirement_dict = dict( name=name,
239                                 type=type,
240                                 version=version,
241                                 fabfile=fabfile,
242                                 method=method )
243        tool_requirements.append( requirement_dict )
244    # Handle tool.tests.
245    tool_tests = []
246    if tool.tests:
247        for ttb in tool.tests:
248            test_dict = dict( name=ttb.name,
249                              required_files=ttb.required_files,
250                              inputs=ttb.inputs,
251                              outputs=ttb.outputs )
252            tool_tests.append( test_dict )
253    tool_dict = dict( id=tool.id,
254                      guid = generate_tool_guid( trans, repository, tool ),
255                      name=tool.name,
256                      version=tool.version,
257                      description=tool.description,
258                      version_string_cmd = tool.version_string_cmd,
259                      tool_config=tool_config,
260                      requirements=tool_requirements,
261                      tests=tool_tests )
262    if 'tools' in metadata_dict:
263        metadata_dict[ 'tools' ].append( tool_dict )
264    else:
265        metadata_dict[ 'tools' ] = [ tool_dict ]
266    return metadata_dict
267def new_tool_metadata_required( trans, id, metadata_dict ):
268    """
269    Compare the last saved metadata for each tool in the repository with the new metadata
270    in metadata_dict to determine if a new repository_metadata table record is required, or
271    if the last saved metadata record can updated instead.
272    """
273    if 'tools' in metadata_dict:
274        repository_metadata = get_latest_repository_metadata( trans, id )
275        if repository_metadata:
276            metadata = repository_metadata.metadata
277            if metadata and 'tools' in metadata:
278                saved_tool_ids = []
279                # The metadata for one or more tools was successfully generated in the past
280                # for this repository, so we first compare the version string for each tool id
281                # in metadata_dict with what was previously saved to see if we need to create
282                # a new table record or if we can simply update the existing record.
283                for new_tool_metadata_dict in metadata_dict[ 'tools' ]:
284                    for saved_tool_metadata_dict in metadata[ 'tools' ]:
285                        if saved_tool_metadata_dict[ 'id' ] not in saved_tool_ids:
286                            saved_tool_ids.append( saved_tool_metadata_dict[ 'id' ] )
287                        if new_tool_metadata_dict[ 'id' ] == saved_tool_metadata_dict[ 'id' ]:
288                            if new_tool_metadata_dict[ 'version' ] != saved_tool_metadata_dict[ 'version' ]:
289                                return True
290                # So far, a new metadata record is not required, but we still have to check to see if
291                # any new tool ids exist in metadata_dict that are not in the saved metadata.  We do
292                # this because if a new tarball was uploaded to a repository that included tools, it
293                # may have removed existing tool files if they were not included in the uploaded tarball.
294                for new_tool_metadata_dict in metadata_dict[ 'tools' ]:
295                    if new_tool_metadata_dict[ 'id' ] not in saved_tool_ids:
296                        return True
297            else:
298                # We have repository metadata that does not include metadata for any tools in the
299                # repository, so we can update the existing repository metadata.
300                return False
301        else:
302            # There is no saved repository metadata, so we need to create a new repository_metadata
303            # table record.
304            return True
305    # The received metadata_dict includes no metadata for tools, so a new repository_metadata table
306    # record is not needed.
307    return False
308def set_repository_metadata( trans, id, changeset_revision, **kwd ):
309    """Set repository metadata"""
310    message = ''
311    status = 'done'
312    repository = get_repository( trans, id )
313    repo_dir = repository.repo_path
314    repo = hg.repository( get_configured_ui(), repo_dir )
315    invalid_files = []
316    sample_files = []
317    ctx = get_changectx_for_changeset( trans, repo, changeset_revision )
318    if ctx is not None:
319        metadata_dict = {}
320        if changeset_revision == repository.tip:
321            for root, dirs, files in os.walk( repo_dir ):
322                if not root.find( '.hg' ) >= 0 and not root.find( 'hgrc' ) >= 0:
323                    if '.hg' in dirs:
324                        # Don't visit .hg directories - should be impossible since we don't
325                        # allow uploaded archives that contain .hg dirs, but just in case...
326                        dirs.remove( '.hg' )
327                    if 'hgrc' in files:
328                         # Don't include hgrc files in commit.
329                        files.remove( 'hgrc' )
330                    # Find all special .sample files first.
331                    for name in files:
332                        if name.endswith( '.sample' ):
333                            sample_files.append( os.path.abspath( os.path.join( root, name ) ) )
334                    for name in files:
335                        # Find all tool configs.
336                        if name.endswith( '.xml' ):
337                            try:
338                                full_path = os.path.abspath( os.path.join( root, name ) )
339                                tool = load_tool( trans, full_path )
340                                if tool is not None:
341                                    can_set_metadata, invalid_files = check_tool_input_params( trans, name, tool, sample_files, invalid_files )
342                                    if can_set_metadata:
343                                        # Update the list of metadata dictionaries for tools in metadata_dict.
344                                        tool_config = os.path.join( root, name )
345                                        metadata_dict = generate_tool_metadata( trans, id, changeset_revision, tool_config, tool, metadata_dict )
346                            except Exception, e:
347                                invalid_files.append( ( name, str( e ) ) )
348                        # Find all exported workflows
349                        elif name.endswith( '.ga' ):
350                            try:
351                                full_path = os.path.abspath( os.path.join( root, name ) )
352                                # Convert workflow data from json
353                                fp = open( full_path, 'rb' )
354                                workflow_text = fp.read()
355                                fp.close()
356                                exported_workflow_dict = from_json_string( workflow_text )
357                                # Update the list of metadata dictionaries for workflows in metadata_dict.
358                                metadata_dict = generate_workflow_metadata( trans, id, changeset_revision, exported_workflow_dict, metadata_dict )
359                            except Exception, e:
360                                invalid_files.append( ( name, str( e ) ) )
361        else:
362            # Find all special .sample files first.
363            for filename in ctx:
364                if filename.endswith( '.sample' ):
365                    sample_files.append( os.path.abspath( os.path.join( root, filename ) ) )
366            # Get all tool config file names from the hgweb url, something like:
367            # /repos/test/convert_chars1/file/e58dcf0026c7/convert_characters.xml
368            for filename in ctx:
369                # Find all tool configs - should not have to update metadata for workflows for now.
370                if filename.endswith( '.xml' ):
371                    fctx = ctx[ filename ]
372                    # Write the contents of the old tool config to a temporary file.
373                    fh = tempfile.NamedTemporaryFile( 'w' )
374                    tmp_filename = fh.name
375                    fh.close()
376                    fh = open( tmp_filename, 'w' )
377                    fh.write( fctx.data() )
378                    fh.close()
379                    try:
380                        tool = load_tool( trans, tmp_filename )
381                        if tool is not None:
382                            can_set_metadata, invalid_files = check_tool_input_params( trans, filename, tool, sample_files, invalid_files )
383                            if can_set_metadata:
384                                # Update the list of metadata dictionaries for tools in metadata_dict.  Note that filename
385                                # here is the relative path to the config file within the change set context, something
386                                # like filtering.xml, but when the change set was the repository tip, the value was
387                                # something like database/community_files/000/repo_1/filtering.xml.  This shouldn't break
388                                # anything, but may result in a bit of confusion when maintaining the code / data over time.
389                                metadata_dict = generate_tool_metadata( trans, id, changeset_revision, filename, tool, metadata_dict )
390                    except Exception, e:
391                        invalid_files.append( ( name, str( e ) ) )
392                    try:
393                        os.unlink( tmp_filename )
394                    except:
395                        pass
396        if metadata_dict:
397            if changeset_revision == repository.tip:
398                if new_tool_metadata_required( trans, id, metadata_dict ) or new_workflow_metadata_required( trans, id, metadata_dict ):
399                    # Create a new repository_metadata table row.
400                    repository_metadata = trans.model.RepositoryMetadata( repository.id, changeset_revision, metadata_dict )
401                    trans.sa_session.add( repository_metadata )
402                    trans.sa_session.flush()
403                else:
404                    # Update the last saved repository_metadata table row.
405                    repository_metadata = get_latest_repository_metadata( trans, id )
406                    repository_metadata.changeset_revision = changeset_revision
407                    repository_metadata.metadata = metadata_dict
408                    trans.sa_session.add( repository_metadata )
409                    trans.sa_session.flush()
410            else:
411                # We're re-generating metadata for an old repository revision.
412                repository_metadata = get_repository_metadata_by_changeset_revision( trans, id, changeset_revision )
413                repository_metadata.metadata = metadata_dict
414                trans.sa_session.add( repository_metadata )
415                trans.sa_session.flush()
416        else:
417            message = "Change set revision '%s' includes no tools or exported workflows for which metadata can be set." % str( changeset_revision )
418            status = "error"
419    else:
420        # change_set is None
421        message = "Repository does not include change set revision '%s'." % str( changeset_revision )
422        status = 'error'
423    if invalid_files:
424        if metadata_dict:
425            message = "Metadata was defined for some items in change set revision '%s'.  " % str( changeset_revision )
426            message += "Correct the following problems if necessary and reset metadata.<br/>"
427        else:
428            message = "Metadata cannot be defined for change set revision '%s'.  Correct the following problems and reset metadata.<br/>" % str( changeset_revision )
429        for itc_tup in invalid_files:
430            tool_file, exception_msg = itc_tup
431            if exception_msg.find( 'No such file or directory' ) >= 0:
432                exception_items = exception_msg.split()
433                missing_file_items = exception_items[7].split( '/' )
434                missing_file = missing_file_items[-1].rstrip( '\'' )
435                if missing_file.endswith( '.loc' ):
436                    sample_ext = '%s.sample' % missing_file
437                else:
438                    sample_ext = missing_file
439                correction_msg = "This file refers to a missing file <b>%s</b>.  " % str( missing_file )
440                correction_msg += "Upload a file named <b>%s</b> to the repository to correct this error." % sample_ext
441            else:
442               correction_msg = exception_msg
443            message += "<b>%s</b> - %s<br/>" % ( tool_file, correction_msg )
444        status = 'error'
445    return message, status
446def get_repository_by_name( trans, name ):
447    """Get a repository from the database via name"""
448    return trans.sa_session.query( trans.model.Repository ).filter_by( name=name ).one()
449def get_changectx_for_changeset( trans, repo, changeset_revision, **kwd ):
450    """Retrieve a specified changectx from a repository"""
451    for changeset in repo.changelog:
452        ctx = repo.changectx( changeset )
453        if str( ctx ) == changeset_revision:
454            return ctx
455    return None
456def change_set_is_malicious( trans, id, changeset_revision, **kwd ):
457    """Check the malicious flag in repository metadata for a specified change set"""
458    repository_metadata = get_repository_metadata_by_changeset_revision( trans, id, changeset_revision )
459    if repository_metadata:
460        return repository_metadata.malicious
461    return False
462def get_configured_ui():
463    # Configure any desired ui settings.
464    _ui = ui.ui()
465    # The following will suppress all messages.  This is
466    # the same as adding the following setting to the repo
467    # hgrc file' [ui] section:
468    # quiet = True
469    _ui.setconfig( 'ui', 'quiet', True )
470    return _ui
471def get_user( trans, id ):
472    """Get a user from the database"""
473    return trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( id ) )
474def handle_email_alerts( trans, repository ):
475    repo_dir = repository.repo_path
476    repo = hg.repository( get_configured_ui(), repo_dir )
477    smtp_server = trans.app.config.smtp_server
478    if smtp_server and repository.email_alerts:
479        # Send email alert to users that want them.
480        if trans.app.config.email_from is not None:
481            email_from = trans.app.config.email_from
482        elif trans.request.host.split( ':' )[0] == 'localhost':
483            email_from = 'galaxy-no-reply@' + socket.getfqdn()
484        else:
485            email_from = 'galaxy-no-reply@' + trans.request.host.split( ':' )[0]
486        tip_changeset = repo.changelog.tip()
487        ctx = repo.changectx( tip_changeset )
488        t, tz = ctx.date()
489        date = datetime( *time.gmtime( float( t ) - tz )[:6] )
490        display_date = date.strftime( "%Y-%m-%d" )
491        try:
492            username = ctx.user().split()[0]
493        except:
494            username = ctx.user()
495        # Build the email message
496        body = string.Template( email_alert_template ) \
497            .safe_substitute( host=trans.request.host,
498                              repository_name=repository.name,
499                              revision='%s:%s' %( str( ctx.rev() ), ctx ),
500                              display_date=display_date,
501                              description=ctx.description(),
502                              username=username )
503        frm = email_from
504        subject = "Galaxy tool shed repository update alert"
505        email_alerts = from_json_string( repository.email_alerts )
506        for email in email_alerts:
507            to = email.strip()
508            # Send it
509            try:
510                util.send_mail( frm, to, subject, body, trans.app.config )
511            except Exception, e:
512                log.exception( "An error occurred sending a tool shed repository update alert by email." )
513def update_for_browsing( trans, repository, current_working_dir, commit_message='' ):
514    # Make a copy of a repository's files for browsing, remove from disk all files that
515    # are not tracked, and commit all added, modified or removed files that have not yet
516    # been committed.
517    repo_dir = repository.repo_path
518    repo = hg.repository( get_configured_ui(), repo_dir )
519    # The following will delete the disk copy of only the files in the repository.
520    #os.system( 'hg update -r null > /dev/null 2>&1' )
521    repo.ui.pushbuffer()
522    commands.status( repo.ui, repo, all=True )
523    status_and_file_names = repo.ui.popbuffer().strip().split( "\n" )
524    # status_and_file_names looks something like:
525    # ['? README', '? tmap_tool/tmap-0.0.9.tar.gz', '? dna_filtering.py', 'C filtering.py', 'C filtering.xml']
526    # The codes used to show the status of files are:
527    # M = modified
528    # A = added
529    # R = removed
530    # C = clean
531    # ! = deleted, but still tracked
532    # ? = not tracked
533    # I = ignored
534    files_to_remove_from_disk = []
535    files_to_commit = []
536    for status_and_file_name in status_and_file_names:
537        if status_and_file_name.startswith( '?' ) or status_and_file_name.startswith( 'I' ):
538            files_to_remove_from_disk.append( os.path.abspath( os.path.join( repo_dir, status_and_file_name.split()[1] ) ) )
539        elif status_and_file_name.startswith( 'M' ) or status_and_file_name.startswith( 'A' ) or status_and_file_name.startswith( 'R' ):
540            files_to_commit.append( os.path.abspath( os.path.join( repo_dir, status_and_file_name.split()[1] ) ) )
541    for full_path in files_to_remove_from_disk:
542        # We'll remove all files that are not tracked or ignored.
543        if os.path.isdir( full_path ):
544            try:
545                os.rmdir( full_path )
546            except OSError, e:
547                # The directory is not empty
548                pass
549        elif os.path.isfile( full_path ):
550            os.remove( full_path )
551            dir = os.path.split( full_path )[0]
552            try:
553                os.rmdir( dir )
554            except OSError, e:
555                # The directory is not empty
556                pass
557    if files_to_commit:
558        if not commit_message:
559            commit_message = 'Committed changes to: %s' % ', '.join( files_to_commit )
560        repo.dirstate.write()
561        repo.commit( user=trans.user.username, text=commit_message )
562    os.chdir( repo_dir )
563    os.system( 'hg update > /dev/null 2>&1' )
564    os.chdir( current_working_dir )
565def load_tool( trans, config_file ):
566    """
567    Load a single tool from the file named by `config_file` and return 
568    an instance of `Tool`.
569    """
570    # Parse XML configuration file and get the root element
571    tree = util.parse_xml( config_file )
572    root = tree.getroot()
573    if root.tag == 'tool':
574        # Allow specifying a different tool subclass to instantiate
575        if root.find( "type" ) is not None:
576            type_elem = root.find( "type" )
577            module = type_elem.get( 'module', 'galaxy.tools' )
578            cls = type_elem.get( 'class' )
579            mod = __import__( module, globals(), locals(), [cls])
580            ToolClass = getattr( mod, cls )
581        elif root.get( 'tool_type', None ) is not None:
582            ToolClass = tool_types.get( root.get( 'tool_type' ) )
583        else:
584            ToolClass = Tool
585        return ToolClass( config_file, root, trans.app )
586    return None
587def build_changeset_revision_select_field( trans, repository, selected_value=None, add_id_to_name=True ):
588    """
589    Build a SelectField whose options are the changeset_revision
590    strings of all downloadable_revisions of the received repository.
591    """
592    repo = hg.repository( get_configured_ui(), repository.repo_path )
593    options = []
594    refresh_on_change_values = []
595    for repository_metadata in repository.downloadable_revisions:
596        changeset_revision = repository_metadata.changeset_revision
597        revision_label = get_revision_label( trans, repository, changeset_revision )
598        options.append( ( revision_label, changeset_revision ) )
599        refresh_on_change_values.append( changeset_revision )
600    if add_id_to_name:
601        name = 'changeset_revision_%d' % repository.id
602    else:
603        name = 'changeset_revision'
604    select_field = SelectField( name=name,
605                                refresh_on_change=True,
606                                refresh_on_change_values=refresh_on_change_values )
607    for option_tup in options:
608        selected = selected_value and option_tup[1] == selected_value
609        select_field.add_option( option_tup[0], option_tup[1], selected=selected )
610    return select_field