如何合并总行和总行? [英] How to merge main and master branches?

查看:18
本文介绍了如何合并总行和总行?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

大约一个月前,我创建了一个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,对于这三个提交是唯一的,但我们只将它们称为提交ABC。我们把它们画成这样。此图中的每个元素都有一个用途:

A <-B <-C   <--bra

CommitC存储两个内容:每个文件的快照和一些元数据。快照充当主提交的数据,并允许您恢复所有文件,无论它们在您(或任何人)提交时的形式如何C元数据包括提交人的姓名、他们的电子邮件地址等;但对Git本身至关重要的是,提交C中的元数据包括以前提交B的散列ID。

我们说提交C指向B。通过读出提交C,Git可以找到较早提交B的哈希ID。

当然,COMMITB还包含数据(每个文件的完整快照)和元数据,包括以前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 branchgit checkout添加一个新的分支名称,或者使用git checkout -b组合这两个名称(或者在Git 2.23或更高版本中,git switch -c)。它的实际工作方式是只创建新的分支名称,指向与当前提交相同的提交:

A--B--C--D   <-- bra, nch

我们现在有两个分支名称,但都选择了相同的提交。现在,我们使用哪个名称并不重要,因为两个名称都选择了提交D。但很快它就会变得重要起来--Git总是希望能够告诉我们我们在哪个分支上,这样git status就可以说on branch braon branch nch。为此,Git将特殊名称HEAD附加到一个分支名称,如下所示:

A--B--C--D   <-- bra (HEAD), nch

或此:

A--B--C--D   <-- bra, nch (HEAD)

附加了HEAD的名称即为当前分支名称。无论提交此名称指向哪个,都是当前提交

现在,我们将以通常的方式创建一个新的提交。它获得一个新的唯一散列ID,但为了保持理智,我们将其命名为CommitE:只有一台计算机可以处理真正的散列ID。让我们把它引进来:

A--B--C--D   <-- bra
          
           E   <-- nch (HEAD)

更新的分支机构名称为nch,因为这是我们的当前分支。当前提交现在是提交E,且是我们签出的提交。

如果git checkout bragit 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作为合并基础继续进行合并。

这一切都是有效的因为两个分支提示提交JL是相关的:它们有一个共享的父级(在本例中是祖父级)。这种共享的父级是一个共同的起点。因此,它们可以转换为自公共起点以来的更改

更改分支机构名称很简单

每个Git存储库都有自己的专用分支机构名称。当您将两个Git存储库彼此挂钩时,真正重要的是提交散列ID,因为它们不能更改和唯一标识提交。因此,如果我们有:

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

我们可以随意将此名称更改为我们喜欢的任何新名称:

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

没有人关心这个名字是bra还是xyzzy或其他什么--嗯,除了非理性的人类,当我们使用像plughcolossal-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不是mastermain,而是像origin/masterorigin/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

您的Gitorigin/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合并提交FE会起作用,因为它们有一个共同的祖先:F的父级是CE的父级是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执行BA执行的操作。同样,C'C的副本,对B'执行CB执行的操作。不过,新的提交具有新的和不同的散列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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆