为什么我无法将代码推送到一个非裸库的已检出分支?

4
我对自己创建的一个场景感到困惑。我在Github上创建了一个仓库(我们称之为A)并将代码推送到该仓库。然后,我将该仓库克隆到本地(我们称之为B),使得本地的源是远程仓库A。
现在,我从本地B克隆出另一个本地实例C。现在,C的远程源是B仓库,而C的上游是A仓库。
A → B → C

这类似于分叉,但我在客户端创建了克隆,而非服务器端。

现在,如果我尝试从C推送到它的源B:

git push origin 

然后我收到了一个错误,指出我不能推送到非裸仓库。我知道向非裸仓库推送可能会导致远程存在但本地不存在的提交丢失。

但是,如果从B推送我的代码到A不是类似于这种情况吗?

我很困惑,如果从B到A是可能的,那为什么不能从C到B呢?

对于合并到A,我们可以像下面这样推送到上游:

git push upstream

2
我可以推送到非裸仓库(只要我不推送到非裸远程仓库的工作树中当前检出的分支)。您确定这不是正在发生的事情吗?如果是这种情况,我会得到一个很长但相当详细的错误消息,其中包括:![remote rejected] HEAD -> master(分支目前被检出)。 - Alderath
是的@Alderath,你说得对。我也收到了同样的信息。我不理解的是,它和我可以在没有任何警告的情况下从B推到A有什么区别。 - harsrawa
我会收到一个错误消息来描述原因。如果允许您将更改推送到非裸存储库的签出分支,则会导致该存储库的工作树与推送的分支不一致。 - Alderath
今天我理解了Git的行为。把B想象成在同一项目中工作的同事。你不会把你的更改推送到他正在工作的相同repo/分支上!Git不允许你这样做。如果你真的想要在那里推送(也许是为了备份你的工作),在B中执行“git config [--global] receive.denyCurrentBranch ignore”来允许它。另一方面,你可以推送到A,因为GitHub repos是裸的。没有人可以在那里编辑文件,因此你永远不会通过推送你的文件来破坏更改。 - Ivan Ferrer Villa
2个回答

8

一些基础知识

  • 每当你使用git clone时,从开发者的角度来看,你会想在刚克隆下来的代码上进行工作。因此,GIT会给你一个“工作树”来进行操作。它被称为树,因为当你考虑到所有的提交和分支,并把它们放在一个图形上时,它就像一棵树。

  • 克隆的存储库称为非裸存储库。要创建一个非裸存储库,只需执行一个简单的git init - 这就是原始程序员使用GIT开始跟踪克隆代码的方式。你也可以将其克隆到一个裸存储库中,但它的细节和有用性应该在一个关于它的适当问题的答案中解释清楚。

  • 裸存储库不包含工作树。它只是用来存储你的代码的 - 如果你愿意的话,可以将其视为由GIT管理的代码服务器。要创建一个裸存储库,只需执行一个简单的git init --bare name_of_repository.git。它将创建一个名为name_of_repository.git的目录,其中包含GIT所需的所有文件。git扩展名只是一个惯例,它不是必需的,可以是任何东西或根本没有。

  • GIT中有一种类似于指针的东西,称为HEAD。它指向你正在工作的分支中活动的最新提交。

  • 分支就像你从远程存储库拉下来的代码的“不同副本”(可能有不同的修改或没有)。它具有开发人员认为合适的任何名称。它们很有用,因为你可以在不担心当前由你或其他人开发的代码的情况下,处理不同的功能或修复不同的问题。稍后,你总是可以将所有内容合并到主分支 - 通常是master - 然后删除那些不再需要的已合并分支。

  • GIT尽力避免不同位置或分支的文件版本之间的问题。因此,在某些情况下,它确定为至少混乱时,它不会允许你进行git push。GIT永远不会错,因为它要求你检查、更改或强制执行你正在做的事情。因此,任何错误都不是GIT的错,而只是你自己的错。

理解情况

让我们考虑以下情况:

  • A仓库是一个裸仓库。B和C仓库都是非裸仓库。这意味着A没有工作目录,仅用于存储。B和C用于需要完成的工作。
  • 一般来说,你(通常)会有分支。通常初学者不会创建分支,因为他正在学习,甚至可能还不知道分支——尽管它们有很多用处。所以他几乎总是在“主”分支上——默认分支。

话虽如此,假设你在B中修改了一些文件。你可以进行任意次数的git commit,甚至在最后进行git push。或者你什么也不做。但你在主分支上。

稍后,你在C中修改了文件。然后你提交并尝试推送到A。记住:你在C的主分支上。 git push成功了!

然后,你尝试将C推送到B。 它不起作用。

结果:GIT会(不会)直接警告你,指出你试图更新非裸仓库B的主分支,而其HEAD指向另一个提交!如果它让你进行推送,你将破坏GIT在仓库B上跟踪的历史记录。它将不知道B发生了什么!你甚至可能会用与B上同名分支上的相同名称覆盖该分支上的修改!因此,如果两个仓库都是非裸仓库,则无法从C推送到B!

现在怎么办?我的世界就这样结束了吗?伟大的I能做什么?GIT如何忽视他主人的愿望?这是纯粹的异端邪说!

解决方案

1 - 在B上有两个分支——主分支和临时分支。并使HEAD指向临时分支。例如:

cd B                  # change to B's working directory
git branch temp       # create 'temp' branch
git checkout temp     # change from master branch to branch temp

2 - 现在,进入C工作目录(简称wd),并拉取B的内容。请注意,我假设B是C的远程仓库(根据您提供的情况):

cd ../C               # change to C's working directory
git pull B master     # pulls B's modifications to C

3 - 在C中修改您的文件。请注意,您正在C的主分支上。然后,在提交C的修改后,将其推送到B的主分支:

git push B master     # pushes C's mods to B's master branch

4 - 现在回到 Bwd,将 HEAD 指向主分支:

cd ../B               # change to B's working directory
git checkout master   # change from temp branch to branch master

5 - 如果您不再需要临时分支,可以将其删除:

git branch -d temp    # delete branch temp

6 - 如果你正在对C语言进行新的修改,那么你不需要同时进行步骤4和5。如果你这样做,每当你想要在C语言中进行修改时,你都需要事先执行步骤1和2。

这样就解决了你的问题! 可能……

澄清和加强

  • git branch name_of_the_branch 创建一个新的分支;
  • git checkout name_of_the_branch 将HEAD指向这个新的分支;
  • git checkout -b name_of_the_branch 在一个命令中创建一个分支并将HEAD指向它。我使用了较长的方法,因为你也应该知道较长的方法;
  • 如前所述,如果您以后还要使用该分支,请勿删除该分支。但我建议这样做是为了避免在拉取/推送或合并时出现两个存储库中的临时分支问题。根据需要创建临时分支 - 使用终端历史记录相当容易 - 然后在之后将其删除;

0

如果你使用

git clone --bare http://github.com/xxx/A B

然后你可以从 C 推送到 B。当然,由于它是裸的,你将无法在 B 上工作。

我仍然不明白为什么你想要这个“额外”的 B 存储库?


这只是我想要尝试的一个随机设置。我想了解为什么在这种情况下会有行为变化。 - harsrawa
在你的设置中,你可以在B和C之间合并,并从B推送到A或从C推送到A。 - Anders Lindgren

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接