解决方案的核心将是使用自动批量重命名的工具/方法。您可以将 mv 与 git add 结合使用,也可以仅使用 git mv。在任何一种情况下,如果您使用不区分大小写的文件系统,您可能需要采取额外的步骤。所以在我们处理批量重命名之前,讨论一下如何处理大小写可能会很有用。
区分大小写
某些系统(或系统+文件系统组合——例如 Mac OS X 上 HFS+ 文件系统的默认变体*)保留大小写,但不区分大小写。在这样的系统上,在进行仅涉及更改名称大小写的重命名时可能需要小心。通常的解决方法是使用一个不仅仅是大小写不同的临时名称作为两个单独大小写不同的名称之间的“桥梁”(例如mv foo.JPG tmp && mv tmp foo.jpg)。
* 可以在 Mac OS X 上使用区分大小写的文件系统(包括大小写
HFS+ 的敏感变体)。
从这里开始,我将假设一个不区分大小写的文件系统。
Mac OS X 上的 mv 命令可以在一个步骤中处理仅更改大小写的重命名。它会给出“覆盖?”提示是否使用-i 选项运行,如果给定-n 选项,它将跳过重命名。它只有通过类 Unix 系统的许多部分的“足以吊死你自己”的默认操作才能成功。
git mv 命令对这种情况有点偏执。除非给出-f/--force 选项,否则它会拒绝操作(“destination exists”错误)。
# this will succeed, though it may fail/prompt if mv is aliased to use -n/-i
mv foo.JPG foo.jpg
# this will succeed
mv -f bar.JPG bar.jpg
# this will succeed but give a warning
git mv -f quux.JPG quux.jpg
批量重命名选项
Perl 重命名
所需的操作很简单,只需编写一些 shell 脚本即可,但如果您需要做很多事情,您可以使用 Perl rename 实用程序(Jordan Lewis 提到的那个)更复杂。你可以试试rename from Debian's perl package,或者如果你觉得可以使用CPAN,你可以安装File::Rename,它包括rename程序。
ksh、bash、zsh、dash
下面使用的-ef 不兼容 POSIX。同样,虽然在 POSIX 中指定了 -e,但它不是纯 Bourne 兼容的。不过,它们都得到了广泛的支持。
for f in *.JPG; do
ff="${f%.JPG}.jpg"
test -e "$f" || continue # possible when not using nullglob
test "$f" != "$ff" || continue # possible when using nocaseglob
if test -e "$ff" &&
! test "$f" -ef "$ff"; then # possible on a case sensitive filesystem
echo "skipping <$f>: destination <$ff> exists and is distinct" 1>&2
continue
fi
# "mv" with "git rm" and "git add"
mv -f "$f" "$ff" &&
git rm --cached "$f" &&
git add "$ff"
done
最后一部分(mv、git rm、git add)可以只替换为git mv :
# "git mv"
git mv -f "$f" "$ff"
如果您非常担心在不区分大小写的系统上重命名可能会失败,那么您可以使用临时名称:
# temp-based "mv" with "git rm" and "git add"
t="$ff.tmp"; while test -e "$t"; do t="$t.tmp"; done
mv -n "$f" "$t" &&
mv -n "$t" "$ff" &&
git rm --cached "$f" &&
git add "$ff"
或者用 git mv:
# temp-based "git mv"
t="$ff.tmp"; while test -e "$t"; do t="$t.tmp"; done
git mv "$f" "$t" &&
git mv "$t" "$ff"
zsh/zmv
zmv 和 git mv 都需要-f。
zsh -c 'autoload zmv && $0 $@' zmv -fp git -o 'mv -f' '(*).JPG' '$1 x.jpg'
现在您已将它们全部重命名并在 Git 的索引中更新,您可以提交它们。
但是其他使用区分大小写的文件系统的 Git 用户是否能够检出它们?
git checkout 重命名后
如果您的历史记录中还有其他用户,他们可能仍然拥有JPG 文件,并且当他们最终使用jpg 文件检出您的提交(其后代)时。他们会发生什么?
无论发生什么,都不需要“rename to temp, commit, rename to final, commit”。 git checkout 在提交之间移动时不会按顺序应用提交。它通过将 HEAD 中的索引和工作树“合并”到新提交来真正起作用。这实际上意味着它直接“跳转”到新提交,同时拖动在 HEAD 和索引/工作树之间发现的非冲突更改。
在内部,Git 将重命名视为删除和添加。我没有找到任何描述 git checkout 关于删除和添加顺序的行为的文档,所以我查看了源代码。 git checkout 在任何更新/添加之前处理所有删除 (cmd_checkout -> switch_branches -> merge_working_tree (-> reset_tree) -> unpack_trees -> check_updates)。
您可以在重命名提交后立即进行测试:
git checkout HEAD~ # note: detached HEAD
# verify that the original names are back in place
git checkout - # back to your branch
# verify that the new names are in place again
文件上的 git blame 似乎表明了一个可能的提交:Make unpack-tree update removed files before any updated files,它最初是在 Git 1.5.6-rc0 (2008-06-18) 中发布的。因此,尽管没有记录(?),但这种行为是专门为支持不区分大小写的文件系统而实现的。
谢谢,莱纳斯!