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_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,
# Avoid slicing shatable for individual hashes (very high overhead)
self.shatable = buffer(self.map, self.sha_ofs, self.nsha * 24)
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
+
def __len__(self):
return int(self.nsha) # int() from long for python 2
for ofs in range(start, start + 24 * self.nsha, 24):
yield self.map[ofs : ofs + 20]
+ def close(self):
+ if self.map is not None:
+ self.shatable = None
+ self.map.close()
+ self.map = None
+
class PackIdxV2(PackIdx):
"""Object representation of a Git pack index (version 2) file."""
# Avoid slicing this for individual hashes (very high overhead)
self.shatable = buffer(self.map, self.sha_ofs, self.nsha*20)
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
+
def __len__(self):
return int(self.nsha) # int() from long for python 2
for ofs in range(start, start + 20 * self.nsha, 20):
yield self.map[ofs : ofs + 20]
+ def close(self):
+ if self.map is not None:
+ self.shatable = None
+ self.map.close()
+ self.map = None
+
_mpi_count = 0
class PackIdxList:
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:
msg):
"""Create a commit object in the pack. The date_sec values must be
epoch-seconds, and if a tz is None, the local timezone is assumed."""
- if adate_tz:
+ if adate_tz is not None:
adate_str = _git_date_str(adate_sec, adate_tz)
else:
adate_str = _local_git_date_str(adate_sec)
- if cdate_tz:
+ if cdate_tz is not None:
cdate_str = _git_date_str(cdate_sec, cdate_tz)
else:
cdate_str = _local_git_date_str(cdate_sec)
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())
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):
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',