Last active
June 11, 2025 17:48
-
-
Save dragon-fish/cb5f8a2afabbf7dea2a4d3655e94189e to your computer and use it in GitHub Desktop.
Fix Git Case-Sensitive File Names
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Fix Git Case-Sensitive File Names | |
| 这个脚本用于修复 Git 仓库中因大小写不敏感导致的文件名冲突问题。 | |
| 它会扫描所有已跟踪的文件,查找大小写冲突的文件名,并将其重命名为临时名称, | |
| 然后再重命名为最终名称,分两次提交。 | |
| 用法: | |
| python fix-git-case-sensitive.py | |
| 警告: | |
| 1. 这个脚本会修改 Git 仓库中的文件名,可能会导致一些IDE临时无法识别文件,或者重复触发索引更新。 | |
| 因此建议在运行此脚本之前关闭所有 IDE 或编辑器。 | |
| 2. 在运行此脚本之前,请确保你已经备份了仓库。 | |
| 3. 确保在干净的工作区运行此脚本,即没有未提交的更改。 | |
| """ | |
| import os | |
| import sys | |
| import subprocess | |
| import time | |
| def print_progress_bar(current, total, prefix="", length=50): | |
| """打印简单的ASCII进度条""" | |
| percent = f"{100 * (current / float(total)):.1f}" | |
| filled_length = int(length * current // total) | |
| bar = '█' * filled_length + ' ' * (length - filled_length) | |
| print(f'\r{prefix} |{bar}| {percent}% ({current}/{total})', end='', file=sys.stderr) | |
| if current == total: | |
| print(file=sys.stderr) | |
| # 确保 git 区分大小写 | |
| subprocess.run(["git", "config", "core.ignorecase", "false"], check=True) | |
| print(">>> 已设置 core.ignorecase 为 false", file=sys.stderr) | |
| # 1) 拿到所有 tracked files | |
| result = subprocess.run(["git", "ls-files"], stdout=subprocess.PIPE, text=True, check=True) | |
| files = result.stdout.splitlines() | |
| TOTAL = len(files) | |
| print(f">>> 正在扫描 {TOTAL} 个文件,查找大小写冲突…", file=sys.stderr) | |
| # 2) 构建目录缓存:dir -> { lowercase_name: actual_name } | |
| dir_cache = {} | |
| for path in files: | |
| d = os.path.dirname(path) or "." | |
| if d not in dir_cache: | |
| try: | |
| entries = os.listdir(d) | |
| except FileNotFoundError: | |
| dir_cache[d] = {} | |
| else: | |
| m = {} | |
| for name in entries: | |
| m[name.lower()] = name | |
| dir_cache[d] = m | |
| # 3) 找到所有冲突 | |
| renames = [] | |
| for idx, gitpath in enumerate(files, start=1): | |
| print_progress_bar(idx, TOTAL, "扫描文件") | |
| d = os.path.dirname(gitpath) or "." | |
| basename = os.path.basename(gitpath) | |
| low = basename.lower() | |
| actual = dir_cache.get(d, {}).get(low) | |
| if actual and actual != basename: | |
| renames.append((gitpath, actual)) | |
| print(f"\n • 发现冲突 #{len(renames)}: Git='{gitpath}' 本地='{d}/{actual}'", file=sys.stderr) | |
| if not renames: | |
| print("✅ 未发现大小写冲突,退出。", file=sys.stderr) | |
| sys.exit(0) | |
| print(f"\n>>> 发现 {len(renames)} 个冲突,将通过两次提交进行修复…\n", file=sys.stderr) | |
| # 4) 第 1 步:old → temp | |
| for i, (old, actual) in enumerate(renames, start=1): | |
| tmp = f".casefix_tmp_{int(time.time())}_{i}" | |
| # 保持原始目录结构,构建完整的最终路径 | |
| old_dir = os.path.dirname(old) | |
| final_path = os.path.join(old_dir, actual) if old_dir else actual | |
| renames[i-1] = (old, tmp, final_path) | |
| print(">>> 步骤 1: 重命名 旧名称 → 临时名称", file=sys.stderr) | |
| for i, (old, tmp, _) in enumerate(renames, start=1): | |
| print_progress_bar(i, len(renames), "重命名") | |
| subprocess.run(["git", "mv", "-v", old, tmp], check=True, capture_output=True) | |
| subprocess.run(["git", "commit", "-m", "chore: fix case-sensitive file names (1/2)"], check=True) | |
| print(">>> 已提交 (1/2)\n", file=sys.stderr) | |
| # 5) 第 2 步:temp → final | |
| print(">>> 步骤 2: 重命名 临时名称 → 最终名称", file=sys.stderr) | |
| for i, (_, tmp, final) in enumerate(renames, start=1): | |
| print_progress_bar(i, len(renames), "重命名") | |
| subprocess.run(["git", "mv", "-v", tmp, final], check=True, capture_output=True) | |
| subprocess.run(["git", "commit", "-m", "chore: fix case-sensitive file names (2/2)"], check=True) | |
| print(">>> 已提交 (2/2)\n", file=sys.stderr) | |
| print(f"🎉 完成!已处理 {len(renames)} 个文件,分两次提交。", file=sys.stderr) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment