如何推送并自动更新我的非裸远程git存储库的工作副本?

时间:2014-08-08 16:24:19

标签: git

另请参阅此相关问题,它有最新答案:https://stackoverflow.com/a/28262104/7918

如何推送并自动更新非裸露远程git存储库的工作副本?

在git之前,人们会说“这是不安全的”,请让我解释为什么在我的情况下这会起作用。我在计算机fooA上有资源BA恰好是我的本地计算机,B位于超级计算网格上。项目通常在A(通过一些测试)开发,然后推送到B以进行更多测试并将作业提交到网格。两个存储库都有工作目录。 AB上的存储库都是私有的,并且与我的用户帐户绑定。

现在git flow就是这样:

  1. 提交A
  2. 按下A
  3. 抓取B
  4. 合并B
  5. 我宁愿省略第3步和第4步。那就是:如果来自A的更改可以自动安全,我希望在推送时更新远程非裸存储库合并(即:当合并是快进时,B上的工作目录是干净的)。

    首先解决方案是删除B上的存储库并使用rsync来同步代码,但这是不可取的,因为有时我会直接在B上进行一些更改,而不是希望这些更改能够轻松覆盖。

    第二个灵魂就是安装this patch,它甚至被合并到mysysgit(但不是git)。此修补程序将updateInstead值添加到receive.denyCurrentBranch git选项。这是可行的,但我宁愿不在许多机器上修补git。

    第三个解决方案(taken from here)将涉及三个git存储库:AB'BB'裸露的位置,并使用挂钩来同步AB。这非常简单,但我猜有三个存储库可能会增加整个系统的脆弱性。

    最后的解决方案将是优先的:即使用存储库B上的钩子自动将推送的更改合并到工作副本。每个人都说这样做是直截了当的,但我想我对git内部的了解是为了修补一些合理的东西。我在这方面做了一些工作,但没有任何实际工作。

    我在寻找:

    • 基于钩子的解决方案,可以在推送时自动合并远程非裸仓库。那不是:要求我重新编译git,并创建其他存储库。

4 个答案:

答案 0 :(得分:6)

困难的部分是让机器B更新,而是准确定义 你希望B更新的方式。例如:

  • 如果在计算机B上,签出的分支是test3,我会推送到其分支test2怎么办?机器B的工作副本是否应该更改?
  • 如果在计算机B上,签出的分支是deploy,但B上有人(甚至可能是我)正在主动编辑工作树文件,我推到B' deploy。它应该消除我现在正在做的事情吗?
  • 如果在计算机B上,检出的分支是deploy,但我在那里进行了一些更改并检查了它们,现在我对{{1和force-push到A如果这是真正的合并会有合并冲突吗? (事实上​​,deploy并不适用于推送,我将在下面详细介绍。)

这些问题很少有正确答案。那个为什么 merge首先有git push选项:如果上面第一个问题的答案被认为是receive.denyCurrentBranch(它通常 no),然后只更新当前签出的分支会引发剩下的问题。如果我们否认能够做到这一点,为什么那么,所有这些问题都消失了,我们不必仔细思考答案! : - )

有一种简单的方法来回避所有这一切,即在接收机器上有一个裸存储库,然后你可以称之为"裸工作树" (在其中没有no目录)在同一台机器上的其他位置。这样就没有"当前分支"的直接概念。首先(虽然它通过后门偷偷溜回来,但是)。

git中存在基本的不对称性,因为当你从远程执行.git时,你会从它们获取提交和其他对象,填充存储库中的对象,然后更新你的远程分支机构。在git fetch之后,您可能会有一个新的git fetch origin,但没有有一个新的origin/master。这为您提供了一个停止点,一个中间步骤,在此期间您可以暂停,休息一下,查看刚刚进入的内容,并决定是否以及如何修改或合并更改。

然而,当您master到远程时,您将提交和其他对象发送出去并且他们(远程)将对象填充到其存储库中,然后他们将其分支git push c0ffee3:master更新为您的提交(现在也是他们的提交),其ID为master。没有暂停评估;没有机会改变或合并;您已使用c0ffee3 替换他们的master。就此而言,您的c0ffee3根本不必您的 c0ffee3。任何合适的存储库对象 - 任何提交ID或任何带注释的标签ID - 如果你强制推送就足够了(前提是没有花哨的远程钩子来拒绝你)。

尽管如此,让我们回到"裸工作树"理念。在这里,在机器master上 - 让我们停止呼叫这个"遥控器"现在,只是说"在这里B"我们将有一个裸存储库,这样我们就可以接受传入推送,无论git认为是什么"当前分支"

接下来,我们将回答"如果"对此提出的问题:*每当我们收到某些分支机构的新内容时,无论我们在做什么之中,我们都会完全吹走我们之前的任何内容,并将其替换为基于我们现在认为属于该分支机构或那些分支机构的新内容。"

(这真的是正确的答案吗?如果我们正在编译或测试过程中会怎么样?好吧,我们声称这是正确的答案;继续。)

我们在B上执行的操作是设置我们的B存储库,其中包含一个钩子 - 这可以是更新后挂钩或后接收挂钩 - 在某些分支更新后运行。两者" post"钩子每次接收只运行一次(基本上每个--bare一次),并给出所有更新的列表。更新后的挂钩将所有更新的ref-name作为参数获取,而post-receive挂钩在stdin上获取所有更新的refs,包括旧的和新的SHA-1。

(这里的复杂性在于push,我可以更新多个分支和/或代码。例如,使用push,我可以告诉您更新git push c0ffee3:master badf00d:refs/tags/new-tag }分支使其指向提交master,并创建指向对象c0ffee3的标记。这里,您的更新后挂钩将获得badf00d,而您的post-receive挂钩将能够从stdin读取两行,大约refs/heads/master refs/tags/new-tag然后oldsha1 c0ffee3 refs/heads/master。当然,这些都是完整的40个字符的SHA-1。)

因为我们已经决定将我们的工作树吹走,所以我们在这个钩子中所要做的就是找出一个有趣的分支是否已被更新。让我们说我们特别关注(并且仅关注)名为0000000 badf00d refs/tags/new-tag的分支,即引用名称develop。然后在作为shell脚本编写的post-receive钩子中,我们的stdin扫描循环可能如下所示:

refs/heads/develop

在更新后的钩子中,我们只检查参数:

do_update=false
while read oldsha newsha ref; do
    [ $ref = refs/heads/develop ] && do_update=true
done

无论哪种方式,如果我们看到有趣的分支发生了变化,我们现在想要进行吹离和重建步骤:

do_update=false
for ref do
    [ $ref = refs/heads/develop ] && do_update=true
done

请注意,上面的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 步骤会填充(删除并重新创建)"裸工作树",但也会产生设置"当前分支&#34的副作用; (并与git的索引混淆)。这是"当前分支"即使我们有一个名义上裸露的存储库,他也设法潜入。我们通常不需要git checkout步骤,但如果您有两个不同的分支机构,则需要部署"部署"以这种方式,它回避了单一当前分支=单一指数"模型git使用,否则可能会留下旧文件。

这里的另一个技巧是,由于rm -rf内没有/home/me/develop.dir目录(因此"裸工作树"),我不会被愚弄进入它,检查一个分支,并开始在那里编辑。当然,我仍然可以愚弄进入它并开始在那里工作,但至少我不会责怪git如果突然我的所有工作得到.git - 编辑。 : - )

答案 1 :(得分:2)

您需要推送到服务器上的其他分支,因为您无法更新当前签出的分支,然后您可以使用post-receive hook。

将这些东西放在服务器上名为.git/hooks/post-update的文件中(它需要是可执行的):

#!/bin/bash
if [ "$1" == "refs/heads/automerge" ] ; then
    git stash
    git merge --ff-only automerge
fi
如果存在本地未提交的修改,

git stash将保存当前工作树的快照,以便您可以在以后需要时恢复它们(使用git stash applygit stash pop)。当你运行

git push origin yourbranch:automerge

在客户端上,服务器上的工作副本将被更新(仅当是快进时)到推送版本。您可以配置push,以便默认推送的分支为automerge

要检查是否有未提交的本地修改,您可以执行以下操作:

if [ `git status --porcelain|wc -l` == 0] ; then
    #status is clean
fi

答案 2 :(得分:2)

感谢您的回答,我想我刚刚创建了python hook脚本,可以满足我的需求。整个脚本有点太长了,无法在此发布,so here is a gist。我想它有很好的记录,并且可以独立存在。

由于大多数人可能会推出自己的钩子,所以我花了一些时间才能理解:

  • 你不能拥有能够合并和更新工作目录拒绝推送错误的钩子。您需要安装uptate / receive(进行验证)和post-update / post-receive更新工作目录。
  • 如果foo签出,那么你真的无法创建一个推送到分支foo将更新工作副本的钩子。 Git人只是不喜欢它。 或者你可以使用前面提到的两个钩子
  • 要合并你需要cd到工作副本目录,钩子在GIT_DIR内执行,所以你需要弄清楚去哪里。如果您在没有cd到工作副本的情况下调用git merge foo,它将不会更新(至少它不适用于git 2.0.1)。

答案 3 :(得分:0)

我印象深刻,但也被上述答案中的深思熟虑所吓倒。我最终只写了一个蹩脚的小 bash oneliner,它完成了我想要的大部分工作,假设我已经完成了本地提交:

git push origin master; ssh -t ec2-user@XXX.com 'cd /var/www/XXX.com/httpdocs; bash --login'

这让我进入了运行 git pull 的工作目录;我本可以将它添加到脚本中,但无法弄清楚如何让它在我的登录目录中查找我的 SSH 密钥。所以我仍然需要输入“git pull”然后“exit”,但它比以前好多了!