#!/usr/bin/env python # -*- coding: utf-8 -*- """ Split Commit Tool - 交互式拆分未提交变更为多个原子提交 """ import os import sys import subprocess from pathlib import Path # 配置 PROTECTED_BRANCHES = ['main', 'master', 'release/', 'hotfix/'] REPO_ROOT = Path(__file__).parent.absolute() os.chdir(REPO_ROOT) def run_command(cmd, capture_output=True): """执行Git命令""" try: result = subprocess.run( cmd, shell=True, capture_output=capture_output, text=True, encoding='utf-8' ) return result.returncode == 0, result.stdout, result.stderr except Exception as e: return False, "", str(e) def is_protected_branch(branch_name): """检查是否为保护分支""" for protected in PROTECTED_BRANCHES: if protected.endswith('/') and branch_name.startswith(protected): return True if branch_name == protected: return True return False def get_current_branch(): """获取当前分支""" success, stdout, stderr = run_command("git branch --show-current") return stdout.strip() if success else "" def analyze_changes(): """分析变更""" print("=" * 60) print("步骤 1: 分析变更") print("=" * 60) # git status print("\n[git status]") success, stdout, stderr = run_command("git status") print(stdout or stderr) # git diff --stat print("\n[git diff --stat]") success, stdout, stderr = run_command("git diff --stat") print(stdout or stderr) return True def create_commit(msg): """使用msg.txt创建提交(避免中文乱码)""" msg_file = REPO_ROOT / "msg.txt" try: with open(msg_file, 'w', encoding='utf-8') as f: f.write(msg) f.write("\n\nCo-Authored-By: Claude Code \n") success, stdout, stderr = run_command("git commit -F msg.txt") if not success: print(f"❌ 提交失败: {stderr}") return False print(f"✅ 提交成功: {msg}") return True finally: if msg_file.exists(): msg_file.unlink() def commit_all_changes(): """一次性提交所有变更""" print("\n" + "=" * 60) print("一次性提交所有变更") print("=" * 60) # 处理删除的文件 run_command("git ls-files --deleted -z | xargs -0 git rm", capture_output=False) # 添加所有变更 run_command("git add -A", capture_output=False) # 创建提交 create_commit("chore: 批量提交变更") # 显示提交历史 print("\n[提交历史]") run_command("git log --oneline -5", capture_output=False) def interactive_split(): """交互式拆分提交""" print("\n" + "=" * 60) print("步骤 2: 分组变更") print("=" * 60) # 获取所有变更 success, status_output, _ = run_command("git status --short") if not success: print("❌ 无法获取变更状态") return False # 分析变更类型 staged_files = [] modified_files = [] deleted_files = [] untracked_files = [] for line in status_output.strip().split('\n'): if not line: continue status = line[:2] file_path = line[3:] if status == 'M ': staged_files.append(file_path) elif status == ' M': modified_files.append(file_path) elif status == 'D ': deleted_files.append(file_path) elif status == ' D': deleted_files.append(file_path) elif status == '??': untracked_files.append(file_path) elif status == 'MM': staged_files.append(file_path) modified_files.append(file_path) # 显示分组建议 groups = [] print("\n[变更分组方案]") order = 1 # 删除文件 if deleted_files: groups.append({ 'order': order, 'files': deleted_files, 'message': '删除旧文件', 'type': 'delete' }) print(f"{order}. 删除 {len(deleted_files)} 个文件") for f in deleted_files[:3]: print(f" - {f}") if len(deleted_files) > 3: print(f" ... 还有 {len(deleted_files) - 3} 个文件") order += 1 # 修改的meta文件 meta_files = [f for f in modified_files if f.endswith('.meta')] if meta_files: groups.append({ 'order': order, 'files': meta_files, 'message': '更新.meta文件', 'type': 'meta' }) print(f"{order}. 修改 {len(meta_files)} 个.meta文件") for f in meta_files[:3]: print(f" - {f}") if len(meta_files) > 3: print(f" ... 还有 {len(meta_files) - 3} 个文件") order += 1 # 场景文件 scene_files = [f for f in modified_files if f.endswith('.unity')] if scene_files: groups.append({ 'order': order, 'files': scene_files, 'message': '更新场景', 'type': 'scene' }) print(f"{order}. 修改 {len(scene_files)} 个场景文件") for f in scene_files: print(f" - {f}") order += 1 # 脚本文件 script_files = [f for f in modified_files if f.endswith('.cs')] if script_files: for script_file in script_files: groups.append({ 'order': order, 'files': [script_file], 'message': f'更新脚本: {Path(script_file).name}', 'type': 'script' }) print(f"{order}. 修改脚本: {script_file}") order += 1 # 未跟踪文件 if untracked_files: groups.append({ 'order': order, 'files': untracked_files, 'message': '添加新文件', 'type': 'untracked' }) print(f"{order}. 添加 {len(untracked_files)} 个新文件") for f in untracked_files[:3]: print(f" - {f}") if len(untracked_files) > 3: print(f" ... 还有 {len(untracked_files) - 3} 个文件") print("\n" + "=" * 60) print("步骤 3: 确认拆分方案") print("=" * 60) choice = input("\n是否开始拆分? [1] 开始拆分 [2] 取消拆分 [3] 自定义分组: ") if choice == '2': print("\n已取消拆分") return False if choice == '3': print("\n自定义分组功能暂未实现,请使用自动分组") return False # 开始拆分 print("\n" + "=" * 60) print("步骤 4: 执行拆分") print("=" * 60) for group in groups: print(f"\n>>> 处理第 {group['order']} 组") # 根据类型执行不同的git操作 if group['type'] == 'delete': # 删除文件 for file_path in group['files']: run_command(f'git rm "{file_path}"', capture_output=False) elif group['type'] == 'untracked': # 添加新文件 run_command('git add -A', capture_output=False) else: # 修改文件 for file_path in group['files']: run_command(f'git add "{file_path}"', capture_output=False) # 创建提交 success = create_commit(group['message']) if not success: print(f"❌ 第 {group['order']} 组提交失败,拆分中止") return False input("\n按回车继续下一组...") print("\n" + "=" * 60) print("步骤 5: 完成") print("=" * 60) print("\n[提交历史]") success, stdout, _ = run_command("git log --oneline -" + str(len(groups))) print(stdout) print(f"\n✅ 拆分完成!共创建 {len(groups)} 个原子提交") return True def main(): """主函数""" print("=" * 60) print("Split Commit - 交互式拆分提交工具") print("=" * 60) # 检查保护分支 current_branch = get_current_branch() print(f"\n当前分支: {current_branch}") if is_protected_branch(current_branch): print("\n❌ 当前为保护分支,无法直接拆分") print("\n选项:") print("[1] 创建新分支再拆分 (推荐)") print("[2] 压成单commit并移到新分支") print("[3] 取消") choice = input("\n请选择: ") if choice == '1': branch_name = input("请输入新分支名: ") if not branch_name: print("❌ 分支名不能为空") return print("\n执行:") print("1. git stash push --include-untracked") print(f"2. git checkout -b {branch_name}") print("3. git stash pop") confirm = input("\n确认执行?(y/n): ") if confirm.lower() == 'y': run_command("git stash push --include-untracked", capture_output=False) run_command(f"git checkout -b {branch_name}", capture_output=False) run_command("git stash pop", capture_output=False) print(f"\n✅ 已切换到新分支: {branch_name}") print("请重新运行此工具进行拆分") elif choice == '2': print("\n功能暂未实现") return # 非保护分支,继续拆分流程 print("\n✅ 非保护分支,可以进行拆分") # 分析变更 if not analyze_changes(): return # 选择模式 print("\n" + "=" * 60) print("选择拆分模式:") print("=" * 60) print("[1] 交互式拆分 (推荐)") print("[2] 一次性提交所有变更") mode = input("\n请选择: ") if mode == '1': interactive_split() elif mode == '2': commit_all_changes() else: print("❌ 无效选择") if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n\n已取消操作") sys.exit(0) except Exception as e: print(f"\n❌ 发生错误: {e}") sys.exit(1)