44
55import os
66import re
7+ import subprocess
78
89MYDIR = os .path .dirname (os .path .abspath (__file__ ))
910
1819Inconsistent FALLBACK_VERSION {} vs {} from git tag.
1920"""
2021
22+ RELEASE_TAG = re .compile (r"([0-9]+\.[0-9]+\.[0-9]+)(?:-rc\.([0-9]+))?" )
23+
24+
25+ def release_version (tag ):
26+ """Normalize a release tag to the internal version format."""
27+ match = RELEASE_TAG .fullmatch (tag )
28+ if not match :
29+ raise RuntimeError (
30+ f"Invalid release tag { tag !r} ; expected X.Y.Z or X.Y.Z-rc.N."
31+ )
32+ base , candidate = match .groups ()
33+ return base if candidate is None else f"{ base } rc{ candidate } "
34+
2135
2236def gitinfo ():
2337 "Extract dictionary of version data from git records."
24- from subprocess import PIPE , Popen
25-
2638 global _cached_gitinfo
2739 if _cached_gitinfo is not None :
2840 return _cached_gitinfo
29- nullfile = open (os .devnull , "w" )
30- kw = dict (stdout = PIPE , stderr = nullfile , cwd = MYDIR , universal_newlines = True )
31- # Release tags may be lightweight, as with v1.4.1.
32- proc = Popen (["git" , "describe" , "--tags" , "--match=v[[:digit:]]*" ], ** kw )
33- desc = proc .stdout .read ()
34- if proc .wait ():
41+ kw = dict (
42+ stdout = subprocess .PIPE ,
43+ stderr = subprocess .DEVNULL ,
44+ cwd = MYDIR ,
45+ text = True ,
46+ )
47+ proc = subprocess .run (["git" , "log" , "-1" , "--format=%H %ci" ], ** kw )
48+ if proc .returncode :
3549 _cached_gitinfo = {}
36- return gitinfo ()
37- proc = Popen (["git" , "log" , "-1" , "--format=%H %ci" ], ** kw )
38- glog = proc .stdout .read ()
39- words = desc .strip ().split ("-" )
40- vtag = words [0 ].lstrip ("v" )
41- vnum = int ((words [1 :2 ] or ["0" ])[0 ])
50+ return _cached_gitinfo
4251 rv = {}
43- rv ["commit" ], rv ["date" ] = glog .strip ().split (None , 1 )
44- rv ["patchnumber" ] = vnum
45- rv ["version" ] = vtag
46- if vnum :
47- rv ["version" ] += ".post" + str (vnum )
52+ rv ["commit" ], rv ["date" ] = proc .stdout .strip ().split (None , 1 )
53+ # --long makes the commit count explicit even at a tag. Split from
54+ # the right so the -rc.N suffix remains part of the release tag.
55+ proc = subprocess .run (
56+ ["git" , "describe" , "--tags" , "--long" , "--match=[0-9]*" ], ** kw
57+ )
58+ if not proc .returncode :
59+ tag , count , _ = proc .stdout .strip ().rsplit ("-" , 2 )
60+ rv ["patchnumber" ] = int (count )
61+ rv ["version" ] = release_version (tag )
62+ if rv ["patchnumber" ]:
63+ rv ["version" ] += ".post" + count
4864 _cached_gitinfo = rv
49- return gitinfo ()
65+ return _cached_gitinfo
5066
5167
5268_cached_gitinfo = None
@@ -56,11 +72,17 @@ def getversion():
5672 """Extract version from gitinfo and/or gitarchive.cfg file.
5773
5874 Process gitinfo first and make sure it matches FALLBACK_VERSION in
59- gitarchive.cfg . Use expanded data from gitarchive.cfg when these
60- sources are from git archive bundle.
75+ fallback_version.py . Use expanded data from gitarchive.cfg when
76+ these sources are from git archive bundle.
6177 """
6278 from fallback_version import FALLBACK_VERSION
6379
80+ if not re .fullmatch (
81+ r"[0-9]+\.[0-9]+\.[0-9]+(?:rc[0-9]+)?\.post0" , FALLBACK_VERSION
82+ ):
83+ raise RuntimeError (
84+ "FALLBACK_VERSION must be X.Y.Z.post0 or X.Y.ZrcN.post0."
85+ )
6486 gitarchivecfgfile = os .path .join (MYDIR , "gitarchive.cfg" )
6587 assert os .path .isfile (gitarchivecfgfile )
6688 with open (gitarchivecfgfile ) as fp :
@@ -69,33 +91,32 @@ def getversion():
6991 ga = dict (gaitems )
7092 gi = gitinfo ()
7193 rv = {}
72- if gi :
94+ if not gi :
95+ # Not a git repository. Require an expanded source archive.
96+ if "$Format:" in ga ["commit" ]:
97+ raise RuntimeError (EMSG_GIT_MISSING )
98+ gi = dict (commit = ga ["commit" ], date = ga ["date" ])
99+ for ref in ga ["refnames" ].split (", " ):
100+ if ref .startswith ("tag: " ) and re .match (r"[0-9]" , ref [5 :]):
101+ gi ["version" ] = release_version (ref [5 :])
102+ gi ["patchnumber" ] = 0
103+ break
104+ rv .update (gi )
105+ if "version" in gi :
73106 afb = FALLBACK_VERSION
74107 gfb = gi ["version" ].split (".post" )[0 ] + ".post0"
75108 if gfb != afb :
76109 raise RuntimeError (EMSG_BAD_FALLBACK_VERSION .format (afb , gfb ))
77- rv .update (gi )
78110 else :
79- # Not a git repository. Require that gitarchive.cfg is expanded.
80- if "$Format:" in ga ["commit" ]:
81- raise RuntimeError (EMSG_GIT_MISSING )
82- rv ["commit" ] = ga ["commit" ]
83- rv ["date" ] = ga ["date" ]
84- # First assume we have an undetermined post-release. Keep version
85- # suffix as ".post0", but set patchnumber to ensure we are post
86- # FALLBACK_VERSION.
111+ # A development checkout without a new-format tag, or an untagged
112+ # archive, has no known commit count. Keep the fallback version.
87113 rv ["version" ] = FALLBACK_VERSION
88114 rv ["patchnumber" ] = 1
89- # If we are at version tag turn this to regular release.
90- mx = re .search (r"\btag: v(\d[^,]*)" , ga ["refnames" ])
91- if mx :
92- rv ["version" ] = mx .group (1 )
93- rv ["patchnumber" ] = 0
94115 # rv['version'] is resolved, let's parse its subfields.
95116 vbase = rv ["version" ].split (".post" )[0 ]
96- mx = re .match (r"^(\d +)\.(\d+)(?: \.(\d+))?((?:[ab]|rc)\d* )?" , vbase )
117+ mx = re .fullmatch (r"([0-9] +)\.([0-9]+) \.([0-9]+)(rc[0-9]+ )?" , vbase )
97118 rv ["major" ] = int (mx .group (1 ))
98119 rv ["minor" ] = int (mx .group (2 ))
99- rv ["micro" ] = int (mx .group (3 ) or 0 )
120+ rv ["micro" ] = int (mx .group (3 ))
100121 rv ["prerelease" ] = mx .group (4 )
101122 return rv
0 commit comments