Bare git repo post-receive hook 不再能够结帐
Bare git repo post-receive hook no longer able to checkout
我在 Web 服务器上使用 post-receive 钩子自动检出对网站的更改,在 Web 服务器上有一个裸 git 回购的通用设置。这一直很好用。
但是,一位同事最近克隆了裸存储库 (git clone <ssh url>
),对他们的本地存储库进行了一些更改,提交了它们,然后将更改推回 (git push origin master
)。我不确定这个过程是否是导致问题的原因,但自从推送后,post-receive 挂钩不再有效。它失败并输出:
fatal: You are on a branch yet to be born
这里是钩子:
#!/bin/bash
GIT_WORK_TREE=/home/marweldc/app git checkout -f
我能够将我的同事所做的更改从远程提取到我的本地实例。 运行 git branch
在服务器上的裸仓库目录中显示 * master
,git log
显示所有更改,包括我同事的,所以远程仓库显然正在跟踪事情。
但是,如果我这样做,比如说 GIT_WORK_TREE=/home/marweldc/app git status
,我会得到以下输出:
# Not currently on any branch.
#
# Initial commit
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# ../CONTRIBUTING.md
# ../README.inno.txt
# <all the other files and folders that should be in the repo>
nothing added to commit but untracked files present (use "git add" to track)
我也试过 运行 GIT_WORK_TREE=/home/marweldc/app git checkout -f master
,结果是:
error: pathspec '.git/master' did not match any file(s) known to git.
我们被难住了。是什么改变导致我们的钩子在之前工作正常时以这种方式失败?
编辑:
我试过的一些东西在下面重新评论:
cat HEAD
从裸回购内部产生 ref: refs/heads/master
GIT_TRACE=1 git rev-parse master
从裸回购中产生
trace: built-in: git 'rev-parse' 'master'
f6462b06f75d126ab932e3cfccef7385da2805ba
但是带有 --work-tree
选项集的相同命令产生
trace: built-in: git 'rev-parse' 'master'
master
fatal: ambiguous argument 'master': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions
- 运行
GIT_WORK_TREE=/home/marweldc/app-new git checkout -f
(要检出的新目录)工作正常
编辑:这里使用的非常旧的 Git 版本 1.7.1 中的单独 Git 和工作树目录存在一些非常具体的(也许是 CentOS 特有的)问题,例如对于这种特定情况,--work-tree=/home/marweldc/app
导致 Git 无法在 Git 目录和工作树之间来回移动。 (其他工作树路径不会导致问题。)
Git 没有注意到它自己未能返回到裸存储库,然后无法对分支名称执行任何操作(因为它无法返回到存储库,所以很可能会失败此时一切。
更现代的 Git 可能一开始就没有错误,或者会注意到无法在工作树和裸存储库之间来回切换。
同时,指定 --git-dir=<path>
似乎可以解决该问题。
原始(一般)答案如下。
这个:
fatal: You are on a branch yet to be born
意思就是它所说的,尽管它所说的可能令人困惑。 :-) 你真的是 "on" 一些尚不存在的分支。
(不清楚您 在哪个分支 — 您可能有一个 "detached HEAD",正如 Git 所说 — 但它看起来确实虽然你不再有 master
分支,如果你曾经有的话。)
简短版
使用git branch
查看您现在所在的分支,以及您可以使用的分支。使用 git symbolic-ref HEAD refs/heads/master
强制将裸存储库的 HEAD
设置回 master
分支。对于其他一些名为 B 的分支,使用 refs/heads/<em>B</em>
.
接下来是长篇回答,即发生了什么。
无论仓库是否是空的,它仍然有一个当前分支
在任何普通的非裸存储库中,您可以通过 运行ning git status
查看您所在的分支。但是 git status
查看工作树,所以在 bare 存储库中,git status
拒绝 运行:没有工作树。
另一种查看您所在分支的方法是 运行 git branch
:
$ git branch
diff-merge-base
* master
stash-exp
这适用于裸仓库和非裸仓库。 *
以当前分支的名称命名。
如何在常规存储库中更改您所在的分支?
答案1是:git checkout
。例如,git checkout master
将你(或我)置于 master
,而 git checkout stash-exp
将你(或我)置于 stash-exp
。
1还有另一种方法,使用 管道命令,但您通常不想使用它,因为它可以弄乱当前提交、索引和工作树之间的区别。但是由于裸存储库没有工作树,这就变成了......嗯,"safe" 太强了,但我们可以说 "much less dangerous".
如何在裸存储库中更改您所在的分支?
答案是还是git checkout
.
当您签出裸仓库中的分支时,这会使用和更改您当前的分支,其方式与您签出非裸仓库中的分支时的方式完全相同。
当然,您不能 git checkout
裸存储库中的分支,因为那样会更新工作树。除非,当您使用 git --work-tree=... checkout ...
时,您 可以 。您提供要签出的分支名称,然后 像往常一样将新分支名称写入 HEAD
。 如果您通过哈希 ID 签出提交,则 像往常一样分离 HEAD
。
未出生(孤儿)分支
但是还有一种特殊情况:如果您将 HEAD
设置为 尚不存在的分支的名称 (就像您对 git checkout --orphan
例如),将名称放入 HEAD
而不创建分支。这在常规(非裸)存储库中很正常,除了您只在两种情况下看到它:
当您第一次创建一个新的空存储库时。您在 master
,但 master
尚不存在。您可以通过在存储库中提交一个提交来解决这种情况,该提交会进入 master
,从而创建 master
。 (新提交是 root 提交,即没有父提交。)
当您使用 git checkout --orphan
时:这与您在新的空存储库中时的设置方式相同,方法是将分支名称写入 HEAD
像往常一样,但没有创建分支。您通过编写一个新的提交来解决这种情况,该提交进入未出生的分支,该分支创建现在出生的分支(存在,指向您刚刚创建的新根提交。)
在裸存储库中做同样的事情
显然,您不能在裸存储库中以通常的方式执行此操作,因为通常的方式是使用工作树,将 git add
内容放入索引,并且 git commit
.裸存储库没有工作树。 (它仍然有索引,并且 git checkout
,当你 提供 具有 --work-tree
的工作树时你可以这样做,仍然写 through 索引。这有点棘手并且会扰乱许多人的 post-receive 部署挂钩,因为他们不理解索引。)
您可以,并且一直在裸存储库中做的是接收git push
es。当您收到推送时,您接受来自另一个 Git 形式的请求(或命令):
- 请将您的
master
分支设置为提交 1234567
- 删除分支
testing
(由您决定是否以及如何检查这些请求,在 pre-receive
或 update
钩子中。如果您没有做任何特别的事情,您会得到 Git 的默认内置检查,允许任何人创建或删除任何分支或标签,但只允许对分支进行快进推送。)
例如,假设在您的裸存储库中,在分支 master
上,并且分支 master
尚不存在 。然后互联网上有人连接到你的服务器并说:"hey, you, bare Git repository, have some commits here, and now create branch master
pointing to commit 1234567
!" 在这种情况下,假设你接受这个请求/命令,一旦你的 Git 完成为他们服务,你 做 现在有一个 master
。这将带您来自:
fatal: You are on a branch yet to be born
至:
On branch master
因为您当前的分支 master
,以前不存在,现在 存在。
使用管道命令
git update-ref
和 git symbolic-ref
管道命令 旨在用于脚本中。他们假设您确切地知道自己在做什么。 (如果你在不知道自己在做什么的情况下使用它们,你可能会弄得一团糟:它们没有内置的安全检查。)其中第二个,特别是,Git 如何在内部将 HEAD
设置为select 你是哪个分行 "on".
如果你运行:
git symbolic-ref HEAD refs/heads/<em>branch</em>
这会将 ref: refs/heads/branch
写入 HEAD
,您现在 在分支 <em> 分支 </em>
上。它 不 更新索引,它 不 更新工作树——但是在裸存储库中,没有工作树,并且接收推送不会更新索引,因此这与接收推送时发生的情况非常相似。
因此,这是一件相当安全的事情。它可以让你改变你所在的分支,而不必使用 git checkout
。这是正常且正确的,尽管有点麻烦——确保你正确拼写分支名称,并包含 refs/heads/
前缀——在裸存储库中设置 HEAD
的方法。
我在 Web 服务器上使用 post-receive 钩子自动检出对网站的更改,在 Web 服务器上有一个裸 git 回购的通用设置。这一直很好用。
但是,一位同事最近克隆了裸存储库 (git clone <ssh url>
),对他们的本地存储库进行了一些更改,提交了它们,然后将更改推回 (git push origin master
)。我不确定这个过程是否是导致问题的原因,但自从推送后,post-receive 挂钩不再有效。它失败并输出:
fatal: You are on a branch yet to be born
这里是钩子:
#!/bin/bash
GIT_WORK_TREE=/home/marweldc/app git checkout -f
我能够将我的同事所做的更改从远程提取到我的本地实例。 运行 git branch
在服务器上的裸仓库目录中显示 * master
,git log
显示所有更改,包括我同事的,所以远程仓库显然正在跟踪事情。
但是,如果我这样做,比如说 GIT_WORK_TREE=/home/marweldc/app git status
,我会得到以下输出:
# Not currently on any branch.
#
# Initial commit
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# ../CONTRIBUTING.md
# ../README.inno.txt
# <all the other files and folders that should be in the repo>
nothing added to commit but untracked files present (use "git add" to track)
我也试过 运行 GIT_WORK_TREE=/home/marweldc/app git checkout -f master
,结果是:
error: pathspec '.git/master' did not match any file(s) known to git.
我们被难住了。是什么改变导致我们的钩子在之前工作正常时以这种方式失败?
编辑:
我试过的一些东西在下面重新评论:
cat HEAD
从裸回购内部产生ref: refs/heads/master
GIT_TRACE=1 git rev-parse master
从裸回购中产生trace: built-in: git 'rev-parse' 'master' f6462b06f75d126ab932e3cfccef7385da2805ba
但是带有
--work-tree
选项集的相同命令产生trace: built-in: git 'rev-parse' 'master' master fatal: ambiguous argument 'master': unknown revision or path not in the working tree. Use '--' to separate paths from revisions
- 运行
GIT_WORK_TREE=/home/marweldc/app-new git checkout -f
(要检出的新目录)工作正常
编辑:这里使用的非常旧的 Git 版本 1.7.1 中的单独 Git 和工作树目录存在一些非常具体的(也许是 CentOS 特有的)问题,例如对于这种特定情况,--work-tree=/home/marweldc/app
导致 Git 无法在 Git 目录和工作树之间来回移动。 (其他工作树路径不会导致问题。)
Git 没有注意到它自己未能返回到裸存储库,然后无法对分支名称执行任何操作(因为它无法返回到存储库,所以很可能会失败此时一切。
更现代的 Git 可能一开始就没有错误,或者会注意到无法在工作树和裸存储库之间来回切换。
同时,指定 --git-dir=<path>
似乎可以解决该问题。
原始(一般)答案如下。
这个:
fatal: You are on a branch yet to be born
意思就是它所说的,尽管它所说的可能令人困惑。 :-) 你真的是 "on" 一些尚不存在的分支。
(不清楚您 在哪个分支 — 您可能有一个 "detached HEAD",正如 Git 所说 — 但它看起来确实虽然你不再有 master
分支,如果你曾经有的话。)
简短版
使用git branch
查看您现在所在的分支,以及您可以使用的分支。使用 git symbolic-ref HEAD refs/heads/master
强制将裸存储库的 HEAD
设置回 master
分支。对于其他一些名为 B 的分支,使用 refs/heads/<em>B</em>
.
接下来是长篇回答,即发生了什么。
无论仓库是否是空的,它仍然有一个当前分支
在任何普通的非裸存储库中,您可以通过 运行ning git status
查看您所在的分支。但是 git status
查看工作树,所以在 bare 存储库中,git status
拒绝 运行:没有工作树。
另一种查看您所在分支的方法是 运行 git branch
:
$ git branch
diff-merge-base
* master
stash-exp
这适用于裸仓库和非裸仓库。 *
以当前分支的名称命名。
如何在常规存储库中更改您所在的分支?
答案1是:git checkout
。例如,git checkout master
将你(或我)置于 master
,而 git checkout stash-exp
将你(或我)置于 stash-exp
。
1还有另一种方法,使用 管道命令,但您通常不想使用它,因为它可以弄乱当前提交、索引和工作树之间的区别。但是由于裸存储库没有工作树,这就变成了......嗯,"safe" 太强了,但我们可以说 "much less dangerous".
如何在裸存储库中更改您所在的分支?
答案是还是git checkout
.
当您签出裸仓库中的分支时,这会使用和更改您当前的分支,其方式与您签出非裸仓库中的分支时的方式完全相同。
当然,您不能 git checkout
裸存储库中的分支,因为那样会更新工作树。除非,当您使用 git --work-tree=... checkout ...
时,您 可以 。您提供要签出的分支名称,然后 像往常一样将新分支名称写入 HEAD
。 如果您通过哈希 ID 签出提交,则 像往常一样分离 HEAD
。
未出生(孤儿)分支
但是还有一种特殊情况:如果您将 HEAD
设置为 尚不存在的分支的名称 (就像您对 git checkout --orphan
例如),将名称放入 HEAD
而不创建分支。这在常规(非裸)存储库中很正常,除了您只在两种情况下看到它:
当您第一次创建一个新的空存储库时。您在
master
,但master
尚不存在。您可以通过在存储库中提交一个提交来解决这种情况,该提交会进入master
,从而创建master
。 (新提交是 root 提交,即没有父提交。)当您使用
git checkout --orphan
时:这与您在新的空存储库中时的设置方式相同,方法是将分支名称写入HEAD
像往常一样,但没有创建分支。您通过编写一个新的提交来解决这种情况,该提交进入未出生的分支,该分支创建现在出生的分支(存在,指向您刚刚创建的新根提交。)
在裸存储库中做同样的事情
显然,您不能在裸存储库中以通常的方式执行此操作,因为通常的方式是使用工作树,将 git add
内容放入索引,并且 git commit
.裸存储库没有工作树。 (它仍然有索引,并且 git checkout
,当你 提供 具有 --work-tree
的工作树时你可以这样做,仍然写 through 索引。这有点棘手并且会扰乱许多人的 post-receive 部署挂钩,因为他们不理解索引。)
您可以,并且一直在裸存储库中做的是接收git push
es。当您收到推送时,您接受来自另一个 Git 形式的请求(或命令):
- 请将您的
master
分支设置为提交1234567
- 删除分支
testing
(由您决定是否以及如何检查这些请求,在 pre-receive
或 update
钩子中。如果您没有做任何特别的事情,您会得到 Git 的默认内置检查,允许任何人创建或删除任何分支或标签,但只允许对分支进行快进推送。)
例如,假设在您的裸存储库中,在分支 master
上,并且分支 master
尚不存在 。然后互联网上有人连接到你的服务器并说:"hey, you, bare Git repository, have some commits here, and now create branch master
pointing to commit 1234567
!" 在这种情况下,假设你接受这个请求/命令,一旦你的 Git 完成为他们服务,你 做 现在有一个 master
。这将带您来自:
fatal: You are on a branch yet to be born
至:
On branch master
因为您当前的分支 master
,以前不存在,现在 存在。
使用管道命令
git update-ref
和 git symbolic-ref
管道命令 旨在用于脚本中。他们假设您确切地知道自己在做什么。 (如果你在不知道自己在做什么的情况下使用它们,你可能会弄得一团糟:它们没有内置的安全检查。)其中第二个,特别是,Git 如何在内部将 HEAD
设置为select 你是哪个分行 "on".
如果你运行:
git symbolic-ref HEAD refs/heads/<em>branch</em>
这会将 ref: refs/heads/branch
写入 HEAD
,您现在 在分支 <em> 分支 </em>
上。它 不 更新索引,它 不 更新工作树——但是在裸存储库中,没有工作树,并且接收推送不会更新索引,因此这与接收推送时发生的情况非常相似。
因此,这是一件相当安全的事情。它可以让你改变你所在的分支,而不必使用 git checkout
。这是正常且正确的,尽管有点麻烦——确保你正确拼写分支名称,并包含 refs/heads/
前缀——在裸存储库中设置 HEAD
的方法。