Skip to content

Instantly share code, notes, and snippets.

@dragon-fish
Last active June 11, 2025 17:48
Show Gist options
  • Select an option

  • Save dragon-fish/cb5f8a2afabbf7dea2a4d3655e94189e to your computer and use it in GitHub Desktop.

Select an option

Save dragon-fish/cb5f8a2afabbf7dea2a4d3655e94189e to your computer and use it in GitHub Desktop.
Fix Git Case-Sensitive File Names
#!/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