-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpackage_git_version.py
executable file
·91 lines (75 loc) · 3.51 KB
/
package_git_version.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/usr/bin/env python
import sh
import re
import sys
from argparse import ArgumentParser
from path import Path
class TagQuery(object):
"""
Class to encapsulate searching for Git versioning information.
"""
def __init__(self, repo_tree_path):
"""
Set up a new query using the local Git repository that ``repo_tree_path`` is contained in.
:param str repo_tree_path: Any relative or absolute path inside of a local Git clone.
"""
self.repo_path = Path(repo_tree_path).expand().abspath()
self.hash_rgx = re.compile("([0-9a-f]+).*tag:\\s([^),]+)")
self.num_rgx = re.compile("^[^0-9]+")
self.git_cmd = sh.Command("git")
def commit_tags(self):
"""
Get the list of dicts containing contextual information for each tag defined across the entire repository.
:return list: A list of ``CommitTag`` objects
"""
log_out = self.git_cmd("log", "--all", {"format": "%H|%d"}, "--decorate", "--topo-order",
"--simplify-by-decoration", _cwd=self.repo_path, _tty_out=False).stdout
tags = []
all_revisions = self.git_cmd("rev-list", "--topo-order", "--all",
_cwd=self.repo_path, _tty_out=False).stdout.splitlines()
for match in self.hash_rgx.finditer(log_out):
next_tag = {"tag": match.group(2), "commit": match.group(1)}
try:
next_tag["index"] = all_revisions.index(next_tag["commit"])
except ValueError:
next_tag["index"] = -1
tag_version = self.num_rgx.sub("", next_tag["tag"])
if not tag_version.strip() or next_tag["index"] == -1:
continue
next_tag["version"] = "{0}-{1}".format(tag_version, next_tag["index"])
tags.append(next_tag)
return tags
def latest_version(self):
"""
Return a string representing the latest version in the repository.
TODO: If some old commit is checked out instead of HEAD, this should change accordingly.
:return str: The latest version according to the state of the Git repository
"""
all_tags = self.commit_tags()
all_tags.sort(key=lambda tag: tag["index"])
if all_tags:
return all_tags[0]["version"]
exc_msg = "There must be at least one tag containing a valid version spec (i.e. one or more numbers)!"
raise Exception(exc_msg)
def version_from_repo(input_repo_path):
"""
Generate a package version for the Git repository containing ``input_repo_path``.
:param input_repo_path: The input repository path
:type input_repo_path: ``path.Path``
:return str: The latest version (e.g. ``0.4.2a-0``), which should be ``distutils``-compatible.
"""
tag_query = TagQuery(input_repo_path)
return tag_query.latest_version()
def main(args=None):
"""
Entry point for determining the version of a package based on the Git repository it resides in.
:param list args: The list of arguments
"""
args = args or sys.argv[1:]
ap = ArgumentParser(description="Determine the version of a package based on the Git repository it resides in.")
ap.add_argument("path", type=Path, default="./", help="A path inside the local repository checkout")
ns = ap.parse_args(args)
repo_version = version_from_repo(ns.path)
sys.stdout.write("Latest package version for path {0!r}: {1}\n".format(ns.path, repo_version))
if __name__ == "__main__": # pragma: no cover
main()