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))
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)
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))
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
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 }
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)
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)
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)
# 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)