Beispiel #1
0
def cmd_next(cfg, args):
    """
    Sets and defines the next stable version string for the most recent and
    reachable tag.

    The string should be supplied in the format "maj.min.patch[.revision]",
    where angular brackets denotes an optional value.

    All values are expected to be decimal numbers without leading zeros.
    """
    next_store = KVStore(NEXT_STORE_FILE)
    repo_info = get_repo_info()

    last_tag = repo_info['last-tag']

    vn = args.next_version_numbers
    user = parse_user_next_stable(vn)
    if not user:
        term.err("Please specify valid version numbers.\nThe expected "
                 "format is <MAJ>.<MIN>.<PATCH>[.<REVISION>], e.g. v0.0.1, "
                 "0.0.1 or 0.0.2.1")
        sys.exit(1)

    custom = "%d.%d.%d" % (int(user['maj']), int(user['min']), int(user['patch']))

    if user['revision'] is not None:
        custom += ".%d" % (int(user['revision']))

    next_store.set(last_tag, custom).save()
    term.out("Set NEXT version string to " + term.next(custom) +
             " for the current tag " + term.tag(last_tag))
Beispiel #2
0
def load_user_config():
    """
    Returns the gitver's configuration: tries to read the stored configuration
    file and merges it with the default one, ensuring a valid configuration is
    always returned.
    """
    try:

        with open(CFGFILE, 'r') as f:
            data = ''
            for line in f:
                l = line.strip()
                if not l.startswith('#'):
                    data += l
            user = json.loads(data)

    except IOError:
        user = dict()

    except (ValueError, KeyError) as v:
        term.err("An error occured parsing the configuration file \"" +
                 CFGFILE + "\": " + v.message +
                 "\nPlease check its syntax or rename it and generate the "
                 "default one with the " + bold("gitver init") + " command.")
        sys.exit(1)

    # merge user with defaults
    return dict(default_config, **user)
Beispiel #3
0
def cmd_next(cfg, args):
    """
    Sets and defines the next stable version string for the most recent and
    reachable tag.

    The string should be supplied in the format "maj.min.patch[.revision]",
    where angular brackets denotes an optional value.

    All values are expected to be decimal numbers without leading zeros.
    """
    next_store = KVStore(NEXT_STORE_FILE)
    repo_info = get_repo_info()

    last_tag = repo_info['last-tag']

    vn = args.next_version_numbers
    user = parse_user_next_stable(vn)
    if not user:
        term.err("Please specify valid version numbers.\nThe expected "
                 "format is <MAJ>.<MIN>.<PATCH>[.<REVISION>], e.g. v0.0.1, "
                 "0.0.1 or 0.0.2.1")
        sys.exit(1)

    custom = "%d.%d.%d" % (int(user['maj']), int(
        user['min']), int(user['patch']))

    if user['revision'] is not None:
        custom += ".%d" % (int(user['revision']))

    next_store.set(last_tag, custom).save()
    term.out("Set NEXT version string to " + term.next(custom) +
             " for the current tag " + term.tag(last_tag))
Beispiel #4
0
def build_format_args(cfg, repo_info, next_custom=None):
    """
    Builds the formatting arguments by processing the specified repository
    information and returns them.

    If a tag defines pre-release metadata, this will have the precedence
    over any existing user-defined string.
    """
    in_next = repo_info['count'] > 0
    has_next_custom = next_custom is not None and len(next_custom) > 0

    vmaj = repo_info['maj']
    vmin = repo_info['min']
    vpatch = repo_info['patch']
    vrev = repo_info['rev']
    vcount = repo_info['count']
    vpr = repo_info['pr']
    vbuildid = repo_info['build-id']
    has_pr = vpr is not None
    has_rev = vrev is not None

    # pre-release metadata in a tag has precedence over user-specified
    # NEXT strings
    if in_next and has_next_custom and not has_pr:
        u = parse_user_next_stable(next_custom)
        if not u:
            term.err("Invalid custom NEXT version numbers detected!")
            sys.exit(1)
        vmaj = u['maj']
        vmin = u['min']
        vpatch = u['patch']
        vrev = u['revision']
        has_rev = vrev is not None

    meta_pr = vpr if has_pr else \
        cfg['default_meta_pr_in_next'] if in_next and has_next_custom else \
        cfg['default_meta_pr_in_next_no_next'] if in_next else ''

    args = {
        'maj': vmaj,
        'min': vmin,
        'patch': vpatch,
        'rev': vrev if has_rev else '',
        'rev_prefix': '.' if has_rev else '',
        'meta_pr': meta_pr,
        'meta_pr_prefix': cfg['meta_pr_prefix'] if len(meta_pr) > 0 else '',
        'commit_count': vcount if vcount > 0 else '',
        'commit_count_prefix':
        cfg['commit_count_prefix'] if vcount > 0 else '',
        'build_id': vbuildid,
        'build_id_full': repo_info['full-build-id']
    }

    return args
Beispiel #5
0
def build_format_args(cfg, repo_info, next_custom=None):
    """
    Builds the formatting arguments by processing the specified repository
    information and returns them.

    If a tag defines pre-release metadata, this will have the precedence
    over any existing user-defined string.
    """
    in_next = repo_info['count'] > 0
    has_next_custom = next_custom is not None and len(next_custom) > 0

    vmaj = repo_info['maj']
    vmin = repo_info['min']
    vpatch = repo_info['patch']
    vrev = repo_info['rev']
    vcount = repo_info['count']
    vpr = repo_info['pr']
    vbuildid = repo_info['build-id']
    has_pr = vpr is not None
    has_rev = vrev is not None

    # pre-release metadata in a tag has precedence over user-specified
    # NEXT strings
    if in_next and has_next_custom and not has_pr:
        u = parse_user_next_stable(next_custom)
        if not u:
            term.err("Invalid custom NEXT version numbers detected!")
            sys.exit(1)
        vmaj = u['maj']
        vmin = u['min']
        vpatch = u['patch']
        vrev = u['revision']
        has_rev = vrev is not None

    meta_pr = vpr if has_pr else \
        cfg['default_meta_pr_in_next'] if in_next and has_next_custom else \
        cfg['default_meta_pr_in_next_no_next'] if in_next else ''

    args = {
        'maj': vmaj,
        'min': vmin,
        'patch': vpatch,
        'rev': vrev if has_rev else '',
        'rev_prefix': '.' if has_rev else '',
        'meta_pr': meta_pr,
        'meta_pr_prefix': cfg['meta_pr_prefix'] if len(meta_pr) > 0 else '',
        'commit_count': vcount if vcount > 0 else '',
        'commit_count_prefix': cfg['commit_count_prefix'] if vcount > 0 else '',
        'build_id': vbuildid,
        'build_id_full': repo_info['full-build-id']
    }

    return args
Beispiel #6
0
def get_repo_info():
    """
    Retrieves raw repository information and returns it for further processing
    """
    hashlen = min_hash_length()
    if not hashlen:
        term.err("Couldn't compute the minimum hash string length")
        sys.exit(1)

    full_build_id = get_build_id()
    if not full_build_id:
        term.err("Couldn't retrieve build id information")
        sys.exit(1)

    tag = last_tag()
    if not tag:
        term.err("Couldn't retrieve the latest tag")
        sys.exit(1)

    data = data_from_tag(tag)
    if data is None:
        term.err("Couldn't retrieve version information from tag \"" + tag +
                 "\".\ngitver expects tags to be in the format "
                 "[v]X.Y.Z[.REVISION][-PRE-RELEASE-METADATA]")
        sys.exit(1)

    vcount = count_tag_to_head(tag)

    return {'maj': data['maj'],
            'min': data['min'],
            'patch': data['patch'],
            'rev': data['revision'],
            'pr': data['prmeta'],
            'count': vcount,
            'full-build-id': full_build_id,
            'build-id': full_build_id[:hashlen],
            'last-tag': tag
    }
Beispiel #7
0
def parse_templates(cfg, templates, repo, next_custom, preview):
    """
    Parse one or more templates, substitute placeholder variables with
    real values and write the result to the file specified in the template.

    If preview is True, then the output will be written to the stdout while
    informative messages will be output to the stderr.
    """
    for t in templates.split(' '):
        tpath = template_path(t)
        if os.path.exists(tpath):
            with open(tpath, 'r') as fp:
                lines = fp.readlines()

            if len(lines) < 2:
                term.err("The template \"" + t + "\" is not valid, aborting.")
                return

            if not lines[0].startswith('#'):
                term.err("The template \"" + t + "\" doesn't define any valid "
                                                 "output, aborting.")
                return

            output = str(lines[0]).strip(' #\n')

            # resolve relative paths to the project's root
            if not os.path.isabs(output):
                output = os.path.join(PRJ_ROOT, output)

            outdir = os.path.dirname(output)

            if not os.path.exists(outdir):
                term.err("The template output directory \"" + outdir +
                         "\" doesn't exists.")

            term.info("Processing template \"" + bold(t) + "\" for " + output +
                      "...")

            lines = lines[1:]
            xformed = Template("".join(lines))
            vstring = build_version_string(cfg, repo, False, next_custom)
            args = build_format_args(cfg, repo, next_custom)
            keywords = {
                'CURRENT_VERSION': vstring,
                'MAJOR': args['maj'],
                'MINOR': args['min'],
                'PATCH': args['patch'],
                'REV': args['rev'],
                'REV_PREFIX': args['rev_prefix'],
                'BUILD_ID': args['build_id'],
                'FULL_BUILD_ID': args['build_id_full'],
                'COMMIT_COUNT': args['commit_count'],
                'COMMIT_COUNT_STR':
                str(args['commit_count']) if args['commit_count'] > 0 else '',

                'COMMIT_COUNT_PREFIX': args['commit_count_prefix'],
                'META_PR': args['meta_pr'],
                'META_PR_PREFIX': args['meta_pr_prefix']
            }

            try:
                res = xformed.substitute(keywords)
            except KeyError as e:
                term.err("Unknown key \"" + e.message + "\" found, aborting.")
                sys.exit(1)

            if not preview:
                try:
                    fp = open(output, 'w')
                    fp.write(res)
                    fp.close()
                except IOError:
                    term.err("Couldn't write file \"" + output + "\"")
                    sys.exit(1)
            else:
                term.out(res)

            wrote_bytes = len(res) if preview else os.stat(output).st_size
            term.info("Done, " + str(wrote_bytes) + " bytes written.")
        else:
            term.err("Couldn't find the \"" + t + "\" template")
            sys.exit(1)
Beispiel #8
0
def parse_templates(cfg, templates, repo, next_custom, preview):
    """
    Parse one or more templates, substitute placeholder variables with
    real values and write the result to the file specified in the template.

    If preview is True, then the output will be written to the stdout while
    informative messages will be output to the stderr.
    """
    for t in templates.split(' '):
        tpath = template_path(t)
        if os.path.exists(tpath):
            with open(tpath, 'r') as fp:
                lines = fp.readlines()

            if len(lines) < 2:
                term.err("The template \"" + t + "\" is not valid, aborting.")
                return

            if not lines[0].startswith('#'):
                term.err("The template \"" + t + "\" doesn't define any valid "
                         "output, aborting.")
                return

            output = str(lines[0]).strip(' #\n')

            # resolve relative paths to the project's root
            if not os.path.isabs(output):
                output = os.path.join(PRJ_ROOT, output)

            outdir = os.path.dirname(output)

            if not os.path.exists(outdir):
                term.err("The template output directory \"" + outdir +
                         "\" doesn't exists.")

            term.info("Processing template \"" + bold(t) + "\" for " + output +
                      "...")

            lines = lines[1:]
            xformed = Template("".join(lines))
            vstring = build_version_string(cfg, repo, False, next_custom)
            args = build_format_args(cfg, repo, next_custom)
            keywords = {
                'CURRENT_VERSION':
                vstring,
                'MAJOR':
                args['maj'],
                'MINOR':
                args['min'],
                'PATCH':
                args['patch'],
                'REV':
                args['rev'],
                'REV_PREFIX':
                args['rev_prefix'],
                'BUILD_ID':
                args['build_id'],
                'FULL_BUILD_ID':
                args['build_id_full'],
                'COMMIT_COUNT':
                args['commit_count'],
                'COMMIT_COUNT_STR':
                str(args['commit_count']) if args['commit_count'] > 0 else '',
                'COMMIT_COUNT_PREFIX':
                args['commit_count_prefix'],
                'META_PR':
                args['meta_pr'],
                'META_PR_PREFIX':
                args['meta_pr_prefix']
            }

            try:
                res = xformed.substitute(keywords)
            except KeyError as e:
                term.err("Unknown key \"" + e.message + "\" found, aborting.")
                sys.exit(1)

            if not preview:
                try:
                    fp = open(output, 'w')
                    fp.write(res)
                    fp.close()
                except IOError:
                    term.err("Couldn't write file \"" + output + "\"")
                    sys.exit(1)
            else:
                term.out(res)

            wrote_bytes = len(res) if preview else os.stat(output).st_size
            term.info("Done, " + str(wrote_bytes) + " bytes written.")
        else:
            term.err("Couldn't find the \"" + t + "\" template")
            sys.exit(1)
Beispiel #9
0
def check_config_dir():
    # checks if configuration directory exists
    if not os.path.exists(CFGDIR):
        term.err("Please run " + bold("gitver init") + " first.")
        sys.exit(1)
Beispiel #10
0
def check_project_root():
    # tries to determine the project's root directory
    if len(PRJ_ROOT) == 0:
        term.err("Couldn't determine your project's root directory, is this "
                 "a valid git repository?")
        sys.exit(1)
Beispiel #11
0
# coding=utf-8

"""
git support library
"""

import sys
import re
from gitver.termcolors import term

# check for the sh package
try:
    import sh
    from sh import ErrorReturnCode, CommandNotFound
except ImportError:
    term.err("A dependency is missing, please install the \"sh\" package and "
             "run gitver again.")
    sys.exit(1)

hash_matcher = r".*-g([a-fA-F0-9]+)"
tag_matcher = r"v{0,1}(?P<maj>\d+)\.(?P<min>\d+)\.(?P<patch>\d+)" \
              r"(?:\.(?P<revision>\d+))?[^-]*(?:-(?P<prmeta>[0-9A-Za-z-.]*))?"


def __git_raw(*args):
    """
    @return sh.RunningCommand
    Proxies the specified git command+args and returns it
    """
    return sh.git(args)