我的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中提出合并请求?
2条答案
按热度按时间ie3xauqp1#
创建一个分支名称。这就是所有的事情,真的。选择一些现有的提交,并使分支名称
master
标识 * that * 提交。这就是您的master
分支。不存在“空分支”
不过,这里已经有一个问题了,因为在Git中,* branch * 这个词是有歧义的。(参见What exactly do we mean by "branch"?)当我们说“分支”时,有时我们指的是一个 * 名称 ,如
master
或develop
,有时我们指的是其他东西。Git真正存储的是一系列的 * commit 。每个提交都有一个唯一的编号:一个哈希ID,例如看起来像
8dca754b1e874719a732bc9ab7b0e14b21b1bc10
。您可以在git log
输出中看到这些。每次提交都保存项目的完整快照,作为一组文件(不是文件夹,只是文件:文件夹(如果有的话)将在运行
git checkout
时根据需要创建以 * 保存 * 文件。而且,每个提交都有一点 * 元数据: 关于提交的信息,比如谁做了它,什么时候做的,为什么做(它的日志消息),最重要的是- 前一次 * 提交的原始哈希ID。正是这些原始哈希ID构成了向后看的提交链。如果我们使用一个大写字母来伪造散列ID-这对人类更有用,但显然不适用于数千个提交-我们可以绘制一个简单的三提交仓库,如下所示:
这里的commit
C
是 * latest * commit。它记录了提交B
是较早的提交,所以我们说C
* 指向 *B
。另一种说法是B
是C
的 * 父代 *。同时commitB
表示commitA
先于B
。CommitA
是第一次提交,因此没有父级。这将使操作停止:历史在这里开始或结束,如果你像Git一样向后工作的话。有时候,当我们说 * branch * 时,我们指的是整个系列的提交。这个提交链,
A-B-C
,从末尾开始向后工作,是一个分支。您指示Git查找 * last * commit,C
,然后它指向更早的commitB
,后者又指向更早的commit。无论有多少个提交,我们总是可以让Git从最后开始,然后一直回到最开始,只要我们知道哪个提交是 * 最后 * 提交。但我们如何找到最后一次提交呢?在这个例子中很简单:只有三个提交,我们让它们使用连续的字母名称,所以显然
C
是最后一个。但是在一个真实的存储库中,可能会有成千上万的提交:它们的哈希ID看起来完全是随机的我们如何知道哪个提交是最后一个提交?此外,如果我们有一系列的提交,比如:
有 * 两个 *“最后一个”提交?
这就是分支名称的作用
一个分支 * name *,比如
master
或develop
,只保存了我们想称之为“分支的一部分”的 * last * commit的实际哈希ID:通过将名称
master
指向commitJ
,我们声明J
是分支master
的 * 最后 * commit。通过将名称develop
指向commitL
,我们声明L
是分支develop
的 * 最后 * commit。请注意,提交
H
、G
和F
,以及之前出现的任何其他提交,都在 * both * 分支上。这是Git的一个特殊功能:通常,大多数提交都在 * every * 分支上。只有最后的几个,或者几百个,或者别的什么,只在一两个或十个分支上。对于一个分支名称存在,它 * 必须 * 指向某个现有的提交。你可以在不使用分支名称的情况下进行提交-这有点棘手:它使用Git称为 * detached HEAD * 模式-但是你不能有分支名称,除非它指向某个实际的提交。通过指向该提交,该分支名称声明该提交 * 是该分支中的最后一次提交。
即使名称指向其他人链中间的提交,也是如此。假设你没有master,但是有:
现在你可以告诉Git:* 创建名称
master
,指向commitH
*,方法是找到H的实际散列并运行:或:
现在您拥有:
请注意,* commits * 根本没有改变。您只是添加了标签
master
-一个分支名称-来记住commitH
的哈希ID。分支名称记住一次提交的hash ID,但还有一个特殊的功能
现在您有了名称
master
来标识提交H
,您可以:然后做一些工作并提交一个新的提交。当你进行一次新的提交时,Git会打包一个新的项目快照并将其写入。Git添加你的名字作为这个新提交的作者和提交者,设置它的日期和时间戳,并使用你的日志消息作为这个提交现在存在的原因。Git添加现有提交
H
的原始哈希ID作为新提交的父提交。然后Git将新提交保存到"所有现有提交"的数据库中,该数据库为新提交分配新的、唯一的哈希ID。我们将这个新的哈希ID命名为I
:现在是棘手的部分:在创建了commit
I
之后,Git * 移动了当前分支的名称 *,使其指向新的commit。由于当前分支 name 是master
,Git修改了master
,使其指向新提交I
:你现在应该问自己两个问题:
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。但它并不完全复制 * 分支名称 。相反,它获取 * 他们的 * 分支名称- 他们的 * 存储库的master
和develop
等等-并 * 重命名 * 它们,称它们为origin/master
和origin/develop
等等。我们的Git刚刚做了一个全新的Git克隆,根本没有分支名称!它有他们所有的分支名称,重命名为我们的
origin/*
* 远程跟踪名称 *。1但是我们的Git希望我们有一个分支-通常是master
,但是如果他们甚至没有master
,我们的Git现在会选择其他名称,例如develop
。作为git clone
的最后一步,我们的Git必须:origin/*name*
名称相同的提交,以及git checkout
提交的分支名称。在第一步中选择的名称为:
-b
参数中提供的名称(如果是git clone -b *name*
),或者master
。最后一个
master
是一个特例,用作最后的手段。因此,如果您克隆一个只有
develop
的存储库,您将获得自己的origin/develop
名称-一个远程跟踪名称,而不是真正的分支-指向其develop
中的最后一次提交。但是作为git clone
的最后一步,你的Git将 * 创建 * 你自己的develop
,指向同一个提交,然后是git checkout develop
,这样你就在一个分支名称develop
上,并 checkout 了这个提交。如果你现在在 your 仓库中创建新名称
master
,你会得到:(with
HEAD
附加到develop
或master
,具体取决于您如何创建名称master
:你用的是git branch
还是git checkout -b
?).现在可以运行
git push origin master
来告诉 their Git:* 创建一个分支名称master
,指向与你的develop
相同的提交。*git push
所做的是将你的提交和他们没有的提交(你所做的任何提交,他们需要用于 create or update some branch name 部分)发送给他们的Git,然后礼貌地要求他们创建或更新一些 * 他们的 * 分支名称,以匹配一些 * 你的 * 分支名称:让你的Git在
origin
上调用Git-在你的名字origin
下存储的URL-并向他们发送他们需要的任何提交,然后要求他们设置 * 他们的 * 分支名为branch1
,branch2
和branch3
,以指向 * 你的 * 名字branch1
,branch2
和branch3
指向的相同提交。因为他们只有在有这些提交的情况下才能这样做,所以如果他们没有提交,你的Git会首先发送给他们。你的Git不仅会给他们发送这些 tip 提交(针对三个分支),还会发送任何 history--任何早期的提交--需要将这些tip提交与其仓库中的其余提交联系起来。1Git文档调用这些 * 远程跟踪分支名称 。但他们不是技术上的“分支”名称。特别是,例如,如果您使用
git checkout origin/master
,则会得到一个分离的HEAD。所以我更喜欢称它们为“远程跟踪名称”,完全放弃了“分支”这个词。2如果你克隆了一个完全空的仓库,没有提交,他们的Git就没有分支名,因为它没有提交,而分支名需要提交。所以在这种情况下,他们什么都不推荐。你的Git必须使用
master
作为你的分支名。当然, 你 * 也没有任何提交。所以现在你会遇到和你用git init
创建一个新的、空的存储库时相同的奇怪状态:你在分支master
上,但是分支master
不存在!你在一个根本不存在的分支上。Git的其他部分称之为“孤儿分支”。这是一种奇怪的状态,在这种状态下,你的下一次提交 * 创建 * 了你所在的分支,这样你就可以正确地在上面了。在此之前,您的HEAD
只记录要 create 的分支的名称。回顾
以下是你学到的(和需要知道的):
git log
命令将显示哈希ID。宇宙中的每个Git都必须同意哈希ID。3git clone
、git fetch
和git push
来说最重要的内容。names 也很重要,因为它们是每个Git找到它的提交的方式,但是 names 是每个Git本地的:它们不一定是通用的,就像哈希ID一样。特别是,分支名称 * 预计 * 移动,随着时间的推移。master
,就像在the otherGit中看到的那样? 或者他们是指myGit中的远程跟踪名称origin/master
? 这些是不同的名称,可能指向不同的提交!在其他Git中的分支名称可能会在现在和下次查看之间发生 * 变化 *。谁有权访问其他Git存储库,他们现在在做什么?3 Git未来会有一个复杂的问题--没有人知道确切的时间--哪些哈希ID会被重新编号,从SHA-1到SHA-256。具体如何处理尚未确定。
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)。