view version.py @ 323:44f94ffe28f7

version works with file and folder, returns null if not in hg. Also a function to get all modules, but seems to get junk at the same time
author Thierry Bertin-Mahieux <bertinmt@iro.umontreal.ca>
date Thu, 12 Jun 2008 17:12:45 -0400
parents 9ebc960260c5
children ce79bf5fa463
line wrap: on
line source

import subprocess as _subprocess
import imp as _imp
import sys
import os

_cache = {}

def src_version(module_name):
    """Return compact identifier of module code.

    @return: compact identifier of module code.
    @rtype: string

    @note: This function tries to establish that the source files and the repo
    are syncronized.  It raises an Exception if there are un-tracked '.py'
    files, or if there are un-committed modifications.  This implementation uses
    "hg id" to establish this.  The code returned by "hg id" is not affected by
    hg pull, but pulling might remove the " tip" string which might have
    appeared.  This implementation ignores the  " tip" information, and only
    uses the code.

    @note: This implementation is assumes that the import directory is under
    version control by mercurial.

    """

    if module_name not in _cache:

        try :
            location = _imp.find_module(module_name)[1]
        except ImportError:
            _cache[module_name] = None
            return None
        #print 'location:', location
        isdir = False
        if os.path.isdir(location) :
            isdir = True
        elif os.path.isfile(location) :
            isdir = False
        else :
            # SEEMS THIS CASE EXIST, FOR WEIRD BUILTIN FUNCTIONS
            #print location,": it's 'not a dir, it's not a file, it's superman!"
            #raise Exception('Unknown location or file type')
            _cache[module_name] = None
            return None


        # we're dealing with a dir
        if isdir :

            # under hg?
            if not os.path.exists( os.path.join( location , '.hg') ) :
                _cache[module_name] = None
                return None

            status = _subprocess.Popen(('hg','st'),cwd=location,stdout=_subprocess.PIPE).communicate()[0]
            #print 'status =', status
            #TODO: check that the process return code is 0 (ticket #45)

            #status_codes = [line[0] for line in  if line and line[0] != '?']
            for line in status.split('\n'):
                if not line: continue
                if line[0] != '?':
                    raise Exception('Uncommitted modification to "%s" in %s (%s)'
                        %(line[2:], __name__,location))
                if line[0] == '?' and line[-3:] == '.py':
                    raise Exception('Untracked file "%s" in %s (%s)'
                        %(line[2:], __name__, location))

            hg_id = _subprocess.Popen(('hg','id'),cwd=location,stdout=_subprocess.PIPE).communicate()[0]

            # This asserts my understanding of hg id return values
            # There is mention in the doc that it might return two parent hash codes
            # but I've never seen it, and I dont' know what it means or how it is
            # formatted.
            tokens = hg_id.split(' ')
            assert len(tokens) <= 2
            assert len(tokens) >= 1
            assert tokens[0][-1] != '+' # the trailing + indicates uncommitted changes
            if len(tokens) == 2:
                assert tokens[1] == 'tip\n'

            _cache[module_name] = tokens[0]

        # we're dealing with a file
        if not isdir :

            folder = os.path.split( os.path.abspath(location) )[0]
            # under hg?
            if not os.path.exists( os.path.join( folder , '.hg') ) :
                _cache[module_name] = None
                return None

            status = _subprocess.Popen(('hg','st',location),cwd=folder,stdout=_subprocess.PIPE).communicate()[0]
            #print 'status =', status

            #status_codes = [line[0] for line in  if line and line[0] != '?']
            for line in status.split('\n'):
                if not line: continue
                if line[0] != '?':
                    raise Exception('Uncommitted modification to "%s" in %s (%s)'
                        %(line[2:], location,folder))
                if line[0] == '?' and line[-3:] == '.py':
                    raise Exception('Untracked file "%s" in %s (%s)'
                        %(line[2:], location, folder))

            hg_id = _subprocess.Popen(('hg','id'),cwd=folder,stdout=_subprocess.PIPE).communicate()[0]

            # This asserts my understanding of hg id return values
            # There is mention in the doc that it might return two parent hash codes
            # but I've never seen it, and I dont' know what it means or how it is
            # formatted.
            tokens = hg_id.split(' ')
            assert len(tokens) <= 2
            assert len(tokens) >= 1
            if tokens[0][-1] == '+' :
                tokens[0] = tokens[0][:-1] # the change was not on this file
            if len(tokens) == 2:
                assert tokens[1] == 'tip\n'

            _cache[module_name] = tokens[0]


    return _cache[module_name]



def get_all_src_versions() :
    """
    Get the version of all loaded module.
    Calls src_version on all loaded modules. These modules are found
    using sys.modules.

    Returns a dictionnary: name->version.
    
    @RETURN dict Dictionnary (module's name) -> (version)
    @SEE src_version
    """
    allmodules = sys.modules
    d = dict()
    for m in allmodules :
        d[m] = src_version(m)
    return d


if __name__ == "__main__" :

    if len(sys.argv) == 2 :
        print 'testing on', sys.argv[1]
        print src_version(sys.argv[1])