GIT post-接收更多分支的挂钩 - 工作流或时间戳问题?
GIT post-receive hook for more branches - workflow or timestamp problem?
我在同一台服务器上有一个开发和生产文件夹,它们后面有 1 个 repo,根据推送的分支推送到这两个文件夹。我希望在将 dev 推送到 repo 时部署开发(测试)文件夹,在推送 master 时部署生产文件夹(www)。
这行得通,但我遇到了重写所有文件的问题,而不仅仅是那些已更改的文件。
示例:
- 我在本地开发分支上,只对文件 index.php 进行更改,提交,
push - 更改被部署到开发文件夹中,只有
index.php 已更改(新文件时间戳)。
下一步:gitcheckout master,git合并dev分支然后
push to master - 更改已正确部署到生产文件夹,
但不仅 index.php 被改变了,而且 FTP 上的所有文件都有新的
时间戳。
下一步:git checkout dev,对 fileX.php 进行一些更改,提交并推送到 dev - 更改已正确部署到 dev 文件夹,但所有文件都在FTP 有了新的时间戳,不仅仅是 fileX.php
这是正常现象还是我做错了什么?如果我有 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
以确定是否正在创建、更新或删除相关引用;但在这种情况下,我们可以假设分支名称 master
和 dev
永远不会被删除。所以我们的新代码是:
#!/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
步骤之后。你一直生活在这个缺陷中,所以也许你并不真正关心这个。
我在同一台服务器上有一个开发和生产文件夹,它们后面有 1 个 repo,根据推送的分支推送到这两个文件夹。我希望在将 dev 推送到 repo 时部署开发(测试)文件夹,在推送 master 时部署生产文件夹(www)。
这行得通,但我遇到了重写所有文件的问题,而不仅仅是那些已更改的文件。
示例:
- 我在本地开发分支上,只对文件 index.php 进行更改,提交, push - 更改被部署到开发文件夹中,只有 index.php 已更改(新文件时间戳)。
下一步:gitcheckout master,git合并dev分支然后
push to master - 更改已正确部署到生产文件夹, 但不仅 index.php 被改变了,而且 FTP 上的所有文件都有新的
时间戳。下一步:git checkout dev,对 fileX.php 进行一些更改,提交并推送到 dev - 更改已正确部署到 dev 文件夹,但所有文件都在FTP 有了新的时间戳,不仅仅是 fileX.php
这是正常现象还是我做错了什么?如果我有 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
以确定是否正在创建、更新或删除相关引用;但在这种情况下,我们可以假设分支名称 master
和 dev
永远不会被删除。所以我们的新代码是:
#!/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
步骤之后。你一直生活在这个缺陷中,所以也许你并不真正关心这个。