Windows下的Git符号链接

11 浏览
0 Comments

Windows下的Git符号链接

我们的开发人员使用混合了Windows和基于Unix的操作系统。因此,在Unix机器上创建的符号链接对于Windows开发人员来说成为了一个问题。在Windows (MSysGit)中,符号链接会被转换为一个文本文件,其中包含指向该文件的路径。相反,我想将符号链接转换为实际的Windows符号链接。

我对此问题的(更新的)解决方法是:

  • 编写一个后检出脚本,递归查找\"符号链接\"文本文件。
  • 用与虚拟 \"符号链接\" 相同的名称和扩展名,用Windows符号链接 (使用mklink) 替换它们
  • 通过向 .git/info/exclude 文件添加条目来忽略这些Windows符号链接

我还没有实现这个,但我相信这是解决这个问题的可靠方法。

  1. 您认为这种方法有什么缺点吗?
  2. 这个后检出脚本可行吗?也就是说,我能否递归查找Git创建的虚拟 \"symlink\" 文件?
admin 更改状态以发布 2023年5月24日
0
0 Comments

2020+ TL;DR Answer

  1. 在Windows 10/11中启用"开发人员模式"——给予mklink权限
  2. 确保在git中启用符号链接,其中至少有一种
    • 系统设置:在安装msysgit时选中复选框
    • 全局设置git config --global core.symlinks true
    • 本地设置git config core.symlinks true

请注意,在Windows上,git对符号链接的支持相对较新。
仍然有一些错误会影响某些git客户端。
特别是,由于libgit2中的漏洞,与相对(..)路径的符号链接在某些程序中被破坏。
例如,GitKraken就受到了影响,因为他们正在等待nodegitlibgit2v0.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将清除"本地"设置,允许更高级别的设置生效。

0
0 Comments

更新说明

对于大多数在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符号链接。

  1. 在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"
    

  2. 使用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
    

    但是,对于像这样的每个激进的行动,拥有一个可以撤消它的选择是很好的...

  3. 在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
    

  4. 限制:

    • 在其路径中具有空格的目录/文件/符号链接应该可以工作。但是制表符或换行符?您自己看着办...(我的意思是:不要这样做,因为它不会工作。)

    • 如果您或其他人在执行像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标签和字体格式。

0