git rebase合并具有不同子文件夹的分支

时间:2019-07-11 01:16:03

标签: git merge rebase git-rebase git-filter-branch

我有两个存储库,然后将它们合并为一个,因此现在每个存储库都被复制到新的/第三个存储库中的一个分支中。 我还使用filter-branch将所有文件从一个存储库移到一个子文件夹,而另一个文件移到另一个子文件夹。

因此,branch_A位于文件夹“ A”下,而“ branch_B”位于文件夹B”下,从git的角度来看,一直以来都是这样。

我想将两个分支合并为一个,以便当我从历史记录中间签出随机提交时,我将同时拥有两个文件夹。

我可以使用rebase来做到这一点,但关键的是,我希望提交保留他们的作者日期,本质上将历史交织在一起。当我重新确定基准时,它只是在最后删除提交,并且所有提交都有今天的日期。

这应该可行,因为当分支跟踪不同的文件夹时,绝对应该没有冲突。

我该怎么做?

谢谢。

1 个答案:

答案 0 :(得分:-1)

可以完成 ,是的:但这不是简单,仅靠变基就无法达到目标。

(我确信其中很多都是您已经知道的,但是值得逐步进行,因为许多Git文档尚有许多不足之处,这在人们的理解上存在空白。)

请记住,存储库是提交的集合。每个提交都是(或包含)(如果我们将其分解,则包含在内,但现在让我们将它们视为完整的实体)是源树的快照,即一些数据,以及有关该快照的一些 信息,即一些元数据。提交本身由唯一的哈希ID标识。该哈希ID只是提交内容的加密校验和。 1 校验和本身不能更改,因为它只是该字符串和该字符串的校验和。本身不会改变,因为这就是提交的原因。

现在,至关重要的是,在每次提交的元数据中,都有parent行:

$ git cat-file -p HEAD | grep parent
parent 90d79d71910415387590a733808140e770382b2f

也就是说,每个提交都包含另一个上一个提交的实际哈希ID。某些提交(合并提交)包含两个或多个父ID,并且至少一个提交( root 提交)没有 个父,但大多数提交只有一个。

这些父哈希ID的作用是在存储库中形成一个向后看的提交链。如果我们从一个几乎为空的存储库开始,其中只有三个提交,然后将它们称为ABC,而不是使用它们实际的笨拙的哈希ID,我们可以像这样画它们:

A <-B <-C

提交C是最新的,它会记住其父B的实际哈希ID。提交B是我们第二次提交,它会记住其父A的实际哈希ID。提交A是第一个,也是第一个,之前没有要记住的提交,因此它是根提交。

如果提交哈希ID确实如此简单,我们可能只记得有3次提交,因此我们的最新提交必须为C。但是它们的外观和行为就像随机数一样,对于我们这些单纯的人类来说,它们是不可能记住的。因此,我们让Git使用分支名称来记住链中的 last 提交:

A <-B <-C   <-- master

名称master会记住实际的哈希ID,因此名称master 指向提交C。同时,C指向BB指向A

如果我们想添加一个 new 提交,我们告诉Git通过使用名称C来提取提交master。 Git通过在名称master上附加特殊名称HEAD来记住我们在分支master上:

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

如果我们想向新的分支添加新的提交,我们告诉Git:创建新的分支名称dev,也指向提交C ,并将HEAD附加到dev现在我们有了:

A--B--C   <-- dev (HEAD), master

现在,我们以通常的方式进行新的提交。 Git通过创建源快照,添加我们的姓名和电子邮件以及日期等,并使用提交C的哈希ID作为新提交的父级来创建提交。新提交将获得一个新的,随机的哈希值(但一旦获得日期和时间标记以及我们的日志消息等,实际上是完全确定性的)。我们将其称为D

A--B--C
       \
        D

魔术部分现在出现了:Git通过写入实际哈希ID 当前分支名称(具有特殊名称HEAD的那个分支名称) >提交D。因此,现在名称dev指向D,而master继续指向C

A--B--C   <-- master
       \
        D   <-- dev (HEAD)

随着我们添加更多提交,有时会遇到如下情况:

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

我们运行git checkout master,它选择提交J并将HEAD附加到master,然后附加git merge dev。合并操作现在将master上的工作-提交HJ之间的变化与dev上的工作,提交HL。如果Git能够自己进行所有合并,则Git也会自行进行最终的合并提交。这个新的合并提交M作为其 first 父级,具有提交J:我们已经检出的提交。它已经提交了L,这是我们告诉Git合并的,作为它的 second 父级。与往常一样,Git将新提交的新哈希ID写入当前分支名称,因此master现在指向新合并M

          I--J
         /    \
...--G--H      M   <-- master
         \    /
          K--L   <-- dev

我们现在可以根据需要删除名称 dev,因为所有提交都可以从M开始并向后进行查找。从M开始,我们必须倒退通过两者 J L

请注意,如果我们将名称dev保留在原位,则通过H加上KL的提交都在两个分支上。 dev中的所有提交现在也在master中。提交{{1}中的IJM ,但其他两个分支中的其他成员。在合并之前,通过master进行的提交在两个分支中。


1 从技术上讲,它是文字词H的校验和,元数据的字节大小包括数据的树哈希ID字符串,一些空格和其他字节,以及然后是元数据的字节使用commit来查看示例提交:校验和是在给定格式指令的情况下,以将要打印的内容作为前缀的结果:

git cat-file -p HEAD

其中b'commit {}\0{}'.format(len(content), content) 包含此处content产生的字节字符串。


从单独的历史记录中获取将创建第二个根目录

让我们回到一个简单的三提交原始存储库,它只有一个分支:

git cat-file -p

现在让我们从另一个具有三个A--B--C <-- master 的不同的三提交存储库中git fetch。如果我们使用名称master作为遥控器的名称,则会得到otherrepo/master

otherrepo

导致:

git remote add otherrepo <url>
git fetch otherrepo

我们可以为第二个分支创建自己的分支名称,而不是使用此远程跟踪名称A--B--C <-- master D--E--F <-- otherrepo/master 。并不重要,但这会使我们的下一个otherrepo/master更容易,所以让我们这样做:

git filter-branch

现在,我们将运行git branch m2 otherrepo/master 命令,将所有内容移动到子目录中。每个这样的命令复制某些原始命令都会提交给具有不同哈希ID和保存的快照但具有相同作者和提交者信息以及相同日志消息的新命令。父哈希ID指向副本,因此我们得出以下结论:

git filter-branch

我们不再需要原始的提交链,一旦我们放弃了A--B--C [abandoned] A'-B'-C' <-- master D--E--F [abandoned] D'-E'-F' <-- m2 的{​​{1}}名称,我们什至无法查找原件,除非我们将其哈希ID记下。因此,如果愿意,我们可以停止绘制它们,而我会这样做(但我会在一个字母的提交名称上保留勾号/撇号,以表示这些是持有重命名为子树文件的名称)。

您现在需要的是发明所需的最终图形

您现在拥有:

refs/original/

您可能会希望拥有:

git filter-branch

或者也许您会喜欢

A'-B'-C'  <-- master

D'-E'-F'  <-- m2

或者,也许完全是另一回事。 很漂亮很清楚,但可能不是很清楚,也许不是真正的 true ,您想要一个新的根落实,可能是A"-E"-B"-F" <-- master ,是以下结果的结果:

  • A"-C"-F" <-- master 中提取树,并从A"中提取到一棵普通树中
  • 使用A'D'或两者皆有的日志消息来做到这一点

在此新的A'提交之后,您可能需要将 just D'子树替换为 just A"子树。或者,也许您想将D'子树替换为E'子树 并将A'子树替换为{{1} }子树。或者,也许您想要第三个组合-这部分是 not 完全清楚的,当然对我来说也不是,也许对您也不是。 :-)

尽管如此,您的工作还是要弄清楚您想合并哪些子树,以及要合并或使用哪些提交消息。然后,您可以根据该结果进行新的提交。这将是您的第二次提交,以B'作为其父项。

此重复次数与链中的提交次数相同。如果链条很复杂-如果D'指向具有分歧和合并的事物,和/或E'指向具有分歧和/或合并的事物,必须弄清楚您想结合这两个历史,包括它们的子树。绝对是这样的情况:提取任何A"提交只会影响master子树,而提取任何m2提交只会影响master子树。但是实际上以任何顺序进行提交,并与任何一个或多个父级进行新提交... 是您必须解决的难题。

一旦确定了想要的内容,进行编程是相对简单的事情。您可以尝试使用A来做,但是只做m2会更容易:设置正确的树并进行新的提交,使用B来写出索引,然后在环境中使用正确的标准输入日志消息(或带有日志消息的git filter-branch文件)和正确的git filter-branch字符串git write-tree,以强制作者和提交者名称,电子邮件地址,以及新提交的日期和时间戳。管道命令git commit-tree -p <parent> [-p <parent> ...] <tree>将进行新的提交,并在其标准输出上生成该新提交的哈希ID。适当地使用它作为后续提交的父级,以建立新的链。

(请注意,您可以使用-F来填写GIT_{AUTHOR,COMMITTER}_{NAME,EMAIL,DATE}的索引,并且/或者您可以以更典型的方式将提交中的文件提取到工作树中,然后使用{{ 1}}建立一个新的索引来编写。有关一些技巧和技巧,请参阅filter-branch源-它只是一个巨大的shell脚本。)

当您完成所有操作并构建了所需的分支提示提交后,请使用git commit-tree或面向用户的git read-tree命令来强制设置特定的一个或多个分支名称。 )以保存所需提交的哈希ID。现在,您可以按照自己想要的方式来重建历史。