from collections import namedtuple
from itertools import islice
from numbers import Integral
+from os import environ
from bup import _helpers, compat, hashsplit, path, midx, bloom, xstat
from bup.compat import range
from bup.helpers import (Sha1, add_error, chunkyreader, debug1, debug2,
+ exo,
fdatasync,
hostname, localtime, log, merge_iter,
mmap_read, mmap_readwrite,
_git_wait(repr(argv), p)
return r
+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 = ('git', 'config', '--get', option)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
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(['git', '--version'],
- stdout=subprocess.PIPE)
- gvs = p.stdout.read()
- _git_wait('git --version', p)
- m = re.match(r'git version (\S+.\S+)', gvs)
- if not m:
- raise GitError('git --version weird output: %r' % gvs)
- _ver = tuple(m.group(1).split('.'))
- needed = ('1','5', '3', '1')
- if _ver < needed:
- raise GitError('git version %s or higher is required; you have %s'
- % ('.'.join(needed), '.'.join(_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:
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):
from __future__ import absolute_import
+from os import environ
from subprocess import check_call
import struct, os, time
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():