依靠远程,更改丢失;发生了什么事?

时间:2018-11-26 20:19:14

标签: git

我今天有一个问题,但我仍然不知道发生了什么。

我想从远程获取更改,然后重新基于它。这些都在同一个分支中,为方便起见,我们假设dev

a --- b --- c --- e <-- local/dev

a --- b --- c --- d <-- remote/dev

我认为这样做的方法是:

git fetch
git checkout dev
git rebase remote/dev

我相当确定我过去曾经这样做过。我期望的结果将是:

a --- b --- c --- d --- e

提交消息似乎在备份,表明这确实是历史状态,但是, e中包含的更改不再存在。我无法解释这一点,也无法通过搜索互联网进一步了解它。

可能是我没有遵循上面概述的步骤,或者可能还有其他因素可以解释发生的情况。我想我的问题是,发生的事情是否超出正常水平,还是必须有其他情况来解释?

在这里,也许git pull --rebase是更好的选择。

1 个答案:

答案 0 :(得分:2)

肯定还有其他情况。 (我不知道它们可能是什么。)

  

在这里,也许git pull --rebase是更好的选择。

那做同样的事情。您的原始命令序列为git checkout dev加上命令对git fetch; git rebasegit pull执行的操作是运行git fetch,然后执行第二个Git命令,通常是git merge,但是git pull --rebase则运行git rebase秒。因此:

git checkout dev; git pull --rebase

与以下相同:

git checkout dev; git fetch; git rebase

只短了四个字符(包括分号和空格)。

从不良的恢复中恢复

请注意,您的原始提交仍可在您的存储库中使用。要找到它们,请使用以下两种机制之一:

  • ORIG_HEAD:这是一个标记,可以在进行任何更改之前设置多个不同的命令。如果您所做的最后更改是git rebase,则ORIG_HEAD将是更改前保存的基准。 (设置它的其他命令是git amgit reset,有时是git merge,当它执行快进操作而不是进行合并时。)

    < / li>
  • HEAD reflog 。这将存储HEAD的多个先前值。每个都有编号和时间戳。旧条目最终会过期:默认情况下,Git确保不会在至少30或90天之前发生这种情况。在没有其他非常有用的背景信息的情况下解释这里发生的情况有些棘手。

    (有关背景信息,请参见Think Like (a) Git。真正发生的是 reachable reflog条目-从引用的当前值可以访问,即-到90天到期默认情况下,无法访问 -from-the-ref条目默认有30天有效期。两者都是可调的,特殊的refs/stash引用具有不同的默认值:隐藏reflog条目永不过期,默认情况下。)

除了只有一个ORIG_HEAD且reflog条目基于 time 而到期,而不是被存储在ORIG_HEAD中的下一个值覆盖的事实外,这些两种方法的工作方式几乎相同。

要查看来自ORIG_HEAD的提交,请使用git log ORIG_HEAD(或其他选项相同)。要查看引用日志中的提交,请使用git reflog showgit log -ggit reflog show实际上会调用git log -g,因此您可以将其他git log选项传递给{{1} }。

示例

让我们看一下由于某种原因出错的rebase —最典型的是,rebase需要解决太多的合并冲突。我们将从开始的命令序列开始,但是将其拼写如下:

git reflog

git checkout dev && git fetch && git rebase 命令将我们带到要重新设置基准的分支上。 git checkout dev步骤填写了git fetch,而origin/dev命令开始了变基,它将复制git rebasedev上的提交}。 origin/dev确保每个命令在下一个命令开始之前成功完成-分号将运行下一个命令,即使上一个命令失败。

副本将在&&指向的提交之后进行。也就是说,在origin/dev之后,我们的存储库中可能包含以下提交图:

git fetch

我们最终希望得到的是这样:

...--o--o--A--B--C   <-- dev (HEAD)
         \
          E--F   <-- origin/dev

其中...--o--o--A--B--C [abandoned] \ E--F <-- origin/dev \ A'-B'-C' <-- dev (HEAD) 是我们的A'副本,A是我们的B'副本,而B是我们的C'副本。

如果一切正常,或者至少,如果 Git认为一切正常,那么重新构建将以以下方式结束:

C

重新设置基准完成后,完成...--o--o--A--B--C <-- ORIG_HEAD, dev@{1} \ E--F <-- origin/dev \ A'-B'-C' <-- dev 设置。 ORIG_HEADdev@{1}的重新配置完成后的引用条目。 (请注意,正如我们执行其他命令一样,条目#1被下推到条目#2,#3,依此类推,因此您必须使用dev或同等功能来检查数字 now < / em>(如果处理不正确)。

如果您完成了基准,并运行git reflog show或查看结果或运行测试,或者由于恐惧而想退回东西,现在可以运行:

git log

或:

git reset --hard ORIG_HEAD

这两个都将:

  • 找到指定的提交,这就是提交git reset --hard dev@{1} 的实际哈希值;
  • 使名称C指向此提交(在过程中将reflog条目向下推一位);和
  • (由于dev,也重新设置了索引和工作树,以便索引和工作树现在与提交--hard相匹配。

请注意,Cgit reset指向ORIG_HEAD刚才的位置。也就是说,我们现在将拥有:

HEAD

...--o--o--A--B--C <-- dev (HEAD), dev@{2} \ E--F <-- origin/dev \ A'-B'-C' <-- ORIG_HEAD, dev@{1} 开始并向后工作的普通git log现在将向我们显示先提交HEAD,然后依次是C,然后依次是B和最右边的A,依此类推。

另一方面,假设我们开始重新建立基础,并且已经到了这一点:

o

我们正处于重新部署的中间,试图通过选择...--o--o--A--B--C <-- dev \ E--F <-- origin/dev \ A'-B' <-- HEAD 做出C来完成C',并且遇到了很多冲突。我们查看冲突并做出决定:毕竟不是时候这样做。我们想回到开始之前的情况。

git status命令将告诉我们,在重新设置的中间,我们处于“分离的HEAD”模式。我们运行:

git rebase --abort

这将停止我们的变基并为我们重新检出dev,为此,我们可以这样做:

...--o--o--A--B--C   <-- dev (HEAD)
         \
          E--F   <-- origin/dev
              \
               A'  <-- HEAD@{2}
                \
                 B'  <-- HEAD@{1}

这次,我绘制了HEAD reflog条目,这些条目记住了A'B'的提交。这些总是存在的-大多数时候我们只是将它们放在图表之外,因为reflog条目通常是不可见的。 ORIG_HEAD也是如此:我们不在乎时将其忽略,因为git log不会看它,除非我们明确要求它。

另一个例子

假设您认为自己已经完成了变基,但是要么退出了它(git rebase --quit,这是一个相对较新的选项),要么实际上仍然处于冲突之中。在这种情况下,您应该先运行git status 以确保事物符合您的想法:

git status

如果这告诉您您处于重新定位的中间,则可以选择完成重新定位或中止它,如上例所示。

如果您确实完成了此操作,则可以使用git reflog查找成功的任何部分樱桃小贴士,然后创建一个指向该分支的新临时分支。例如,假设我们像以前一样成功制作了A'B',看到了C'的冲突,并且意外地终止了git rebase --abort的重新设置。我们投入了大量工作来解决与A'B'的冲突,并希望将其退回。现在我们运行:

git reflog

查找HEAD@{1}HEAD@{2}等,以验证我们是否确实拥有:

...--o--o--A--B--C   <-- dev (HEAD)
         \
          E--F   <-- origin/dev
              \
               A'  <-- HEAD@{2}
                \
                 B'  <-- HEAD@{1}

由于B'很有价值,因此我们给它一个新的分支名称,例如new-dev

git checkout -b new-dev HEAD@{1}

现在我们有了这张图,不用HEAD@{...}部分就可以绘制出来:

...--o--o--A--B--C   <-- dev
         \
          E--F   <-- origin/dev
              \
               A'-B'  <-- new-dev (HEAD)

,我们可以恢复正常工作。最终,我们可以使dev指向提交B'或新的C'或我们选择的任何内容;但就目前而言,我们很高兴在new-dev保持不变的情况下从事dev的工作。

要记住的要点

  • 提交通常是永久的,并且完全不可更改。他们的真实名字是他们的丑陋的哈希ID,但是对于人类来说,记住和处理它们是不可能的,因此我们给他们起名字。只要它们可达(请参阅Think Like (a) Git),它们就会一直存在。

  • 分支名称是保存哈希ID的人类可读标识符。 我们选择名称; Git 选择它们的值(基础提交的哈希ID)。每当我们提交 new 时,当前分支名称的值就会自动更新。每个名称都指向当我们git log分支时Git应该显示的 last 提交,以及Git应该签出 the 提交。当我们git checkout分支时。

  • 使用带有分支名称的git checkout会将名称HEAD附加到分支名称之一,以便新的提交将更新该分支名称。将git checkout与提交哈希ID或名称(不是分支名称)一起使用,分离名称HEAD,使其直接指向某个提交。

  • 使用git reset,我们可以移动 current 分支使其指向 any 提交,或者如果我们处于分离的HEAD模式,移动分离的HEAD(即名称HEAD本身)以指向任何提交。这样做会中止所有正在进行的合并,选择或还原。它(至少在现代的Git中)不会终止正在进行的变基。 Git将保持在“分离式HEAD”模式,并且您的重新设置实际上仍将继续。此时,使用git rebase --continuegit rebase --skip可能会丢弃很多提交。

  • ORIG_HEAD就像是reflog的廉价(在所有意义上)变体:它记住上次操作{em>一个,该操作来自移动HEAD的最后一次操作很多。

  • 真实的reflog(HEAD有一个,每个分支名称有一个)保存了许多个先前的值。使用git reflog showgit log -g(带有分支名称)或名称HEAD(如果需要)来显示这些分支或{{1}的引用日志}。

  • HEAD的创作者:

    1. 列出要复制的提交。
    2. 分离HEAD,使其指向提交副本之后的提交。
    3. 每一次要复制的
    4. Cherry-picking 1 一次。每个樱桃采摘可能会有冲突;如果是这样,Git会停止并让您解决冲突。
    5. 最后一次选择完成后,移动分支名称以指向最后复制的提交。这会重新附加git rebase并将HEAD设置为分支名称的先前值,该值现在也位于分支的引用日志中。
  • ORIG_HEAD并没有什么特别的事情。这是为了方便快捷。我建议您避免使用它,但是如果您确实确定要在git pull之后立即运行git mergegit rebase,那么它将为您执行以下操作:运行{{1} },然后运行第二个Git命令。


1 git fetch实际上运行git fetch;在现代的Git中,两者都内置于Git所称的内部,即 sequencer 。其他一些重新设置模式也实际上使用了Cherry-Pick。非交互式rebase的默认设置实际上是不同的路径,使用git rebase --interactivegit cherry-pick复制提交。从某种意义上说,该路径略有缺陷,因为它不处理重命名以及基于Cherry-pick的方法。