另请参阅此相关问题,它有最新答案:https://stackoverflow.com/a/28262104/7918.
如何推送并自动更新我的非裸远程git存储库的工作副本?
在git之前,人们会说"这是不安全的",请让我解释为什么在我的情况下这会起作用.我有储存库foo在机器A和B.A碰巧是我的本地机器,B在超级计算网格上.项目通常是在A(通过一些测试)开发的,然后推送到B更多测试并向网格提交作业.两个存储库都有工作目录.库上都A和B是私有并且连接到我的用户帐户.
现在git flow就是这样的:
A ABB我宁愿省略第3步和第4步.那就是:我希望在推送时更新远程非裸存储库,如果更改A可以自动安全地合并(即:当合并是快进时,工作目录)在B干净).
第一个解决方案是删除存储库B并仅用于rsync同步代码,但这是不可取的,因为有时我会直接进行一些更改B,并且不希望轻易覆盖这些更改.
第二个灵魂就是安装这个补丁,它甚至被合并到mysysgit(但不是git).此补丁updateInstead为receive.denyCurrentBranchgit选项增加了价值.这是可行的,但我宁愿不在许多机器上修补git.
第三种解决方案(从这里取)将涉及有三个Git仓库:A,B',B.哪里B'是裸的,并使用钩子来同步A和B.这非常简单,但我猜有三个存储库可能会增加整个系统的脆弱性.
最后的解决方案将是优先的:即使用存储库B上的钩子自动将推送的更改合并到工作副本.每个人都说这样做是直截了当的,但我想我对git内部的了解是为了修补一些合理的东西.我在这方面做了一些工作,但没有任何实际工作.
我在找什么:
困难的部分是不是有机器B更新,而是精确定义如何你想B更新.例如:
B,检出的分支是test3,我推到它的分支test2怎么办?机器B的工作副本是否应该更改?B,已签出是分支deploy,但有人(甚至我)在B积极工作的编辑,树中的文件,我推B的deploy.它应该消除我现在正在做的事情吗?B,检出的分支是deploy,但我在那里进行了一些更改并检查它们,现在我进行更改A并强制推送到deploy,如果这是一个真正的合并将会出现合并冲突?(事实上,merge并不适用于推送,我将在下面详细介绍.)这些问题很少有正确答案.这就是为什么首先 git push有receive.denyCurrentBranch选项的原因:如果假设上面第一个问题的答案是no(通常是 no),那么只更新当前检出的分支会引发剩下的问题.如果我们否认能够做到这一点,为什么那么,所有这些问题都消失了,我们不必仔细思考答案!:-)
有一种简单的方法来回避所有这一切,即在接收机器上有一个裸存储库,然后.git在同一台机器上的其他地方你可能称之为"裸工作树"(其中没有目录).那样的话,首先没有"当前分支"的直接概念(尽管它从后门偷偷溜回来,就像它一样).
在git中存在一个基本的不对称性,因为当你git fetch从远程访问时,你会从它们获取提交和其他对象,将它们放入存储库中,然后更新远程分支.之后git fetch origin,你可以有一个新的origin/master,但你不要有一个新的master.这为您提供了一个停止点,一个中间步骤,在此期间您可以暂停,休息一下,查看刚刚进入的内容,并决定是否以及如何修改或合并更改.
git push c0ffee3:master但是,当你到远程时,你将你的提交和其他对象发送出来,他们(远程)将对象填充到他们的存储库中,然后他们将他们的分支更新master为你的ID(也就是他们的提交)ID c0ffee3.评估没有停顿; 没有机会改变或合并; 你用你的替换了他们.就此而言,你根本不必是你的.任何合适的存储库对象 - 即任何提交ID或任何带注释的标签ID - 如果你强制推送就足够了(前提是没有花哨的远程钩子来拒绝你).masterc0ffee3c0ffee3 master
尽管如此,让我们回到"裸工作树"的想法.在这里,在机器上B-let停止现在调用这个"遥控器",并且只是说"在这里B" - 我们将有一个裸存储库,这样我们就可以接受传入推送而不管git可能认为是"当前分支".
接下来,我们将回答"假设"问题:*每当我们收到某些分支的新内容时,无论我们在做什么,我们都会完全吹走我们之前的任何东西.有了它,并根据我们现在认为在该分支机构或那些分支机构中的新内容替换它."
(这真的是正确的答案吗?如果我们正处于编译或测试阶段,那该怎么办?嗯,我们声称这是正确的答案;继续.)
B然后,我们在这里做的是--bare使用钩子设置我们的存储库 - 这可以是更新后的钩子或后接收钩子 - 在更新某些分支之后运行.两个"post"钩子每次接收只运行一次(基本上每次一次push),并给出所有更新的列表.更新后的挂钩将所有更新的ref-name作为参数获取,而post-receive挂钩在stdin上获取所有更新的ref,包括旧的和新的SHA-1.
(这里的复杂性在于push,我可以更新多个分支和/或标记.例如git push c0ffee3:master badf00d:refs/tags/new-tag,我可以告诉您更新master分支以使其指向提交c0ffee3,并创建指向对象的标记badf00d.在这里,您的post-update挂钩会得到refs/heads/master refs/tags/new-tag,而你的后收到钩将能够读取两条线,大概oldsha1 c0ffee3 refs/heads/master,然后0000000 badf00d refs/tags/new-tag,从标准输入,这些都将是当然的整整40个字符的SHA-1).
因为我们已经决定只吹掉"裸工作树",所以我们在这个钩子中所要做的就是找出一个有趣的分支是否已被更新.假设我们特别关注(并且仅关注)一个名为分支的分支develop,即ref-name refs/heads/develop.然后在作为shell脚本编写的post-receive钩子中,我们的stdin扫描循环可能如下所示:
do_update=false
while read oldsha newsha ref; do
[ $ref = refs/heads/develop ] && do_update=true
done
Run Code Online (Sandbox Code Playgroud)
在更新后的钩子中,我们只检查参数:
do_update=false
for ref do
[ $ref = refs/heads/develop ] && do_update=true
done
Run Code Online (Sandbox Code Playgroud)
无论哪种方式,如果我们看到有趣的分支发生了变化,我们现在想要进行吹离和重建步骤:
blow_away_and_rebuild()
{
local target_dir=$1 branch=$2
rm -rf $target_dir
mkdir $target_dir
GIT_WORK_TREE=$target_dir git checkout -f $target_dir
}
if $do_update; then
blow_away_and_rebuild /home/me/develop.dir develop
fi
exit 0 # succeed, regardless of actual success
Run Code Online (Sandbox Code Playgroud)
请注意,git checkout上面的步骤填充(删除并重新创建)"裸工作树",但也有设置"当前分支"的副作用(并与git的索引混淆).这就是"当前分支机构"即使我们拥有一个名义上裸露的存储库也能够潜入其中的方式.我们通常不需要这个rm -rf步骤,但如果你有两个不同的分支,你将以这种方式"部署",它会回避git使用的"单个当前分支=单个索引"模型,否则可能会留下旧文件.
这里的另一个技巧是,由于其中/home/me/develop.dir没有.git目录(因此"裸工作树"),我不会被愚弄进入它,检查分支,并开始编辑那里.当然,我仍然可以愚弄进入它并开始在那里工作,但至少我不会责怪git,如果突然我的所有工作都得到了rm -rf.:-)
感谢您的回答,我想我刚刚创建了 python 挂钩脚本,它可以满足我的需要。整个脚本有点太长了,无法在这里发布,所以这里是一个要点。我想它有很好的文档记录并且可以独立存在。
由于大多数人可能会自己动手,因此我花了一些时间来理解一些哥达式的东西:
uptate/ receive(进行验证)和post-update/post-receive更新工作目录。foo时将更新工作副本foo。Git 的人就是不喜欢它。或者您可以使用前面提到的两个钩子。cd工作副本目录,挂钩在内部执行GIT_DIR,因此您需要弄清楚从那里去哪里。如果您在git merge foo没有 cd'ing 到工作副本的情况下进行调用,它将不会更新(至少它在 git 上不起作用2.0.1)。