在Windows中的Git符号链接
问题是:我们的开发人员使用基于Windows和Unix的操作系统。 因此,在Unix机器上创build的符号链接成为Windows开发者的一个问题。 在Windows(msysgit)中,符号链接被转换为一个文本文件,并带有它指向的文件的path。 相反,我想将符号链接转换为实际的Windows符号链接。
( 更新 )的解决scheme,我必须这样做:
- 写一个post-checkout脚本,recursion地查找“符号链接”文本文件。
- 将它们replace为具有相同名称和扩展名的windows符号链接(使用mklink)作为虚拟“符号链接”
- 通过将条目添加到.git / info / exclude中,忽略这些窗口符号链接
我没有实现这一点,但我相信这是一个坚实的方法来解决这个问题。
我给你的问题是:
- 你认为这种方法有什么缺点?
- 这个后结帐脚本甚至可以实现吗? 即我可以recursion地找出假的“符号链接”文件git创build?
- 有没有人已经在这样的脚本? =)
谢谢一堆!
您可以通过查找具有120000
模式的文件来find符号链接,可能使用以下命令:
git ls-files -s | awk '/120000/{print $4}'
一旦你更换了链接,我build议使用git update-index --assume-unchanged
标记它们,而不是将它们列在.git/info/exclude
。
我刚才问了一个完全相同的问题(不是在这里,只是在一般情况下),并最终提出了一个非常类似的解决scheme,OP的主张。 首先,我将直接回答问题1 2和3,然后我将发布最终使用的解决scheme。
- 提出的解决scheme确实存在一些缺点,主要是由于存储库污染的可能性增加,或者意外地在“Windows符号链接”状态下添加重复文件。 (更多关于这个在下面的“限制”。)
- 是的,后付款脚本是可以实现的! 也许不是一个字面上的后
git checkout
步骤,但下面的解决scheme已经足够好,以至于不需要字面的结账后脚本。 - 是!
解决scheme:
我们的开发人员与OP的情况大致相同:基于Windows和Unix的主机,存储库和子模块与许多git符号链接混合在一起,并且在MsysGit的发行版本中没有本地支持(用于智能地处理Windows主机上的这些符号链接。
感谢Josh Lee指出git使用特殊文件模式120000
提交符号链接的事实。 有了这些信息,可以添加一些git别名,以便在Windows主机上创build和操作git符号链接。
-
在Windows上创buildgit符号链接
更新2014-11-12 (见下文)
git config --global alias.add-symlink '!__git_add_symlink(){ argv=($@) argc=${#argv[@]} # Look for options options=(" -h") o_help="false" case "${argv[@]}" in *" -h"*) o_help="true" ;; esac if [ "$o_help" == "true" -o "$argc" -lt "2" ]; then echo "\ Usage: git add-symlink <src> <dst> * <src> is a RELATIVE PATH, respective to <dst>. * <dst> is a RELATIVE PATH, respective to the repository'\''s root dir. * Command must be run from the repository'\''s root dir." return 0 fi src_arg=${argv[0]} dst_arg=${argv[1]} if [ ! -e "$dst_arg" ]; then echo "ERROR: Target $dst_arg does not exist; not creating invalid symlink." return 1 fi hash=$(echo -n "$src_arg" | git hash-object -w --stdin) git update-index --add --cacheinfo 120000 "$hash" "$dst_arg" git checkout -- "$dst_arg" }; __git_add_symlink "$@"'
用法:
git add-symlink <src> <dst>
,其中<src>
是相对于要链接到的文件或目录的当前位置的相对引用 (相对于<dst>
),<dst>
是相对引用 (相对于存储库的根)到链接的所需目的地。例如,存储库树:
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上创build,如下所示:
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硬链接+连接replacegit符号链接
git config --global alias.rm-symlink '!__git_rm_symlink(){ git checkout -- "$1" link=$(echo "$1") POS=$'\''/'\'' DOS=$'\''\\\\'\'' doslink=${link//$POS/$DOS} dest=$(dirname "$link")/$(cat "$link") dosdest=${dest//$POS/$DOS} if [ -f "$dest" ]; then rm -f "$link" cmd //C mklink //H "$doslink" "$dosdest" elif [ -d "$dest" ]; then rm -f "$link" cmd //C mklink //J "$doslink" "$dosdest" else echo "ERROR: Something went wrong when processing $1 . . ." echo " $dest may not actually exist as a valid target." fi }; __git_rm_symlink "$1"' git config --global alias.rm-symlinks '!__git_rm_symlinks(){ for symlink in $(git ls-files -s | egrep "^120000" | cut -f2); do git rm-symlink "$symlink" git update-index --assume-unchanged "$symlink" done }; __git_rm_symlinks'
用法:
git rm-symlink dir/foo/bar/lnk_file git rm-symlink lnk_bar git update-index --assume-unchanged dir/foo/bar/lnk_file git update-index --assume-unchanged lnk_bar
这将逐个删除git符号链接,将其replace为NTFS硬链接(在文件的情况下)或NTFS联结(在目录的情况下)。 使用硬连接+连接而不是“真正的”NTFS符号链接的好处是不需要提升UAC权限以便创build它们。 最后,在您自己的闲暇时间,您可以select使用
git update-index
修改(或不修改)符号链接。为了方便起见,你也可以运行:
git rm-symlinks
这将删除当前存储库中的所有git符号链接,根据需要将其replace为硬链接+连接,并自动标记
git status
忽略的更改。要从子模块中删除符号链接,只需使用git的内置支持来遍历它们:
git submodule foreach --recursive git rm-symlinks
但是,对于这样的每一个激烈的行动,逆转是很好的有…
-
在Windows上恢复git符号链接
git config --global alias.checkout-symlinks '!__git_checkout_symlinks(){ POS=$'\''/'\'' DOS=$'\''\\\\'\'' for symlink in $(git ls-files -s | egrep "^120000" | cut -f2); do git update-index --no-assume-unchanged "$symlink" dossymlink=${symlink//$POS/$DOS} cmd //C rmdir //Q "$dossymlink" 2>/dev/null git checkout -- "$symlink" echo "Restored git symlink $symlink <<===>> $(cat $symlink)" done }; __git_checkout_symlinks'
用法:
git checkout-symlinks
,它取消了git rm-symlinks
,有效地将存储库恢复到它的自然状态(除了你的改变,它应该保持不变)。对于子模块:
git submodule foreach --recursive git checkout-symlinks
-
限制:
- 只能从回购的根,否则,怪事发生…
- input其中一个别名时,基于标签的自动完成function会中断
-
如果人们在做一些像
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 #
哎呦…
因此,将这些别名作为构build项目之前和之后为Windows用户执行的步骤,而不是在结帐之后或在推送之前,很好。 但每种情况都不一样 这些别名对我来说已经足够有用,真正的结账后解决scheme并不是必需的。
希望有所帮助!
参考文献:
http://git-scm.com/book/en/Git-Internals-Git-Objects
http://technet.microsoft.com/en-us/library/cc753194
更新2014年11月12日:因为我个人只使用上面的rm-symlinks
和checkout-symlinks
别名,我设法忽略了add-symlink
别名中相当讨厌的错误。 以前, -n
没有被传递给echo
语句,这个语句负责创buildgit符号链接文件,这个文件稍后将作为add-symlink
操作的一部分添加到暂存区域中。 这意味着一个尾随的换行符(Windows主机上的0x0D 0x0A
)被添加到使用add-symlink
创build的所有git符号add-symlink
。 虽然这些git符号链接仍然可以在Windows主机上使用rm-symlinks
进行“移除”,但是如果他们曾经被委托给公共仓库,然后克隆在一个真正的基于posix的系统上,那么这些链接总是会在另一个仓库中出现边 。 此问题已得到解决, add-symlink
现在应按预期工作。
最新版本的git scm (testet 2.11.1)允许启用符号链接。 但是你必须再次使用符号链接克隆版本库git clone -c core.symlinks=true <URL>
。 您需要使用pipe理员权限运行此命令。 也可以使用mklink在Windows上创build符号链接。 检查维基 。
它应该在msysgit中实现,但有两个缺点:
- 符号链接仅在Windows Vista和更高版本中可用(2011年不应该是问题,但它是…),因为旧版本只支持目录连接。
- (大)微软认为符号链接是一个安全风险,所以只有pipe理员可以在默认情况下创build它们。 你需要提升git进程的权限,或者使用fstool在你工作的每台机器上改变这个行为。
我做了一个快速search,正在积极完成这个工作,见问题224 。
我build议你不要在回购协议中使用符号链接。 将实际内容存储在回购库中,然后将符号链接放在指向内容的回购站点旁边。
因此,可以说,你正在使用回购'比较托pipe你的网站* nix与托pipe胜利。 将内容存储在您的repo中,可以说/httpRepoContent
和c:\httpRepoContent
这是通过GIT,SVN等同步的文件夹。
然后,使用符号链接replace您的Web服务器的内容文件夹( /var/www
和c:\program files\web server\www
{names does not matter,如果您必须编辑} ”。 networking服务器会在“正确”的位置看到内容,但是您可以使用源代码pipe理。
但是,如果您需要在回购中使用符号链接,则需要查看某种前/后提交脚本。 我知道你可以用它来做一些事情,比如通过一个格式化程序parsing代码文件,所以应该可以在不同的平台之间转换符号链接。
如果任何人知道一个好的地方学习如何做这些脚本的通用源代码控制,SVN GIT MG,那么请添加评论。
对于那些在Vista,Win7或更高版本上使用CygWin的人来说,native git
命令可以创buildWindows应用程序(如Android Studio)识别的“正确”符号链接。 您只需要将CYGWIN
环境variables设置为包含winsymlinks:native
或winsymlinks:nativestrict
就像这样:
export CYGWIN="$CYGWIN winsymlinks:native"
这个方面的缺点是CygWin shell必须“以pipe理员身份运行”才能拥有创build这些符号链接所需的操作系统权限。 一旦他们被创build,但是,不需要特殊的权限来使用它们。 只要他们没有在另一个开发人员的存储库中更改, git
此后运行良好,具有正常的用户权限。
就个人而言,我只使用这个由Windows应用程序(即非CygWin)导航的符号链接,因为这增加了困难。
有关此选项的更多信息,请参阅此问题: 如何在Windows 7中使用cygwin进行符号链接
这是一个批处理脚本,用于根据Josh Lee的回答转换库中的符号链接,仅用于文件。 有一些额外的pipe理员权限检查脚本是在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
我正在寻找一个简单的解决scheme来处理Windows上的unix符号链接。 非常感谢上面的Git别名。 对rm符号链接可以做一些小的优化,以防止在别名意外运行的情况下,目标文件夹中的文件不会被删除。 请注意循环中的新if条件,以确保文件在逻辑运行之前不再是指向目录的链接。
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 repo目录之间一直使用sym链接。 我喜欢把他们分开。 在Windows上,我使用mklink / j选项。 交界处似乎让git正常行为:
>mklink /j <location(path) of link> <source of link>
例如:
>mklink /jc:\gitRepos\Posts C:\Bitnami\wamp\apache2\htdocs\Posts
我们使用的一个简单的技巧就是连续调用两次git add -all
。
例如,我们的Windows 7提交脚本调用:
>git add -all >git add -all
第一个添加将该链接视为文本并添加要删除的文件夹。
第二个添加正确地遍历链接,并通过恢复文件来解除删除。
它比其他一些build议的解决scheme不那么优雅,但它是一个简单的修复,我们的一些遗留环境,增加了符号链接。