【问题标题】:When does git lose changes during a merge?git 在合并期间什么时候丢失更改?
【发布时间】:2017-04-30 13:01:15
【问题描述】:

让我们这么说:

  1. 我们有一个 master 分支,其中一位同事意外添加了一系列本应属于新功能的提交(我们称之为 A B C)。
  2. 我发现了这一点,我告诉他将这些提交移到一个新分支,但保留其他不相关的提交,这些提交稍后在 master 中完成。我把我问的这个问题发给他,并告诉他按照回复:git: how to move a branch's root two commits back
  3. 几天后,当新功能分支准备就绪时,我将其合并到 master 中。
  4. 解决合并中的所有冲突后,我提交更改...
  5. ...我发现那些第一次提交(A B C)已经消失了。
  6. 我问我的同事,他说“他认为”他使用链接中提到的方法移动了这些更改(基本上:检查最后一个常见的提交,然后使用 git cherry-pick 只选择我们后来想要),但他记不清了。
  7. 我检查了 repo 的历史记录,A B C 在功能分支中,一开始。它们看起来像是从 master 成功迁移过来的。

鉴于上述情况,谁能解释为什么 git 会丢失这些更改? (我个人的理论是 git 以某种方式“记住”了我们已经撤消的提交 A B C,所以当它们来自新功能分支时,git 决定不合并它们。编辑:抱歉,如果这个解释听起来太像“神奇的想法” ",但我不知所措。如果它是正确的,我欢迎任何尝试用更专业的术语来解释这个解释。

很抱歉无法提供更多细节,但我没有亲自在 repo 中进行这些更改,因此无法提供所做操作的确切细节。

编辑:好的,按照这里的建议,我让我的同事在他的机器上执行git reflog,所以我将结果粘贴到这里。回到我之前的(链接的)问题,我们有这样一棵树:

A - B - C - D - E - F  master
            \ 
             \- G - H  new feature branch

我们想将 B 和 C 移至新功能分支。

所以,他发给我的git reflog 就在这里。提交 5acb457 将对应于上图中的“提交 A”:

4629c88 HEAD@{59}: commit: blah
f93f3d3 HEAD@{60}: commit: blah
57b0ea7 HEAD@{61}: checkout: moving from master to feature_branch
4b39fbf HEAD@{62}: commit: Added bugfix F again
4fa21f2 HEAD@{63}: commit: undid checkouts that were in the wrong branch
1c8b2f9 HEAD@{64}: reset: moving to origin/master
5acb457 HEAD@{65}: checkout: moving from 5acb4576eca4b44e0a7574eea19cca067c039dc5 to master
5acb457 HEAD@{66}: checkout: moving from master to 5acb4576eca4b44e0a7574eea19cca067c039dc5
1c8b2f9 HEAD@{67}: checkout: moving from 1c8b2f9bf54ca1d80472c08f3ce7d9028a757985 to master
1c8b2f9 HEAD@{68}: rebase: checkout master
5acb457 HEAD@{69}: checkout: moving from master to 5acb4576eca4b44e0a7574eea19cca067c039dc5
1c8b2f9 HEAD@{70}: checkout: moving from 5acb4576eca4b44e0a7574eea19cca067c039dc5 to master
5acb457 HEAD@{71}: checkout: moving from master to 5acb4576eca4b44e0a7574eea19cca067c039dc5
1c8b2f9 HEAD@{72}: merge origin/master: Fast-forward
5acb457 HEAD@{73}: checkout: moving from master to master
5acb457 HEAD@{74}: checkout: moving from 5acb4576eca4b44e0a7574eea19cca067c039dc5 to master
5acb457 HEAD@{75}: checkout: moving from undo_branch to 5acb4576eca4b44e0a7574eea19cca067c039dc5
5acb457 HEAD@{76}: checkout: moving from master to undo_branch
1c8b2f9 HEAD@{77}: checkout: moving from undo_branch to master
525dbce HEAD@{78}: cherry-pick: Bugfix F
a1a5028 HEAD@{79}: cherry-pick: Bugfix E
32f8968 HEAD@{80}: cherry-pick: Feature C
8b003cb HEAD@{81}: cherry-pick: Feature B
5acb457 HEAD@{82}: checkout: moving from 5acb4576eca4b44e0a7574eea19cca067c039dc5 to undo_branch
5acb457 HEAD@{83}: checkout: moving from master to 5acb4576eca4b44e0a7574eea19cca067c039dc5
1c8b2f9 HEAD@{84}: checkout: moving from 1c8b2f9bf54ca1d80472c08f3ce7d9028a757985 to master
1c8b2f9 HEAD@{85}: pull origin HEAD:master: Fast-forward
5acb457 HEAD@{86}: checkout: moving from master to 5acb4576eca4b44e0a7574eea19cca067c039dc5
5acb457 HEAD@{87}: reset: moving to 5acb4576eca4b44e0a7574eea19cca067c039dc5
1c8b2f9 HEAD@{88}: merge origin/master: Fast-forward
5acb457 HEAD@{89}: reset: moving to 5acb4576eca4b44e0a7574eea19cca067c039dc5
1c8b2f9 HEAD@{90}: checkout: moving from 5acb4576eca4b44e0a7574eea19cca067c039dc5 to master
5acb457 HEAD@{91}: checkout: moving from master to 5acb4576eca4b44e0a7574eea19cca067c039dc5
1c8b2f9 HEAD@{92}: merge origin/master: Merge made by the 'recursive' strategy.
7b912cd HEAD@{93}: checkout: moving from 7b912cdf33843d28dd4a7b28b37b5edbe11cf3b9 to master
7b912cd HEAD@{94}: cherry-pick: Bugfix F
df7a9cd HEAD@{95}: cherry-pick: Bugfix E
d4d0e41 HEAD@{96}: cherry-pick: Feature C
701c8cc HEAD@{97}: cherry-pick: Feature B
5acb457 HEAD@{98}: checkout: moving from master to 5acb4576eca4b44e0a7574eea19cca067c039dc5
22ecc3a HEAD@{99}: checkout: moving from 5acb4576eca4b44e0a7574eea19cca067c039dc5 to master
5acb457 HEAD@{100}: checkout: moving from master to 5acb4576eca4b44e0a7574eea19cca067c039dc5
22ecc3a HEAD@{101}: commit: bugfix E
3b568bc HEAD@{102}: checkout: moving from feature_branch to master
57b0ea7 HEAD@{103}: commit: blah
152c5b9 HEAD@{104}: checkout: moving from master to feature_branch
3b568bc HEAD@{105}: commit: bugfix D
fe3bbce HEAD@{106}: checkout: moving from feature_branch to master
152c5b9 HEAD@{107}: commit: blah
2318ebc HEAD@{108}: commit: blah
cc5ea32 HEAD@{109}: commit: blah
a5c2303 HEAD@{110}: commit: blah
544a99a HEAD@{111}: commit: blah
299f86a HEAD@{112}: commit: Feature G
fe3bbce HEAD@{113}: checkout: moving from master to feature_branch
fe3bbce HEAD@{114}: commit: Feature C
3852e71 HEAD@{115}: commit: Feature B
5acb457 HEAD@{116}: merge origin/master: Fast-forward

任何人都可以理解这 4 个连续的 cherry-picks 吗?我怀疑他并没有真正做git cherry-pick master~3 的事情,尤其是~3 部分(当我第一次看到它时,这确实让我很生气)。

【问题讨论】:

  • 当然,最明显和最可能的原因仅仅是您的同事按照说明从 master 中删除了 A B C,但忘记在特性分支上提交它们,或者可能确实在特性分支上提交它们但从未推那个? git reflog 在您同事的机器上的功能分支上应该会告诉您是否是这种情况......但如果是这样,那么您也不应该在功能分支中使用 A B C 。如果你还有 feature 分支,你能检查一下吗?
  • 正如@hvd 建议的那样,您可以通过git reflog 审查丢失的提交。这为您提供了带有提交哈希的提交历史记录。然后找到引用您想要获取的提交的行。
  • 好的,您已经验证提交是功能分支的一部分。那挺好的。您是否还可以检查内容是否是功能分支的一部分(并且没有被后来的某些提交意外恢复)?也就是说,检查特性分支,看看是否一切正常。
  • 所以你主要关心的是第(3)项,最终的合并结果。合并结果的历史记录中有哪些提交根本不重要,除非它们与您在git checkout 合并提交时获得的树有什么关系?
  • 我之所以问,是因为每当您“更改”提交时,提交的 ID 都会更改(因为您永远无法更改任何提交——您会得到新的副本) .这会影响所有底层细节,但不会影响最终的合并结果:合并结果取决于三个输入(一旦有更多时间,我将在答案中描述)。

标签: git version-control rebase


【解决方案1】:

提交 A、B 和 C 丢失的原因是因为这是您共享给同事的链接所做的。让我们用下图来说明:

1.假设您的同事所做的原始提交历史是

...X---A---B---C---D---E  master

2。将ABC 移动到feature 分支。 所以你的同事从master(提交E)或任何提交创建了一个新的feature 分支。并通过以下步骤进行变基:

git checkout -b feature
git cherry-pick master~5 master~2

...X---A---B---C---D---E  master
                        \
                         A'---B'---C' feature 

3.修改master分支 by,

git checkout X
git cherry-pick master~2..master
git branch -f master
git checkout master

提交结构如下:

...X---D---E  master
     \
       A'---B'---C' feature 

所以直接原因是命令git cherry-pick master~2..master。它将直接在提交X 上重新提交DE,因此您在master 分支上找不到ABC

更新:

根据git flog,这些 HEAD 信息似乎不足以显示您的同事所做的事情。而feature 分支似乎是从提交C 而不是D 签出的

3b568bc HEAD@{105}: commit: bugfix D
fe3bbce HEAD@{106}: checkout: moving from feature_branch to master
152c5b9 HEAD@{107}: commit: blah
2318ebc HEAD@{108}: commit: blah
cc5ea32 HEAD@{109}: commit: blah
a5c2303 HEAD@{110}: commit: blah
544a99a HEAD@{111}: commit: blah
299f86a HEAD@{112}: commit: Feature G
fe3bbce HEAD@{113}: checkout: moving from master to feature_branch
fe3bbce HEAD@{114}: commit: Feature C

所以结构应该是:

A---B---C---D---E  master
         \
          G---H feature

如果您只想更改提交结构,例如:

A ---D---E  master
 \
  B---C---G---H feature

您可以将master 分支和feature 分支重置为原始分支,然后在master 分支上进行cherry-pick 提交,详细信息如下:

git checkout master
git reset --hard <original commit id for E>
git checkout feature 
git reset --hard  <original commit id for H>
git checkout master
git checkout <commit id for A>
git cherry-pick master~4..master~2 #To make the commits as A---D---E (drop B and C)
git branch -f master
git checkout master

【讨论】:

  • 但是,即使我稍后将功能分支与 master 合并,情况也会如此吗? A、B、C的变化都在特征分支;我刚刚检查过。
  • 命令git cherry-pick master~m..master~n 将重写master 分支以删除从master~mmaster~n-1 的提交。提交 A、B 和 C 可能做了,所以你只能从 feature 分支找到这些提交。我更新了我的答案,让你重置为你想要的。
【解决方案2】:

让我们专注于合并结果,但首先快速浏览一下这部分(我已经重新绘制了一点图表):

回到我之前的(链接的)问题,我们有这样一棵树:

A--B--C--D--E--F   <-- master
          \ 
           G--H   <-- feature

我们想将 B 和 C 移至新功能分支。

结果应该是这样的(勾号表示您现在拥有的提交是副本,而不是原件,因此它们的哈希 ID 已更改,因此所有获得原件的人必须争先恐后地确保他们也使用新副本)。但我只是假设它实际上看起来像这样:

A--D'-E'-F'   <-- master
    \
     B'-C'-G'-H'   <-- feature

(请注意,唯一未复制并切换到的提交是A!)。

当你现在运行时:

git checkout master
git merge feature

Git 会按照这个顺序做这些事情:

  1. 获取当前提交的哈希 ID (git rev-parse HEAD)。
  2. 获取featuregit rev-parse feature)的tip的hash ID。
  3. 找到这两个提交的(单个,在这种情况下)合并基础。 merge base 的技术定义是 DAG 中的 Lowest Common Ancestor,但笼统地说,就是在两个分支分歧之前,简单来说就是“commit D”。
  4. 运行等于git diff D' F' 的内容:将合并基础与master 的尖端进行比较。这是“自合并基础以来我们在 master 上所做的更改”:文件(及其哈希 ID 版本)的大列表,以及任何计算出的重命名信息等。
  5. 运行等于git diff D' H' 的内容:将合并基础与feature 的尖端进行比较。这是“他们在feature 上所做的更改”,与第 4 步中的方式相同。我在第 4 步中使用“我们”一词,在第 5 步中使用“他们”,因为我们可以使用 git checkout --ours 和 @ 987654337@ 在合并冲突期间提取特定文件:--ours 指提交 F' 中的文件,即“我们”更改的内容,--theirs 指提交 H' 中的文件。
  6. 尝试合并差异以获得单个变更集。

    如果 Git 能够自己完成所有这些合并,它就宣布胜利,将这个单一变更集应用到基本提交 D',并进行新的提交——我们称之为 M 进行合并——通常方式(使master 移动到指向M),除了M 有两个父母:

    A--D'-E'-F'-----M   <-- master
        \          /
         B'-C'-G'-H'   <-- feature
    

    如果自动合并失败,但是,Git 会举起它的比喻手,给你留下一团糟,你必须自己清理。我们稍后会讨论这个。

三输入,一输出

请注意,此三路合并有 三个输入

  • 合并基础的树
  • 当前 (--ours, HEAD) 提示提交的树
  • 另一个 (--theirs) 提示提交的树

合并基础在这里起作用,因为它是——事实上,最好的——两个提交偏离的共同起点。 Git 能够直接获取两个分支提示,因为每个提交都是一个完整的快照:1 它永远不必查看所有中间提交,除非根据图表来找到合并基地。

我们还故意掩盖了一些微妙的技术问题,例如配对破坏和重命名查找(见脚注 1),以及合并策略等问题(-s ours 表示我们甚至没有看看他们的)和策略选项(-X ours-X theirs)。但只要您只是在运行 git merge feature 并且几乎不需要担心重命名,那就不是问题。

但是——这是关键项目之一——为了弄清楚 Git 将要做什么,您必须绘制图表,或者以其他方式确定合并基础。 获得合并基础提交的哈希 ID 后,您可以(如果愿意)git diff 合并基础针对两个提示提交,看看 Git 会做什么。但是如果合并基础不是你期望的提交,那么合并将不会做你期望它做的事情。


1与 Mercurial 相比,每个提交都或多或少地存储为来自其父提交的增量或变更集。那么,您可能会认为,Mercurial 必须从合并基础开始,沿着每个分支链向前推进。但是这里有两点需要注意:首先,Mercurial 很可能必须在合并基础之前开始,因为这也可能是来自早期提交的变更集。其次,假设沿着链到任一尖端,进行了一些更改,然后退出。当 Mercurial 去合并最终的变更集以实现与 Git 相同的合并时,提交及其回退还原对最终结果没有影响。所以从这个意义上说,中间提交毕竟都不重要!我们只需要它们来重构要合并的两个最终变更集。

不过,事实上,Mercurial 并没有任何做这些,因为 Mercurial 中的每个文件偶尔都会被完全完整地重新存储,这样 Mercurial 就不必遵循极长的变更集链重建文件。因此,Mercurial 所做的实际上与 Git 所做的相同:它只是提取基本提交,然后提取两个提示提交,然后进行两个差异。

这里有一个很大的技术差异,那就是 Mercurial 不必猜测重命名:中间提交,它再次就像 Git - 它必须遍历到 find em> 合并基础,每个都记录与其父提交有关的任何重命名,因此 Mercurial 可以确定每个文件的原始名称是什么,以及它在任一提示中的新名称可能是什么。 Git 不记录重命名:它只是猜测,如果路径 dir/file.txt 出现在合并库中,但不在一个或两个提示提交中,则可能dir/file.txt 在一个或两个提示提交中被重命名。如果提示提交 #1 的 other/new.txt 不在合并库中,则这是重命名的候选文件。

在某些情况下,Git 无法通过这种方式找到重命名。还有额外的控制旋钮。如果文件更改“太多”,则有一个会破坏配对,即让 Git 说仅仅因为 dir/file.txt 在基础和尖端中,它实际上可能不是 same 文件.为了重命名检测的目的,还有另一个设置 Git 声明要匹配的文件的阈值。最后,有一个最大配对队列大小,可配置为diff.renameLimitmerge.renameLimit。默认的合并配对队列大小大于默认的 diff 配对队列大小(从 Git 版本 1.7.5 开始,目前为 400 对 1000)。


如果有冲突你会得到的混乱

当 Git 声明“合并冲突”时,它会在第 6 步中间停止。它不会进行新的合并提交 M。相反,它会让你一团糟,存储在两个地方:

  • 工作树 对它作为自动合并可以做什么以及使用冲突标记写出的所有冲突合并进行了最好的猜测。如果file.txt 有冲突——Git 无法将“我们所做的”与“他们所做的”合并——它可能有几行如下所示:

    <<<<<<< HEAD
    stuff from the HEAD commit
    =======
    stuff from the other commit (H' in our case)
    >>>>>>> feature
    

    如果您将merge.conflictStyle 设置为diff3(我推荐此设置;另请参阅Should diff3 be default conflictstyle on git?),则将上面的内容修改为包含合并库中的内容(在我们的例子中提交D'),即,什么文本在“我们”和“他们”更改之前就已经存在:

    <<<<<<< HEAD
    stuff from the HEAD commit
    ||||||| merged common ancestors
    this is what was there before the two
    changes in our HEAD commit and our other commit
    =======
    stuff from the other commit (H' in our case)
    >>>>>>> feature
    
  • 同时,index——你构建 next 提交的地方——对于每个冲突文件的每个“槽”最多有三个条目。在这种情况下,对于file.txt,有file.txt的三个版本,分别编号:

    • :1:file.txt:这是 file.txt 的副本,因为它出现在合并库中。
    • :2:file.txt:这是file.txt 的副本,它出现在我们的(HEAD)提交中。
    • :3:file.txt:这是 file.txt 的副本,因为它出现在他们的(feature 的提示)提交中。

现在,file.txt 中存在冲突并不意味着 Git 无法自行解决一些 其他 更改。例如,假设合并基础版本为:

this is file.txt.
it has a bunch of lines.
we plan to change some of them on one side of the merge.
we plan to change other lines on the other side.
here is something to change without conflict:
la la la, banana fana fo fana
here is something else
to change with conflict:
this is what was there before the two
changes in our HEAD commit and our other commit
and finally,
here is something to change without conflict:
one potato two potato

HEAD 中,让我们以这种方式读取文件,使用我们希望达到这一点的任何提交:

this is file.txt.
it has a bunch of lines.
we plan to change some of them on one side of the merge.
we plan to change other lines on the other side.
here is something to change without conflict:
a bit from the Name Game
here is something else
to change with conflict:
stuff from our HEAD commit
and finally,
here is something to change without conflict:
one potato two potato

(请注意,我们进行了两个不同的更改区域。默认情况下,git diff 会将它们组合成一个差异块,因为它们之间只有一个上下文行,但git merge 会将它们视为单独的更改。)

在另一个 (feature) 分支中,让我们进行一组不同的更改,以便 file.txt 读取:

this is file.txt.
it has a bunch of lines.
we plan to change some of them on one side of the merge.
we plan to change other lines on the other side.
here is something to change without conflict:
la la la, banana fana fo fana
here is something else
to change with conflict:
stuff from the other commit (H' in our case)
and finally,
here is something to change without conflict:
cut potato and deep fry to make delicious chips

同样,我们做了两处更改,但只有一处发生了冲突。

work-tree 版本的合并文件将接受每个 冲突的更改,以便完整读取文件:

this is file.txt.
it has a bunch of lines.
we plan to change some of them on one side of the merge.
we plan to change other lines on the other side.
here is something to change without conflict:
a bit from the Name Game
here is something else
to change with conflict:
<<<<<<< HEAD
stuff from the HEAD commit
=======
stuff from the other commit (H' in our case)
>>>>>>> feature
and finally,
here is something to change without conflict:
cut potato and deep fry to make delicious chips

作为合并的人,解决冲突是你的工作。

您可以选择这样做:

git checkout --ours file.txt

或:

git checkout --theirs file.txt

但其中任何一个都只是将“我们的”或“他们的”index 版本(从插槽 2 或 3)复制到工作树。无论您选择哪一个,您都将丢失 other 分支的更改。

您可以手动编辑文件,删除冲突标记并保留或修改部分或全部剩余行以解决冲突。

或者,当然,您可以使用任何您喜欢的合并工具来处理冲突。

不过,在所有情况下,您的工作树中的任何内容都将是您的最终产品。然后你应该运行:

git add file.txt

清除阶段 1、2 和 3 条目并将文件的工作树版本复制到正常的阶段零 file.txt。这告诉 Git 现在已为 file.txt 解析合并。

您必须对所有剩余的未合并文件重复此操作。在某些情况下(重命名/重命名冲突、重命名/删除、删除/修改等)还有一些工作要做,但归根结底是确保索引只有最后的零阶段条目你想要的,没有更高阶段的条目。 (您可以使用git ls-files --stage 来查看all all 中的条目,尽管git status 很好地总结了有趣的条目。特别是,所有具有与HEAD 提交完全匹配的零阶段条目的文件非常无聊,git status 会直接跳过它们。如果有成百上千个这样的文件,那非常很有帮助。)

解析完索引中的所有文件后,运行git commit。这使得合并提交Min 提交的内容是您的索引中的任何内容,即,您 git add-ed 删除更高阶段索引条目并插入零阶段条目的任何内容。

使用git checkout检查同时解析

如上所述,git checkout --oursgit checkout --theirs 只是从索引槽 2 或 3 获取副本并将其写入工作树。这不会解析索引条目:所有未合并的插槽 1、2 和 3 条目仍然存在。您必须返回git add 工作树文件以将其标记为已解决。正如我们还注意到的,这会丢失来自其他提示提交的任何更改。

不过,如果这是您想要的,那么有一条捷径。你可以:

git checkout HEAD file.txt

或:

git checkout MERGE_HEAD file.txt

这会从 HEAD (F') 或 MERGE_HEAD (H') 提交中提取 file.txt 的版本。在此过程中,它将内容写入file.txt 的第 0 阶段,从而消除第 1、2 和 3 阶段。实际上,它获得了--ours--theirs 版本andgit adds 一下子得到结果。

同样,这会丢失来自提示提交的所有更改。

很容易弄错

这些解决步骤很容易出错。尤其是git checkout --oursgit checkout --theirs,以及它们使用HEADMERGE_HEAD 的快捷版本,去掉对方的对文件的更改。您将拥有的唯一迹象是合并结果缺少一些更改。就 Git 而言,这是正确的结果:想要那些更改被删除了;这就是为什么您在进行合并提交之前以这种方式设置阶段零索引条目的原因。

也很容易获得令人惊讶的合并基础,特别是如果您尝试执行大量 git rebasegit cherry-pick 工作来复制提交并移动分支名称以指向新副本。仔细研究提交 DAG 总是值得的。从“A DOG”那里获得帮助:git log --all --decorate --oneline --graphall decorate oneline graph;或使用gitk 或其他一些图形查看器来可视化提交图。 (除了--all,您还可以考虑使用有问题的两个分支名称,即 DOG 而不是任何旧的 A DOG:git log --decorate --oneline --graph master feature。生成的图表可能更简单且更易于阅读。但是,如果您做了很多变基和挑选,--all 可能会透露更多信息。您甚至可以将其与特定的 reflog 名称结合使用,例如 feature@5,尽管这有点冗长并且会导致图表非常混乱。)

【讨论】:

  • 非常感谢您的详细回答。然而......对于我的特殊情况,整个事情在一开始就崩溃了,因为我什至不确定最初的变基是否正确完成(正如我之前提到的,它是由我的同事完成的;我给他发了指示,但他“不确定”他是否 100% 关注了他们),换句话说,我什至不确定这棵树是否真的像你在答案开头所画的那样;这只是我的想要的结果。
  • (续)我发布整个git reflog 日志的原因正是为了让人们帮助我阐明我的同事是否正确地遵循了我的指示。看着它,我的印象是他没有;链接的答案告诉他git cherry-pick master~3..master,在我们的例子中,这将有 2 次精心挑选的提交......但在上面的日志中,我看到他做了 4 次挑选,包括他应该移动到的 2 次提交另一个分支。所以我怀疑整个事情从那里分崩离析。
  • 尽管如此,你给了我这样的想法,也许我应该为我的两个分支寻找共同的祖先。所以...:stackoverflow.com/questions/43712418/…。答案赞赏:-)
  • 我看到你已经得到了答案(是的,它是git merge-base &lt;commit1&gt; &lt;commit2&gt;:它向你展示了 a 合并基础,如果只有一个,它就是 the 合并基础;使用--all 来获取所有这些,尽管很少有超过一个——它需要在通向基础的路径中交叉合并)。同时,要查看图表,请使用上述git log --graph 或图形查看器。 reflog 历史记录可能会让你解决它,但这是很多工作,而查看器只会显示它。
【解决方案3】:

你已经得到了很长很好的答案。让我补充一下:

我个人的理论是 git 以某种方式“记住”了我们撤消的提交 A B C,所以当它们来自新功能分支时,git 决定不合并它们。

Git 永远不会“以某种方式”“记住”关于存储库内容的任何内容。它也不会根据你以前做过的事情来决定做或不做任何事情。在这方面它是非常干净的。它的所有命令只是用于处理其提交(以及在较低级别上,它存储的所有其他对象)正在构建的有向无环图的工具。为了使它更容易,它只会添加东西,不会更改或删除任何东西。

除了提交(即作者、时间戳、父提交等)、树(即目录)、blob(即二进制数据)和一些不太重要的东西之外,实际上没有数据结构或进一步的管理信息关于存储库中的文件等。合并提交不会留下任何特定于“合并”的信息;这只是与多个父母的提交。

当然不会发生什么神奇的、无证的事情。存储库非常开放,您可以使用 git 命令查看所有内容,并且所有内容都有完整的文档(如果您有兴趣,请谷歌“git 数据结构”或“git 内部”)。如果您愿意,即使修改内部对象也很容易。

bit 保存历史信息的地方有一点,这就是所谓的“rerere 缓存”,它存储以前的冲突解决方案,因此确实可以改变未来合并的行为。确实非常方便,但默认情况下未启用,并且肯定与手头的主题无关。

编辑:如果这个解释听起来太像“神奇的想法”,我很抱歉,但我很茫然。如果它是正确的,我欢迎任何尝试用更专业的术语来解释这个解释

相信消息来源,卢克。很高兴您尝试了解 git,并且坚信一切都是平淡无奇的,希望对您有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-19
    • 2020-02-21
    • 2020-12-23
    • 1970-01-01
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    相关资源
    最近更新 更多