reraise)
from bup.io import path_msg
from bup.helpers import (Sha1, add_error, chunkyreader, debug1, debug2,
+ exo,
fdatasync,
hostname, localtime, log,
merge_dict,
if rv != 0:
raise GitError('%r returned %d' % (cmd, rv))
-def _git_capture(argv):
- p = subprocess.Popen(argv, stdout=subprocess.PIPE, env=_gitenv())
- r = p.stdout.read()
- _git_wait(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 = (b'git', b'config', b'--get', option)
The instance variable 'ignore_midx' can force this function to
always act as if skip_midx was True.
"""
+ if self.bloom is not None:
+ self.bloom.close()
self.bloom = None # Always reopen the bloom as it may have been relaced
self.do_bloom = False
skip_midx = skip_midx or self.ignore_midx
if os.path.exists(self.dir):
if not skip_midx:
midxl = []
+ midxes = set(glob.glob(os.path.join(self.dir, b'*.midx')))
+ # remove any *.midx files from our list that no longer exist
+ for ix in list(d.values()):
+ if not isinstance(ix, midx.PackMidx):
+ continue
+ if ix.name in midxes:
+ continue
+ # remove the midx
+ del d[ix.name]
+ ix.close()
+ self.packs.remove(ix)
for ix in self.packs:
if isinstance(ix, midx.PackMidx):
for name in ix.idxnames:
d[os.path.join(self.dir, name)] = ix
- for full in glob.glob(os.path.join(self.dir,b'*.midx')):
+ for full in midxes:
if not d.get(full):
mx = midx.PackMidx(full)
(mxd, mxf) = os.path.split(mx.name)
assert name.endswith(b'.pack')
self.filename = name[:-5]
self.file.write(b'PACK\0\0\0\2\0\0\0\0')
- self.idx = list(list() for i in range(256))
+ self.idx = PackIdxV2Writer()
def _raw_write(self, datalist, sha):
self._open()
def _update_idx(self, sha, crc, size):
assert(sha)
if self.idx:
- self.idx[byte_int(sha[0])].append((sha, crc,
- self.file.tell() - size))
+ self.idx.add(sha, crc, self.file.tell() - size)
def _write(self, sha, type, content):
if verbose:
finally:
f.close()
- obj_list_sha = self._write_pack_idx_v2(self.filename + b'.idx', idx,
- packbin)
+ obj_list_sha = idx.write(self.filename + b'.idx', packbin)
nameprefix = os.path.join(self.repo_dir,
b'objects/pack/pack-' + obj_list_sha)
if os.path.exists(self.filename + b'.map'):
"""Close the pack file and move it to its definitive path."""
return self._end(run_midx=run_midx)
- def _write_pack_idx_v2(self, filename, idx, packbin):
+
+class PackIdxV2Writer:
+ def __init__(self):
+ self.idx = list(list() for i in range(256))
+ self.count = 0
+
+ def add(self, sha, crc, offs):
+ assert(sha)
+ self.count += 1
+ self.idx[byte_int(sha[0])].append((sha, crc, offs))
+
+ def write(self, filename, packbin):
ofs64_count = 0
- for section in idx:
+ for section in self.idx:
for entry in section:
if entry[2] >= 2**31:
ofs64_count += 1
fdatasync(idx_f.fileno())
idx_map = mmap_readwrite(idx_f, close=False)
try:
- count = _helpers.write_idx(filename, idx_map, idx, self.count)
+ count = _helpers.write_idx(filename, idx_map, self.idx,
+ self.count)
assert(count == self.count)
idx_map.flush()
finally:
idx_sum.update(b)
obj_list_sum = Sha1()
- for b in chunkyreader(idx_f, 20*self.count):
+ for b in chunkyreader(idx_f, 20 * self.count):
idx_sum.update(b)
obj_list_sum.update(b)
namebase = hexlify(obj_list_sum.digest())
return None
-def rev_list_invocation(ref_or_refs, count=None, format=None):
+def rev_list_invocation(ref_or_refs, format=None):
if isinstance(ref_or_refs, bytes):
refs = (ref_or_refs,)
else:
refs = ref_or_refs
argv = [b'git', b'rev-list']
- if isinstance(count, Integral):
- argv.extend([b'-n', b'%d' % count])
- elif count:
- raise ValueError('unexpected count argument %r' % count)
if format:
argv.append(b'--pretty=format:' + format)
return argv
-def rev_list(ref_or_refs, count=None, parse=None, format=None, repo_dir=None):
+def rev_list(ref_or_refs, parse=None, format=None, repo_dir=None):
"""Yield information about commits as per "git rev-list". If a format
is not provided, yield one hex hash at a time. If a format is
provided, pass it to rev-list and call parse(git_stdout) for each
"""
assert bool(parse) == bool(format)
- p = subprocess.Popen(rev_list_invocation(ref_or_refs, count=count,
+ p = subprocess.Popen(rev_list_invocation(ref_or_refs,
format=format),
env=_gitenv(repo_dir),
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([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:
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):
or a commit. The content of all blobs that can be seen from trees or
commits will be added to the list.
"""
- try:
- for d in self._join(self.get(id)):
- yield d
- except StopIteration:
- log('booger!\n')
+ for d in self._join(self.get(id)):
+ yield d
_cp = {}
class MissingObject(KeyError):
def __init__(self, oid):
self.oid = oid
- KeyError.__init__(self, 'object %r is missing' % oid.encode('hex'))
+ KeyError.__init__(self, 'object %r is missing' % hexlify(oid))
WalkItem = namedtuple('WalkItem', ['oid', 'type', 'mode',