如何创建一个空的 master 并从现有分支中提出 PR

Roo*_*nes 6 git

我的仓库中有一个分支:开发。代码已经提交并推送到那里。

\n\n

存储库中没有主分支。

\n\n

我想创建一个主分支,以便我可以向其中提出拉取请求。

\n\n

我已经尝试过两种方法:\n1. 从开发创建主分支:

\n\n
git checkout develop\ngit checkout -b master develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里的问题是没有什么可以比较的:master 和development 已经是一样的了。

\n\n
    \n
  1. 从孤立分支创建主分支:
  2. \n
\n\n
git checkout --orphan master\ngit reset --hard\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里的问题是分支有不同的历史记录,我得到:\n“没有\xe2\x80\x99t任何东西可以比较。(分支)是完全不同的提交历史记录”

\n\n

git 版本 2.17.2 (Apple Git-113)

\n\n

如何创建一个空的 master 以便我可以从现有的开发中提出 Pull 请求?

\n

tor*_*rek 8

只需创建一个分支名称即可。真的,这就是全部。选择一些现有的提交并使分支名称master标识提交。那现在是你的master分支机构。

\n\n

不存在“空枝”

\n\n

不过,已经存在一个问题了,因为Git 中“分支”这个词是不明确的。(请参阅“分支”到底是什么意思?)当我们说“分支”时,有时我们指的是名称,例如masterdevelop,有时我们指的是其他东西。

\n\n

Git 真正存储的是一系列提交。每个提交都有一个分配给它的唯一编号:哈希 ID,8dca754b1e874719a732bc9ab7b0e14b21b1bc10例如看起来像这样。您可以在git log输出中看到这些。

\n\n

每次提交都保存项目的完整快照,作为一组文件(不是文件夹,只是文件:文件夹(如果有)将在运行时根据需要创建以保存git checkout文件)。并且,每个提交都有一些元数据:有关提交的信息,例如谁、何时、为何(其日志消息),以及\xe2\x80\x94至关重要的\xe2\x80\x94前一个的原始哈希ID犯罪。

\n\n

正是这些原始哈希 ID 构成了向后查找的提交链。如果我们使用单个大写字母来伪造哈希 ID\xe2\x80\x94,这对人类来说更有用,但显然不适用于数千个提交\xe2\x80\x94,我们可以绘制一个简单的三提交存储库像这样:

\n\n
A <-B <-C\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里的commitC最新的commit。它记录了 commitB是较早的提交,所以我们说C 指向 B. 另一种说法是 that Bis C\'s Parent。同时 commitB说 commitA出现在 之前B。提交A是任何人所做的第一个提交,因此没有父级。这可以让操作停止:历史记录开始\xe2\x80\x94或结束,如果你像Git那样向后工作\xe2\x80\x94here。

\n\n

有时,当我们说分支时,我们指的是整个提交系列。这个提交链A-B-C,从末尾开始并向后工作,是一个分支。您指示 Git 查找最后一次提交 ,C然后该提交指向较早的提交B,该提交又再次指向。无论有多少次提交,只要我们知道哪个提交是最后一次提交,我们总是可以让 Git 从最后开始,一直回到开头。但是我们如何找到最后一次提交呢?

\n\n

在这个例子中,很简单:只有三个提交,我们让它们使用连续的字母名称,所以显然C是最后一个。但在真实的存储库中,可能有数千次提交:

\n\n
$ git rev-list --all --count\n58314\n
Run Code Online (Sandbox Code Playgroud)\n\n

它们的哈希 ID 看起来完全是随机的。我们如何知道哪个提交是最后一个一个提交?此外,如果我们有一系列提交,例如:

\n\n
             I--J\n            /\n...--F--G--H\n            \\\n             K--L\n
Run Code Online (Sandbox Code Playgroud)\n\n

哪里有两个“最后”提交?

\n\n

这就是分支名称的用武之地

\n\n

分支名称,如master或,仅保存最后develop一个分支的实际哈希 ID,仅保存我们想要称为“分支的一部分”

\n\n
             I--J   <-- master\n            /\n...--F--G--H\n            \\\n             K--L   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

通过使名称master指向 commit J,我们声明这J是该分支的最后一次master提交。通过使名称develop指向 commit L,我们声明这L是分支的最后一次提交develop提交。

\n\n

请注意,提交H、和G、以及F以及之前出现的任何其他内容都在两个分支上。这是 Git 的一个独特功能:通常,大多数提交都是在每个分支上。它只是最后几个\xe2\x80\x94或几百个,或任何\xe2\x80\x94,仅在一个或两个或十个分支上。

\n\n

对于存在的分支名称,它必须指向某个现有的提交。您可以在不使用分支名称 \xe2\x80\x94 的情况下进行提交,这有点棘手:它使用 Git 所谓的分离 HEAD模式 \xe2\x80\x94 但您不能拥有分支名称,除非它指向某个实际提交。通过指向该提交,该分支名称声明该提交该分支中的最后一次提交。

\n\n

即使名称指向其他人链中间的提交也是如此。假设你没有大师,但

\n\n
...--F--G--H--K--L   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在,您可以告诉 Git:创建名称masterH,通过查找 H 的实际哈希并运行来指向提交:

\n\n
git branch master <hash-of-H>\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者:

\n\n
git checkout -b master <hash-of-H>\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在你有:

\n\n
...--F--G--H   <-- master\n            \\\n             K--L   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,提交根本没有改变。您刚刚添加了标签master\xe2\x80\x94a 分支名称\xe2\x80\x94 以记住 commit 的哈希 ID H

\n\n

分支名称会记住一次提交的哈希 ID,但还有另一个特殊功能

\n\n

master现在您有了标识 commit 的名称H,您可以:

\n\n
git checkout master\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后做一些工作并进行新的提交。当您进行新的提交时,Git 会打包项目的新快照并将其写出。Git 将您的姓名添加为此新提交的作者和提交者,设置其日期和时间戳,并使用您的日志消息作为此提交现在存在的原因。Git 将现有提交的原始哈希 ID 添加H为新提交的父提交。然后,Git 将新提交保存到“所有现有提交”的数据库中,该数据库为新提交分配新的唯一哈希 ID。我们将这个新的哈希 ID 称为I

\n\n
             I   <-- ???\n            /\n...--F--G--H   <-- ???\n            \\\n             K--L   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在有一个棘手的部分:创建 commit 后I,Git会移动当前分支名称,使其指向新的提交。由于当前分支名称master,Git 会进行更改master,使其指向新的提交I

\n\n
             I   <-- master (HEAD)\n            /\n...--F--G--H\n            \\\n             K--L   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

你现在应该问自己两个问题:

\n\n
    \n
  1. Git 如何知道它应该移动master而不是develop应该移动?
  2. \n
  3. 现有提交会发生什么H
  4. \n
\n\n

这里问题 1 的答案如图:我已将特殊名称附加HEAD到 name 上master。当您使用git checkout选择分支时,Git 会将此名称 附加HEAD分支。这不仅告诉 Git 你现在签出了哪个提交,还告诉 Git 哪个名称当您进行新提交时需要更新

\n\n

Q2 的答案并不那么明显,直到您发现 Git 的一般原则,即:一旦进行提交,该提交将永远冻结。 任何现有提交中的任何内容都不能由您或 Git 更改\xe2\x80\x94。原因是提交的实际哈希 ID 是提交内数据的校验和。更改任何数据,即使是单个位,并且更改校验和\xe2\x80\x94,你会得到一个新的、不同的提交,而不是原始提交。原始提交保持不变。

\n\n

提交内的文件全部被冻结、只读并永久保存。它们也被压缩,并采用特殊的 Git-only 格式。这有助于防止 Git 存储库立即变得巨大:如果每次提交都保存每个文件\xe2\x80\x94(确实如此)\xe2\x80\x94,存储库的大小如何不会完全失控?Git 使用的技巧之一是,如果您保存文件的相同版本,它只会重新使用现有的冻结副本。它可以做到这一点,因为冻结的副本无法更改。

\n\n

(我喜欢将这些冻结的、仅限 Git 的文件副本称为“冻干”。在 Git 存储库中,冻干的文件实际上没有名称:它们具有哈希 ID 号。请注意,它们必须解冻并“重新水化”为普通文件以供您使用\xe2\x80\x94冻结的副本对于完成任何工作是无用的。这就是为什么git checkout要创建一个工作树,您可以在其中完成您的工作。提交中的冻干文件成为工作树中的普通文件,Git 首先使用与提交一起存储的冻干名称创建保存它们所需的任何文件夹。)

\n\n

每个 Git 存储库都有自己单独的分支名称

\n\n

通常,我们用来git clone创建存储库,然后将我们的工作发送回我们稍后开始使用的存储库git push。该git clone过程从原始存储库复制提交。但它并没有完全复制分支名称。相反,它采用它们的分支名称\xe2\x80\x94及其存储库的master等等develop\xe2\x80\x94并重命名它们,调用它们origin/master等等origin/develop

\n\n

刚刚克隆了他们的 Git,我们的Git根本没有分支名称!它有他们所有的分支名称,重命名为我们的origin/* 远程跟踪名称1 但是我们的 Git 通常希望我们有一个分支\xe2\x80\x94 master,但如果他们甚至没有master,我们的 Git 现在会选择一些其他名称,例如develop. 作为最后一步git clone,我们的 Git 必须:

\n\n
    \n
  • 选择一个分支名称,
  • \n
  • 在我们的存储库中创建该分支名称,指向与该名称相同的提交,并且origin/name
  • \n
  • git checkout按该分支名称提交。
  • \n
\n\n

第一步选择的名称是:

\n\n
    \n
  • -b如果您说 ,则为您在论证中提供的名称,或者git clone -b name
  • \n
  • 他们的Git 推荐的名称(如果他们提出了推荐),或者
  • \n
  • master
  • \n
\n\n

最后一个master是特殊情况,用作最后的手段。2

\n\n

因此,如果您克隆一个只有 的存储库develop,您将获得自己的origin/develop名称\xe2\x80\x94a 远程跟踪名称,而不是真正的分支\xe2\x80\x94,指向其develop. 但是,作为 的最后一步git clone,您的 Git 将创建您自己的develop,指向同一个提交,然后git checkout develop您就可以使用您的一个分支名称 ,develop并签出此提交。

\n\n

master如果您现在在存储库中创建新名称,您将得到以下结果:

\n\n
...--o--o   <-- develop, master, origin/develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

HEAD附加到developmaster,具体取决于您创建名称的方式master:您使用了git branch, 或 吗git checkout -b?)。

\n\n

您现在可以运行git push origin master告诉他们的Git:创建一个分支名称master,指向与您的develop. 所做git push的就是向他们的 Git 发送您拥有的任何提交,他们不会发送您所做的任何提交,他们将需要创建或更新某些分支名称部分 \xe2\x80\x94,然后礼貌地要求他们创建或更新一些分支名称以匹配您的一些分支名称:

\n\n
git push origin branch1 branch2 branch3\n
Run Code Online (Sandbox Code Playgroud)\n\n

让您的 Git 在origin您的名字 \xe2\x80\x94 下存储的 URL 处调用origin位于 \xe2\x80\x94 的 Git,并向他们发送所需的任何提交,然后要求他们设置名为、 、 的分支,并指向相同的提交你的名字、、 和指向。由于他们只有在拥有这些提交的情况下才能执行此操作,因此如果他们没有这些提交,您的 Git 将首先向他们发送提交。您的 Git 不仅会向他们发送这些提示提交(对于三个分支),还会发送任何历史记录\xe2\x80\x94 任何早期提交\xe2\x80\x94 需要将这些提示提交连接到其存储库中的其余提交。branch1branch2branch3branch1branch2branch3

\n\n
\n\n

1 Git 文档将这些远程跟踪分支名称称为。但从技术上讲,它们并不是分支名称。特别是,如果你git checkout origin/master,你最终会得到一个分离的头。所以我更喜欢直接称它们为远程跟踪名称,完全放弃“分支”这个词。

\n\n

2如果您克隆一个完全空的存储库,没有提交,则其 Git 没有分支名称,因为它没有提交,而分支名称需要提交。所以在这种情况下他们不推荐任何东西。然后,您的 Git 必须用作master分支名称。当然,也没有任何提交。因此,现在您会遇到与创建新的空存储库时相同的特殊状态git init:您位于分支master,但分支master不存在!您位于一个不存在的分支上。Git 的其他部分将此称为孤儿分支。这是一种奇怪的状态,您的下一次提交会创建您所在的分支,以便您可以正确地使用它。在此之前,您只需记录要创建的HEAD分支的名称。

\n\n
\n\n

审查

\n\n

以下是您已学到的内容(以及需要了解的内容):

\n\n
    \n
  • Git以冻结的仅限 Git 的格式存储提交,即存储文件。您的 Git 会将文件提取到工作树中,您可以在其中查看和使用它们。工作树不是存储库数据库的一部分,并且不会被复制:只有提交才会像这样批量复制。(您可以复制分支名称,但一次只能复制一个名称,而不是像提交那样批量复制。)
  • \n
  • 提交由哈希 ID 标识。每个提交的哈希 ID 都是唯一的。该git log命令将显示哈希 ID。宇宙中的每个 Git 都必须就哈希 ID 达成一致。3
  • \n
  • 每个提交都会存储其前一个提交的哈希 ID。这些形成了向后看的链条。
  • \n
  • Git 使用分支名称来查找提示提交。每个名称存储一个哈希 ID:应被视为分支末尾的提交的 ID 。Git 从那里开始向后工作。
  • \n
  • 历史只不过是一个向后看的承诺链。
  • \n
  • 提交是共享的\xe2\x80\x94,对于git clonegit fetch来说最重要git push。名称也很重要,因为它们是每个 Git 查找其提交的方式,但名称对于每个 Git 来说都是本地的:它们不一定是通用的,就像哈希 ID 一样。特别是,随着时间的推移,分支名称预计会发生变化。
  • \n
  • 当您进行新提交时,分支名称会自动移动以包含新提交。当它们确实像这样移动时,现有的早期提交就成为历史的一部分。如果您要“向后”移动分支名称\xe2\x80\x94,以便历史提交成为分支的尖端(您可以这样做,我们只是没有介绍如何操作)\xe2\x80\x94,它可能是很难找到后来的提交!(你知道它们的哈希 ID 吗?暂时有办法找到它们。)
  • \n
  • 分支这个词是不明确的:当你说或听到它时,想想你的意思是分支名称,还是一系列提交,从最后一个开始向后工作。如果有人说远程分支,他们是否指的是分支名称,例如master其他Git 中看到的 ? 或者他们的意思是我的Git中看到的远程跟踪名称?origin/master 这些是不同的名称,并且可能指向不同的提交!从现在到您下次查看时,其他 Git 中的分支名称可能会发生变化。谁有权访问其他 Git 存储库,他们现在正在对其执行什么操作?
  • \n
\n\n
\n\n

3 Git\xe2\x80\x94 未来将出现复杂情况,没有人确切知道什么时候,\xe2\x80\x94 中的哈希 ID 将被重新编号,从 SHA-1 变为 SHA-256。具体如何处理尚未确定。

\n

  • @m01010011:这主要是十多年来与 Git 打交道来之不易的知识。在某个时候(2016 年左右?)我决定需要揭开 Git 的神秘面纱。这很困难,因为 Git 本身在不断发展,但至少在这一切背后有一些基础理论。 (2认同)