TL;DR
您可能想使用git archive --format=tar ... | tar -C <path> -x ...。 (我不清楚您是否要剥离 dist/ 从提取的路径名中,但--strip-components=1 会这样做;请参阅the tar documentation。)有很多棘手的小细节,尽管。其中只有一个是 ... 部分中的内容。
长
我需要使用 git 钩子选择一个已经存在的子文件夹 dist/ 并将其发送到本地服务器。
你可以做到这一点。您只是不能轻松做到这一点(嗯,这有多容易取决于您可用的工具)。幸运的是,你已经用linux 交叉标记了它,所以我们可以假设你有 Linux 工具。
让我们先看看你现有的 post-receive hook 并注意一些缺陷:
#!/bin/bash
(到目前为止一切顺利)
unset GIT_INDEX_FILE
这条线没有任何用处(但无害)。不过有趣的是:在未设置索引文件的情况下,Git 将使用什么索引?答案当然是默认索引,$GIT_DIR/index。
我将下一行分成几部分:
git --work-tree=/Users/kevin/Source/local-host/www.kevvin.me/ \
这里的--work-tree 选项很重要:服务器存储库可能是一个裸存储库。这会覆盖裸露并选择不同的工作树。然后索引需要匹配这个工作树,并且通常会。
如果存储库不是裸露的,则可能存在许多问题,包括使用主索引来索引此备用工作树。将索引文件指向其他地方可能更明智,甚至使用添加的工作树 (git worktree add),尽管当我进行实验时,这在接收存储库时效果很差。 (特别是现代 Git 允许您将 receive.denyCurrentBranch 设置为 updateInstead,但如果您使用添加的工作树这样做,Git 将无法更新添加的工作树。)
--git-dir=/Users/kevin/Source/www.kevvin.me/.git
由于这是一个接收后挂钩,它应该已经将$GIT_DIR 设置为正确的目录。用--git-dir 覆盖$GIT_DIR 似乎是一件很奇怪的事情。此外,这里的路径通常是用于非裸存储库的路径,因此我们处于“未设置的 GIT_INDEX_FILE 似乎可疑”领域。
checkout -f
这将运行git checkout -f,并按照两个选项的指示设置$GIT_DIR 和$GIT_WORK_TREE。 git checkout -f 会签出哪个分支?嗯,那是在the git checkout documentation,部分内容是:
您可以省略 ,在这种情况下,命令会退化为“检查 当前分支”...
(强调我的)。那么 current 分支是哪个分支呢?嗯,这是在$GIT_DIR 中设置的当前分支。
请注意,您将检查当前分支无论哪些分支和/或标签和/或注释实际上由git push 更新。因此,这个特殊的接收后脚本虽然可以使用,但不是很好好。我们绝对可以做得更好。
更好的部署脚本
首先,我们应该阅读git push更新的名称:
while read old new ref; do
...
done
在循环内部,我们可以检查 $old、$new 和 $ref 以查看:
- 成功更改了哪个引用? (
$ref)
- 在更改之前该引用名称的哈希 ID 是什么? (
$old)
- 现在该引用名称的哈希 ID 是什么? (
$new)
我们还可以检查引用是创建($old 是全零)还是删除($new 是全零)。不过,大概您只想在一个特定的分支名称refs/heads/<em>name</em> 已更新时进行部署;并且大概该名称存在并且永远不会被删除。因此,我们可以只检查名称,如果要部署的分支名为 deployme,则该名称必须与 refs/heads/deployme 完全匹配:
while read old new ref; do
case $ref in
refs/heads/deployme) deploy $new;; # target branch updated: deploy it
*) continue;; # something else happened: ignore
esac
done
这就是我们要运行的循环;现在我们需要编写deploy 函数。
请注意,如果您想将不同分支部署到不同位置以进行测试,您可以扩展循环和/或设计deploy函数。现在,我们将坚持一个简单的。
部署功能
既然您要做的是从提交的快照中提取 dist/ 子目录,让我们首先编写我们的 deploy 函数来归档 dist/ 目录:
# called with one argument, which is the hash ID of the
# commit we are to deploy
deploy() {
git archive --format=tar $1 dist/ | ...
}
git archive 命令(这里不需要任何--git-dir 或--work-tree 参数)将把dist 目录的内容作为tar 归档写入存储在由hash ID 标识的提交中$1。所以tar,当我们运行它时,只会得到以dist/ 开头的路径。这意味着我们不必大惊小怪地限制我们提取的内容。我们可以-x 一切。
我们需要的tar 命令是:
tar -C /Users/kevin/Source/local-host/www.kevvin.me/
如果您想放弃dist/ 部分,则与--strip-components=1 相同。但是这里有一个风险:如果 old 提交//Users/kevin/Source/local-host/www.kevvin.me/ 的内容有一个名为 junk 的文件,而 new 提交中的 dist/junk 不再存在,该怎么办? ?这个tar -C 命令不会删除 junk 文件。
如果这是您想要的,我们就完成了;但如果没有,我们需要一些方法来清除我们不提取的所有文件。一种很好的方法是提取文件,不是直接提取到/Users/kevin/Source/local-host/www.kevvin.me/,而是提取到同一文件系统上的临时目录。然后,我们可以重命名 www.kevvin.me 目录并重命名 新的临时目录,这样可以最大限度地减少中断时间——整个过程只需要两次重命名——然后清理重命名后的www.kevvin.me 目录。
请注意,如果您这样做,您应该考虑到第二个 git push 启动 new 部署一个 new 替换的可能性,而旧的替换是仍在部署或清理部署。让这个工作正常由你决定(或者只是忍受它并假设它不会发生)。基于 pid 的锁定系统在这里不是一个坏主意,确实是万无一失的,但是我在这里写起来太复杂了。
最后,干净地部署有一种相当肮脏/丑陋的方法,即首先删除所有内容,然后提取 tar 文件。我将首先展示一个,因为它是最简单的方法,但也是最糟糕的方法:
deploy() {
local target=/Users/kevin/Source/local-host/www.kevvin.me
rm -rf $target && mkdir $target
git archive --format=tar $1 -- dist/ | tar -C $target -x --strip-components=1
}
这是一个使用临时目录,没有锁定:
deploy() {
local target=/Users/kevin/Source/local-host/www.kevvin.me
local tmpdir=$(mktemp -d $(dirname $target)/.newXXXXXXXX)
local olddir=$(mktemp -d $(dirname $target)/.oldXXXXXXXX)
git archive --format=tar $1 -- dist/ | tar -C $tmpdir -x --strip-components=1
mv $target $olddir && mv $tmpdir $target && rm -rf $olddir
}
当然,您可以对此进行任何数量的额外调整。您还可以编写一个工具来进行就地更新,读取 tar 存档并将其内容与现有树的现有内容进行比较。但是,如果此过程很慢,请注意您将有一段时间部署的树将混合新旧内容。这就是目录交换技巧如此出色的原因。请注意,它假定 $(dirname $target)/.newXXXXXXXX 生成的路径与目标位于同一挂载点。