]> arthur.barton.de Git - bup.git/commitdiff
git: overhaul git version checking
authorRob Browning <rlb@defaultvalue.org>
Tue, 11 Feb 2020 02:47:13 +0000 (20:47 -0600)
committerRob Browning <rlb@defaultvalue.org>
Sun, 1 Mar 2020 22:55:43 +0000 (16:55 -0600)
Rework the git version tetsting to handle versions like "1.5.2-rc3"
or (apparently) "1.5.2-rc3 (something ...)".  Add tests for parsing of
all the version types in the current git tag history that we need to
support.

Support and document BUP_ASSUME_GIT_VERSION_IS_FINE=1 as an escape
hatch in case the parsing isn't sufficiently comprehensive, or
upstreeam changes their practices in future releases.

Signed-off-by: Rob Browning <rlb@defaultvalue.org>
Tested-by: Rob Browning <rlb@defaultvalue.org>
Documentation/bup.md
lib/bup/git.py
lib/bup/t/tgit.py

index 3c15868e0382468c939bc4985c2644194bd0aeeb..16ac7df5a36cd5ed095047f64e675ed3907e25c2 100644 (file)
@@ -112,6 +112,13 @@ pages.
 :   Report the version number of your copy of bup.
 
 
+# ENVIRONMENT
+
+`BUP_ASSUME_GIT_VERSION_IS_FINE`
+:   If set to `true`, `yes`, or `1`, assume the version of `git`
+    in the path is acceptable.
+
+
 # SEE ALSO
 
 `git`(1) and the *README* file from the bup distribution.
index d1515e3f3f95823f77714e3d396810aa9a43e1e8..d46ebf9f4ff21f522970f3b22881d38174b5ff71 100644 (file)
@@ -20,6 +20,7 @@ from bup.compat import (buffer,
                         reraise)
 from bup.io import path_msg
 from bup.helpers import (Sha1, add_error, chunkyreader, debug1, debug2,
+                         exo,
                          fdatasync,
                          hostname, localtime, log,
                          merge_dict,
@@ -57,6 +58,14 @@ def _git_wait(cmd, p):
     if rv != 0:
         raise GitError('%r returned %d' % (cmd, rv))
 
+def _git_exo(cmd, **kwargs):
+    kwargs['check'] = False
+    result = exo(cmd, **kwargs)
+    _, _, proc = result
+    if proc.returncode != 0:
+        raise GitError('%r returned %d' % (cmd, proc.returncode))
+    return result
+
 def git_config_get(option, repo_dir=None):
     cmd = (b'git', b'config', b'--get', option)
     p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
@@ -1138,31 +1147,54 @@ def check_repo_or_die(path=None):
     sys.exit(14)
 
 
-_ver = None
-def ver():
-    """Get Git's version and ensure a usable version is installed.
-
-    The returned version is formatted as an ordered tuple with each position
-    representing a digit in the version tag. For example, the following tuple
-    would represent version 1.6.6.9:
+def is_suitable_git(ver_str):
+    if not ver_str.startswith(b'git version '):
+        return 'unrecognized'
+    ver_str = ver_str[len(b'git version '):]
+    if ver_str.startswith(b'0.'):
+        return 'insufficient'
+    if ver_str.startswith(b'1.'):
+        if re.match(br'1\.[012345]rc', ver_str):
+            return 'insufficient'
+        if re.match(br'1\.[01234]\.', ver_str):
+            return 'insufficient'
+        if re.match(br'1\.5\.[012345]($|\.)', ver_str):
+            return 'insufficient'
+        if re.match(br'1\.5\.6-rc', ver_str):
+            return 'insufficient'
+        return 'suitable'
+    if re.match(br'[0-9]+(\.|$)?', ver_str):
+        return 'suitable'
+    sys.exit(13)
+
+_git_great = None
+
+def require_suitable_git(ver_str=None):
+    """Raise GitError if the version of git isn't suitable.
+
+    Rely on ver_str when provided, rather than invoking the git in the
+    path.
 
-        (1, 6, 6, 9)
     """
-    global _ver
-    if not _ver:
-        p = subprocess.Popen([b'git', b'--version'], stdout=subprocess.PIPE)
-        gvs = p.stdout.read()
-        _git_wait('git --version', p)
-        m = re.match(br'git version (\S+.\S+)', gvs)
-        if not m:
-            raise GitError('git --version weird output: %r' % gvs)
-        _ver = tuple(int(x) for x in m.group(1).split(b'.'))
-    needed = (1, 5, 3, 1)
-    if _ver < needed:
-        raise GitError('git version %s or higher is required; you have %s'
-                       % ('.'.join(str(x) for x in needed),
-                          '.'.join(str(x) for x in _ver)))
-    return _ver
+    global _git_great
+    if _git_great is not None:
+        return
+    if environ.get(b'BUP_GIT_VERSION_IS_FINE', b'').lower() \
+       in (b'yes', b'true', b'1'):
+        _git_great = True
+        return
+    if not ver_str:
+        ver_str, _, _ = _git_exo([b'git', b'--version'])
+    status = is_suitable_git(ver_str)
+    if status == 'unrecognized':
+        raise GitError('Unexpected git --version output: %r' % ver_str)
+    if status == 'insufficient':
+        log('error: git version must be at least 1.5.6\n')
+        sys.exit(1)
+    if status == 'suitable':
+        _git_great = True
+        return
+    assert False
 
 
 class _AbortableIter:
@@ -1200,11 +1232,8 @@ class _AbortableIter:
 class CatPipe:
     """Link to 'git cat-file' that is used to retrieve blob data."""
     def __init__(self, repo_dir = None):
+        require_suitable_git()
         self.repo_dir = repo_dir
-        wanted = (1, 5, 6)
-        if ver() < wanted:
-            log('error: git version must be at least 1.5.6\n')
-            sys.exit(1)
         self.p = self.inprogress = None
 
     def _abort(self):
index a51cc8f99dac92ac786b2d66207da682708f7511..370e0c1593d5560eb173f86147e8d0f191dcd238 100644 (file)
@@ -25,6 +25,46 @@ def exo(*cmd):
     return readpipe(cmd)
 
 
+@wvtest
+def test_git_version_detection():
+    with no_lingering_errors():
+        # Test version types from git's tag history
+        for expected, ver in \
+            (('insufficient', b'git version 0.99'),
+             ('insufficient', b'git version 0.99.1'),
+             ('insufficient', b'git version 0.99.7a'),
+             ('insufficient', b'git version 1.0rc1'),
+             ('insufficient', b'git version 1.0.1'),
+             ('insufficient', b'git version 1.4.2.1'),
+             ('insufficient', b'git version 1.5.5'),
+             ('insufficient', b'git version 1.5.6-rc0'),
+             ('suitable', b'git version 1.5.6'),
+             ('suitable', b'git version 1.5.6.1'),
+             ('suitable', b'git version 2.14.0-rc0'),
+             ('suitable', b'git version 2.14.0 (something ...)'),
+             ('suitable', b'git version 111.222.333.444-rc555'),
+             ('unrecognized', b'huh?')):
+            WVMSG('Checking version validation: %r' % ver)
+            WVPASSEQ(expected, git.is_suitable_git(ver_str=ver))
+            try:
+                if expected == 'insufficient':
+                    WVEXCEPT(SystemExit, git.require_suitable_git, ver)
+                elif expected == 'suitable':
+                    git.require_suitable_git(ver_str=ver)
+                elif expected == 'unrecognized':
+                    WVEXCEPT(git.GitError, git.require_suitable_git, ver)
+                else:
+                    WVPASS(False)
+            finally:
+                git._git_great = None
+            try:
+                environ[b'BUP_GIT_VERSION_IS_FINE'] = b'true'
+                git.require_suitable_git(ver_str=ver)
+            finally:
+                del environ[b'BUP_GIT_VERSION_IS_FINE']
+                git._git_great = None
+
+
 @wvtest
 def testmangle():
     with no_lingering_errors():