我今天有一个问题,但我仍然不知道发生了什么。
我想从远程获取更改,然后重新基于它。这些都在同一个分支中,为方便起见,我们假设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
是更好的选择。
答案 0 :(得分:2)
肯定还有其他情况。 (我不知道它们可能是什么。)
在这里,也许
git pull --rebase
是更好的选择。
那做同样的事情。您的原始命令序列为git checkout dev
加上命令对git fetch; git rebase
。 git 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 am
,git reset
,有时是git merge
,当它执行快进操作而不是进行合并时。)
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 show
或git log -g
(git 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 rebase
上dev
上的提交}。 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_HEAD
是dev@{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
相匹配。请注意,C
会git 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 --continue
或git rebase --skip
可能会丢弃很多提交。
ORIG_HEAD
就像是reflog的廉价(在所有意义上)变体:它记住上次操作{em>一个,该操作来自移动HEAD
的最后一次操作很多。
真实的reflog(HEAD
有一个,每个分支名称有一个)保存了许多个先前的值。使用git reflog show
或git log -g
(带有分支名称)或名称HEAD
(如果需要)来显示这些分支或{{1}的引用日志}。
HEAD
的创作者:
git rebase
并将HEAD
设置为分支名称的先前值,该值现在也位于分支的引用日志中。 ORIG_HEAD
并没有什么特别的事情。这是为了方便快捷。我建议您避免使用它,但是如果您确实确定要在git pull
之后立即运行git merge
或git rebase
,那么它将为您执行以下操作:运行{{1} },然后运行第二个Git命令。
1 git fetch
实际上运行git fetch
;在现代的Git中,两者都内置于Git所称的内部,即 sequencer 。其他一些重新设置模式也实际上使用了Cherry-Pick。非交互式rebase的默认设置实际上是不同的路径,使用git rebase --interactive
和git cherry-pick
复制提交。从某种意义上说,该路径略有缺陷,因为它不处理重命名以及基于Cherry-pick的方法。