Skip to main content

scripts-execute-rename

#!/usr/bin/env python3 """ AM.1.3.1: Execute batch rename of files and directories.

This script performs the actual git mv operations to rename files and directories from UPPERCASE to lowercase.

IMPORTANT: Run with --dry-run first to preview changes.

Usage: python3 scripts/lowercase-migration/execute-rename.py --dry-run python3 scripts/lowercase-migration/execute-rename.py python3 scripts/lowercase-migration/execute-rename.py --directories-only python3 scripts/lowercase-migration/execute-rename.py --files-only """

import os import json import subprocess import sys import time from pathlib import Path from datetime import datetime

def load_inventory(inventory_path: Path) -> dict: """Load inventory from JSON file.""" with open(inventory_path) as f: return json.load(f)

def remove_lock_file(): """Remove git index.lock file if it exists.""" lock_file = Path("/Users/halcasteel/PROJECTS/coditect-rollout-master/.git/modules/submodules/core/coditect-core/index.lock") if lock_file.exists(): try: lock_file.unlink() except: pass

def run_git_mv(old_path: Path, new_path: Path, dry_run: bool = True, max_retries: int = 3) -> bool: """Execute git mv command with retry on lock file errors.

On macOS (case-insensitive filesystem), renaming files that only differ
in case requires a two-step process through an intermediate name.
"""
if dry_run:
print(f" [DRY-RUN] git mv '{old_path}' '{new_path}'")
return True

# Check if this is a case-only rename (macOS case-insensitive fix)
old_name = old_path.name
new_name = new_path.name
is_case_only_rename = old_name.lower() == new_name.lower() and old_name != new_name

for attempt in range(max_retries):
try:
# Remove any stale lock file before each attempt
remove_lock_file()
time.sleep(0.1) # Small delay to avoid race conditions

if is_case_only_rename:
# Two-step rename for case-insensitive filesystems
# Step 1: Rename to temporary name
temp_path = old_path.parent / f"_temp_{old_name}"
result = subprocess.run(
['git', 'mv', str(old_path), str(temp_path)],
capture_output=True,
text=True,
check=True
)
# Step 2: Rename from temporary to final lowercase name
remove_lock_file()
time.sleep(0.1)
result = subprocess.run(
['git', 'mv', str(temp_path), str(new_path)],
capture_output=True,
text=True,
check=True
)
print(f" [OK] {old_path} -> {new_path} (via temp)")
else:
# Standard rename
result = subprocess.run(
['git', 'mv', str(old_path), str(new_path)],
capture_output=True,
text=True,
check=True
)
print(f" [OK] {old_path} -> {new_path}")
return True
except subprocess.CalledProcessError as e:
if "index.lock" in str(e.stderr) and attempt < max_retries - 1:
print(f" [RETRY] Lock file detected, retrying ({attempt + 1}/{max_retries})...")
time.sleep(0.5)
continue
print(f" [ERROR] Failed to rename {old_path}: {e.stderr}")
return False
return False

def rename_directories(inventory: dict, root_path: Path, dry_run: bool = True) -> int: """Rename all directories (deepest first).""" print("\n" + "=" * 60) print("RENAMING DIRECTORIES (deepest first)") print("=" * 60)

success_count = 0
fail_count = 0

# Directories are already sorted by depth (deepest first)
for item in inventory['directories_to_rename']:
old_path = root_path / item['path']
new_name = item['new_name']
new_path = old_path.parent / new_name

if not old_path.exists():
# Check if already renamed (case-insensitive)
if new_path.exists():
print(f" [SKIP] Already renamed: {item['path']}")
continue
print(f" [SKIP] Not found: {item['path']}")
continue

if run_git_mv(old_path, new_path, dry_run):
success_count += 1
else:
fail_count += 1

print(f"\nDirectories: {success_count} renamed, {fail_count} failed")
return success_count

def rename_files(inventory: dict, root_path: Path, dry_run: bool = True) -> int: """Rename all files.""" print("\n" + "=" * 60) print("RENAMING FILES") print("=" * 60)

success_count = 0
fail_count = 0

for item in inventory['files_to_rename']:
# Path might have changed if parent directory was renamed
# Try the original path first, then the lowercase version
old_path = root_path / item['path']

if not old_path.exists():
# Try lowercase directory path
lowercase_dir_path = root_path / item['path'].lower().rsplit('/', 1)[0] if '/' in item['path'] else root_path
old_path = Path(str(old_path).lower().rsplit('/', 1)[0]) / item['name'] if '/' in item['path'] else root_path / item['name']

# More robust: search for the file
if not old_path.exists():
# Try finding the file with lowercase directory
possible_path = root_path / item['path'].lower()
if not possible_path.parent.exists():
print(f" [SKIP] Parent directory not found for: {item['path']}")
continue
old_path = possible_path.parent / item['name']

if not old_path.exists():
print(f" [SKIP] Not found: {item['path']}")
continue

new_name = item['new_name']
new_path = old_path.parent / new_name

if old_path == new_path:
print(f" [SKIP] Same name: {item['path']}")
continue

if run_git_mv(old_path, new_path, dry_run):
success_count += 1
else:
fail_count += 1

print(f"\nFiles: {success_count} renamed, {fail_count} failed")
return success_count

def main(): # Parse arguments dry_run = '--dry-run' in sys.argv dirs_only = '--directories-only' in sys.argv files_only = '--files-only' in sys.argv auto_confirm = '--yes' in sys.argv or '-y' in sys.argv

# Find paths
script_path = Path(__file__).resolve()
root_path = script_path.parent.parent.parent
inventory_path = root_path / 'context-storage' / 'lowercase-migration' / 'inventory.json'

if not inventory_path.exists():
print("Error: Inventory not found. Run inventory-uppercase.py first.")
sys.exit(1)

# Load inventory
inventory = load_inventory(inventory_path)

print("=" * 60)
print("LOWERCASE MIGRATION - EXECUTE RENAME")
print("=" * 60)
print(f"Root path: {root_path}")
print(f"Inventory: {inventory_path}")
print(f"Mode: {'DRY-RUN' if dry_run else 'LIVE'}")
print(f"Directories to rename: {len(inventory['directories_to_rename'])}")
print(f"Files to rename: {len(inventory['files_to_rename'])}")

if not dry_run and not auto_confirm:
print("\n" + "!" * 60)
print("WARNING: This will rename files using git mv!")
print("Make sure you have committed all changes first.")
print("!" * 60)
response = input("\nProceed? (yes/no): ")
if response.lower() != 'yes':
print("Aborted.")
sys.exit(0)
elif not dry_run and auto_confirm:
print("\n[Auto-confirmed with --yes flag]")

# Change to root directory
os.chdir(root_path)

total_renamed = 0

# Rename directories first (deepest first to avoid path issues)
if not files_only:
total_renamed += rename_directories(inventory, root_path, dry_run)

# Then rename files
if not dirs_only:
total_renamed += rename_files(inventory, root_path, dry_run)

print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
print(f"Total items renamed: {total_renamed}")

if dry_run:
print("\nThis was a DRY-RUN. No files were actually renamed.")
print("To execute, run: python3 scripts/lowercase-migration/execute-rename.py")
else:
print("\nNext steps:")
print("1. Run: python3 scripts/lowercase-migration/update-references.py")
print("2. Run: git status to review changes")
print("3. Commit changes: git commit -m 'refactor: rename files to lowercase'")

if name == 'main': main()