Windows下的Git符号链接
Windows下的Git符号链接
我们的开发人员使用混合了Windows和基于Unix的操作系统。因此,在Unix机器上创建的符号链接对于Windows开发人员来说成为了一个问题。在Windows (MSysGit)中,符号链接会被转换为一个文本文件,其中包含指向该文件的路径。相反,我想将符号链接转换为实际的Windows符号链接。
我对此问题的(更新的)解决方法是:
- 编写一个后检出脚本,递归查找\"符号链接\"文本文件。
- 用与虚拟 \"符号链接\" 相同的名称和扩展名,用Windows符号链接 (使用mklink) 替换它们
- 通过向 .git/info/exclude 文件添加条目来忽略这些Windows符号链接
我还没有实现这个,但我相信这是解决这个问题的可靠方法。
- 您认为这种方法有什么缺点吗?
- 这个后检出脚本可行吗?也就是说,我能否递归查找Git创建的虚拟 \"symlink\" 文件?
2020+ TL;DR Answer
- 在Windows 10/11中启用"开发人员模式"——给予
mklink
权限 - 确保在git中启用符号链接,其中至少有一种
- 系统设置:在安装msysgit时选中复选框
- 全局设置:
git config --global core.symlinks true
- 本地设置:
git config core.symlinks true
请注意,在Windows上,git对符号链接的支持相对较新。
仍然有一些错误会影响某些git客户端。
特别是,由于libgit2中的漏洞,与相对(..
)路径的符号链接在某些程序中被破坏。
例如,GitKraken就受到了影响,因为他们正在等待nodegit
将libgit2
从v0.x
(回归)更新到v1.x
(修复)。
重新创建丢失/损坏的符号链接
与多个git客户端报告的成功不同,有一种(越来越强制和危险的)选项
- 检出:
git checkout -- path/to/symlink
- 还原(自git v2.23.0起):
git restore -- path/to/symlink
- 切换分支(离开和回来)
- 硬重置:
git reset --hard
- 删除本地仓库并重新克隆
故障排除
git config --show-scope --show-origin core.symlinks
将显示设置所在的级别(即"范围")、持久化它的配置文件(即"源")以及当前设置的值。
很可能是"本地"配置覆盖了"全局"或"系统"设置。git config --unset core.symlinks
将清除"本地"设置,允许更高级别的设置生效。
更新说明
对于大多数在Windows上使用symlinks和git
以及共享与*nix系统中的库的问题困扰着的开发者,这个问题已经解决了--只要你稍微更新一下你对 mklink
的理解并打开开发者模式。
在深入研究以下关于git的高级技巧之前,请查看这个更现代的解答。
老系统:
我之前也问过这个问题(不是在这里,只是一般性地),最后得出了和OP提出的方案非常相似的解决方案。我将发布我最终使用的解决方案。
但首先,我将直接回答OP的三个问题:
问题:‘这种方法是否有任何缺点?’
答案:提出的解决方案确实有一些缺点,主要是易于库污染或在文件处于其“Windows符号链接”状态时意外添加重复文件。 (有关下面“限制”部分的更多信息。)
问题:“这个post-checkout脚本能实现吗? 即我是否可以递归找到git创建的虚拟“符号链接”文件?“
答案:是的,post-checkout脚本是可以实现的! 可能不是直接post-
git checkout
步骤,但下面的解决方案已经满足了我的需求,因此实际的post-checkout脚本是不必要的。
问题:“有没有人已经开发了这样的脚本?”
答案:是的!
解决方案:
我们的开发人员的情况与OP的情况非常相似:Windows和类Unix主机,具有许多git符号链接的库和子模块,并且在Windows主机上智能处理这些符号链接的发布版本的MsysGit尚未得到本地支持(但即将推出)。
感谢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
\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 )" 用法:使用
git add-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硬链接+junctions替换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.
用法:
git rm-symlinks [symlink] [symlink] [...]
此别名可以逐个删除git符号链接,也可以一次性全部删除。符号链接将被替换为NTFS硬链接(对于文件)或NTFS连接(对于目录)。相对于“真正的”NTFS符号链接,使用硬链接+junctions的好处在于不需要提升的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'
使用:
git checkout-symlinks [symlink] [symlink] [...]
,它会撤消git rm-symlinks
,有效地将存储库恢复到其自然状态(除了您的更改,应保持不变)。并且对于子模块:
git submodule foreach --recursive git checkout-symlinks
-
限制:
-
在其路径中具有空格的目录/文件/符号链接应该可以工作。但是制表符或换行符?您自己看着办...(我的意思是:不要这样做,因为它不会工作。)
-
如果您或其他人在执行像
git add -A
这样的可能会对整个存储库产生广泛影响的事情之前忘记了git checkout-symlinks
,本地存储库可能会进入受污染的状态。使用我们之前的"示例存储库":
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
..." 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://technet.microsoft.com/en-us/library/cc753194
最近更新日期:2019-03-13
- POSIX兼容性(好吧,除了那些
mklink
调用以外)-没有更多的Bashisms! - 支持带空格的目录和文件。
- 零和非零退出状态码(用于正确/失败的请求指令的通信)现在得到正确保留/返回。
add-symlink
别名现在更像ln(1),可以从仓库的任何目录中使用,而不仅仅是仓库的根目录。rm-symlink
别名(单数)已被rm-symlinks
别名(复数)取代,后者现在接受多个参数(或根本没有参数,可以找到整个存储库中的所有符号链接,与以前相同),以有选择地将git符号链接转换为NTFS硬链接+连接点。checkout-symlinks
别名也已更新,以接受多个参数(或无参数(== everything))来有选择地扭转上述变换。
最后的说明:虽然我确实测试了使用Bash 3.2(甚至是3.1)加载和运行这些别名的过程,但是对于那些可能因为任何原因而仍然停留在这样古老版本上的人,要知道这些版本是以其解析器错误闻名的。如果您在尝试安装其中任何一个别名时遇到问题,您应该首先考虑升级您的shell(对于Bash,请使用CTRL+ X,CTRL + V检查版本)。或者,如果您正在尝试将它们粘贴到终端仿真器中进行安装,则可以将它们粘贴到文件中并对其进行源化,例如
. ./git-win-symlinks.sh
要求直接翻译并保留原文中的HTML标签和字体格式。