mirror of
https://github.com/django/django.git
synced 2025-12-23 09:19:27 +00:00
Added script to archive EOL stable branches.
This also fixed a small bash issue in `confirm_release.sh` script.
This commit is contained in:
parent
18b13cf6c4
commit
532c1058a7
2 changed files with 152 additions and 1 deletions
151
scripts/archive_eol_stable_branches.py
Normal file
151
scripts/archive_eol_stable_branches.py
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd, *, cwd=None, env=None, dry_run=True):
|
||||||
|
"""Run a command with optional dry-run behavior."""
|
||||||
|
environ = os.environ.copy()
|
||||||
|
if env:
|
||||||
|
environ.update(env)
|
||||||
|
if dry_run:
|
||||||
|
print("[DRY RUN]", " ".join(cmd))
|
||||||
|
else:
|
||||||
|
print("[EXECUTE]", " ".join(cmd))
|
||||||
|
try:
|
||||||
|
result = subprocess.check_output(
|
||||||
|
cmd, cwd=cwd, env=environ, stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
result = e.output
|
||||||
|
print(" [ERROR]", result)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
print(" [RESULT]", result)
|
||||||
|
return result.decode().strip()
|
||||||
|
|
||||||
|
|
||||||
|
def validate_env(checkout_dir):
|
||||||
|
if not checkout_dir:
|
||||||
|
sys.exit("Error: checkout directory not provided (--checkout-dir).")
|
||||||
|
if not os.path.exists(checkout_dir):
|
||||||
|
sys.exit(f"Error: checkout directory '{checkout_dir}' does not exist.")
|
||||||
|
if not os.path.isdir(checkout_dir):
|
||||||
|
sys.exit(f"Error: '{checkout_dir}' is not a directory.")
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_branches(checkout_dir, include_fn):
|
||||||
|
"""Return list of remote branches filtered by include_fn."""
|
||||||
|
result = run(
|
||||||
|
["git", "branch", "--list", "-r"],
|
||||||
|
cwd=checkout_dir,
|
||||||
|
dry_run=False,
|
||||||
|
)
|
||||||
|
branches = [b.strip() for b in result.split("\n") if b.strip()]
|
||||||
|
return [b for b in branches if include_fn(b)]
|
||||||
|
|
||||||
|
|
||||||
|
def get_branch_info(checkout_dir, branch):
|
||||||
|
"""Return (commit_hash, last_update_date) for a given branch."""
|
||||||
|
commit_hash = run(["git", "rev-parse", branch], cwd=checkout_dir, dry_run=False)
|
||||||
|
last_update = run(
|
||||||
|
["git", "show", branch, "--format=format:%ai", "-s"],
|
||||||
|
cwd=checkout_dir,
|
||||||
|
dry_run=False,
|
||||||
|
)
|
||||||
|
return commit_hash, last_update
|
||||||
|
|
||||||
|
|
||||||
|
def create_tag(checkout_dir, branch, commit_hash, last_update, *, dry_run=True):
|
||||||
|
"""Create a tag locally for a given branch at its last update."""
|
||||||
|
tag_name = branch.replace("origin/", "", 1)
|
||||||
|
msg = f'"Tagged {tag_name} for EOL stable branch removal."'
|
||||||
|
run(
|
||||||
|
["git", "tag", "--sign", "--message", msg, tag_name, commit_hash],
|
||||||
|
cwd=checkout_dir,
|
||||||
|
env={"GIT_COMMITTER_DATE": last_update},
|
||||||
|
dry_run=dry_run,
|
||||||
|
)
|
||||||
|
return tag_name
|
||||||
|
|
||||||
|
|
||||||
|
def delete_remote_and_local_branch(checkout_dir, branch, *, dry_run=True):
|
||||||
|
"""Delete a remote branch from origin and the maching local branch."""
|
||||||
|
try:
|
||||||
|
run(
|
||||||
|
["git", "branch", "-D", branch],
|
||||||
|
cwd=checkout_dir,
|
||||||
|
dry_run=dry_run,
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print(f"[ERROR] Local branch {branch} can not be deleted.")
|
||||||
|
|
||||||
|
run(
|
||||||
|
["git", "push", "origin", "--delete", branch.replace("origin/", "", 1)],
|
||||||
|
cwd=checkout_dir,
|
||||||
|
dry_run=dry_run,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Archive Django branches into tags and optionally delete them."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--checkout-dir", required=True, help="Path to Django git checkout"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="Print commands instead of executing them",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--branches", nargs="*", help="Specific remote branches to include (optional)"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
validate_env(args.checkout_dir)
|
||||||
|
dry_run = args.dry_run
|
||||||
|
checkout_dir = args.checkout_dir
|
||||||
|
|
||||||
|
if args.branches:
|
||||||
|
wanted = set(f"origin/{b}" for b in args.branches)
|
||||||
|
else:
|
||||||
|
wanted = set()
|
||||||
|
|
||||||
|
branches = get_remote_branches(checkout_dir, include_fn=lambda b: b in wanted)
|
||||||
|
if not branches:
|
||||||
|
print("No branches matched inclusion criteria.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\nMatched branches:")
|
||||||
|
print("\n".join(branches))
|
||||||
|
print()
|
||||||
|
|
||||||
|
branch_updates = {b: get_branch_info(checkout_dir, b) for b in branches}
|
||||||
|
print("\nLast updates:")
|
||||||
|
for b, (h, d) in branch_updates.items():
|
||||||
|
print(f"{b}\t{h}\t{d}")
|
||||||
|
|
||||||
|
if (
|
||||||
|
input("\nDelete remote branches and create tags? [y/N]: ").strip().lower()
|
||||||
|
== "y"
|
||||||
|
):
|
||||||
|
for b, (commit_hash, last_update_date) in branch_updates.items():
|
||||||
|
print(f"Creating tag for {b} at {commit_hash=} with {last_update_date=}")
|
||||||
|
create_tag(checkout_dir, b, commit_hash, last_update_date, dry_run=dry_run)
|
||||||
|
print(f"Deleting remote branch {b}")
|
||||||
|
delete_remote_and_local_branch(checkout_dir, b, dry_run=dry_run)
|
||||||
|
run(
|
||||||
|
["git", "push", "--tags"],
|
||||||
|
cwd=checkout_dir,
|
||||||
|
dry_run=dry_run,
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Done.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -25,7 +25,7 @@ echo "Download checksum file ..."
|
||||||
curl --fail --output "$CHECKSUM_FILE" "${MEDIA_URL_PREFIX}/pgp/${CHECKSUM_FILE}"
|
curl --fail --output "$CHECKSUM_FILE" "${MEDIA_URL_PREFIX}/pgp/${CHECKSUM_FILE}"
|
||||||
|
|
||||||
echo "Verify checksum file ..."
|
echo "Verify checksum file ..."
|
||||||
if [ -n "${GPG_KEY}" ] ; then
|
if [ -n "${GPG_KEY:-}" ] ; then
|
||||||
gpg --recv-keys "${GPG_KEY}"
|
gpg --recv-keys "${GPG_KEY}"
|
||||||
fi
|
fi
|
||||||
gpg --verify "${CHECKSUM_FILE}"
|
gpg --verify "${CHECKSUM_FILE}"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue