【问题标题】:What happened to this Git repo and how can I restore sanity to it?这个 Git repo 发生了什么,我怎样才能恢复它的理智?
【发布时间】:2017-02-26 21:33:22
【问题描述】:

我从一个会议回来,在一个关键项目的主分支中发现了一个令人愉快的奇怪之处。我在会议之前提交的一些文件不再出现在大师的头上。

症状

如果我通过 TFS 的 Web 界面或 Visual Studio 中的集成查看包含丢失文件的目录的历史记录,则对目录中任何内容的最后一次提交是我添加了几个丢失的文件。没有后续提交删除它们。

如果我通过 TFS Web 界面导航项目,则目录中不存在我的文件。

如果我使用git log [directory] 从命令行检查它们,最后出现的提交是 8 月 24 日之后的提交,在添加丢失的文件之前。缺少五个提交。

如果我在项目的顶层执行git log .,就会出现这五个提交消息确实

在我离开时,一个当前不可用的同事对项目进行了两次提交。它们不会触及文件丢失的项目的同一部分。这位同事很有可能在 8 月 24 日之后,但在随后的五次提交之前,最后一次从 master 中拉出。

理论

根据我有限的理解,我推测该同事在尝试引入他的更改时遇到了冲突,而不是解决它们,可能已经完成了 git reset 以继续进行,但我承认我对这些领域的了解git 充其量是不稳定的。

问题

  • 这符合上述症状吗?
  • 我可以在日志中查找表明此类操作的内容吗?
  • 如果这是可能的原因,我如何才能恢复似乎已撤消的五次左右的提交?

【问题讨论】:

  • 尝试运行git reflog 你应该会看到更多的提交

标签: git tfs


【解决方案1】:

短版

  1. 是的。

  2. 使用--full-history,如果需要,还可以使用-m 和更多标志。

  3. 您可以使用git cherry-pick 将原始提交重新复制到新的合并后提交;或者手动做一些可能更简单。您可以在任何给定的“坏”合并上使用git show -m 来获取针对双方父母的差异,然后手动应用丢弃的更改,或者甚至按摩差异并使用git apply。或者,您可以在错误合并之前检查提交,自己手动重新进行合并,将“错误”合并的树与更正的树进行比较,从而提出一个可能适用于的补丁你现在拥有的提交链。

第 2 点的加长版

使用git log --full-history -- <path> 避免丢弃一些触及给定路径的提交。如果没有--full-history,Git 倾向于丢弃仅从一侧修改文件的合并,例如,在每个文件的基础上,使用仅我们或他们的策略解决冲突。 (这对我来说似乎是一个错误:某些提交限制的情况,就像这里的路径一样,可能应该 include 一些甚至所有组合 diff 显式丢弃的合并 em>。但这是一个哲学上的改变/“错误”,而不是一个彻底的“明显不正确的行为”错误。)

添加-m --name-status 以观察合并提交本身的修改。添加--merges 以观察only 合并:

git log --full-history -m --name-status --merges -- <path>...

您可以将--name-status 替换为-p 以使git log 显示差异。或者,一旦您发现了可疑的合并(通过其哈希 ID),请使用 git show -m &lt;hash&gt; 详细查看它们。

请注意,对于 -mgit loggit show 的合并输出会发生一些变化:

commit <sha1> (from <parentN>)
Merge: <parent1> <parent2>
Author: ...

-m 标志在这里所做的是将合并拆分为多个虚拟提交,每个虚拟提交一个。然后针对该特定父级进行差异。

场景(或者你是怎么到这里的,或者第 1 点的长版本)

很多时候,有人运行git merge 并通过简单地选择“左侧版本”或“右侧版本”而不是“大部分来自基础,并在适当的情况下从左侧一点点”来错误处理合并冲突, 并根据需要从右边一点点”。结果是冲突文件丢失了一半合并的所有更改

有时正确的做法,有时则不是。 Git 产生了冲突,因为至少有一些更改发生了冲突。当某些更改发生冲突时,很可能只有“A 列”或“B 列”有正确答案。但是我们可能在同一个文件的不同行中存在冲突的更改。

例如,考虑这个(相当人为的)文本文件的片段:

  #       base             column A           column B

 94:   We show that       We show that       We show that
 95:   2 + 2 = 5.         2 + 2 = 3.         2 + 2 = 4.
 96:
 97:
 98:
 99:
100:
101:
102:   This moth proof... This math proof... This moth proof...

“基本”版本有两个错误,一个是 2 + 2 = 5 的声明,另一个是 moth proof

“A 列”中一个分支上的版本试图纠正它:2+2 不是 5,它是数学(或 "maths")证明,而不是飞蛾证明。

另一个分支上的版本,“B 列”,确实纠正了它——但那个版本的作者错过了第 102 行的错误。

在这种情况下,正确的合并策略是从第二个更改的版本中获取第 95 行,从第一个更改的版本中获取第 102 行。一些合并工具使这很容易正确地完成——我自己在一个文件上使用vimmerge.conflictstyle = diff3 合并冲突标记,因此甚至不必查看第 102 行没有冲突——一些合并工具试图向你展示一个全局视图,让你很容易看到这个并说“哦,只使用第二个父级”(B栏)。

简而言之,您的理论可能是正确的(尽管git reset 可能不是罪魁祸首)。但是,要确定这一点,您必须观察同一个人重新执行相同的合并 - 假设他或她尚未学会正确地执行合并!

第 3 点的加长版

我认为,绘制一些提交图时效果最好。您可以从各种工具中获取此信息,包括 git log --graph(可选使用 --oneline--decorate 之类的标志),以及 gitk 之类的可视化工具。他们倾向于垂直呈现图表,较新的提交位于顶部。出于文本原因,我喜欢将它们水平绘制,将较新的提交向右绘制。

假设图表当前看起来像这样:

...--o--*--o--o--o--E--M--o--o--F   <-- branch
         \            /
          A--B--C--D-´   <-- topic [label may no longer exist]

这里,* 是错误合并 M 的合并基础。当时有人破坏了东西,更主要的branch 的尖端是提交E,而topic 分支的尖端是提交D。进行合并的人在分支branch,事情看起来像这样:

...--o--*--o--o--o--E   <-- branch
         \
          A--B--C--D    <-- topic

他们运行了git merge branch,或者可能从某些 GUI 中执行了等效操作,导致他们走上了错误的合并花园路径。这产生了合并冲突,他们不恰当地解决并提交了。

您可以重新进行整个合并。只需按 ID 签出 commit E,和/或给它一个分支名称。使用git checkout -b,我们可以同时做到这两点:

git checkout -b remerge <id-of-E>

(或者,您可以在“分离 HEAD”模式下执行此操作,这就是我为快速一次性测试所做的操作。稍后您可以随时使用 git checkout -b 为分离的 HEAD 命名。)

现在提交 E 是最新的,只需重新运行合并:

git merge <id-of-D>

Git 将执行与“当时”相同的合并操作,因为它从同一个图表开始将同一个提交合并到同一个提交中。 (注意:如果您启用了git rerere,您可能希望在此处暂时禁用它,特别是如果您是错误合并的人。:-))

如果您现在解决冲突(这次正确)并提交结果,您将获得一个新的合并 M2

...--o--*--o--o--o--E--M--o--...   <-- branch
         \           \/
          A--B--C--D-´\
                    \  \
                     `--M2   <-- remerge

您现在可以比较 MM2

git diff <id-of-M> HEAD

查看将M 更改为M2 所需的内容。

这里有很多选择

不管你有没有M2,你都可以:

  • 查看提交 A--B--C--D,如果您只需要其中的部分或全部,请使用 git cherry-pick 将它们复制到您现在所在的位置:

    ...--o--*--o--o--o--E--M--o--o--F--A'-B'-C'-D'   <-- branch
             \            /
              A--B--C--D-´
    

    这里的A'B'C'D'AD 的精选副本。

  • 尝试git apply-ing 在MM2 之间的差异F

    ...--o--*--o--o--o--E--M--o--o--F--G   <-- branch
             \            /
              A--B--C--D-´
    

    (将git diff 输出保存在一个文件中,或者重新运行git diff 并通过管道传输到git apply)。

  • 您现在甚至可以将M2 合并到branch 中(尽管这可能会很混乱:提交FM2 的合并基础是D 和@ 的虚拟合并987654399@,这是一个糟糕的情况,因为我们首先在这里假设了冲突!),或者挑选M2 对抗它的第一个父级E

根据情况,如果没有太多,我可能会选择樱桃采摘A--B--C--D,甚至将它们复制到新的主题分支并合并;或者只是git apply-ing M-vs-M2 diff。

使用 rebase 复制主题分支以另一种方式重做合并

“复制到新主题分支”方法可能值得在这里单独描述一下,因为git rebase 是执行此操作的命令。以下是如何使用git rebasetopic 复制到retopic。假设我们从这个开始:

...--o--*--o--o--o--E--M--o--o--F   <-- branch
         \            /
          A--B--C--D-´   <-- topic

首先,我们需要一个名为retopic的分支:

git checkout -b retopic topic

(如果名称 topic 消失了,使用提交的 ID D。)现在我们有了:

...--o--*--o--o--o--E--M--o--o--F   <-- branch
         \            /
          A--B--C--D-´   <-- topic, HEAD -> retopic

现在只需运行git rebase --onto branch &lt;id-of-E&gt;。如果E 的 ID 不方便,但提交的 ID A 是,使用&lt;id-of-A&gt;^(注意帽子后缀)生成提交的 ID *。我们在这里所做的只是指示git rebase 复制以D 结尾的提交(其中retopic 点),并从提交A 开始(通过排除* 和更早的提交,这些提交可以从提交@ 访问987654426@)。

在发生冲突时解决冲突——您可能希望在开始变基之前启用git rerere——完成后,您将拥有:

                                  A'-B'-C'-D'   <-- HEAD -> retopic
                                 /
...--o--*--o--o--o--E--M--o--o--F   <-- branch
         \            /
          A--B--C--D-´   <-- topic

您现在可以 git checkout branchgit merge --no-ff retopic 从 rebase 复制的提交中进行 new 合并 M2。 (注意:A'D' 的部分或全部可能在复制过程中完全丢失,具体取决于错误合并 M 中保留的内容。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-21
    • 2017-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多