P04-01: 同時発生の更新規約

This commit is contained in:
Agent Control Runner
2026-10-16 06:46:41 +09:00
parent b543c26c95
commit 16207d353a
3 changed files with 236 additions and 2 deletions
+43 -2
View File
@@ -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.