在合并过程中,Git 什么时候会丢失更改?

5
假设现在有这么一种情况:
1. 我们有一个主分支,其中一位同事不小心添加了一系列提交(称之为A B C),这些提交本应属于一个新功能。 2. 我发现了这个问题,并告诉他将这些提交移动到一个新分支中,但是保留其他不相关的提交。我给他发送了这个问题,并告诉他按照回答进行操作:git:如何将分支的根节点向后移动两个提交。 3. 几天后,当新功能分支准备好后,我将其合并到主分支。 4. 在解决合并中的所有冲突后,我提交了更改...... 5. ......然后我发现那些第一次提交(A B C)已经消失了。 6. 我问我的同事,他说“他认为”他使用链接中提到的方法移动了这些更改(基本上:检查最后一个公共提交,然后使用git cherry-pick只选择我们想要的提交),但他记不清了。 7. 我检查了仓库的历史记录,A B C在功能分支的开头确实存在。它们似乎已经成功地从主分支迁移。
鉴于上述情况,有人可以解释为什么git丢失了这些更改吗?(我的个人理论是,git某种程度上“记住”了我们撤消的提交A B C,因此当它们来自新的功能分支时,git决定不合并它们。编辑:很抱歉,如果这个解释听起来像是“神奇的思维”,但我还是无从下手。如果我是对的,欢迎任何试图用更技术性的术语来表达这个解释)。
很抱歉不能提供更多详细信息,但我没有亲自在仓库中进行这些更改,因此无法给出确切的细节。
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

有人能理解连续四个 cherry-pick 吗?我怀疑他并没有真正执行 git cherry-pick master~3,特别是其中的 ~3 部分(当我第一次看到它时也让我感到困惑)。


2
最明显和最有可能的原因是你的同事按照指示从主分支中删除了A B C,但忘记在特性分支上提交它们,或者可能已经在特性分支上提交了它们,但从未推送过?在同事的机器上,在特性分支上运行git reflog命令,就可以告诉你是否是这种情况...但如果是这样,那么特性分支中也不应该有A B C。如果你仍然拥有特性分支,你能检查一下吗? - user743382
1
正如@hvd所建议的那样,您可以通过git reflog来查看丢失的提交。这将给您提供一个带有提交哈希的提交历史记录。然后找到引用您想要获取的提交的行。 - pRaNaY
1
好的,你已经验证了提交是特性分支的一部分。这很好。你能否也检查一下内容是否属于特性分支(并且没有被某个后续提交意外地还原)?也就是说,检出特性分支,看看是否一切正常。 - user743382
1
你的主要关注点是第三项,即最终合并结果。除了它们可能与您在git checkout合并提交时获得的树相关之外,历史记录中有哪些提交并不重要。 - torek
1
我之所以这样问是因为每当您“更改”提交时,提交的ID会发生变化(因为您永远都不能更改任何提交—您只能获得新的副本)。这会影响所有底层细节,但不会影响最终的合并结果:合并结果取决于三个输入(我将在稍后回答中描述这三个输入)。 - torek
显示剩余8条评论
3个回答

5
“commits A、B和C丢失的原因是你分享给同事的链接导致了这个结果。下面通过图表来说明:”
“1. 假设你的同事最初的提交历史如下:”
...X---A---B---C---D---E  master

2. 将 A, BC 移动到 feature 分支。 因此,您的同事从主分支(提交 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分支,方法如下:

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。它会将提交DE直接变基在提交X上,所以你不能在主分支上找到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)提交(commit)。具体步骤如下:

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

但是即使我之后将功能分支与主分支合并,情况也会是这样吗?A、B、C中的更改都在功能分支中;我刚刚检查了。 - PaulJ
1
命令 git cherry-pick master~m..master~n 将重写 master 分支以删除从 master~mmaster~n-1 的提交。提交 A、B 和 C 可能已经完成,因此您只能从 feature 分支中找到这些提交。我已经为您更新了答案,以便您可以按照自己的意愿进行重置。 - Marina Liu

3

让我们集中关注合并结果,但首先快速浏览一下这部分内容(我稍微重新绘制了图表):

To get back to my previous (linked) question, we had a tree like this:

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

And we wanted to move B and C to the new feature branch.

结果应该看起来像这样(勾号表示您现在拥有的提交是 副本,而不是原始提交,因此它们的哈希 ID 已更改,因此每个获得原始提交的人都必须匆忙确保 他们 也使用新的副本)。但我假设它确实看起来像这样:
A--D'-E'-F'   <-- master
    \
     B'-C'-G'-H'   <-- feature

请注意,唯一没有复制和切换的提交是 A

现在运行以下命令:

git checkout master
git merge feature

Git会按照以下顺序执行这些操作:
  1. Get the hash ID of the current commit (git rev-parse HEAD).
  2. Get the hash ID of the tip of feature (git rev-parse feature).
  3. Locate the (single, in this case) merge base of those two commits. The technical definition of the merge base is the Lowest Common Ancestor in the DAG, but loosely speaking, it's just before the two branches diverge, which is simply "commit D'".
  4. Run what amounts to git diff D' F': diff the merge base with the tip of master. This is "what we changed on master since the merge base": a big list of files (and their hash ID versions), along with any computed rename information and the like.
  5. Run what amounts to git diff D' H': diff the merge base with the tip of feature. This is "what they changed on feature", in the same way as in step 4. I use the word "we" for step 4, and "they" here in step 5, because we can use git checkout --ours and git checkout --theirs to extract particular files during a merge conflict: --ours refers to files in commit F', i.e., what "we" changed, and --theirs refers to files in commit H'.
  6. Attempt to combine the differences to get a single changeset.

    If Git is able to do all this combining on its own, it declares victory, applies this single changeset to the base commit D', and makes a new commit—let's call this M for merge—in the usual way (so that master moves to point to M), except that M has two parents:

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

    If the automatic merge fails, however, Git throws up its metaphorical hands and leaves you a mess that you must clean up yourself. We'll go into this in a moment.

三个输入,一个输出

请注意这个三向合并有三个输入

  • 合并基础树(tree for the merge base)
  • 当前分支的树(--ours, HEAD)的最新提交(commit)
  • 另一个分支的树(tree for the other)(--theirs)的最新提交(commit)

这里采用合并基础是因为它是两个提交分离之前的公共起点, 实际上也是最好的一种方法。Git能够直接链接两个分支的最新提交,因为每个提交都是一个完整的快照,除了在图形方面找到合并基础外,它永远不需要查看所有中间提交。

我们故意略过了许多微妙的技术问题(例如配对-断开和重命名查找(见脚注1)以及像合并策略(-s ours表示我们甚至不看theirs)和策略选项(-X ours或-X theirs)这样的东西)。但只要你运行的是git merge feature且没有太多重命名需要担心,那就没有问题。

但是要记住,这是其中一个关键信息-为了弄清楚Git将要做什么,你必须绘制图形或以其他方式确定合并基础。一旦你获得了合并基础提交的哈希ID,你就可以(如果你想)git diff合并基础和两个最新的提交,看看Git会做什么。但是,如果合并基础不是您预期的提交,那么合并将不会按照您的预期进行。


1与Mercurial相比,每个提交都更或多或少存储为与其父提交(parent commit)的delta或变更集。因此,您可能会认为Mercurial必须从合并基础开始,沿着每个分支链向前推进每个提交。但这里有两件事情需要注意:首先,Mercurial可能需要从合并基础之前开始,因为该提交也可能是较早提交的变更集。其次,假设向任一端点的链中进行了某些更改,然后被撤消。当Mercurial尝试组合最终的变更集以实现与Git相同的合并时,提交及其撤消还原对最终结果没有影响。因此,在那种意义上,所有的中间提交都无关紧要!我们只需要用它们来重建要组合的最终两个变更集。

实际上,Mercurial没有像Git一样对每个文件进行跟踪,因为Mercurial会将每个文件全部保留,这样就不需要跟随极长的变更集链来重建文件。因此,Mercurial实际上与Git执行的操作是相同的:它提取基础提交,然后提取两个端点提交,并做出两个差异。
这里有一个重要的技术差异,即Mercurial不必猜测重命名的文件:中间提交记录了任何针对父提交的重命名,因此Mercurial可以确定每个文件的原始名称以及其在任一端点上的新名称。Git不记录重命名:它只是猜测如果路径dir/file.txt出现在合并基础中,但未出现在一个或两个端点提交中,那么dir/file.txt可能在一个或两个端点提交中被重命名。如果端点提交#1具有合并基础中没有的other/new.txt,则该文件是重命名的候选文件。
在某些情况下,Git无法用这种方式找到重命名。还有其他控制开关。当文件发生“太多”更改时,即Git认为 dir/file.txt 在基础和端点中都存在,但实际上可能并不是相同的文件时,有一个选项来断开匹配。还有一个选项可以设置Git为重命名检测目的而声明文件匹配的阈值。最后,还有一个最大匹配队列大小,可以配置为diff.renameLimitmerge.renameLimit。默认合并匹配队列大小比默认的差异匹配队列大小更大(自Git版本1.7.5以来,当前为400 vs 1000)。
冲突时可能会出现混乱
当Git宣布“合并冲突”时,它停在第6步中间。它不会创建新的合并提交M,而是留下一堆烂摊子,在两个地方存储:
  • The work-tree has its best guess at what it could do as an automated merge, plus all the conflicting merges written out with conflict markers. If file.txt has a conflict—a place where Git was unable to merge "what we did" with "what they did"—it might have a few lines that look like this:

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

    If you set merge.conflictStyle to diff3 (I recommend this setting; see also Should diff3 be default conflictstyle on git?), the above is modified to include what's in the merge base (commit D' in our case), i.e., what text was there before both "we" and "they" changed it:

    <<<<<<< 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
    
  • Meanwhile, the index—the place where you build the next commit to make—has up to three entries per "slot" for each conflicted file. In this case, for file.txt, there are three versions of file.txt, which are numbered:

    • :1:file.txt: this is a copy of file.txt as it appears in the merge base.
    • :2:file.txt: this is a copy of file.txt as it appears in our (HEAD) commit.
    • :3:file.txt: this is a copy of file.txt as it appears in their (tip of feature) commit.
现在,仅仅因为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

我们又进行了两次更改,但只有一个冲突。

工作树版本的合并文件将采用每个不发生冲突的更改,因此该文件将完整读取:

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

但是这两种方法都只是将“ours”或“theirs”索引版本(从槽2或3)复制到工作树中。无论你选择哪种方法,你都会失去来自另一个分支的更改。
你可以手动编辑文件,删除冲突标记,并保留或修改一些或所有剩余行以解决冲突。
当然,你也可以使用任何你喜欢的合并工具来处理冲突。
在所有情况下,无论如何,你的工作树中的内容将是你的最终产品。然后你应该运行:
git add file.txt

将阶段1、2和3的条目清除,并将工作树版本的file.txt拷贝到普通的阶段0中。这告诉Git合并现在已经解决了file.txt

您必须对所有剩余未合并的文件重复此操作。在某些情况下(例如重命名/重命名冲突,重命名/删除,删除/修改等),需要做更多的工作,但最终目标是确保索引仅具有所需的最终阶段0条目,没有更高级别的条目。(您可以使用git ls-files --stage查看所有阶段的所有条目,尽管git status很好地总结了有趣的部分。特别是,所有具有与头部提交完全匹配的阶段0条目的文件非常无聊,而git status会直接跳过它们。如果有成百上千个这样的文件,则非常有帮助)。

一旦您已解决索引中的所有文件,您就可以运行git commit,以生成合并提交M。提交包含索引中的内容,即您git add所选的内容,以删除较高阶段的条目并插入阶段0条目。

使用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的版本。 这样做,它将内容写入stage zero以获取file.txt的--ours或--theirs版本,并一次性清除1、2和3阶段。 实际上,它获取了--ours或--theirs版本并添加了结果。
同样,这会丢失来自tip提交的任何更改。
很容易搞错这个过程。特别是,git checkout --ours和git checkout --theirs及其使用HEAD和MERGE_HEAD的快捷方式会删除文件的另一侧的更改。您唯一的指示是合并结果缺少某些更改。 就Git而言,那是正确的结果:您想要那些更改被删除;这就是为什么在进行合并提交之前设置了stage-zero索引条目的原因。
还很容易出现意外的合并基础,特别是如果您尝试进行大量的git rebase或git cherry-pick工作来复制提交并将分支名称移动到新副本。 值得仔细研究提交DAG。 从“A DOG”获得帮助:git log --all --decorate --oneline --graph,即all decorate oneline graph;或使用gitk或其他图形查看器来可视化提交图。 (您可以考虑使用两个相关的分支名称,即master feature而不仅仅是任何旧的A DOG:git log --decorate --oneline --graph master feature。 生成的图形可能更简单,更易读。 但是,如果您进行了大量的重新基础化和挑选樱桃,则--all可能会揭示更多内容。 您甚至可以将其与特定的reflog名称(例如feature@5)相结合,尽管这有点冗长并且会产生非常混乱的图形。)

非常感谢您详细的回答。然而,对于我的特殊情况,整个过程在一开始就失败了,因为我甚至不确定最初的变基是否正确(正如我之前提到的,它是由我的同事完成的;我给他发了说明,但他“不确定”他是否完全遵循了它们),换句话说,我甚至不确定树在您回答开头时是否真的像您画的那样;那只是我的期望结果。 - PaulJ
我发布整个“git reflog”日志的原因正是为了让人们帮助我阐明我的同事是否正确遵循了我的指示。看着它,我的印象是他没有;链接的答案告诉他要“git cherry-pick master~3..master”,在我们的情况下这将会挑选2个提交...但是在上面的日志中,我看到他做了4个cherry-pick,包括他应该移动到另一个分支的2个提交。所以我怀疑整件事情从那时候开始就崩溃了。 - PaulJ
尽管如此,你给了我一个想法,也许我应该寻找两个分支的共同祖先。所以...:https://dev59.com/oaHia4cB1Zd3GeqPSFmN 。感谢回答 :-) - PaulJ
我看到你已经得到了答案(是的,它是 git merge-base <commit1> <commit2>:它会显示一个合并基础,如果只有一个,那就是the合并基础;使用--all获取所有合并基础,尽管通常只有一个 - 这需要在通往基础的路径上进行交叉合并)。同时,要查看图形,请使用如上所述的git log --graph或图形查看器。Reflog历史记录可能会让您解决问题,但这需要很多工作,而查看器只会显示它。 - torek

0
你已经得到了非常详细和优秀的回答。让我再补充一下:
我的个人理论是,git 在某种程度上“记住”了我们取消的提交 A、B、C,因此当它们来自新功能分支时,git 决定不合并它们。
Git 从未以任何方式“记住”有关存储库内容的任何信息。也不会基于您之前所做的任何事情决定要做或不要做任何事情。在这方面,它非常干净。它的所有命令只是工作于其提交(以及在更低级别上,它存储的所有其他对象)构建的有向无环图的工具。为了使它更容易使用,它只会添加内容,永远不会更改或删除任何东西。
除了提交(即作者、时间戳、父提交等)、树(即目录)、blob(即二进制数据)和一些不太重要的内容外,存储库中没有任何数据结构或其他管理信息与文件之类的资料相关。合并提交不会留下任何特定于“合并”的信息;它只是一个具有多个父提交的提交。

肯定没有神奇的、未记录的东西发生。 仓库非常开放,你可以使用git命令查看所有内容,并且所有内容都有完整文档记录(如果您感兴趣,可以搜索“git数据结构”或“git内部原理”)。即使您希望修改内部对象,也很容易。

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

编辑:如果这种解释听起来太像“神秘思考”,我很抱歉。 如果正确,欢迎任何试图用更技术化的术语来表述这一解释的尝试

相信源代码,卢克。你正在努力理解git,强烈相信一切都是平凡而非神奇的应该有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接