GIT post-接收更多分支的挂钩 - 工作流或时间戳问题?

GIT post-receive hook for more branches - workflow or timestamp problem?

我在同一台服务器上有一个开发和生产文件夹,它们后面有 1 个 repo,根据推送的分支推送到这两个文件夹。我希望在将 dev 推送到 repo 时部署开发(测试)文件夹,在推送 master 时部署生产文件夹(www)。

这行得通,但我遇到了重写所有文件的问题,而不仅仅是那些已更改的文件。

示例:

这是正常现象还是我做错了什么?如果我有 1 台本地 PC git - 1 个开发域和 1 个生产域,也许你能推荐一个更好的 git 工作流程?

感谢帮助

post-接收挂钩:

#!/bin/sh
while read oldrev newrev ref
do
  branch=`echo $ref | cut -d/ -f3`
  if [ "master" == "$branch" ]; then
    git --work-tree=/home/www/domain.com/subdomains/www --git-dir=/home/www/domain.com/subdomains/repos/myrepo.git checkout master -f
    echo 'changes pushed to production'
  else
    git --work-tree=/home/www/domain.com/subdomains/test--git-dir=/home/www/domain.com/subdomains/repos/myrepo.git checkout dev -f
    echo 'changes pushed to dev'
  fi
done

这个 post-receive hook 有严重的缺陷。好消息是它很容易修复。

您的特定问题的根源在于每个 Git 存储库,包括接收推送的裸存储库 1,从一个(只有一个)开始) 索引。索引跟踪(单个,又一次)工作树。即使裸存储库的全部要点是 no 工作树也是如此:它仍然有一个索引。

当一个人使用 git --work-tree=<path> 和这个裸存储库来执行 git checkout 操作时,该检出操作使用(一个,单个)索引来跟踪它存储在(临时)中的内容添加)工作树。也就是说,在 git checkout 期间,这个裸仓库变成了非裸仓库:它有一个索引和一个工作树,工作树是为这个 git checkout 操作选择的.

对于每个 后续 git --work-tree=<path> checkout 操作,Git 将 假设 索引正确描述了当前结帐到目标工作树。 (Git 可能会发现,在它工作时,索引没有正确描述目标工作树,但它开始假设它确实如此。) Git 决定更新的文件in 该工作树将基于此假设,可能会进行一些更正,但并非总是如此。这个主要假设,即索引正确描述了当前对工作树的检查,当且仅当 path 参数时成立后续 git checkout中使用的是path参数相同的用于上一个 git checkout.

但是我们有两个不同的 git checkout 命令:

git --work-tree=/home/www/domain.com/subdomains/www ...

和:

git --work-tree=/home/www/domain.com/subdomains/test

(这里有一点:上面的剪切和粘贴出了点问题,因为 --git-dir 选项与 --work-tree 融合在一起;我在这里做出了关于修复它的自己的假设) .

这是两条不同的路径。因此,他们需要两个不同的索引文件——或者根本不需要索引。2


1A 裸存储库 是 Git 存储库,其中 core.bare 设置为 true.裸存储库没有工作树,其 Git 数据库文件存储在顶层,而不是在单独的 .git 目录下。

2当没有索引时,如初始git checkout,Git会根据需要建立一个。这是计算密集型的,并且在非裸存储库中,我们实际上使用索引来构建 next 提交,破坏索引会丢失我们精心构建的下一个提交:Git 从 current 再次提交构建新的,所以我们必须重新开始。

工作树本身不受此重建过程的影响,但如果我们能负担得起每个工作树一个索引文件的 space——而且索引文件通常很小——似乎很可能我们应该这样做。如果您愿意,它仍然是一个选择。


解决问题

首先,虽然它并没有真正相关,但让我们解决 其他 明显(但次要)的问题:

while read oldrev newrev ref
do
  branch=`echo $ref | cut -d/ -f3`

假设我们添加一个名为 master/v1.1 标签。这个 ref 的全称是 refs/tags/master/v1.1。让我们看看我们在这里得到了什么:

$ ref=refs/tags/master/v1.1
$ branch=`echo $ref | cut -d/ -f3`
$ echo $branch
master

糟糕:我们的代码会认为我们已经更新了 branch master,而我们所做的是 添加标签 master/v1.1。我们可能不会使用这样的标签,但为什么不正确地使用呢?让我们检查整个参考,而不是使用 cut

case $ref in
refs/heads/*) branch=${ref#refs/heads/};;
*) continue;; # not a branch
esac

用我们的测试参考进行尝试,我们得到:

$ case $ref in
> refs/heads/*) branch=${ref#refs/heads/};;
> *) echo not a branch;;
> esac
not a branch

refs/heads/master 替换 ref 我们得到:

$ ref=refs/heads/master
$ case $ref in
refs/heads/*) branch=${ref#refs/heads/};;
*) echo not a branch;;
esac
$ echo $branch
master

(这部分没有PS2输出,因为我用control-P重做了前面的case。)

为了真正的正确性,我们还应该查看 $old$new 以确定是否正在创建、更新或删除相关引用;但在这种情况下,我们可以假设分支名称 masterdev 永远不会被删除。所以我们的新代码是:

#!/bin/sh
while read oldrev newrev ref
do
    case $ref in
    refs/heads/*) branch=${ref#refs/heads/};;
    *) continue;; # not a branch - do not deploy
    esac

    case "$branch" in
    master) wt=/home/www/domain.com/subdomains/www loc=production;;
    dev) wt=home/www/domain.com/subdomains/test loc=dev;;
    *) continue;; # not an INTERESTING branch - do not deploy
    esac

    # It's an interesting branch; deploy it to $wt.  Use an index
    # based on the branch name.  There is no need to specify the
    # git directory, which is in $GIT_DIR right now because this is
    # a post-receive script.
    GIT_INDEX_FILE=index.$branch git --work-tree=$wt checkout $branch -f
    echo "deployed to $loc"
done

这里 git checkout-f 选项有点危险:它会覆盖或删除工作树中修改过的文件,即使我们现在有一个适当的索引来跟踪哪些文件应该处于哪种状态。不过之前有过,所以一直留着。

这个 post-receive 脚本(我应该注意它完全未经测试:注意拼写错误)仍然有缺陷。我们 git checkout 在这里的任何分支,都会留下这个存储库设置,以向 clones 这个存储库的任何人推荐该分支。解决这个问题需要更新 HEAD:

git symbolic-ref HEAD refs/heads/master

git checkout 步骤之后。你一直生活在这个缺陷中,所以也许你并不真正关心这个。