summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNatalia <124304+nessita@users.noreply.github.com>2025-11-26 13:22:52 -0300
committerNatalia <124304+nessita@users.noreply.github.com>2025-11-26 20:08:24 -0300
commit4b5dcc96f2996150ff2675233ec0a69f67b7dc9b (patch)
tree2bcef9c0eaf1e6db4c8521d43ce7decaf105404f
parent0e85bdbde1c1fdbd3a92cdb6d31fab788811da63 (diff)
[4.2.x] Added script to archive EOL stable branches.
This also fixed a small bash issue in `confirm_release.sh` script. Backport of 532c1058a7dd2616181259c94eb92f2477038d2c from main.
-rw-r--r--scripts/archive_eol_stable_branches.py151
-rwxr-xr-xscripts/confirm_release.sh2
2 files changed, 152 insertions, 1 deletions
diff --git a/scripts/archive_eol_stable_branches.py b/scripts/archive_eol_stable_branches.py
new file mode 100644
index 0000000000..c2cafc9c5c
--- /dev/null
+++ b/scripts/archive_eol_stable_branches.py
@@ -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()
diff --git a/scripts/confirm_release.sh b/scripts/confirm_release.sh
index 920f2061af..c3b4d12c5a 100755
--- a/scripts/confirm_release.sh
+++ b/scripts/confirm_release.sh
@@ -25,7 +25,7 @@ echo "Download checksum file ..."
curl --fail --output "$CHECKSUM_FILE" "${MEDIA_URL_PREFIX}/pgp/${CHECKSUM_FILE}"
echo "Verify checksum file ..."
-if [ -n "${GPG_KEY}" ] ; then
+if [ -n "${GPG_KEY:-}" ] ; then
gpg --recv-keys "${GPG_KEY}"
fi
gpg --verify "${CHECKSUM_FILE}"