Git: 我忘记创建分支 master,如果我的存储库已经有另一个分支,我该如何创建它?

Git: i forgot create branch master, how i can create it if my repository already has another branch?

我的存储库有问题,但我没有办法解决它。请指正。

我在 bitbucket 上创建了一个存储库,克隆了它并收到消息 “您似乎克隆了一个空存储库”。接下来,我使用命令 git checkout -b my-feature 创建了新分支 my-feature 并添加了一些代码。最后,我使用命令 git push --set-upstream origin my-feature 推送到远程存储库。全部完成,但我刚刚意识到我的存储库没有分支主机。现在,它只有分支 my-feature.

我如何创建分支 master 但仍然保留 my-feature 分支(这意味着我不想创建新的存储库)?

您现在拥有的是一个 Git 存储库——或者更确切地说,是两个非常相似的存储库;我们暂时将其视为一个——只有一个 b运行ch 名称,my-feature这不是错误情况。不要求任何 Git 存储库中有 b运行ch 名称 master

不过,如果您想要一个 b运行ch 名称 master,您所要做的就是创建一个。 master.1 A b运行ch name in Git 没有什么特别的,只是一种查找方式具体提交。 Git 不是 about b运行ches。 Git 都是关于提交的。


1好吧,几乎 没什么:到处都是一些 运行dom 的小东西。例如,大多数 人类 会认为名字 master 是有意义的。


Git 是关于提交

要了解这里发生了什么,以及为什么可以按原样创建一个 master,让我们看看 Git 是如何工作的。同样,Git 就是关于 提交 。所以要知道的主要事情是提交是什么,我们如何找到它们,以及它们如何在存储库中积累。

  • 首先要知道的是每个提交都有编号。这些数字真的很大,看起来很丑陋而且很奇怪:它们不像 1-2-3 那样简单地计数。例如,this commit is numbered 71ca53e8125e36efbda17293c50027d31681a41f。任何给定提交的编号对于该提交是完全唯一的。如果您在 Git 存储库中有此提交,它会有相同的编号。如果你没有这个提交——你没有,因为它是 Git 的提交,在 Git 的 Git 存储库中——那么你没有任何提交数.

    唯一性属性是为什么这些数字又大又丑。它们是通过 运行 对提交内容使用加密哈希函数计算得出的。这有一个后果:数字与内容紧密相关,因此内容永远不会改变。任何提交的任何部分——任何内部 Git 对象,真的——永远不会改变,因为它的数量取决于它的内容。这就是魔法:这就是两个不同的 Git 程序如何同意只有 this commit 获得 this number.2

    因为数字来自散列函数,所以它们被称为散列 ID。因为最初的哈希函数是(现在仍然是)SHA-1,所以它们也被称为 SHA-1 IDsSHA-1s .因为 Git 正在转向 still-larger 哈希,Git 正在将内部名称从 SHA1 更改为 OID,或对象 ID。 (提交是四种内部对象类型之一,它们都使用相同的哈希系统。)

    我自己大多称这些 哈希 ID,但请注意其他名称。

  • 要知道的另一件事是每个提交存储两部分:

    • 提交的主要数据是所有文件的快照Git在提交时知道的。我们赢了这里不详细介绍快照的来源,但它不是你的工作树,它是 Git 使用三个名称的东西:index暂存区,或缓存。所有的名字指的是同一个东西。

    • 除了快照之外,每个提交还包含一些元数据,或者关于提交本身的信息。这包括提交作者的姓名和电子邮件地址,例如,date-and-time-stamp 表示提交的时间。您输入的日志消息,解释 为什么 您进行提交,在此处;您会在 git log 输出中看到该日志消息。

      对于 Git 来说至关重要的是,Git 会在此元数据中添加一些内容供自己使用。每个提交都会存储一些 较早 提交的哈希 ID 列表。

      大多数提交只存储一个哈希 ID,用于一个较早的提交。我们可以将这些 普通提交 与没有较早哈希 ID 或 two-or-more 较早哈希 ID 的提交区分开来。至少有一个提交——第一个提交——实际上 不能 存储任何更早的提交哈希 ID,所以它不会;我们称之为 root commit.3


2pigeonhole principal tells us that this scheme must eventually fail. The number of bits in the hash ID is designed to make it such that the failure is so many trillions of years in the future that we don't care about it. There's a small flaw in this idea,不过暂时没问题。

3一个仓库可以有多个根提交,但这至少有点不寻常。此处不赘述。


哈希 ID 太笨拙:输入 b运行ch 名称

让我们绘制一个简单的存储库,其中只有三个提交。我们将调用这三个提交,而不是它们实际的丑陋的大哈希 IDABC——我们将这样绘制它们:

A <-B <-C

请记住,每个人都有一个快照和一些元数据。提交 C 是这三个提交中的 last,因此它是最近的,在某种程度上也是最重要的。在提交 C 中,我们有最新的快照,形式为 read-only。我们还有元数据,包括早期提交的哈希 ID B。让我们“开始”提交 C,但使用此哈希 ID。

在提交 B 中,我们有快照和元数据,包括早期提交 A 的哈希 ID。在 提交 A 之前,我们可以 比较 保存在 BC 中的文件.所有 相同 的文件都是无趣的,但对于 已更改 的文件,我们可以显示更改。这非常有用——这就是 git showgit log -p 会做的,如果我们 on/using 提交 C:它将显示 更改 BC.

如果我们使用 git log,我们现在可以 Git 向 返回 一步,从 CB .现在我们有了快照和元数据,包括提交 A 的哈希 ID,但这次我们将继续查找提交 A。通过将其快照与 B 中的快照进行比较,我们可以看到 发生了什么变化 。因此 git log -p 可以打印提交 B 的日志消息,然后显示 更改 AB.

再一次,我们可以 Git 后退一步,提交 A。提交 A,作为第一个提交,没有更早的提交:其之前的提交哈希 ID 列表是空的。所以提交 A 是我们的根提交,并且 A 中的所有文件都是“新的”。 git log -p 命令只会将它们显示为新文件,并且由于没有 较早的 提交,它将在此处停止。

请注意 Git 工作 向后 。 Git 中的所有事物通常都是如此:它们总是向后工作,从最新到最早。原因是那些嵌入的哈希 ID:它们看起来像指向后方的箭头。我们从提交 C 开始,因为它是 最近的 提交。但是,我们确实必须知道提交 C.

的哈希 ID

我们可以记下最新提交的哈希ID。我们可以将它保存在一张纸片、一块白板或其他任何东西上。从末尾开始,我们告诉 Git 查看 C,Git 可以自己找到所有 较早的 提交。但这似乎很愚蠢。我们有一台 计算机 。为什么不让 计算机 将提交 C 的哈希 ID 保存在某处?

这就是 b运行ch 名称的作用。 b运行ch 名称仅包含 最新的哈希 ID 提交是 b运行ch 的一部分。我们可以这样画:

A <-B <-C   <--branch

为了进行 new 提交,我们 Git 打包了快照和元数据。我们新提交的元数据,我们称之为 D,将包括提交 C 的实际哈希 ID,这是通过读取存储在 b运行ch 名称下的哈希 ID 找到的branch。所以新提交 D 将指向现有提交 C:

A--B--C
       \
        D

(我选择了线而不是箭头,因为我这里没有很好的箭头图形。我们知道任何提交中的任何内容都不会改变,提交中的箭头总是向后指向,然后出来的提交,因此不能改变它们指向的位置。所以这些线也同样有效,只要我们记住 Git 不能跟随它们 forwards,只能向后。)

现在提交 D 存在并且有一个哈希 ID——通过对提交中的所有内容进行哈希计算,包括我们 创建时的 date-and-time-stamp commit D——现在我们可以 Git 将哈希 ID 写入 name branch,这样 name 指向提交 D 而不是提交 C:

A--B--C
       \
        D   <-- branch

现在我们可以重新整理整个事情了:

A--B--C--D   <-- branch

B运行ch 名称查找提交,无论有多少 b运行ch 名称

让我们再次开始我们的 three-commit 设置,但还没有设置 D。让我们将名字称为 main(就像 GitHub 现在通常做的那样)而不是 master,但事实上,任何名字都可以。让我们把它画下来,但这次我想在我们的画中再添加一件事:

A--B--C   <-- main (HEAD)

括号中的新内容是HEAD。我们用这个来标记 我们正在使用的 b运行ch 名称 。目前只有一个名称,因此我们 可以 使用的名称只有一个,但我们将通过添加一个新名称来更改它。

现在让我们创建一个新名称,develop我们必须选择一些现有的提交 来使这个名称存在,因为 b运行ch 名称 需要 指向一些现有的提交。所以让我们选择提交 C,它是 main 上的最新提交,也是我们现在使用的提交。我们运行:

git branch develop

并得到:

A--B--C   <-- develop, main (HEAD)

注意现在两个 b运行ch 名称都指向提交 C。这意味着提交 Cboth b运行ches 上的最后一次提交。很好,在 Git 中;这意味着 所有三个提交 也在两个 b运行ches 上。

特殊名称 HEAD 仍然附加到 main,所以我们实际上仍然 使用 这个名称 main。让我们 运行 git checkout develop 这样做:

A--B--C   <-- develop (HEAD), main

我们不再使用 name main 作为我们当前的 name。它仍然存在并且仍然指向提交 C,但现在 HEAD 附加到名称 develop。该名称还指向提交 C,因此无需更改任何其他内容, 也不会更改任何其他内容。我们仍然“在”提交 C,但是现在,我们在“在”它,因为我们在“在”名字 develop.

现在我们将像以前一样进行新的提交 D。 Git 将打包所有内容并写出一个提交,它会获得一个新的、唯一的哈希 ID。 (如果我们放入完全相同的文件,使用完全相同的提交消息,并在完全相同的 time 进行提交,我们将获得与上次相同的哈希 ID——但是如果 time 不同,或者其他任何内容发生了变化,我们将得到一个完全不同的哈希 ID。不过,我仍将其称为 D。)

作为提交的最后一步,Git 将更新 current b运行ch name,但现在是 develop,不是main,所以现在我们得到:

A--B--C   <-- main
       \
        D   <-- develop (HEAD)

我们还有两个b运行ch名字;我们仍然有 HEAD 附加到 develop;但是现在我们有一个新的提交 D,并且 name develop 选择了新的提交 D.

我们现在可以切换到最新的 main 提交 git checkout main,它选择提交 C,或者最新的 develop 提交 git checkout develop,选择提交 D.

请注意,in 中的文件每次提交都是 read-only,在当时以它们的形式永久冻结。这意味着 Git 必须复制提交的文件 out,以便我们可以使用和更改它们。我们不会在这里详细介绍这一点,但请记住:您看到和使用的文件 不在存储库中! 它们 复制存储库中提取。

您是如何陷入这种情况的:b运行ch 名称在没有提交的情况下无法存在

刚开始时,您使用 Bitbucket 创建了一个空存储库。绘制一个空的存储库不是很有趣:


没有提交,要使 b运行ch 名称存在,它必须 指向一些现有提交 .没有!所以 b运行ch 名称也不能存在。

然后您克隆了这个空的存储库,在您的机器上制作了一个副本。当你这样做时,你会收到警告:

You appear to have cloned an empty repository

Git 给你这个警告是因为你处于同样的奇怪状态:没有提交,就没有 b运行ch 名称存在。

尽管没有b运行ch名称存在,Git仍然需要特殊名称HEAD“附加到" 当前的 b运行ch 名称。在这种情况下发生的是 Git 将初始虚拟名称推入内部 HEAD 文件,以便 HEAD 附加到 mastermain 或其他任何内容你选择的名字作为你的默认首字母 b运行ch,或者给 git init 如果你有一个带有名字参数的 git init 的新版本。

这时候可以运行git checkout -b想吃多少就吃多少。每个人都会将一个新名称推入特殊的 HEAD 名称中。 b运行ch name 继续不存在,and 继续是 current b运行ch name,在你处于不存在的 b运行ch 的这种奇怪的特殊状态。

所以,当你 运行:

git checkout -b my-feature

你告诉你的 Git 设置你的空存储库,这样你就可以在名为 my-feature.

的 non-existent b运行ch 上

当您在此状态下进行第一次提交时,这会创建 b运行ch。 任何人在一个空的存储库中进行的第一次提交是 根提交,没有更早的提交:

A   <-- my-feature (HEAD)

第一个提交,我们可以称之为 A,不管它得到什么哈希 ID,它没有父项,所以它只是坐在那里。但是现在有一个提交!现在可以有一个b运行ch的名字了!事实上,现在可以有无限多个 b运行ch 名字。他们都只需要指向 A。这是唯一的提交,因此所有 b运行ch 名称都必须指向此处。让我们通过 运行ning git branch xyzzy(使用 current-and-only-available 提交)在这里创建一个 b运行ch xyzzy

A   <-- my-feature (HEAD), xyzzy

创建 second 提交后 B,您有两个提交:

A   <-- xyzzy
 \
  B   <-- my-feature (HEAD)

您可以继续创建新的 b运行ch 个名字,这一次,您可以选择提交 A,或提交 B,让他们 point-to。所以现在你可以创建一个 master 指向 AB.

你现在需要做什么

您现在要做的就是选择一些现有的提交。 运行 git log 在您的(单个)b运行ch my-branch 上获取您的提交列表。如果只有一个,那就是唯一可用的。如果有多个,则那些是可用的。选择一些可用的提交并告诉 git branch 在那里放置一个 b运行ch 名称。假设可用哈希之一是 a123456.4 然后 运行:

git branch master a123456

和 Git 将在您自己的存储库中创建名称 master,指向哈希 ID 以 a123456.

开头的现有提交

也可以使用git checkout -b master <em>hash</em>,意思是create the name ,然后附加到它。如果您省略哈希 ID,git branchgit checkout -b 都假定您打算使用 当前提交 ,如通过使用特殊名称 [=85] 找到的=].

现在您有了 name,使用 git push 请求 Bitbucket 上的 Git 创建 相同的名称 他们的 存储库中,使用相同的哈希 ID:

git push --set-upstream origin master

--set-upstream 是可选的,但会做你想做的事情。5


4实际的散列会很长而且很难输入。使用 cut-and-paste 用鼠标或其他东西来抓取整个东西,或者只是输入前四个或更多字符。如果您输入的内容类似于散列 ID 的第一部分,Git 将尝试确定它是否是以您输入的内容开头的较长散列 ID 的缩写。

5参见