P04-01: 同時発生の更新規約
This commit is contained in:
@@ -141,6 +141,44 @@ def git_bytes(*args):
|
||||
return subprocess.check_output(['git', '--no-replace-objects', *args], cwd=ROOT, env=env)
|
||||
|
||||
|
||||
def parse_blob_batch(stdout, oids):
|
||||
"""Decode length-framed output without splitting binary payloads into lines."""
|
||||
result = {}
|
||||
offset = 0
|
||||
for oid in oids:
|
||||
end = stdout.find(b'\n', offset)
|
||||
require(end >= 0, 'truncated blob batch header')
|
||||
header = stdout[offset:end].split(b' ')
|
||||
require(len(header) == 3 and header[0] == oid.encode('ascii')
|
||||
and header[1] == b'blob' and re.fullmatch(rb'[0-9]+', header[2]) is not None,
|
||||
'invalid blob batch header: ' + oid)
|
||||
size = int(header[2])
|
||||
start = end + 1
|
||||
end = start + size
|
||||
require(end < len(stdout) and stdout[end:end + 1] == b'\n',
|
||||
'truncated blob batch payload or missing LF: ' + oid)
|
||||
result[oid] = stdout[start:end]
|
||||
offset = end + 1
|
||||
require(offset == len(stdout), 'extra blob batch output')
|
||||
return result
|
||||
|
||||
|
||||
def read_blobs(oids):
|
||||
"""One process per request; no cache can cross object namespaces."""
|
||||
requested = list(oids)
|
||||
require(all(isinstance(oid, str) and re.fullmatch('[0-9a-f]{40}', oid) is not None
|
||||
for oid in requested), 'invalid full blob OID')
|
||||
requested = sorted(set(requested))
|
||||
if not requested:
|
||||
return {}
|
||||
env = _GIT_ENV if _GIT_ENV is not None else git_environment()
|
||||
# Inherit stderr and raise on nonzero status, exactly as git_bytes does.
|
||||
stdout = subprocess.check_output(['git', '--no-replace-objects', 'cat-file', '--batch'],
|
||||
input=('\n'.join(requested) + '\n').encode('ascii'),
|
||||
cwd=ROOT, env=env)
|
||||
return parse_blob_batch(stdout, requested)
|
||||
|
||||
|
||||
def git_environment():
|
||||
env = {k: v for k, v in os.environ.items() if not k.startswith('GIT_')}
|
||||
env.update(GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1', GIT_CONFIG_NOSYSTEM='1',
|
||||
@@ -197,9 +235,11 @@ def allowed_path(path):
|
||||
|
||||
def product_manifest(entries):
|
||||
result = {}
|
||||
blobs = read_blobs(oid for path, (_, oid) in entries.items()
|
||||
if selected_path(path) and path != CHECKER_PATH)
|
||||
for path, (_, oid) in sorted(entries.items()):
|
||||
if selected_path(path) and path != CHECKER_PATH:
|
||||
data = git_bytes('cat-file', 'blob', oid)
|
||||
data = blobs[oid]
|
||||
result[path] = {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
|
||||
return result
|
||||
|
||||
@@ -337,9 +377,10 @@ def actual_checkout():
|
||||
require(not git_bytes('status', '--porcelain=v1', '--untracked-files=all',
|
||||
'--ignore-submodules=none'), 'checkout is not clean')
|
||||
require(not git_bytes('ls-files', '--others', '-z'), 'untracked/ignored substitution')
|
||||
blobs = read_blobs(oid for _, oid in entries.values())
|
||||
for path, (mode, oid) in entries.items():
|
||||
actual = safe(ROOT, path)
|
||||
require(actual.read_bytes() == git_bytes('cat-file', 'blob', oid), 'HEAD/tree/FS bytes: ' + path)
|
||||
require(actual.read_bytes() == blobs[oid], 'HEAD/tree/FS bytes: ' + path)
|
||||
require(('100755' if actual.stat().st_mode & 0o111 else '100644') == mode,
|
||||
'HEAD/tree/FS mode: ' + path)
|
||||
# Also reject untracked directory symlinks, including ones ignored by Git.
|
||||
|
||||
Reference in New Issue
Block a user