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

yhuiod9q  于 2023-06-20  发布在  Git
关注(0)|答案(2)|浏览(277)

我的repo中有一个分支:开发。代码已经提交并推到那里。
仓库里没有master分支。
我想创建一个主分支,这样我就可以向它提出一个拉取请求。
我已经尝试过两种方法:1.从develop创建master分支:

git checkout develop
git checkout -b master develop

问题是没有什么可比较的master和develop都是一样的。
1.从孤儿创建master分支:

git checkout --orphan master
git reset --hard

这里的问题是分支有不同的历史,我得到:“没有什么可比较的。(分支)是完全不同的提交历史”
git版本2.17.2(Apple Git-113)
我如何创建一个空的master,以便我可以从已经存在的develop中提出合并请求?

ie3xauqp

ie3xauqp1#

创建一个分支名称。这就是所有的事情,真的。选择一些现有的提交,并使分支名称master标识 * that * 提交。这就是您的master分支。

不存在“空分支”

不过,这里已经有一个问题了,因为在Git中,* branch * 这个词是有歧义的。(参见What exactly do we mean by "branch"?)当我们说“分支”时,有时我们指的是一个 * 名称 ,如masterdevelop,有时我们指的是其他东西。
Git真正存储的是一系列的 * commit 。每个提交都有一个唯一的编号:一个哈希ID,例如看起来像8dca754b1e874719a732bc9ab7b0e14b21b1bc10。您可以在git log输出中看到这些。
每次提交都保存项目的完整快照,作为一组文件(不是文件夹,只是文件:文件夹(如果有的话)将在运行git checkout时根据需要创建以 * 保存 * 文件。而且,每个提交都有一点 * 元数据:
关于提交的信息,比如谁做了它,什么时候做的,为什么做(它的日志消息),最重要的是-
前一次 * 提交的原始哈希ID。
正是这些原始哈希ID构成了向后看的提交链。如果我们使用一个大写字母来伪造散列ID-这对人类更有用,但显然不适用于数千个提交-我们可以绘制一个简单的三提交仓库,如下所示:

A <-B <-C

这里的commit C是 * latest * commit。它记录了提交B是较早的提交,所以我们说C * 指向 * B。另一种说法是BC的 * 父代 *。同时commit B表示commit A先于B。Commit A是第一次提交,因此没有父级。这将使操作停止:历史在这里开始或结束,如果你像Git一样向后工作的话。
有时候,当我们说 * branch * 时,我们指的是整个系列的提交。这个提交链,A-B-C,从末尾开始向后工作,是一个分支。您指示Git查找 * last * commit,C,然后它指向更早的commit B,后者又指向更早的commit。无论有多少个提交,我们总是可以让Git从最后开始,然后一直回到最开始,只要我们知道哪个提交是 * 最后 * 提交。但我们如何找到最后一次提交呢?
在这个例子中很简单:只有三个提交,我们让它们使用连续的字母名称,所以显然C是最后一个。但是在一个真实的存储库中,可能会有成千上万的提交:

$ git rev-list --all --count
58314

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

I--J
            /
...--F--G--H
            \
             K--L

有 * 两个 *“最后一个”提交?

这就是分支名称的作用

一个分支 * name *,比如masterdevelop,只保存了我们想称之为“分支的一部分”的 * last * commit的实际哈希ID:

I--J   <-- master
            /
...--F--G--H
            \
             K--L   <-- develop

通过将名称master指向commit J,我们声明J是分支master的 * 最后 * commit。通过将名称develop指向commit L,我们声明L是分支develop的 * 最后 * commit。
请注意,提交HGF,以及之前出现的任何其他提交,都在 * both * 分支上。这是Git的一个特殊功能:通常,大多数提交都在 * every * 分支上。只有最后的几个,或者几百个,或者别的什么,只在一两个或十个分支上。
对于一个分支名称存在,它 * 必须 * 指向某个现有的提交。你可以在不使用分支名称的情况下进行提交-这有点棘手:它使用Git称为 * detached HEAD * 模式-但是你不能有分支名称,除非它指向某个实际的提交。通过指向该提交,该分支名称声明该提交 * 是该分支中的最后一次提交。
即使名称指向其他人链中间的提交,也是如此。假设你没有master,但是有:

...--F--G--H--K--L   <-- develop

现在你可以告诉Git:* 创建名称master,指向commit H *,方法是找到H的实际散列并运行:

git branch master <hash-of-H>

或:

git checkout -b master <hash-of-H>

现在您拥有:

...--F--G--H   <-- master
            \
             K--L   <-- develop

请注意,* commits * 根本没有改变。您只是添加了标签master-一个分支名称-来记住commit H的哈希ID。

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

现在您有了名称master来标识提交H,您可以:

git checkout master

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

I   <-- ???
            /
...--F--G--H   <-- ???
            \
             K--L   <-- develop

现在是棘手的部分:在创建了commit I之后,Git * 移动了当前分支的名称 *,使其指向新的commit。由于当前分支 namemaster,Git修改了master,使其指向新提交I

I   <-- master (HEAD)
            /
...--F--G--H
            \
             K--L   <-- develop

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

  1. Git怎么知道应该移动master而不是develop
    1.现有的commit H会发生什么?
    Q1的答案在图中:我将特殊名称HEAD附加到名称master。当你使用git checkout选择一个分支时,Git会将这个名字HEAD * 附加到该分支上。这不仅告诉Git你现在 checkout 了哪个提交,还告诉Git当你进行新的提交时,哪个 name 需要更新。
    Q2的答案并不明显,直到你发现Git的一般原则,即:**一旦提交,该提交将被永久冻结。***任何现有提交中的任何内容 * 都不能被更改--你不能,Git也不能。这样做的原因是提交的实际哈希ID是提交内部数据的校验和。更改任何数据,即使是一个位,也会更改校验和-您将得到一个新的、不同的提交,而不是原来的提交。原始提交保持不受干扰。
    提交中的文件都是冻结的、只读的,并且永远保存。它们也被压缩了,并且是一种特殊的Git格式。这有助于防止Git仓库立即变得巨大:如果每次提交都会保存每个文件--它确实是这样做的--仓库的大小怎么会不完全失控呢?Git使用的一个技巧是,如果你保存的是一个文件的 * 相同 * 版本,它只是 * 重新使用现有的冻结副本 *。它可以做到这一点,因为冻结的副本 * 不能 * 改变。
    (我喜欢把这些冻结的、只支持Git的文件副本称为“冻干”。在Git存储库中,这些冻干的文件实际上没有名字:他们有散列ID号码代替。请注意,它们必须被解冻并“再水化”为普通文件,以便您使用它们冻结的副本对于完成任何 * 新 * 工作都是无用的。这就是为什么git checkout创建了一个 * 工作树 *,在那里你可以做你的工作。提交中的冻干文件变成了工作树中的普通文件,Git首先使用与提交一起存储的冻干名称创建任何需要保存它们的文件夹。

每个Git仓库都有自己独立的分支名称

通常,我们使用git clone来创建我们的存储库,然后将我们的工作发送回我们后来使用git push开始的存储库。git clone进程从原始存储库复制 commits。但它并不完全复制 * 分支名称 。相反,它获取 * 他们的 * 分支名称- 他们的 * 存储库的masterdevelop等等-并 * 重命名 * 它们,称它们为origin/masterorigin/develop等等。
我们的Git刚刚做了一个全新的Git克隆,根本没有分支名称!它有他们所有的分支名称,重命名为我们的origin/* * 远程跟踪名称 *。1但是我们的Git希望我们有一个分支-通常是master,但是如果他们甚至没有master,我们的Git现在会选择其他名称,例如develop。作为git clone的最后一步,我们的Git必须:

  • 选择一个分支名称,
  • create 我们仓库中的分支名称,指向与origin/*name*名称相同的提交,以及
  • git checkout提交的分支名称。

在第一步中选择的名称为:

  • -b参数中提供的名称(如果是git clone -b *name*),或者
  • Git推荐的名字 their,如果他们提出了建议,或者
  • master

最后一个master是一个特例,用作最后的手段。
因此,如果您克隆一个只有develop的存储库,您将获得自己的origin/develop名称-一个远程跟踪名称,而不是真正的分支-指向其develop中的最后一次提交。但是作为git clone的最后一步,你的Git将 * 创建 * 你自己的develop,指向同一个提交,然后是git checkout develop,这样你就在一个分支名称develop上,并 checkout 了这个提交。
如果你现在在 your 仓库中创建新名称master,你会得到:

...--o--o   <-- develop, master, origin/develop

(with HEAD附加到developmaster,具体取决于您如何创建名称master:你用的是git branch还是git checkout -b?).
现在可以运行git push origin master来告诉 their Git:* 创建一个分支名称master,指向与你的develop相同的提交。* git push所做的是将你的提交和他们没有的提交(你所做的任何提交,他们需要用于 create or update some branch name 部分)发送给他们的Git,然后礼貌地要求他们创建或更新一些 * 他们的 * 分支名称,以匹配一些 * 你的 * 分支名称:

git push origin branch1 branch2 branch3

让你的Git在origin上调用Git-在你的名字origin下存储的URL-并向他们发送他们需要的任何提交,然后要求他们设置 * 他们的 * 分支名为branch1branch2branch3,以指向 * 你的 * 名字branch1branch2branch3指向的相同提交。因为他们只有在有这些提交的情况下才能这样做,所以如果他们没有提交,你的Git会首先发送给他们。你的Git不仅会给他们发送这些 tip 提交(针对三个分支),还会发送任何 history--任何早期的提交--需要将这些tip提交与其仓库中的其余提交联系起来。
1Git文档调用这些 * 远程跟踪分支名称 。但他们不是技术上的“分支”名称。特别是,例如,如果您使用git checkout origin/master,则会得到一个分离的HEAD。所以我更喜欢称它们为“远程跟踪名称”,完全放弃了“分支”这个词。
2如果你克隆了一个完全空的仓库,没有提交,他们的Git就没有分支名,因为它没有提交,而分支名需要提交。所以在这种情况下,他们什么都不推荐。你的Git必须使用master作为你的分支名。当然,
你 * 也没有任何提交。所以现在你会遇到和你用git init创建一个新的、空的存储库时相同的奇怪状态:你在分支master上,但是分支master不存在!你在一个根本不存在的分支上。Git的其他部分称之为“孤儿分支”。这是一种奇怪的状态,在这种状态下,你的下一次提交 * 创建 * 了你所在的分支,这样你就可以正确地在上面了。在此之前,您的HEAD只记录要 create 的分支的名称。

回顾

以下是你学到的(和需要知道的):

  • Git以冻结的Git专用格式存储 commits,其中存储文件。你的Git会将这些文件解压到一个 * 工作树 * 中,在那里你可以看到并使用它们。工作树 * 不是 * 仓库数据库的一部分,不会被复制:只有 commits 才会像这样被大量复制。(你可以复制分支名称,但每次只复制一个名称,而不是像提交那样全部复制。
  • 提交由散列ID标识。散列ID对于每个提交是唯一的。git log命令将显示哈希ID。宇宙中的每个Git都必须同意哈希ID。3
  • 每个提交存储其前任提交的散列ID。这些形成向后看的链。
  • Git使用 * 分支names* 来查找 tip commits。每个名称存储 * 一个 * 哈希ID:应该被认为是分支的 end 的提交的ID。Git从那里向后工作。
  • 历史只不过是一个向后看的提交链。
  • commits 是共享的内容-对于git clonegit fetchgit push来说最重要的内容。names 也很重要,因为它们是每个Git找到它的提交的方式,但是 names 是每个Git本地的:它们不一定是通用的,就像哈希ID一样。特别是,分支名称 * 预计 * 移动,随着时间的推移。
  • 分支名称 * 自动 * 移动以包含新的提交,因为你做了新的提交。当他们这样做时,现有的早期提交只是成为历史的一部分。如果你要把一个分支的名字“向后”移动--这样历史提交就变成了分支的顶端(你可以这样做,我们只是没有提到如何做)--可能很难找到后面的提交!你知道他们的hash id吗?有办法找到他们,一段时间。)
    • 分支 * 这个词有歧义:当你说或者听到它的时候,想想你是指 * 分支名称 *,还是 * 一些提交序列,从最后一个开始,然后反向工作 *。如果有人说 remote分支 ,他们的意思是 * 分支名称,比如master,就像在the otherGit中看到的那样? 或者他们是指myGit中的远程跟踪名称origin/master 这些是不同的名称,可能指向不同的提交!在其他Git中的分支名称可能会在现在和下次查看之间发生 * 变化 *。谁有权访问其他Git存储库,他们现在在做什么?

3 Git未来会有一个复杂的问题--没有人知道确切的时间--哪些哈希ID会被重新编号,从SHA-1到SHA-256。具体如何处理尚未确定。

tcomlyy6

tcomlyy62#

为了解决这个问题,-torek的详细解释真的很有帮助。让我总结一下你可以遵循的解决它的步骤:
1.开始查找初始提交的哈希。您可以通过UI导航到“README.md“文件,选择“Blame”,然后单击“History”。打开最近的提交并复制与之关联的40个字符的散列。
1.接下来,执行以下Git命令:
-> git checkout -B master
-> git reset --hard<commit hash copied in step 1>
-> git push origin master
这些命令将创建一个名为“master”的新分支,将其重置为步骤1中获得的哈希指定的提交,并将更改推送到远程存储库。
完成这些步骤后,您可以进入UI并创建从“develop”分支到“master”分支的拉取请求(PR)。

相关问题