为什么我无法从浅克隆中推送代码?

55

git clone --depth 命令选项表示

--depth <depth> 
Create a shallow clone with a history truncated to the specified number of revisions. 
A shallow repository has a number of limitations 
(you cannot clone or fetch from it, nor push from nor into it),
 but is adequate if you are only interested in the recent history of a large project with a long history,
 and would want to send in fixes as patches. 
为什么浅克隆有这个限制?为什么它是一个仅使用补丁的工作流程?
对于某些项目工作流程,我需要将单个分支的最新提交传递给程序员,然后让他们能够向主服务器推送(快进)开发。这部分是为了安全性,知识产权保护和存储库大小,以及减少大型存储库会给幼稚的程序员带来的混乱。是否有一种Git工作流程可以实现此功能?
更新:根据Karl Bielefeldt的答案,git checkout --orphan 应该是正确的答案。但是,仍然需要将那个分支单独“克隆”到新用户,并能够有效地推送它。
手册中说明:
创建一个名为<new_branch>的新孤立分支,从<start_point>开始并切换到该分支。在此新分支上进行的第一次提交将没有父级,它将成为完全与所有其他分支和提交断开连接的新历史记录的根。
索引和工作树将被调整,就好像您以前运行了git checkout <start_point>一样。这使您能够启动一个新的历史记录,通过轻松运行git commit -a记录与<start_point>类似的路径集,使其成为根提交。
当您想要发布来自提交树的树时,而不需要公开其完整历史记录时,这可能很有用。您可能希望这样做,以发布一个项目的开源分支,该项目的当前树是“干净的”,但其完整历史记录包含专有或受限制的代码位。
如果您想启动一个记录与<start_point>完全不同的一组路径的断开历史记录,则应在创建孤立分支后立即清除索引和工作树,从工作树的顶部运行git rm -rf。然后,您将准备好准备新文件,通过从其他位置复制它们,提取tarball等重新填充工作树。
VonC提供的Junio评论链接很有趣。我认为手册应在这种情况下提供指导,并允许正确的命令[e.g. clone <branch> --options]只提取存储库的相关部分。显然,具有一些底层历史记录的关联提交和SHA1会锁定匹配的存储库,从而增加push成功的概率。
更新Git 1.9.0:发布说明14 Feb '14。
“从浅克隆的存储库提取以前被禁止,主要原因是涉及的代码路径没有得到仔细验证,我们不会费心支持这种用法。此版本试图以更加受控的方式允许从浅克隆的存储库传输对象(即接收器成为具有截断历史记录的浅存储库)。”
这对于浅克隆者来说是个好消息。接下来可能是窄克隆。

12
减少大型代码库给一个不熟悉的程序员带来的困惑。我认为你需要新的开发人员 :) - Andy
3
那些新开发者刚开始时可能是一些天真的编码者;-) 还有一些混淆是因为要适应 Git 本身,这可能是一个挑战,因此我们会从简单的开始... - Philip Oakley
4
拥有提交列表的概念可能是Git中最基本的概念。如果我一开始接触的Git仓库总是只包含一个提交,我想我会更加困惑。 - Zaz
Git 2.5(2015 年第二季度)支持单个提取提交!我已经编辑了我的答案,现在引用“从远程 Git 存储库拉取特定提交”。 - VonC
1
目前似乎可以这样做:查看https://dev59.com/W2w05IYBdhLWcg3w6GLH#21217267 - pedrorijo91
显示剩余2条评论
3个回答

22
作为 Git 的主要维护者,Junio C. Hamano(Git 维护者)如他所说

规则不是更像:

如果您的浅存储库历史记录不足够长并且其他存储库在您的截断历史之前分叉,您将无法计算公共祖先,因此无法推送。

更新2014年:请查看"git clone --depth 1(浅克隆)比它表现出来的有用吗?":该限制将随 Git 1.9 解除!

更新2015年:使用Git 2.5+,您甚至可以获取单个提交。请参见“从远程 git 存储库拉取特定提交


原始答案(2011年8月):

实际上,仔细想想,它比“无法计算公共”强得多。

历史记录可能如下所示:

          R---R---R
         /
  --R---R---X---X---S---S---S

其中S是您在浅仓库中拥有的提交,R是存在于接收您推送的仓库中的提交。
由于您的历史记录是浅的,因此两个仓库都没有需要存在的提交 'X',以便保持接收方仓库的完整历史记录;接收方一开始就不是浅的,我们也不想使其变得浅。
如果您在一段时间前进行了浅克隆,并且在另一端进展时没有进行通信,而另一端的进展包括重置和重建历史记录,则您将看到类似的拓扑结构。
上图中最左边的 'S' 可能是当您使用深度为 1 的浅克隆时分支的顶部,自那以后,远程端可能已经丢弃了最顶层的三个提交并已重建其历史记录,导致右边的 'R'。
在这种情况下,将无法推送到远程的 HEAD
因此,在某些情况下它可以工作,但不受支持:
如果我必须说些什么...

  • 我认为 "不受支持" 是提供足够信息的简洁方式,但只适用于聪明人。
  • 并非每个人都很聪明;有些人会自己尝试,看到该操作似乎对其有限的尝试起作用,并会得出它大多数时候都有效的结论。
    他们为自己的智慧而感到高兴,因为他们说“大多数时候”而不是“总是”。
    当他们发现它不起作用时,他们会感到沮丧,即使他们已经被警告过了。

有关浅克隆更新过程的更多信息,请参见 "如何更新 git 浅克隆?"。

此外,浅克隆的收益一开始就不是很大:http://blogs.gnome.org/simos/2009/04/18/git-clones-vs-shallow-git-clones/ - VonC
感谢提供Nabble参考。由于当前防火墙的限制,我无法访问该博客,稍后再看。在我们的情况下,我们知道R-R-R会与S-S-S连接,因为我们已经设置好了。现在需要找出哪种方法是可行的[如果有的话]以及其前提条件。 - Philip Oakley
@Philip:delta 是用于推送或拉取操作的。否则,除非您明确触发了对象打包归档的创建,否则一切都将被压缩在基础中。但是,当推回浅克隆时缺少公共历史记录确实是一个潜在的问题。 - VonC
还有一条关于深度问题的评论在git-owner@vger.kernel.org上,http://article.gmane.org/gmane.comp.version-control.git/177617解释得非常清楚。 - Philip Oakley
@VonC 或许你在另一个回答中提供的一些信息也与这里相关?https://dev59.com/W2w05IYBdhLWcg3w6GLH#21217267 - onionjake
显示剩余2条评论

11

有没有一种git工作流程可以实现这个?

有的,它是通过将修复程序作为补丁发送的。 git format-patch专门设计了这个功能。如果你想要更多细节,它被称为“门卫”工作流程。很难相信像你们这样关心“安全和知识产权保护”的组织还没有使用类似的东西,其中一个人或一个小团队负责审核“不受信任”的更改,然后再将其纳入真正的构建中。


根据你的评论,我现在更清楚你的要求了。我建议创建一个“孤立”分支(参见git checkout --orphan),从你想让开发人员开始的任何点开始。只将该分支克隆到另一个对开发人员可访问的不同存储库,并让他们从该存储库正常克隆、推送和拉取。
然后,当您需要重新将他们的更改集成到官方受保护的存储库中时,只需拉取其分支,使用git branch复制一份以避免覆盖原始的孤立分支(以防您之后需要重复此过程),然后将副本重新设置为原始分支点并按照正常方式合并。历史记录将显示他们直接从您的受保护存储库中工作。
这比正常情况要复杂一些,但这是额外隔离的代价。

1
问题不在于他们的更改,而是其他所有分支和深层历史中的内容,此外我们需要缓解管理层的“担忧”。他们只会将其推回到自己的开发分支,而且有必要使其看起来“集成”。因此,存在许多相互冲突的优先事项! - Philip Oakley
“孤立分支”选项看起来确实很有趣。我会好好研究一下,看看在我们当地组织问题方面的利弊。 - Philip Oakley
1
我认为你不能轻易地将孤立分支的更改合并到主分支中,因为没有共同的提交可供搜索。 - eloyesp
制作孤立分支的问题在于它会启动一个完全不同的提交历史记录,这意味着从一个分支合并到另一个分支是不可能的。 - sungiant
1
@eloyesp:你不要将变基操作应用到主分支,而是应该将其应用到“原始分支点”。这样做是完全可行的(我刚刚测试过)。 - Edgar Bonet
@sungiant:你不是从孤立分支合并,而是从它的变基副本合并。 - Edgar Bonet

1

我找到了一个使用git捆绑包的解决方法。

这个解决方案将完全复制相同的提交到另一个仓库,就像“git push”一样,并且不需要重新基于或导致更改提交ID。

不幸的是,它需要访问目标主机的shell(例如ssh)。

我将通过示例展示解决方案。

首先,为演示目的获取一个浅克隆。

让我们从https://github.com/skarnet/s6-rc获取单个提交版本v0.5.0.0,并将其作为浅克隆存储到新存储库中。

在我的示例中,我将在命令中使用shell变量而不是直接包含示例设置,因为这样可以让您直接从此帖子中复制/粘贴指令,然后将变量分配为适用于您情况的不同值。

因此,请随意替换以下变量分配以使用不同的URL和版本。

在我们的示例中,可以使用以下命令创建浅克隆:

$ url=https://github.com/skarnet/s6-rc
$ rel=v0.5.0.0
$ git init ${url##*/} && cd ${url##*/}
$ git pull --depth=1 "$url" $rel:master

这将创建一个名为"s6-src"(使用上述变量值)的子目录,其中包含新克隆的内容。

现在,我们在本地仓库中拥有一个只包含单个提交且所有父级历史记录都缺失的浅克隆。我们将此单个提交捆绑成git捆绑文件:

$ b=$rel.gbnd
$ git bundle create $b HEAD

这将创建一个名为v0.5.0.0.gbnd的文件,利用之前设置的shell变量。
现在,您需要将此文件复制到目标机器上,通常会希望进行推送。(只有git push拒绝从浅克隆中推送,因此不起作用,至少不使用较旧的git版本。)
在目标主机上,按照以下步骤创建一个新的子目录存储库,其中包含与之前捆绑的相同提交:
$ url=https://github.com/skarnet/s6-rc
$ rel=v0.5.0.0
$ git init ${url##*/} && cd ${url##*/}
$ c=`git bundle unbundle $b | cut -d " " -f 1`; echo "$c"
$ git tag $rel $c # optional: create a tag for the imported commit.
$ git reset --hard $c
$ git fetch --depth=1 .

请注意,您应将变量设置为与复制捆绑包的主机上相同的值。
还要注意,如果存储库已经存在,则可以省略“git init”。
就是这样!
但是,后面的说明仅适用于常规检出。
也许您想将浅克隆捆绑包导入到“裸”存储库中。
在这种情况下,请执行以下操作:
$ url=https://github.com/skarnet/s6-rc
$ rel=v0.5.0.0
$ cd ${url##*/}.git
$ c=`git bundle unbundle $b | cut -d " " -f 1`; echo "$c"
$ git tag $rel $c
$ git fetch --depth=1 . $rel

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