如何合并总行和总行? [英] How to merge main and master branches?
问题描述
大约一个月前,我创建了一个GIT回购,其中的主分支称为‘master’。几天前,当我尝试提交并推送到同一个repo时,它将我的更改推送到‘master’分支,但我收到一条消息,说主分支已更改为‘main’分支。
我已尝试合并,但收到错误消息称无法合并不相关的历史记录(显然它们将具有不相关的历史记录,因为‘Main’分支刚刚创建)
现在我所有的代码都在‘master’分支上,而不是主分支上,所以我想知道如何才能将所有内容都移到‘main’分支上?
仅供参考:我确实做了一些研究,我理解GitHub做出这个改变的全部原因,我只是想知道如何弄清楚这一点。
推荐答案
要认识到的一点是,它只提交该事项。承诺就是Git的全部。一旦你进入了提交,提交本身就会在一个扭曲的小球中找到其他提交。那么:分支名称有什么用处?它不是什么都不是,但它有点接近。
提交的真实名称是其散列ID。但是提交散列ID看起来是随机的,并且无法预测某个提交的散列ID是什么。一旦找到一个提交,就可以使用该提交来查找更多提交。但您必须先找到其中的一个--这就是分支机构名称的用武之地。名称允许您开始。它会让你进入犯罪的巢穴。从名称中,您现在可以找到某个特定提交的散列ID。该提交允许您找到另一个提交,这使您可以找到另一个提交,依此类推。
现在我所有的代码都在‘master’分支上,而不是主分支上,所以我想知道如何才能将所有内容都移到‘main’分支上?
这里的TL;DR是,您处于一个棘手的情况,没有唯一正确的答案。你必须决定你想做什么。您可以:
- 将您自己的
master
分支重命名为main
,并尝试让原始存储库克隆的所有其他用户使用您的提交;或 - 确定如何合并和/或重新执行两个存储库中的部分或全部提交。
换句话说,您可能所要做的就是重命名分支。但肯定还是有一些问题,因为现在您有两个分支机构名称。现在是时候仔细看看整个事情了:为什么重要的是提交,以及这些名称是如何真正起作用的?
长
让我们从相关提交的最简单形式开始:一个小的、简单的线性链。假设我们创建了一个新的、完全空的存储库,其中没有提交。Git分支名称有一条规则:分支名称必须正好包含一(1)个现有有效提交的哈希ID。1由于没有提交,所以不能有分支名称。
为了解决此问题,我们进行了第一次提交。如果您使用GitHub,他们通常会为您进行第一次提交,其中只包含自述文件和/或许可证类型文件。拥有第一个提交允许您创建任意多个分支名称:它们都将存储一个提交的散列ID。
请注意,每个提交都有自己唯一的散列ID。这个散列ID在所有所有Git存储库中都是通用的。2这就是Git散列ID如此之大和丑陋的原因。3它还允许Git程序连接到使用其他Git存储库的其他Git程序,并通过交换散列ID来确定每个存储库有哪些提交。因此,散列ID至关重要。但它们对人类毫无用处,因为他们不能保持它们的正直。这就是我们有分支机构名称的原因。
关于这些散列ID和底层对象(提交和Git存储的非提交对象,在脚注1中提到),还有一件事需要了解:散列ID只是存储对象的奇特校验和。Git使用散列ID查找对象--提交或其相关数据--,但随后还确保存储对象的校验和与用于查找它的校验和匹配。因此,在Git中,任何存储对象的任何部分都不能更改。如果校验和不匹配,Git将声明存储已损坏,并拒绝继续。 总之,假设我们从一个提交开始,一个名为bra
的分支,然后又创建了两个提交,这样我们现在就有了一个只有三个提交的小型存储库。这三个提交有三个难看的大散列ID,对于这三个提交是唯一的,但我们只将它们称为提交A
、B
和C
。我们把它们画成这样。此图中的每个元素都有一个用途:
A <-B <-C <--bra
CommitC
存储两个内容:每个文件的快照和一些元数据。快照充当主提交的数据,并允许您恢复所有文件,无论它们在您(或任何人)提交时的形式如何C
。元数据包括提交人的姓名、他们的电子邮件地址等;但对Git本身至关重要的是,提交C
中的元数据包括以前提交B
的散列ID。
我们说提交C
指向B
。通过读出提交C
,Git可以找到较早提交B
的哈希ID。
B
还包含数据(每个文件的完整快照)和元数据,包括以前COMMITA
的散列ID。因此,Git可以从B
中找到A
。COMMITA
有点特殊,因为这是第一次提交。它没有指向任何早期提交的向后箭头,因为没有早期提交。Git称之为根提交。它让Git停止倒退。
C
。要查找提交C
,我们使用分支名称bra
。它包含COMMITC
的哈希ID,因此bra
指向C
,这就是我们开始的方式。
1不存在现有但无效的提交。说&Existing,Valid Commit&qot;的真正意义在于,散列ID不仅仅用于仅仅提交,因此您可以拥有有效的散列ID,但用于不是提交的内容。但是,您还不需要处理这些未提交的散列ID。您必须处理提交散列ID,因此我们关心这些ID。
2从技术上讲,只要这两个Git存储库从不相遇,两个不同的提交可以具有相同的散列ID。提交满足其doppelgänger会导致悲剧和悲伤,所以这是不好的。(好的,从技术上讲,发生的是两个Git,因为他们为了交换承诺而进行Git性爱,只是出现了故障。可悲的是那些Gits的用户,他们期待着某种美丽的孩子。)
3几年前,就连这一点也开始变得不够。有关详细信息,请参阅How does the newly found SHA-1 collision affect Git?。
在一个分支上添加新提交
鉴于我们有:
A <-B <-C <--bra
我们从将CommitC
提取到工作区开始。每次提交的内容不能更改,这包括存储的文件。4因此,现在我们已提交C
";已签出";。Git使用名称bra
记住C
的哈希ID,并知道当前提交具有此哈希ID。
git add
.5通知Git这些更新,然后使用git commit
构建一个新提交。Git保存新的快照,并添加相应的元数据,包括当前提交的哈希ID,以生成指向现有提交的新提交D
:
A <-B <-C <--bra
D
作为git commit
的最后一步,Git将最新提交的散列ID存储到分支名称中。由于COMMITD
指向现有的COMMITC
,我们现在希望通过名为bra
的分支,通过查看COMMITD
:
A <-B <-C <-D <--bra
现在提交已完成。
4文件内容作为Blob对象存储在存储库中。这会压缩它们并对它们进行重复数据消除,因此当两个提交共享相同的文件内容时,它们实际上也共享内部对象。不过,您通常不需要知道或关心这一点。
5git add
步骤操作Git称为索引、临时区域或缓存的东西。为了节省篇幅,我省略了所有有用的细节。
多个分支机构名称
要使用多个分支,我们通常使用git branch
和git checkout
添加一个新的分支名称,或者使用git checkout -b
组合这两个名称(或者在Git 2.23或更高版本中,git switch -c
)。它的实际工作方式是只创建新的分支名称,指向与当前提交相同的提交:A--B--C--D <-- bra, nch
我们现在有两个分支名称,但都选择了相同的提交。现在,我们使用哪个名称并不重要,因为两个名称都选择了提交D
。但很快它就会变得重要起来--Git总是希望能够告诉我们我们在哪个分支上,这样git status
就可以说on branch bra
或on branch nch
。为此,Git将特殊名称HEAD
附加到一个分支名称,如下所示:
A--B--C--D <-- bra (HEAD), nch
或此:
A--B--C--D <-- bra, nch (HEAD)
附加了HEAD
的名称即为当前分支名称。无论提交此名称指向
E
:只有一台计算机可以处理真正的散列ID。让我们把它引进来:
A--B--C--D <-- bra
E <-- nch (HEAD)
更新的分支机构名称为nch
,因为这是我们的当前分支。当前提交现在是提交E
,且是我们签出的提交。
如果git checkout bra
或git switch bra
在Git 2.23或更高版本中,我们选择bra
作为当前分支
D
作为当前提交。因此COMMITD
成为已签出的那个:
A--B--C--D <-- bra (HEAD)
E <-- nch
现在,我们进行的任何新提交都将更新名称bra
:
F <-- bra (HEAD)
/
A--B--C--D
E <-- nch
这是我们通常在Git存储库中进行的那种分支。请注意,提交A-B-C-D
位于两个分支上,因为无论我们从哪个名称开始,当我们向后工作时,我们都会找到所有这些提交。但要查找提交E
的唯一方法是从名称nch
开始。查找提交F
的唯一方法是从名称bra
开始。
分支名称查找提交
这就是分支机构名称的用处。他们找到分支的开始-嗯,结束?-提交。事实上,在Git中,分支就是这样定义的。名称保存分支上的最后提交的哈希ID。无论名称中的散列ID是什么,它都是最后提交,即使还有更多提交。当我们拥有:
F <-- bra
/
A--B--C--D <-- main
E <-- nch
有三个最后提交,即使在D
之后有两个提交。也有三种方法查找提交A-B-C-D
:我们可以从名称main
开始倒查,也可以从其他两个名称中的任意一个开始倒查。
历史如何关联
假设我们有以下内容:
I--J <-- br1
/
...--G--H
K--L <-- br2
我们可以选择这两个分支名称中的任何一个-因此提交J
或提交L
-然后要求Git合并其他最后一次提交。在不涉及其他任何重要细节的情况下,Git处理该合并请求的方法是向后查找最佳共享提交,在本例中为提交H
。然后使用COMMITH
作为合并基础继续进行合并。
这一切都是有效的因为两个分支提示提交J
和L
是相关的:它们有一个共享的父级(在本例中是祖父级)。这种共享的父级是一个共同的起点。因此,它们可以转换为自公共起点以来的更改。
更改分支机构名称很简单
每个Git存储库都有自己的专用分支机构名称。当您将两个Git存储库彼此挂钩时,真正重要的是提交散列ID,因为它们不能更改和唯一标识提交。因此,如果我们有:
A--B--C <-- bra (HEAD)
我们可以随意将此名称更改为我们喜欢的任何新名称:
A--B--C <-- xyzzy (HEAD)
没有人关心这个名字是bra
还是xyzzy
或其他什么--嗯,除了非理性的人类,当我们使用像plugh
或colossal-cave-adventure
这样的唤起人心的名字时,他们的脑海里会冒出一些想法。而且,当我们使用Git克隆共享工作时,我们人类也喜欢共享我们的分支名称,以帮助保持我们自己的理智。因此,我们通常不会随意地对分支进行重命名。但真正的名字真的不重要,至少对Git来说不重要。
如果这是您自己的情况-您有一个master
,他们将名称更改为main
-您只需将您的master
重命名为main
,您和他们将使用相同的名称来查找相同的提交。这将是简单而简单的。然而,这不是你的情况,因为如果这是你的情况,你不会看到关于无关历史的投诉。
多个根提交
上面的所有图表都只有一个根提交:在我们的例子中,提交A
。(好的,...--G--H
可能有一个根提交。)但在Git中,有许多不同的方法可以创建额外的根提交。一种方法是使用git checkout --orphan
(或git switch --orphan
)。假设我们从:开始
A--B--C <-- bra (HEAD)
然后使用此技术创建新根提交<[2-31],不指向或指向名为的任何对象:
A--B--C <-- bra
D <-- nch (HEAD)
这在Git中运行得很好,如果愿意,我们可以继续创建更多提交:
A--B--C <-- bra
D--E--F <-- nch (HEAD)
现在不能做的是简单地合并这两个分支,因为git merge
需要找到最好的公共祖先。Git做到了这一点,从每一端开始,向后工作,直到历史相遇……在这种情况下,他们从来没有见过面!一段历史结束(开始?)在A
处,另一端(开始?)在D
,而不会遇到相同的在两个分支上提交。
多个存储库
考虑到以上所有内容,让我们将克隆添加到图片中。请记住,每个Git存储库本质上是两个数据库:
一个数据库包含提交对象和其他内部Git对象。每个对象都有一个难看的大散列ID作为其键,Git在一个简单的key-value datastore中查找实际值。
另一个数据库有名称-分支机构名称、标记名和其他类似名称-每个名称存储一个哈希ID。这些哈希ID使您进入提交,以便您可以找到所有提交。
git clone url
时,您让Git创建一个新的空存储库,其中没有提交和分支,然后调用其他Git并让Git根据您提供的URL查看其他存储库。另一个Git有它的两个数据库:提交和其他对象(按散列ID为关键字)和名称到散列ID(按名称为关键字)。它们向您的Git发送所有对象,Git将这些对象放入您自己的数据库中。
您现在有他们的所有提交,但没有他们的分支机构名称。
为了查找这些提交,您的Git获取它们的分支名称并更改它们。您的Git不是master
或main
,而是像origin/master
或origin/main
这样的名称。这些名称是您的Git远程跟踪名称。他们记住他们的Git在他们的分支机构名称中具有的哈希ID。
这些远程跟踪名称同样适用于查找提交。现在,您实际上根本不需要任何分支机构名称。但git clone
还没有完成:它的最后一步是运行git checkout
(或git switch
),为您挑选某个分支机构名称。
当然,您还没有分支机构,但是git checkout
/git switch
有一个特殊的特性:如果您要求Git签出一个不存在的名称,您的Git将扫描您的远程跟踪名称。如果他们有master
,您现在就有一个origin/master
,当您尝试git checkout master
时,您的Git将创建您自己的master
,指向与您的origin/master
相同的提交。当然,这与他们的master
提交是相同的!
这意味着您现在在自己的存储库中拥有:
A--B--C <-- master (HEAD), origin/master
现在,假设它们将它们的名称master
更改为main
。如果这是他们所做的一切-如果他们只是重命名他们的分支-在您运行git fetch
从他们那里获得任何新的提交(没有)并更新您的远程跟踪名称之后,您将得到以下结果:
A--B--C <-- master (HEAD), origin/master, origin/main
您的Git将origin/main
添加到您的存储库,以记住他们的main
。他们实际上已经删除了他们的名称master
,您的Git可能应该删除您的origin/master
以匹配,但Git的默认设置不会这样做。6因此您最终得到了两个远程跟踪名称,其中一个已经过时。您可以使用以下命令手动清理:
git branch -d -r origin/master
或:
git fetch --prune origin
(git fetch
有一个副作用,那就是立即更新所有远程跟踪名称,包括从它们那里获得任何新的提交,所以这样通常更好。然而,这需要更长的时间,因为它必须通过互联网或URL所在的任何地方调用他们的Git。)
6若要使Git以这种方式运行,请对所有存储库使用git config --global fetch.prune true
。
如果他们这样做了,事情就会变得合理
假设确实这样做:将他们的master
重命名为main
,而不实际添加或删除任何提交。或者,他们可能会重命名,然后添加更多提交。让我们来画下后者:它有点复杂,但最终都是一样的。
他们有:
A--B--C <-- master
然后您运行git clone
,得到:
A--B--C <-- master (HEAD), origin/master
在您自己的存储库中。(我们可以省略其存储库中的HEAD
,因为我们通常不关心他们签出哪个分支。)然后将master
重命名为main
,并添加CommitsD-E
。您运行git fetch
并获得:
A--B--C <-- master (HEAD), origin/master
D--E <-- origin/main
您的Git无法删除origin/master
,即使它们不再有master
,因此我们将其保留在绘图中。请注意,它是无害的:它只标记为CommitC
。我们可以删除它-我们可以设置fetch.prune
或运行git fetch --prune
或其他任何东西-或者保留它;它实际上并不重要。分支机构名称并不重要!只会把事情做好。提交C
仍然存在,无论是否有名称指向它。
无论如何,您可能会创建自己的新提交F
:
F <-- master (HEAD)
/
A--B--C
D--E <-- origin/main
如果您要求Git合并提交F
和E
,会起作用,因为它们有一个共同的祖先:F
的父级是C
,E
的父级是C
。
这告诉我们这不是他们所做的而不是。
似乎发生了什么事
如果我们假设您没有进行一系列不相关的提交,那么在他们的Git存储库中-在GitHub上-肯定发生了什么事情:他们进行了新的根提交,并使用名称main
来查找它:
A--B--C <-- master
D <-- main
然后,他们可能删除了他们的名字master
。这给他们的存储库中留下了这样的信息:
A--B--C ???
D <-- main
此时-或就在此之前-他们可能复制了部分或全部A-B-C
提交到D
之后的新提交:
A--B--C ???
D--B'-C' <-- main
这里,CommitB'
是CommitB
的副本:它对D
执行B
对A
执行的操作。同样,C'
是C
的副本,对B'
执行C
对B
执行的操作。不过,新的提交具有新的和不同的散列ID,并向后指向提交D
作为它们的根。因此,当您运行git fetch
将您的Git连接到他们的Git时,他们的新提交是这些D-B'-C'
提交,因此您在存储库中最终得到:
A--B--C <-- master (HEAD), origin/master
D--B'-C' <-- origin/main
如果删除您的origin/master
(因为它们的master
已消失),则没有什么真正的变化:您自己的Git仍在查找提交C
。他们的Git找不到提交C
--他们现在甚至可能已经把它扔掉了;Gits最终会删除无法找到的提交--但你的Git可以,通过你的master
。如果您在那之后进行了新的提交,就像我们之前抽签的F
,那么您甚至可以拥有以下内容:
F <-- master (HEAD)
/
A--B--C <-- origin/master
D--B'-C' <-- origin/main
您无法进行合并,因为这些链没有共享历史记录。
那么您可以做些什么?
您现在面临一堆选择。使用哪些Git存储库取决于您要做多少工作、要让其他人做多少工作,以及您对其他Git存储库的控制程度。
您可以:
继续使用您的提交(仅限)并强制其他所有人切换。
没有理由更改提交。原版仍然和以前一样好。有人犯了一个错误,抄袭了它们。让他们承认自己的错误:将您的
master
重命名为main
,使用git push --force origin main
,并让GitHub(或其他中央存储服务器)存储库使用您的提交,每个人都同意使用main
。复制您喜欢的提交,并将它们添加到其上次提交的末尾。
假设他们的提交
C'
与您的(和他们最初的)提交C
具有相同的保存的快照,或者无论提交的是什么,这都是原始文件的最后一个副本,您可能只需在C'
之后添加您的工作,对每个提交使用git cherry-pick
,或者git rebase --onto
执行多个挑剔操作。有关如何做到这一点,请参阅其他StackOverflow问题。与
--allow-unrelated-histories
合并。此技术可以花费您最少的时间和精力,但它可能会变得混乱和痛苦:中间的Rebase/Cherry-Pick选项可能会更快、更容易。
--allow-unrelated-histories
所做的只是假装在单独的根提交之前,只有一次提交,其中没有文件。在某些情况下,这很容易奏效。在大多数情况下,您会得到许多需要手动操作的添加/添加冲突。它还有一个相当难看的副作用,那就是在存储库中留下额外的、几乎无用的提交,然后您将永远携带这些提交。如果没有人查看这段历史(以及这两个根源),没有人会关心,但它仍然存在。这是否会困扰你(或其他人)则完全是另一回事。
我不可能为您选择这些选项中的一个,这不一定是所有选项中的一个,但到了这一点,您至少应该很好地了解发生了什么,以及为什么这些是处理它的方法。
这篇关于如何合并总行和总行?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!