123 lines
6.4 KiB
Python
123 lines
6.4 KiB
Python
"""Explicitly authorized, fail-closed archive/retire operations in the new clone."""
|
|
import argparse
|
|
from datetime import datetime, timezone
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
|
|
ROOT = Path('/home/qishizhe/digital-drosophila-main-integration-20261001')
|
|
RECOVERY = Path('/home/qishizhe/digital-drosophila-branch-recovery-20261001.git')
|
|
AUDIT = ROOT / 'registry/branch-consolidation-20261001'
|
|
|
|
|
|
def require(value, message):
|
|
if not value:
|
|
raise RuntimeError(message)
|
|
|
|
|
|
def git(*args, cwd=ROOT):
|
|
# Existing payloads were checked separately. This operation transfers refs
|
|
# and Git objects only; it neither replaces nor uploads any LFS object.
|
|
env = dict(os.environ, GIT_LFS_SKIP_SMUDGE='1', GIT_LFS_SKIP_PUSH='1')
|
|
return subprocess.check_output(['git', *args], cwd=cwd, env=env, text=True).strip()
|
|
|
|
|
|
def snapshot():
|
|
refs = {}
|
|
default = None
|
|
for line in git('ls-remote', '--symref', 'origin').splitlines():
|
|
value, name = line.split('\t')
|
|
if value.startswith('ref: '):
|
|
if name == 'HEAD':
|
|
default = value[5:]
|
|
else:
|
|
refs[name] = value
|
|
return refs, default
|
|
|
|
|
|
def write_receipt(name, **data):
|
|
with (AUDIT / name).open('x') as f:
|
|
json.dump(dict(schema='branch-consolidation-ref-audit-v1',
|
|
created_utc=datetime.now(timezone.utc).isoformat(), **data),
|
|
f, indent=2, sort_keys=True)
|
|
f.write('\n')
|
|
print(json.dumps(data, indent=2))
|
|
|
|
|
|
def main(action):
|
|
require(Path.cwd().resolve() == ROOT, 'Independent integration clone required')
|
|
plan = json.loads((AUDIT / 'plan.json').read_text())
|
|
require(git('remote', 'get-url', 'origin') == plan['remote'], 'Unexpected remote')
|
|
branches = {'refs/heads/' + name: sha for name, sha in plan['branches'].items()}
|
|
tags = {plan['archive_tag_prefix'] + name.removeprefix('codex/'): sha
|
|
for name, sha in plan['branches'].items()}
|
|
before, default_before = snapshot()
|
|
|
|
if action == 'archive':
|
|
require(not (AUDIT / 'archive-recovery.json').exists(), 'Archive receipt already exists')
|
|
require(not RECOVERY.exists(), 'Recovery clone already exists; inspect, do not replace')
|
|
require({k: v for k, v in before.items() if k.startswith('refs/heads/')} == branches,
|
|
'Remote heads changed since authorized inventory')
|
|
for tag, sha in tags.items():
|
|
ref = 'refs/tags/' + tag
|
|
require(ref not in before, 'Archive tag already exists; inspect before proceeding')
|
|
require(not git('tag', '--list', tag), 'Local archive tag already exists')
|
|
git('tag', '-a', tag, sha, '-m',
|
|
'Archive before user-authorized main consolidation; preserve this exact historical tip. Do not rewrite.')
|
|
git('push', '--atomic', 'origin', *['refs/tags/' + tag + ':refs/tags/' + tag for tag in tags])
|
|
after, _ = snapshot()
|
|
for tag, sha in tags.items():
|
|
require(after.get('refs/tags/' + tag + '^{}') == sha, 'Remote archived commit differs')
|
|
git('init', '--bare', str(RECOVERY))
|
|
git('fetch', '--no-tags', plan['remote'],
|
|
*['refs/tags/' + tag + ':refs/tags/' + tag for tag in tags], cwd=RECOVERY)
|
|
recovered = {tag: git('rev-parse', 'refs/tags/' + tag + '^{commit}', cwd=RECOVERY)
|
|
for tag in tags}
|
|
require(recovered == tags, 'Fresh remote recovery failed')
|
|
git('fsck', '--full', '--no-reflogs', cwd=RECOVERY)
|
|
write_receipt('archive-recovery.json', pass_all=True, remote=plan['remote'],
|
|
remote_heads_before=branches, remote_default_before=default_before,
|
|
tags=tags, fresh_remote_recovery_path=str(RECOVERY),
|
|
recovered_commits=recovered, git_fsck_pass=True,
|
|
lfs_payload_downloaded=False, old_remote_branches_deleted=False)
|
|
|
|
elif action == 'retire':
|
|
require(not (AUDIT / 'remote-cleanup.json').exists(), 'Cleanup receipt already exists')
|
|
require(json.loads((AUDIT / 'archive-recovery.json').read_text())['pass_all'], 'Recovery not verified')
|
|
require(json.loads((AUDIT / 'merge-audit-final.json').read_text())['pass_all'], 'Merge not verified')
|
|
require(default_before == 'refs/heads/main', 'Remote default must already be main')
|
|
require(git('branch', '--show-current') == 'main', 'Wrong local branch')
|
|
require(git('status', '--porcelain') == '', 'Integration clone is not clean')
|
|
main_sha = git('rev-parse', 'HEAD')
|
|
expected = dict(branches, **{'refs/heads/main': main_sha})
|
|
require({k: v for k, v in before.items() if k.startswith('refs/heads/')} == expected,
|
|
'Remote heads changed; stop cleanup')
|
|
for tag, sha in tags.items():
|
|
require(before.get('refs/tags/' + tag + '^{}') == sha, 'Remote archive missing/different')
|
|
require(git('rev-parse', 'refs/tags/' + tag + '^{commit}', cwd=RECOVERY) == sha,
|
|
'Recovery archive differs')
|
|
git('merge-base', '--is-ancestor', sha, main_sha)
|
|
leases = ['--force-with-lease=' + ref + ':' + sha for ref, sha in branches.items()]
|
|
# Delete only these seven exact branch refs, atomically and with their
|
|
# recorded old OIDs. No local refs, tags, commits, worktrees or LFS deletion.
|
|
git('push', '--atomic', *leases, 'origin', *[':' + ref for ref in branches])
|
|
after, default_after = snapshot()
|
|
require({k: v for k, v in after.items() if k.startswith('refs/heads/')} ==
|
|
{'refs/heads/main': main_sha}, 'Unexpected final heads')
|
|
require(default_after == 'refs/heads/main', 'Default branch changed unexpectedly')
|
|
for tag, sha in tags.items():
|
|
require(after.get('refs/tags/' + tag + '^{}') == sha, 'Archive lost after deletion')
|
|
write_receipt('remote-cleanup.json', pass_all=True, remote=plan['remote'],
|
|
main_commit=main_sha, remote_default=default_after,
|
|
deleted_remote_branch_refs=branches, preserved_archive_tags=tags,
|
|
remaining_remote_heads={'main': main_sha},
|
|
all_old_tips_ancestors_of_main=True, local_branches_deleted=False,
|
|
source_worktrees_write_operations=False, lfs_objects_deleted=False)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument('action', choices=['archive', 'retire'])
|
|
main(p.parse_args().action)
|