blob: 0682104f696655c2580ced2e8f29ed6628d75982 [file] [log] [blame]
#!/usr/bin/env python3
import argparse
import sys
import os
from typing import Dict, List, Optional, Set
# Allow importing of root-relative modules.
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(os.path.join(ROOT_DIR))
#pylint: disable=wrong-import-position
from python.tools.git_utils import (
get_all_branches,
get_branch_children,
get_current_branch,
run_git_command,
topological_sort_branches,
)
#pylint: enable=wrong-import-position
def main():
parser = argparse.ArgumentParser(
description='Deletes local branches identical (no diff) to effective parent. Updates children.',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
'--dry-run',
action='store_true',
help="Show actions without making changes.")
args = parser.parse_args()
if args.dry_run:
print("--- DRY RUN MODE ---")
# --- Phase 1: Check and Map ---
print("Analyzing branch structure...")
sorted_branches: List[str] = []
parent_graph: Dict[str, Optional[str]] = {}
try:
sorted_branches, parent_graph = topological_sort_branches()
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Dependency analysis error: {e}", file=sys.stderr)
sys.exit(1)
if not sorted_branches:
print("No branches with parent configurations found.")
sys.exit(0)
remap: Dict[str, str] = {} # {pruned_branch: effective_parent_it_matched}
branches_to_prune: Set[str] = set()
all_local_branches = set(get_all_branches())
mainline_branches = {'origin/main', 'origin/ui-canary', 'origin/ui-stable'}
print("Checking branches against effective parents...")
for branch in sorted_branches:
original_parent = parent_graph.get(branch)
if original_parent is None:
continue
effective_parent = remap.get(original_parent, original_parent)
if not effective_parent:
continue
if effective_parent not in all_local_branches and effective_parent not in mainline_branches:
continue
diff_cmd = ['diff', '--quiet', effective_parent, branch]
try:
diff_result = run_git_command(diff_cmd, check=False)
is_identical = (diff_result.returncode == 0)
except Exception as e:
print(
f"Warning: Error diffing '{effective_parent}'..'{branch}': {e}. Skipping.",
file=sys.stderr)
continue
if is_identical:
print(
f"- Found: '{branch}' identical to effective parent '{effective_parent}'. Marking for prune."
)
branches_to_prune.add(branch)
remap[branch] = effective_parent
if not branches_to_prune:
print("\nNo branches found to be pruned.")
sys.exit(0)
# --- Phase 2: Perform Actions ---
print("\n--- Actions ---")
if args.dry_run:
print("Dry Run - Would perform:")
for branch_to_prune in sorted(list(branches_to_prune)):
final_parent = remap.get(branch_to_prune, "???")
print(
f" - Delete branch '{branch_to_prune}' (identical to '{final_parent}')"
)
children = get_branch_children(branch_to_prune, list(all_local_branches))
children_to_reparent = [c for c in children if c not in branches_to_prune]
if children_to_reparent:
print(
f" - Re-parent children ({', '.join(children_to_reparent)}) to '{final_parent}'"
)
else:
print("Performing re-parenting and deletions...")
current_checked_out_branch = get_current_branch()
all_local_branches_list = list(all_local_branches)
reparent_errors = False
processed_children = set()
print("Updating parent config for children...")
for branch_to_prune in branches_to_prune:
new_parent = remap.get(branch_to_prune)
if not new_parent:
print(
f"Error: No remap parent for '{branch_to_prune}'. Skipping children.",
file=sys.stderr)
reparent_errors = True
continue
children = get_branch_children(branch_to_prune, all_local_branches_list)
for child in children:
if child not in branches_to_prune and child not in processed_children:
try:
print(
f" - Setting parent of '{child}' to '{new_parent}' (was '{branch_to_prune}')"
)
run_git_command(['config', f'branch.{child}.parent', new_parent])
processed_children.add(child)
except Exception as e:
print(f"Error updating config for '{child}': {e}", file=sys.stderr)
reparent_errors = True
print("Deleting branches...")
delete_errors = False
for branch_to_prune in sorted(list(branches_to_prune)):
if branch_to_prune == current_checked_out_branch:
print(f"Skipping delete of '{branch_to_prune}' (checked out).")
continue
print(f" - Deleting branch '{branch_to_prune}'...")
try:
# Use -D for force delete, as branch might not appear merged
run_git_command(['branch', '-D', branch_to_prune])
except SystemExit:
delete_errors = True # Report error but continue deleting others
except Exception as e:
print(
f"Unexpected error deleting '{branch_to_prune}': {e}",
file=sys.stderr)
delete_errors = True
if reparent_errors or delete_errors:
print("\nWarning: Errors occurred.", file=sys.stderr)
sys.exit(1)
print(f"\n--- Pruning process finished ---")
print(
f"Branches {'would have been' if args.dry_run else 'were'} pruned: {len(branches_to_prune)}"
)
if __name__ == "__main__":
main()