Git在Windows中的符号链接

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

我必须的(更新)解决方案是:

  • 编写一个后签出脚本,递归地查找“符号链接”;文本文件。
  • 将它们替换为Windows符号链接(使用mklink),具有与dummy "符号链接"相同的名称和扩展;
  • 通过在文件. /信息/排除中添加一个条目来忽略这些Windows符号链接

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

  1. 如果有的话,你认为这种方法有什么缺点?
  2. 这个后签出脚本是可实现的吗?也就是说,我能否递归地找到虚拟的“symlink”;Git创建的文件?
152534 次浏览

你可以通过查找模式为120000的文件来找到符号链接,可能使用以下命令:

git ls-files -s | awk '/120000/{print $4}'

一旦你替换了链接,我建议用git update-index --assume-unchanged标记它们为不变,而不是在.git/info/exclude中列出它们。

它应该在MSysGit中实现,但有两个缺点:

  • 符号链接只在Windows Vista及以后版本中可用(在2011年不应该是问题,但它是…),因为旧版本只支持目录连接
  • 微软认为符号链接存在安全风险,因此默认情况下只有管理员可以创建符号链接。您需要提升Git进程的特权,或者使用fstool在您工作的每台机器上更改此行为。

我做了一个快速搜索,这方面的工作正在积极进行;参见问题224

我建议您不要在存储库中使用符号链接。将实际内容存储在存储库中,然后在存储库外放置指向内容的符号链接。

因此,假设您正在使用一个存储库来比较在类unix系统上托管站点与在Windows上托管站点。将内容存储在你的存储库中,比如/httpRepoContentc:\httpRepoContent,这是通过Git、SVN等同步的文件夹。

然后,将你的web服务器的内容文件夹(/var/wwwc:\program files\web server\www{名称并不重要,如果你必须编辑})替换为指向你的存储库中的内容的符号链接。web服务器将看到内容实际上是在“正确”的位置,但你可以使用你的源代码控制。

但是,如果您需要在存储库中使用符号链接,您将需要研究一些类似于前/后提交脚本的东西。我知道您可以使用它们来做一些事情,例如通过格式化程序解析代码文件,因此应该可以在平台之间转换符号链接。

如果有人知道一个好地方可以学习如何为常见的源代码控制、SVN、Git和MG执行这些脚本,那么请添加评论。

请注意更新

对于大多数Windows开发人员来说,符号链接和git在Windows上的挣扎以及与*nix系统共享回购的问题,这个主题是一个解决的问题——一旦你更新了你对mklink的Windows理解并打开开发人员模式。

在深入下面的git hacks讨论之前,请先查看这个更现代的答案

老系统:

我曾经问过这个完全相同的问题(不是在这里,只是在一般情况下),最后得出了一个与OP的命题非常相似的解决方案。 我会发布我最终使用的解决方案。

但首先我将直接回答OP的3个问题:

问:“如果有的话,你认为这种方法有什么缺点?”

答:提议的解决方案确实有一些缺点,主要是增加了存储库污染的可能性,或者在“Windows符号链接”中不小心添加了重复的文件。州。(详情见“限制”;下面)。

问:“这个后检出脚本是可行的吗?”也就是说,我是否可以递归地找到虚拟的“symlink”;git创建的文件?

答:是的,后检出脚本是可实现的!也许不是字面的后-git checkout步骤,但下面的解决方案已经很好地满足了我的需求,字面的后签出脚本是不必要的。

问:“有人写过这样的剧本吗?”

答:是的!

解决方案:

我们的开发人员和OP的情况差不多:混合了Windows和类unix的主机、带有许多git符号链接的存储库和子模块,并且在MsysGit的发布版本中(目前)还没有本地支持在Windows主机上智能处理这些符号链接。

感谢Josh Lee指出git使用特殊文件模式120000提交符号链接。有了这些信息,就可以添加一些git别名,以便在Windows主机上创建和操作git符号链接。

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

    用法: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"
    
  2. < p > 用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.
    

    用法:

     git rm-symlinks [symlink] [symlink] [...]
    

    这个别名可以一个接一个地删除git符号链接或一次性删除所有符号链接。符号链接将被NTFS硬链接(对于文件)或NTFS连接(对于目录)所取代。使用硬链接+连接而不是“true”的好处NTFS符号链接是为了创建提升的UAC权限,不需要它们。

    要从子模块中移除符号链接,只需使用git内置的迭代支持:

     git submodule foreach --recursive git rm-symlinks
    

    但是,对于每一个像这样激烈的行动,有一个逆转是很好的……

  3. < p > 在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. < p > 限制:

    • 路径中有空格的目录/文件/符号链接应该工作。但是制表符和换行符呢?YMMV…(我的意思是:不要这样做,因为它不会工作。)

    • 如果你自己或其他人在做一些可能造成广泛后果的事情(如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 <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://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检查版本)。或者,如果你试图通过将它们粘贴到终端模拟器中来安装它们,你可能会更幸运地将它们粘贴到一个文件中并将其源化,例如as

. ./git-win-symlinks.sh

好运!

下面是一个批处理脚本,用于转换存储库中的符号链接,仅用于文件,基于Josh Lee的回答https://gist.github.com/Quazistax/8daf09080bf54b4c7641是一个带有额外管理员权限检查的脚本。

@echo off
pushd "%~dp0"
setlocal EnableDelayedExpansion


for /f "tokens=3,*" %%e in ('git ls-files -s ^| findstr /R /C:"^120000"') do (
call :processFirstLine %%f
)
REM pause
goto :eof


:processFirstLine
@echo.
@echo FILE:    %1


dir "%~f1" | find "<SYMLINK>" >NUL && (
@echo FILE already is a symlink
goto :eof
)


for /f "usebackq tokens=*" %%l in ("%~f1") do (
@echo LINK TO: %%l


del "%~f1"
if not !ERRORLEVEL! == 0 (
@echo FAILED: del
goto :eof
)


setlocal
call :expandRelative linkto "%1" "%%l"
mklink "%~f1" "!linkto!"
endlocal
if not !ERRORLEVEL! == 0 (
@echo FAILED: mklink
@echo reverting deletion...
git checkout -- "%~f1"
goto :eof
)


git update-index --assume-unchanged "%1"
if not !ERRORLEVEL! == 0 (
@echo FAILED: git update-index --assume-unchanged
goto :eof
)
@echo SUCCESS
goto :eof
)
goto :eof


:: param1 = result variable
:: param2 = reference path from which relative will be resolved
:: param3 = relative path
:expandRelative
pushd .
cd "%~dp2"
set %1=%~f3
popd
goto :eof

对于那些在Windows VistaWindows 7或更高版本上使用Cygwin的人来说,本机git命令可以创建"Windows应用程序(如Android工作室)可以识别的符号链接。你只需要将环境变量CYGWIN设置为包含winsymlinks:nativewinsymlinks:nativestrict:

export CYGWIN="$CYGWIN winsymlinks:native"

这样做的缺点(一个重要的缺点)是Cygwin shell必须“以管理员身份运行”。这样它才能拥有创建那些符号链接所需的操作系统权限。不过,一旦创建了它们,就不需要特殊的权限来使用它们。只要它们没有在存储库中被另一个开发人员更改,git随后就可以在正常的用户权限下正常运行。

就我个人而言,我将这个只有用于由Windows应用程序(即非cygwin)导航的符号链接,因为这增加了难度。

有关此选项的更多信息,请参阅堆栈溢出问题: Windows 7下如何与Cygwin做符号链接 .

我一直在文档根目录和Git存储库目录之间使用符号链接。我喜欢把它们分开。在Windows上,我使用mklink / j选项。这个连接似乎可以让Git正常运行:

>mklink /j <location(path) of link> <source of link>

例如:

>mklink /j c:\gitRepos\Posts C:\Bitnami\wamp\apache2\htdocs\Posts

我一直在寻找一种简单的解决方案来处理Windows上的Unix符号链接。非常感谢您在之前的回答中提供Git别名。

可以对rm-符号链接进行一个小小的优化,以便在别名意外第二次运行时它不会删除目标文件夹中的文件。请观察循环中的新如果条件,以确保在逻辑运行之前文件不是到目录的链接。

git config --global alias.rm-symlinks '!__git_rm_symlinks(){
for symlink in $(git ls-files -s | egrep "^120000" | cut -f2); do
*if [ -d "$symlink" ]; then
continue
fi*
git rm-symlink "$symlink"
git update-index --assume-unchanged "$symlink"
done
}; __git_rm_symlinksenter

Git SCM的最新版本(在2.11.1版本上测试)允许启用符号链接。但是你必须用符号链接再次克隆储存库git clone -c core.symlinks=true <URL>。该命令需要使用管理员权限执行。也可以在Windows上使用mklink创建符号链接。

查看wiki

Enter image description here

我们使用的一个简单技巧是连续调用git add --all两次。

例如,我们的Windows 7提交脚本调用:

git add --all
git add --all

第一个添加将链接视为文本,并添加用于删除的文件夹。

第二个添加正确地遍历链接,并通过恢复文件来撤销删除操作。

它没有其他一些提议的解决方案那么优雅,但它是对添加了符号链接的一些遗留环境的简单修复。

因此,自从发布了很多这些答案以来,Git已经发生了变化,下面是正确的指令,以使符号链接在Windows中正确工作:

2018年8月


1. 确保Git安装时带有符号链接支持

During install of Git on Windows . sh /> . sh /> . sh

2. 告诉Bash创建硬链接而不是符号链接

(git文件夹)/ etc / bash.bashrc

添加到底部- MSYS=winsymlinks:nativestrict

3.将Git配置设置为使用符号链接

git config core.symlinks true

git clone -c core.symlinks=true <URL>

注意:我已经尝试将其添加到全局Git配置中,但目前它对我来说并不适用,所以我建议将其添加到每个存储库中…

4. 提取存储库

注意:除非您在最新版本的Windows 10中启用了开发人员模式,否则您需要以管理员身份运行Bash来创建符号链接

5. 重置所有符号链接(可选)

如果您有一个现有的存储库,或者正在使用子模块,您可能会发现符号链接没有被正确地创建,因此可以运行这些命令来刷新存储库中的所有符号链接。

find -type l -delete
git reset --hard

注意:这将重置自上次提交以来的所有更改,所以请确保您已经先提交了

2020+ TL;DR答案

  1. 启用“开发者模式”;在Windows 10/11中——赋予mklink权限
  2. 确保在git中启用符号链接(至少)其中之一
    • 系统设置:安装msysgit时选中复选框
    • 全局设置: git config --global core.symlinks true
    • 本地设置: git config core.symlinks true

小心, git在Windows上对符号链接的支持是相对新的。 有一些错误仍然影响一些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将显示设置的级别(又名“scope"),其中保存它的配置文件(又名“origin"),以及设置的当前值。很可能是“local”;配置正在覆盖“global”;煤气报修或“;设置。git config --unset core.symlinks将清除"local"设置,使更高级别的设置生效。

我刚刚尝试了Git 2.30.0(发布2020-12-28)。

这是一个完整的答案,但仍然有一些有用的花絮。(你可以随意做出自己的回答。)

Git Wiki入口

在安装Git for Windows时,有一个文档链接

Enter image description here

这个链接把你带到这里:https://github.com/git-for-windows/git/wiki/Symbolic-Links——这是一个相当长的讨论。

供你参考:至少有三种“链接”。为了强调这个维基词条的一个重要方面:我不知道这个,但是有几种方法都是“kind of”;表面上是符号链接,但在技术层面上是非常不同的:

  • git bash's "ln -s" 这只是副本的东西。哦,男孩。这对我来说是意外的。 (供参考:平原Cygwin做做这个。Mobaxterm 做到了这一点。相反,它们都创建了stat命令实际上识别为“符号链接”的东西
  • cmd.exe的内置"mklink"/ D"参数 它创建一个目录符号链接。李(见微软文档) < / >
  • cmd.exe的内置"mklink"/ J"参数。 它创建了一个目录连接,又名软链接,又名重解析点。李(参见微软文档)。< / >

发行说明条目

另外,符号链接在发行说明中不断弹出。截至2.30.0,这里仍然被列为“已知问题”:

在1703年以前的Windows 10上,或者当开发人员模式被关闭时,克隆带有符号链接的存储库时需要特殊权限,因此默认情况下禁用对符号链接的支持。使用git clone -c core.symlinks=true <URL>来启用它,详见在这里

下面是一个PowerShell脚本,用Windows替换Unix符号链接。

# This fixes permission denied errors you might get when
# there are Git symbolic links being used on repositories that
# you share in both POSIX (usually the host) and Windows (VM).
#
# This is not an issue if you are checking out the same
# repository separately in each platform. This is only an issue
# when it's the same working set (AKA make a change without
# committing on OS X, go to Windows VM and Git status would show
# you that change).
#
# Based on this answer on Stack Overflow: http://stackoverflow.com/a/5930443/18475
#
# No warranties. Good luck.
#
# NOTE: It must be run in elevated PowerShell


$ROOT = $PWD


$symlinks = &git ls-files -s | gawk '/120000/{print $4}'
foreach ($symlink in $symlinks) {
$content = &Get-Content $symlink
$content = $content.Replace("/", "\")
$filename = $symlink.Split("/")[-1]
cd (dirname $symlink)
rm $filename
echo Linking $content -> $filename
New-Item -ItemType SymbolicLink -Path $filename -Target $content
&git update-index --assume-unchanged $symlink
cd $ROOT
}