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 在服务器上的裸仓库目录中显示 * mastergit 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.

我们被难住了。是什么改变导致我们的钩子在之前工作正常时以这种方式失败?

编辑:

我试过的一些东西在下面重新评论:

编辑:这里使用的非常旧的 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 pushes。当您收到推送时,您接受来自另一个 Git 形式的请求(或命令):

  • 请将您的 master 分支设置为提交 1234567
  • 删除分支testing

(由您决定是否以及如何检查这些请求,在 pre-receiveupdate 钩子中。如果您没有做任何特别的事情,您会得到 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-refgit 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 的方法。