98 lines
2.4 KiB
Python
98 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Create an orphan commit containing a single file in a GitHub repo.
|
|
|
|
Usage:
|
|
python3 scripts/orphan-commit.py <pat> <owner/repo> <file-path> [message]
|
|
|
|
Example:
|
|
python3 scripts/orphan-commit.py ghp_xxx my-org/my-repo ./payload.bin "init"
|
|
"""
|
|
|
|
import base64
|
|
import json
|
|
import sys
|
|
from urllib.request import Request, urlopen
|
|
|
|
TOKEN = sys.argv[1] if len(sys.argv) > 1 else None
|
|
REPO = sys.argv[2] if len(sys.argv) > 2 else None
|
|
FILE = sys.argv[3] if len(sys.argv) > 3 else None
|
|
MSG = sys.argv[4] if len(sys.argv) > 4 else "init"
|
|
|
|
if not TOKEN or not REPO or not FILE:
|
|
print(
|
|
"Usage: orphan-commit.py <pat> <owner/repo> <file> [message]", file=sys.stderr
|
|
)
|
|
sys.exit(1)
|
|
|
|
owner, name = REPO.split("/", 1)
|
|
|
|
API = "https://api.github.com"
|
|
HEADERS = {
|
|
"Authorization": f"Bearer {TOKEN}",
|
|
"Accept": "application/vnd.github+json",
|
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
|
|
def gh(method, path, body=None):
|
|
req = Request(f"{API}{path}", method=method, headers=HEADERS)
|
|
if body:
|
|
req.data = json.dumps(body).encode()
|
|
resp = urlopen(req)
|
|
return json.loads(resp.read()) if resp.status != 204 else None
|
|
|
|
|
|
with open(FILE, "rb") as f:
|
|
content = f.read()
|
|
|
|
filename = FILE.rsplit("/", 1)[-1]
|
|
|
|
# 1. Create blob
|
|
blob = gh(
|
|
"POST",
|
|
f"/repos/{owner}/{name}/git/blobs",
|
|
{
|
|
"content": base64.b64encode(content).decode(),
|
|
"encoding": "base64",
|
|
},
|
|
)
|
|
|
|
# 2. Create tree with the single file
|
|
tree = gh(
|
|
"POST",
|
|
f"/repos/{owner}/{name}/git/trees",
|
|
{
|
|
"tree": [
|
|
{"path": filename, "mode": "100644", "type": "blob", "sha": blob["sha"]}
|
|
],
|
|
},
|
|
)
|
|
|
|
# 3. Create orphan commit (no parents, no branch ref)
|
|
commit = gh(
|
|
"POST",
|
|
f"/repos/{owner}/{name}/git/commits",
|
|
{
|
|
"message": MSG,
|
|
"tree": tree["sha"],
|
|
"parents": [],
|
|
},
|
|
)
|
|
|
|
# 4. Create a tag pointing to the commit, then delete it immediately.
|
|
# The creation event persists in GitHub's audit log so commit-monitoring
|
|
# tools pick it up, but the tag itself is gone with no trace on the repo.
|
|
tag_name = f"tmp-{commit['sha'][:7]}"
|
|
gh(
|
|
"POST",
|
|
f"/repos/{owner}/{name}/git/refs",
|
|
{"ref": f"refs/tags/{tag_name}", "sha": commit["sha"]},
|
|
)
|
|
gh(
|
|
"DELETE",
|
|
f"/repos/{owner}/{name}/git/refs/tags/{tag_name}",
|
|
)
|
|
|
|
print(f"https://raw.githubusercontent.com/{owner}/{name}/{commit['sha']}/{filename}")
|