【问题标题】:git post-receive for multiple worktrees on the same servergit post-receive 用于同一服务器上的多个工作树
【发布时间】:2026-02-09 15:40:01
【问题描述】:

我正在关注this tutorial,它一直运行良好。我们现在想在服务器上使用相同的包运行多个帐户,我正在尝试修改 post-receive,但我没有做正确的事情并且找不到答案。目标是让单独的 cpanel 类型帐户运行来自我们的 github 存储库的相同代码,每当我们推送更改时,所有帐户都会发生更新。

post-receive 中的原始代码是这样的:

#!/bin/bash 
git --work-tree=/path/to/firstInstance --git-dir=/var/control/project.git checkout -f

它按预期工作,每当我们从本地 repo 推送并更新 github 时,我们的远程服务器也会更新到 /var/control/project.git,然后它会被推送到第一个帐户。

当我为不同的工作树添加另一行时,有 3 行内容如下:

#!/bin/bash
git --work-tree=/path/to/firstInstance --git-dir=/var/control/project.git checkout -f
git --work-tree=/path/to/secondInstance --git-dir=/var/control/project.git checkout -f

我可以将新文件添加到两个实例,但删除只发生在第二个实例上。显然我做得不对,但我在这里或其他地方找不到我在网上寻找的东西。任何帮助将不胜感激。

【问题讨论】:

    标签: git saas git-post-receive git-worktree


    【解决方案1】:

    您(大概1)正在使用一个裸存储库,这是正确的做法,但是您遇到了一个问题,只有在您拥有之后 成为一名 Git 大师。 ? 诀窍是每个工作树需要一个 index2 我们稍后会定义所有这些术语,但 TL;DR 部分是使用这个有点神奇的序列:

    GIT_INDEX_FILE=index.firstInstance git --work-tree=/path/to/firstInstance --git-dir=/var/control/project.git checkout -f
    GIT_INDEX_FILE=index.secondInstance git --work-tree=/path/to/secondInstance --git-dir=/var/control/project.git checkout -f
    

    这绝对可以简化;要了解如何操作,请继续阅读。


    1链接中的说明说使用git init --bare,所以我假设你正在这样做。

    2还有其他技巧也可以,但我会用这个。


    这里发生了什么

    一个普通的 Git 克隆由三个部分组成:

    • 存储库本身,它本身由两个主数据库和许多辅助数据库组成;和
    • 一个索引和一个工作树,它们配对在一起。

    使用这些普通存储库之一,git worktree add 命令可以添加更多工作树。每个都有一个索引和工作树对。它们绑定在一起(有点松散,但足以始终将它们视为一对)。

    bare 克隆会省略工作树而不省略索引。这意味着您的裸存储库有一个空闲索引:它将绑定到的工作树不存在。因此,您可以运行git --work-tree=<em>path command argument1 argument2</em> ...。这会临时为这个裸存储库分配一个工作树。 (单个)索引和此工作树现在绑定在一起。使用标准索引和该工作树运行您提供的一个命令。

    问题在于,一个标准索引现在描述了那个工作树,而不是任何其他工作树。如果你运行 git --work-tree=<em>otherpath command ...</em>,你调用 Git 时会使用它的标准索引和绑定在一起的 other 路径。 Git 假设索引正确地描述了这个 other 工作树。如果没有,事情有时会失败。更准确地说,索引会跟踪工作树中的内容,Git 部分只是假设它是正确的。3


    3Git 会进行一些检查。索引中有缓存数据关于该工作树,Git 将验证至少其中一些数据保持正确。它信任多少数据以及不信任多少数据取决于它在进行这些检查时所看到的内容。这意味着当缓存数据正确描述工作树时,效果很难预测。有时一切正常!但有时它不会。当它失败时,它会意外失败并且很难调试。


    我们对此做了什么

    我们利用了 Git 能够使用多个索引这一事实。如上所述,如果您将额外的工作树添加到标准(非裸)存储库,每个添加的工作树都有自己的索引。4 但是当我们使用 --work-tree 或 @987654327 @ 覆盖标准工作树,或者为其他裸存储库提供一个,我们没有覆盖标准工作树的标准索引。使用GIT_INDEX_FILE 环境变量允许我们覆盖标准索引(其名称只是index)。

    我们需要对除一个“额外”工作树之外的所有工作树执行此操作。我们与裸存储库一起使用的其中一个工作树可以使用标准索引,因此我们实际上只需要在此处的两个 git 命令之一上分配一个 GIT_INDEX_FILE 环境变量。但是为了对称性,或者添加第三个结帐的能力,我们可以每次都覆盖标准索引。

    请注意,如果 bash 脚本的当前工作目录已经是裸存储库目录,则不需要 --git-dir 选项(或 GIT_DIR 环境变量),因此在许多情况下您可以省略 --git-dir=。为了简化脚本,您可以运行:

    cd /var/control/project.git
    

    在脚本的前面并省略每个 --git-dir= 选项。

    还请注意,这些 git checkout -f 命令不提供提交哈希 ID 或分支名称,因此它们始终检查由特殊名称 HEAD 标识的任何提交。


    4这些添加的工作树有自己的HEAD 引用和其他引用。我们这里不处理这个问题。这是否以及何时出现问题是另一个话题。

    【讨论】:

    • 非常感谢,它运行良好,我已按照您的建议先更改目录以消除 --git-dir 的重复块。不过,我认为工作树设置不正确。我从其中一个中删除了 .git 目录,当从 git 推送时,我仍然可以在其中添加/删除/编辑文件。所以我不确定我做错了什么,我会做更多的阅读以了解它。我希望能够在每次更新发生时在每个工作树中运行 post-receive,基本上是运行一些命令来更新作曲家、迁移等。
    • 这里从其中一个目录中删除.git 目录是什么意思?如果你使用--git-dir,你是在告诉 Git 在哪里可以找到.git 目录,所以两个都没有 中应该有一个.git 目录。 (但这也不重要。)
    最近更新 更多