更新说明
对于大多数为符号链接而苦苦挣扎的 Windows 开发人员来说git
在 Windows 上以及与 *nix 系统共享存储库的问题,这个主题是一个已解决的问题 - 一旦您更新了对 Windows 的理解mklink
一点并打开开发者模式。
看到这个更现代的答案 https://stackoverflow.com/a/59761201/237059在深入研究以下 git hacks 的深入讨论之前。
旧系统:
我不久前问过这个完全相同的问题(不是在这里,只是一般性的),最后得出了一个与OP的提议非常相似的解决方案。
我将发布我最终使用的解决方案。
但首先我将直接回答 OP 的 3 个问题:
问:“您认为这种方法有哪些缺点(如果有的话)?”
答:所提出的解决方案确实有一些缺点,主要是增加存储库污染的可能性,或者在文件处于“Windows 符号链接”状态时意外添加重复文件。 (更多信息请参见下面的“限制”。)
问:“这个结帐后脚本是否可以实现?即我可以递归地找出 git 创建的虚拟“符号链接”文件吗?”
答:是的,结帐后脚本是可以实现的!也许不是字面上的后git checkout
步骤,但下面的解决方案已经足够满足我的需求,不需要字面的结帐后脚本。
问:“有人已经写过这样的剧本了吗?”
A: Yes!
解决方案:
我们的开发人员的情况与 OP 的情况大致相同:混合了 Windows 和类 Unix 主机、存储库和带有许多 git 符号链接的子模块,并且 MsysGit 的发行版本中还没有本地支持来智能地处理 Windows 主机上的这些符号链接。
感谢 Josh Lee 指出 git 使用特殊文件模式提交符号链接的事实120000
。有了这些信息,就可以添加一些 git 别名,以便在 Windows 主机上创建和操作 git 符号链接。
-
在 Windows 上创建 git 符号链接
git config --global alias.add-symlink '!'"$(cat <<'ETX'
__git_add_symlink() {
if [ $# -ne 2 ] || [ "$1" = "-h" ]; then
printf '%b\n' \
'usage: git add-symlink <source_file_or_dir> <target_symlink>\n' \
'Create a symlink in a git repository on a Windows host.\n' \
'Note: source MUST be a path relative to the location of target'
[ "$1" = "-h" ] && return 0 || return 2
fi
source_file_or_dir=${1#./}
source_file_or_dir=${source_file_or_dir%/}
target_symlink=${2#./}
target_symlink=${target_symlink%/}
target_symlink="${GIT_PREFIX}${target_symlink}"
target_symlink=${target_symlink%/.}
: "${target_symlink:=.}"
if [ -d "$target_symlink" ]; then
target_symlink="${target_symlink%/}/${source_file_or_dir##*/}"
fi
case "$target_symlink" in
(*/*) target_dir=${target_symlink%/*} ;;
(*) target_dir=$GIT_PREFIX ;;
esac
target_dir=$(cd "$target_dir" && pwd)
if [ ! -e "${target_dir}/${source_file_or_dir}" ]; then
printf 'error: git-add-symlink: %s: No such file or directory\n' \
"${target_dir}/${source_file_or_dir}" >&2
printf '(Source MUST be a path relative to the location of target!)\n' >&2
return 2
fi
git update-index --add --cacheinfo 120000 \
"$(printf '%s' "$source_file_or_dir" | git hash-object -w --stdin)" \
"${target_symlink}" \
&& git checkout -- "$target_symlink" \
&& printf '%s -> %s\n' "${target_symlink#$GIT_PREFIX}" "$source_file_or_dir" \
|| return $?
}
__git_add_symlink
ETX
)"
Usage: git add-symlink <source_file_or_dir> <target_symlink>
,其中源文件或目录对应的参数必须采用路径的形式相对于目标符号链接。您可以按照通常使用的方式使用此别名ln
.
例如,存储库树:
dir/
dir/foo/
dir/foo/bar/
dir/foo/bar/baz (file containing "I am baz")
dir/foo/bar/lnk_file (symlink to ../../../file)
file (file containing "I am file")
lnk_bar (symlink to dir/foo/bar/)
可以在 Windows 上创建,如下所示:
git init
mkdir -p dir/foo/bar/
echo "I am baz" > dir/foo/bar/baz
echo "I am file" > file
git add -A
git commit -m "Add files"
git add-symlink ../../../file dir/foo/bar/lnk_file
git add-symlink dir/foo/bar/ lnk_bar
git commit -m "Add symlinks"
-
用 NTFS 硬链接+连接替换 git 符号链接
git config --global alias.rm-symlinks '!'"$(cat <<'ETX'
__git_rm_symlinks() {
case "$1" in (-h)
printf 'usage: git rm-symlinks [symlink] [symlink] [...]\n'
return 0
esac
ppid=$$
case $# in
(0) git ls-files -s | grep -E '^120000' | cut -f2 ;;
(*) printf '%s\n' "$@" ;;
esac | while IFS= read -r symlink; do
case "$symlink" in
(*/*) symdir=${symlink%/*} ;;
(*) symdir=. ;;
esac
git checkout -- "$symlink"
src="${symdir}/$(cat "$symlink")"
posix_to_dos_sed='s_^/\([A-Za-z]\)_\1:_;s_/_\\\\_g'
doslnk=$(printf '%s\n' "$symlink" | sed "$posix_to_dos_sed")
dossrc=$(printf '%s\n' "$src" | sed "$posix_to_dos_sed")
if [ -f "$src" ]; then
rm -f "$symlink"
cmd //C mklink //H "$doslnk" "$dossrc"
elif [ -d "$src" ]; then
rm -f "$symlink"
cmd //C mklink //J "$doslnk" "$dossrc"
else
printf 'error: git-rm-symlink: Not a valid source\n' >&2
printf '%s =/=> %s (%s =/=> %s)...\n' \
"$symlink" "$src" "$doslnk" "$dossrc" >&2
false
fi || printf 'ESC[%d]: %d\n' "$ppid" "$?"
git update-index --assume-unchanged "$symlink"
done | awk '
BEGIN { status_code = 0 }
/^ESC\['"$ppid"'\]: / { status_code = $2 ; next }
{ print }
END { exit status_code }
'
}
__git_rm_symlinks
ETX
)"
git config --global alias.rm-symlink '!git rm-symlinks' # for back-compat.
Usage:
git rm-symlinks [symlink] [symlink] [...]
此别名可以一举删除 git 符号链接或一次性删除所有符号链接。符号链接将替换为 NTFS 硬链接(对于文件)或 NTFS 连接(对于目录)。使用硬链接+连接而不是“真正的”NTFS 符号链接的好处是创建它们不需要提升 UAC 权限。
要从子模块中删除符号链接,只需使用 git 的内置支持来迭代它们:
git submodule foreach --recursive git rm-symlinks
但是,对于每一个像这样的激烈行动,逆转都是很好的......
-
在 Windows 上恢复 git 符号链接
git config --global alias.checkout-symlinks '!'"$(cat <<'ETX'
__git_checkout_symlinks() {
case "$1" in (-h)
printf 'usage: git checkout-symlinks [symlink] [symlink] [...]\n'
return 0
esac
case $# in
(0) git ls-files -s | grep -E '^120000' | cut -f2 ;;
(*) printf '%s\n' "$@" ;;
esac | while IFS= read -r symlink; do
git update-index --no-assume-unchanged "$symlink"
rmdir "$symlink" >/dev/null 2>&1
git checkout -- "$symlink"
printf 'Restored git symlink: %s -> %s\n' "$symlink" "$(cat "$symlink")"
done
}
__git_checkout_symlinks
ETX
)"
git config --global alias.co-symlinks '!git checkout-symlinks'
Usage: git checkout-symlinks [symlink] [symlink] [...]
,这会撤消git rm-symlinks
,有效地将存储库恢复到其自然状态(除了您的更改,这should保持完好无损)。
对于子模块:
git submodule foreach --recursive git checkout-symlinks
-
限制:
-
路径中带有空格的目录/文件/符号链接应该可以工作。但是制表符还是换行符? YMMV…(我的意思是:不要那样做,因为它will not work.)
-
如果您自己或其他人忘记git checkout-symlinks
在做一些可能产生广泛影响的事情之前,例如git add -A
,本地存储库最终可能会出现在污染状态。
使用之前的“示例存储库”:
echo "I am nuthafile" > dir/foo/bar/nuthafile
echo "Updating file" >> file
git add -A
git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: dir/foo/bar/nuthafile
# modified: file
# deleted: lnk_bar # POLLUTION
# new file: lnk_bar/baz # POLLUTION
# new file: lnk_bar/lnk_file # POLLUTION
# new file: lnk_bar/nuthafile # POLLUTION
#
哎呀...
因此,最好将这些别名作为 Windows 用户在构建项目之前和之后执行的步骤,而不是在签出之后或推送之前执行。但每种情况都不同。这些别名对我来说足够有用,不需要真正的结帐后解决方案。
参考:
http://git-scm.com/book/en/Git-Internals-Git-Objects http://git-scm.com/book/en/Git-Internals-Git-Objects
http://technet.microsoft.com/en-us/library/cc753194 http://technet.microsoft.com/en-us/library/cc753194
最后更新: 2019-03-13
- POSIX 合规性(好吧,除了那些
mklink
当然是打电话)——没有了Bashisms https://wiki.ubuntu.com/DashAsBinSh#I_am_a_developer._How_can_I_avoid_this_problem_in_future.3F!
- 支持其中包含空格的目录和文件。
- 现在可以正确保留/返回零和非零退出状态代码(分别用于传达所请求命令的成功/失败)。
- The
add-symlink
别名现在更像ln(1) http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ln.html#tag_20_67并且可以从存储库中的任何目录使用,而不仅仅是存储库的根目录。
- The
rm-symlink
别名(单数)已被取代rm-symlinks
别名(复数),现在接受多个参数(或根本不接受参数,它像以前一样找到整个存储库中的所有符号链接),用于有选择地将 git 符号链接转换为 NTFS 硬链接+连接。
- The
checkout-symlinks
别名也已更新为接受多个参数(或根本没有,==一切),以选择性地反转上述转换。
最后注意事项:虽然我确实使用 Bash 3.2(甚至 3.1)测试了加载和运行这些别名,但对于那些可能由于各种原因仍停留在这些古老版本上的人来说,请注意,像这些旧版本一样因其解析器错误而臭名昭著。如果您在尝试安装任何这些别名时遇到问题,您首先应该考虑的是升级您的 shell(对于 Bash,请使用 CTRL+X、CTRL+V 检查版本)。或者,如果您尝试通过将它们粘贴到终端模拟器中来安装它们,则将它们粘贴到文件中并获取它可能会更幸运,例如作为
. ./git-win-symlinks.sh