Git接收后部署在随机点停止工作

时间:2016-11-23 08:51:15

标签: git bash git-post-receive

我有一个git的post-receive hook设置,它根据分支检查dev / staging / production。出于某种原因,dev和staging没有问题。但生产不断破产。推送主分支后,尽管在最初设置后仍在工作,但更新无法检出到正确的位置。

#!/bin/bash
while read oldrev newrev refname
do
    branch=$(git rev-parse --symbolic --abbrev-ref $refname)
    if [ "master" == "$branch" ]; then
        GIT_WORK_TREE=/var/www/production git checkout -f $branch
    elif [ "staging" == "$branch" ]; then
        GIT_WORK_TREE=/var/www/staging git checkout -f $branch
    else
        GIT_WORK_TREE=/var/www/dev git checkout -f $branch
    fi
done

我尝试将主分支更改为名为production的分支,并遇到同样的问题。最初工作并在一段时间后停止,原因我无法解决。

if语句正常工作,因为在checkout语句下面添加touch命令时,会在正确的目录中成功创建一个文件。这也排除了权限,因为在这方面所有3个目录都相同。

如果有人有任何想法,或者可以看到可能导致此行为的事情,那就太棒了!

1 个答案:

答案 0 :(得分:0)

数十亿和数十亿 1 许多部署脚本中存在同样的错误。

问题是Git有一个索引。

更确切地说,Git需要每个工作树的索引。 2

裸存储库有 no 工作树,但是Git仍然有一个索引 - 在(1)索引中,在该裸存储库的文件index中找到。这意味着您可以使用GIT_WORK_TREE或等效项强制存在一(1)个工作树,并使用该索引将一个分支签出到该工作树中。

您的部署脚本与许多其他人一样,使用该索引检查三个不同的分支到三个不同的工作树。当Git相信索引并使用它来构建对假设为单一工作树的最小更改时,事情会出错,而您需要检查每个分支。您将生产分支编写到/var/www/production处的工作树;然后使用保存在(单个)索引中的状态更新工作树,该索引正确描述(单个)工作树中的内容,以更新/var/www/staging中的不同工作树staging分支,因此Git只使用其保存的知识更改必要的文件,并相信这是/var/www/staging中的内容......好吧,你明白了。 : - )

治愈就是做这些不同的事情:

  • 使用三个不同的工作树和三个不同的索引文件。然后,索引文件实际上将匹配工作树和Git"进行最小的更改"会好好的。新的内置git worktree add 应该是一个很好的方法,虽然我没有尝试过这个。从逻辑上讲,设置updateInstead receive.denyCurrentBranch模式应该更新相应的工作树。这需要一个现代化的Git; git worktree进入2.5,在2.6中有一些重要的修复,并且从那以后有更多(虽然更小)的修复。 注意2016年12月增加但它没有实际上即使在Git版本2.11中工作。它最终可能成为一种选择。

  • 或者,您可以在设置GIT_INDEX_FILE的同时设置变量GIT_WORK_TREE,并且只有三个单独的索引文件。 Git将根据需要创建它们,因此这是您可以对现有部署脚本进行的最小更改:

    GIT_WORK_TREE=/var/www/production GIT_INDEX_FILE=$GIT_DIR/index.production \
        git checkout $branch
    
  • 或者,确保Git重建索引和/或工作树。如果删除整个工作树(或在空工作树上指向Git),Git会注意到当前索引毫无价值。然后它会重新检查所有内容。

最后一种方法比前两种方法耗费更多时间,但如果仔细检查,确实有一个优势。考虑Git更新文件时Web服务器会发生什么。 Git查看索引以查看现在已签出的内容,并查看您为git checkout提供的内容,以了解应该签出的内容。我们说文件index.htmlblah.htmlfoo.css必须更新。 Git更改其中一个,然后,您的Web服务器获得一个新连接...并在阅读 new {{1时读取 index.html }}

会发生什么?谁知道?这里的要点是您的Web服务器看到不一致的快照。它可能不是非常不一致,并且不会很长时间,也许它不是问题,但如果你想要真正可靠的软件,你可能想要避免它。基本上,您需要让Web服务器读取旧快照,直到新快照完全准备就绪,您可以通过冻结Web服务器或将转换作为原子操作来完成。

现在考虑如果您的服务器执行此操作会发生什么:

blah.html

这为您提供了一个相对最短的时间,在此期间服务器被停止或冻结(而不是完全杀死/停止它,您可能只是发送一个通知,其目录已更改,然后等待几秒钟让它切换)。成本是您必须暂时拥有旧树和新树,并且设置新树所需的时间比交换几个文件要长。

顺便提及

此:

newtree=/var/www/newtree.$$
oldtree=/var/www/production.$$
# neither of these trees should exist, but do this
# in case we had a crash or something that left them behind
rm -rf $newtree $oldtree
mkdir $newtree

# populate the new tree
GIT_WORK_TREE=$tmptree git checkout $branch

# freeze / terminate the server (may not need this
# depending on how clever the server is -- it needs
# to notice the changeover)
service httpd stop

# swap the new tree in and the old one out, quickly
# (this is just two easy rename operations)
mv /var/www/production $oldtree
mv $newtree /var/www/production

# unfreeze/resume the server
service httpd start

# finally, delete the old tree (this does not need to be fast)
rm -rf $oldtree

有点误导,因为branch=$(git rev-parse --symbolic --abbrev-ref $refname) 根本不一定是分支。它可能是$refname(分支,refs/heads/master)或master(不是分支 - 标签)或refs/tags/v1.2(既不是分支或标签)。它在这里已经足够好了,但它可能更明智:

refs/notes/commits

其中case $refname in refs/heads/production) deploy production;; refs/heads/staging) deploy staging;; refs/heads/dev) deploy dev;; *) ;; # do nothing esac 是一个shell函数,它将命名分支(deploy)部署到$1。否则,您需要重新部署/var/www/$1以便推送到dev并进行广告代码制作。

1 RIP CES,虽然实际上他从未这么说过。

2 每个工作树还有一个HEAD,master也可以在这里正确管理,但我再也没有在部署中尝试过这个脚本。我不是100%确定如果部署的分支指向相同的提交ID会发生什么:我使用的工作流程通常要求无论如何都不会发生,所以{{{ 1}}始终在移动git worktree。移动git checkout <branch>可确保HEAD能够完成某些工作。测试带有指向同一提交ID的两个分支的单独索引,共享HEAD方法可能会很有趣,看看会发生什么。

在任何情况下,与单个HEAD混淆的一个副作用是新克隆将检出不同的默认分支(因为默认分支由origin&#39; HEAD确定)。