我有一个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个目录都相同。
如果有人有任何想法,或者可以看到可能导致此行为的事情,那就太棒了!
答案 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
应该是一个很好的方法,虽然我没有尝试过这个。从逻辑上讲,设置 注意2016年12月增加但它没有实际上即使在Git版本2.11中工作。它最终可能成为一种选择。updateInstead
receive.denyCurrentBranch
模式应该更新相应的工作树。这需要一个现代化的Git; git worktree
进入2.5,在2.6中有一些重要的修复,并且从那以后有更多(虽然更小)的修复。
或者,您可以在设置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.html
,blah.html
和foo.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确定)。