tools (utils): better repository version handling:

- removed save_dir_path and .hg_data/pickle method: it is not needed to save repository data anymore as its version is now saved on installation by setuptools_scm
- added "distance" (number of commits since last tag)
- use twisted's which method to find the hg executable
- result is now cached on first execution
- fixed dirstate method
- at last resort, try to find the repository version using the package version, as it is saved there by setuptools_scm
This commit is contained in:
Goffi 2018-04-05 15:23:38 +02:00
commit 3951ea451a

View file

@ -21,10 +21,13 @@
import unicodedata
import os.path
from sat.core.constants import Const as C
from sat.core.log import getLogger
log = getLogger(__name__)
import datetime
from dateutil import parser as dateutil_parser
from twisted.python import procutils
import subprocess
import calendar
import time
import sys
@ -34,6 +37,11 @@ import textwrap
import functools
NO_REPOS_DATA = u'repository data unknown'
repos_cache_dict = None
repos_cache = None
def clean_ustr(ustr):
"""Clean unicode string
@ -103,22 +111,19 @@ def generatePassword(vocabulary=None, size=20):
vocabulary = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)]
return u''.join([random.choice(vocabulary) for i in range(15)])
def getRepositoryData(module, as_string=True, is_path=False, save_dir_path=None):
def getRepositoryData(module, as_string=True, is_path=False):
"""Retrieve info on current mecurial repository
Data is gotten by using the following methods, in order:
- using "hg" executable
- looking for a ".hg_data" file in the root of the module
this file must contain the data dictionnary serialized with pickle
- looking for a .hg/dirstate in parent directory of module (or in module/.hg if
is_path is True), and parse dirstate file to get revision
- checking package version, which should have repository data when we are on a dev version
@param module(unicode): module to look for (e.g. sat, libervia)
module can be a path if is_path is True (see below)
@param as_string(bool): if True return a string, else return a dictionary
@param is_path(bool): if True "module" is not handled as a module name, but as an
absolute path to the parent of a ".hg" directory
@param save_path(str, None): if not None, the value will be saved to given path as a pickled dict
/!\\ the .hg_data file in the given directory will be overwritten
@return (unicode, dictionary): retrieved info in a nice string,
or a dictionary with retrieved data (key is not present if data is not found),
key can be:
@ -126,13 +131,23 @@ def getRepositoryData(module, as_string=True, is_path=False, save_dir_path=None)
- branch: branch name
- date: ISO 8601 format date
- tag: latest tag used in hierarchie
- distance: number of commits since the last tag
"""
global repos_cache_dict
if as_string:
global repos_cache
if repos_cache is not None:
return repos_cache
else:
if repos_cache_dict is not None:
return repos_cache_dict
if sys.platform == "android":
# FIXME: workaround to avoid trouble on android, need to be fixed properly
return u"Cagou android build"
from distutils.spawn import find_executable
import subprocess
KEYS=("node", "node_short", "branch", "date", "tag")
repos_cache = u"Cagou android build"
return repos_cache
KEYS=("node", "node_short", "branch", "date", "tag", "distance")
ori_cwd = os.getcwd()
if is_path:
@ -140,12 +155,22 @@ def getRepositoryData(module, as_string=True, is_path=False, save_dir_path=None)
else:
repos_root = os.path.abspath(os.path.dirname(module.__file__))
hg_path = find_executable('hg')
try:
hg_path = procutils.which('hg')[0]
except IndexError:
log.warning(u"Can't find hg executable")
hg_path = None
hg_data = {}
if hg_path is not None:
os.chdir(repos_root)
try:
hg_data_raw = subprocess.check_output(["hg","log", "-r", "-1", "--template","{node}\n{node|short}\n{branch}\n{date|isodate}\n{latesttag}"])
hg_data_raw = subprocess.check_output(["hg","log", "-r", "-1", "--template","{node}\n"
"{node|short}\n"
"{branch}\n"
"{date|isodate}\n"
"{latesttag}\n"
"{latesttagdistance}"])
except subprocess.CalledProcessError:
hg_data = {}
else:
@ -157,68 +182,63 @@ def getRepositoryData(module, as_string=True, is_path=False, save_dir_path=None)
else:
hg_data = {}
if not hg_data:
# .hg_data pickle method
log.debug(u"Mercurial not available or working, trying other methods")
if save_dir_path is None:
log.debug(u"trying .hg_data method")
try:
with open(os.path.join(repos_root, '.hg_data')) as f:
import cPickle as pickle
hg_data = pickle.load(f)
except IOError as e:
log.debug(u"Can't access .hg_data file: {}".format(e))
except pickle.UnpicklingError:
log.warning(u"Error while reading {}, can't get repos data".format(f.name))
if not hg_data:
# .hg/dirstate method
log.debug(u"trying dirstate method")
if is_path:
os.chdir(repos_root)
else:
os.chdir(os.path.abspath(os.path.join('..', repos_root)))
os.chdir(os.path.abspath(os.path.dirname(repos_root)))
try:
with open('.hg/dirstate') as hg_dirstate:
hg_data['node'] = hg_dirstate.read(20).encode('hex')
hg_data['node_short'] = hg_data['node'][:12]
except IOError:
log.warning(u"Can't access repository data")
log.debug(u"Can't access repository data")
# we restore original working dir
os.chdir(ori_cwd)
# data saving
if save_dir_path is not None and hg_data:
if not os.path.isdir(save_dir_path):
log.warning(u"Given path is not a directory, can't save data")
if not hg_data:
log.debug(u"Mercurial not available or working, trying package version")
try:
import pkg_resources
pkg_version = pkg_resources.get_distribution(C.APP_NAME_FILE).version
version, hg_node, hg_distance = pkg_version.split('-')
except ImportError:
log.warning("pkg_resources not available, can't get package data")
except pkg_resources.DistributionNotFound:
log.warning("can't retrieve package data")
except ValueError:
log.warning(u"package version doesn't fit: {pkg_version}".format(pkg_version=pkg_version))
else:
import cPickle as pickle
dest_path = os.path.join(save_dir_path, ".hg_data")
try:
with open(dest_path, 'w') as f:
pickle.dump(hg_data, f, 2)
except IOError as e:
log.warning(u"Can't save file to {path}: {reason}".format(
path=dest_path, reason=e))
if version != C.APP_VERSION:
log.error("Incompatible version ({version}) and pkg_version ({pkg_version})".format(
version=C.APP_VERSION, pkg_version=pkg_version))
else:
log.debug(u"repository data saved to {}".format(dest_path))
hg_data = {'node_short': hg_node, 'distance': hg_distance}
repos_cache_dict = hg_data
if as_string:
if not hg_data:
return u'repository data unknown'
strings = [u'rev', hg_data['node_short']]
try:
if hg_data['modified']:
strings.append(u"[M]")
except KeyError:
pass
try:
strings.extend([u'({branch} {date})'.format(**hg_data)])
except KeyError:
pass
return u' '.join(strings)
repos_cache = NO_REPOS_DATA
else:
strings = [u'rev', hg_data['node_short']]
try:
if hg_data['modified']:
strings.append(u"[M]")
except KeyError:
pass
try:
strings.extend([u'({branch} {date})'.format(**hg_data)])
except KeyError:
pass
try:
strings.extend([u'+{distance}'.format(**hg_data)])
except KeyError:
pass
repos_cache = u' '.join(strings)
return repos_cache
else:
return hg_data