如何防止git push-to-deploy创建" refs"工作树上面的文件夹?

时间:2016-04-18 03:36:33

标签: git

我目前有以下简单的" push-to-deploy"接收后的策略:

#!/bin/sh

while read oldrev newrev refname
do
    branch=$(git rev-parse --symbolic --abbrev-ref $refname)
    path="/var/www/html/$branch"
    mkdir -p $path
    git --work-tree=$path checkout -f $branch
    chmod -R g+w $path
    chown -R apache $path
done

除了所需的/var/www/html/refs之外,这样做的目的是创建一个/var/www/html/{branch}文件夹。

refs文件夹如下所示:

/var/www/html/refs
    heads/
        branch1/
        branch2/
        etc...

但它们都是空的,删除它们似乎没有负面影响。

Git是否有理由对创建此文件夹感到困惑?

我尝试提供--git-dir并没有任何区别 我在CentOS 7上有最新版本的Git。

1 个答案:

答案 0 :(得分:2)

虽然我无法重现您的问题,但此方法中的方法似乎错误

while read oldrev newrev refname
do

这部分目前还不错......

    branch=$(git rev-parse --symbolic --abbrev-ref $refname)

这似乎是试图剥离refs/heads/。虽然这是一个很好的目标,但这肯定是错误的做法,因为如果引用不在refs/heads/(例如,如果某人正在推动refs/notes/commitsrefs/tags/v1.1 ),您将获得某种缩短的名称(notes/commitsv1.1分支名称,因此可能不应该部署。

根据实际发生的情况,您似乎根本没有缩短名称(这是我在这里无法重现的内容)。

    path="/var/www/html/$branch"
    mkdir -p $path
    git --work-tree=$path checkout -f $branch
    chmod -R g+w $path
    chown -R apache $path
done

只要$branch扩展为分支名称,其余部分就可以了,我将在稍后讨论一些其他问题。 (它还假设部署脚本作为超级用户运行,这可能不是一个好主意,但这完全是一个不同的问题。)

修复此特定问题的一种方法是通过更改循环顶部来确保$branch确实是分支名称:

    case $refname in
    refs/heads/*) branch=${refname#refs/heads/};;
    *) continue;; # not a branch name, skip entirely
    esac

即,检查引用是否实际上是分支名称,如果不是,则跳过它。如果 是分支名称,则分支的名称是前面剥去refs/heads/的引用名称。

这留下了剩下的问题。第一个是这一行:

    git --work-tree=$path checkout -f $branch

git在将$branch签出到$path的工作树时使用的索引文件是默认索引文件,$GIT_DIR/index。假设我们然后同时或一个接一个地推送两个不同的分支,这样循环运行两次(因为有两个名称要更新,或者每次推送一次)。

在第一次循环中,假设$branchmaster。我们将git checkout ... -f master,它将使用索引文件内容作为缓存来智能地了解哪些操作和不需要签出到$path的工作树中,在这种情况下将是/var/www/html/master

第二次循环中,假设$branchdevelop。我们将git checkout ... -f develop,它将使用相同的索引文件(现在匹配刚检出的master - 分支提交)来决定应更新哪些文件(如果有的话)在$path的工作树中,现在是/var/www/html/develop

如果大多数文件相同,git可能根本不会检查它们,因为它从缓存中知道它们已经存在。 (确切的细节因操作系统和文件系统而异,但是git尝试使用目录时间戳来优化签出过程。只要develop目录及其子目录不是太新,这就会咬你最终)

有两种方法可以解决这个问题,其中只有一种我自己测试过。一个是首先删除工作树的内容,这样它就是全新的,git可以确定没有任何缓存,git必须重新创建所有内容。 (这种方法的优点是易于实现,但缺点是速度有点慢。)另一种方法是使用每分支索引文件,这样git的缓存数据实际上与签出匹配。

使用每个分支索引,您需要找到一个放置索引的位置,该索引将与其他每个分支安全地分开。您可以将其放入工作树(例如,在.git目录中),或者将其放在存储库和每个分支树之外的并行目录中,或者在git存储库中创建一个目录抓住他们。请记住考虑分支命名为ab/cd的可能性,以及删除所述分支,然后创建名为ab的新分支。

这将我们带到第二个问题,即同样的可能性。假设分支ab/cd存在一段时间而您mkdir -p /var/www/html/ab/cd。然后假设分支已删除

首先,我们将工作树/var/www/html/ab/cd抛在脑后。也许这甚至是正确的,但也许不是。其次,当删除推送发生时,我们将尝试git checkout ... -f ab/cd,这将失败,因为分支ab/cd消失了。最后,如果还创建了另一个新的分支ab,我们将mkdir -p /var/www/html/ab(尽可能这样做)但无法从那里删除cd/

要解决所有这些问题,我们需要完全排除分支删除,或以某种方式处理它。

我们只能通过部署"祝福"来实现前者。分支名称。这也解决了我们剩下的大部分问题:事实上,我们甚至根本不需要mkdir -p,因为无论是谁设置脚本都可以在mkdir同时保佑分支名称

要做到这一点,我们可以匹配受祝福的分支名称,而不是部署任意分支名称。有很多方法可以做到这一点,但最简单的方法是用这样的方式替换我们的$refname个案例:

case $refname in
refs/heads/master) branch=master;;
refs/heads/test) branch=test;;
*) continue;;  # master and test are the only blessed branches
esac

现在,即使删除分支mastertest,我们也会保留html树。 (我们仍然应该修复结帐问题,不要试图检查它们是否被删除。)

让我们看另一个选项,即自动删除已删除的分支并自动创建新创建的分支。这是我们探索设置每个分支索引文件的地方,并展示了如何避免签出已删除的分支。

如果$oldrev为40 0 s,则创建分支。如果$newrev为40 0 s,则已删除。 (最多允许其中一个,对于正常更新,两者都不是特殊的NULL-sha1。)

所以,现在我们可能会这样做(警告,未经测试):

NULL_SHA1=0000000000000000000000000000000000000000 # 40 0's

while read oldrev newrev refname; do
    case $refname in
    refs/heads/*) branch=${refname#refs/heads/};;
    *) continue;;
    esac

    path="/var/www/html/$branch"
    pbdir="$GIT_DIR/perbranchindex/$branch"
    case $oldrev,$newrev in
    $NULL_SHA1,*)
        # new branch: create path and index file
        mkdir -p "$path"
        mkdir -p "$pbdir" && : > "$pbdir/index"
        ;;
    *,$NULL_SHA1)
        # deleted branch: remove path and index file
        rm -rf "$path" "$pbdir"
        continue # and skip checkout too
    *)
        # normal update
        ;;
    esac
    GIT_INDEX_FILE="$pbdir/index" git --work-tree="$path" checkout -f "$branch"
    chmod -R g+w "$path"
    chown -R apache "$path"
done

编辑:我将上述内容转换为常规部署脚本,并决定不在更新时创建索引文件目录和部署路径是一个错误。部署脚本(仍然主要是未经测试且启用了调试)是here