最后的问题。这本来是一个要求帮助解决这个问题的帖子,但我想出了在编写它的过程中。
git reset --hard [commit_hash]
git push -f
话虽如此,git的先前行为仍然令我感到困惑,所以如果有人能告诉我为什么这个问题正在发生,那将会很棒。
原帖
我一直在线查看Stackoverflow,我认为这是解决方案How to delete the last n commit on Github and locally?
但是当我尝试推荐的命令并且真的可以使用一些建议时,我会看到一些特殊的行为。
背景
2分支:m分支和d分支
它们应具有相同的日志历史记录,通常由处理合并的应用程序处理。由于愚蠢的原因,仅在d分支上存在合并冲突。当我试图解决合并冲突时,我愚蠢地忽略了我当时的文本编辑器并不像我通常使用的其他应用程序那样自动保存。这导致我提交代码中包含合并冲突格式并推送到远程。由于我的git缺乏经验,我尝试了一个恢复思维,只会让我回到正确的提交哈希,但做了另一个提交。所以d-branch提交日志看起来像这样
a - 恢复,发展的头部
b - 坏的解决合并冲突
c - 主人的头
d - 旧提交
e - 旧提交
当我执行git reset --hard HEAD^^
时,它会移动d分支头以提交 e 。
如果我改为git reset --hard HEAD^
,则移动d分支头以提交 b 。然后,如果我再次这样做,它会再次跳到 e 。这也适用于git reset --hard HEAD~1
有人可以解释为什么会这样吗?我可以提供其他任何信息吗?
由于
答案 0 :(得分:2)
有人可以解释为什么会这样吗?
是的:这是尝试将复杂的(好的,不是那个复杂的)图形视为线性的结果。
正如您所见,每个提交都有一个哈希ID。此哈希ID是指定特定提交的方式,而不必担心如何 。
每次提交还会保存/存储/提供其父提交的哈希ID。好吧,就是说,如果它有一个父级,它有一个保存的哈希ID。合并提交具有两个父项,因此它提供两个不同的哈希ID。 ("具有两个或更多父项的提交"是合并提交的实际定义。另一个特殊情况,这里没有出现,是 no 父提交。这样的提交称为 root 提交,您可以在新存储库中首次提交其中一个提交。)
将存储哈希ID的行为视为保留指向另一个提交的箭头。如果我们有几个普通(非合并,非root)提交将箭头保持到其父提交,我们可以使用这些箭头绘制它们。为了使图形易于管理,我们可以使用一个大写字母而不是40个字符的不可发音的哈希ID:
... <--E <--F <--G
请注意,这些箭头附加到子提交,而不是父项,但由于任何提交都不能永远更改,我们可以记住它们指向像这样向后,并将它们绘制成连接线,以简化绘图:
...--E--F--G
我们需要一种方法来查找分支的 last 提交,以及分支名称的内容,例如master
和{{1}在Git中,分支名称存储分支的 last 提交的哈希ID - 分支的 tip ,正如Git所说的那样 - 我们可以将其绘制为另一个箭头的名称:
develop
每当我们进入分支并进行 new 提交时,Git会通过使新提交作为其父级提供当前提示来将提交添加到分支:
...--E--F--G <-- master
然后更改分支名称,以便它指向我们刚刚制作的新提交(现在我们不必在单独的行上绘制它以留出旧的空间...--E--F--G <-- master
\
H
箭头):
master
如果您有两个指向同一提交的名称,则只有一个可以是当前分支。我们会添加...--E--F--G--H <-- master
来记住哪一个是最新的:
(HEAD)
然后我们将添加新提交:
...--E--F--G <-- develop (HEAD), master
现在我们将查看...--E--F--G <-- master
\
H <-- develop (HEAD)
并在其中添加新提交:
master
现在让我们通过以下方式进行新的合并提交:
...--E--F--G--I <-- master (HEAD)
\
H <-- develop
移动我们的HEAD:
git checkout develop
然后:
...--E--F--G--I <-- master
\
H <-- develop (HEAD)
(这可能是一种错误的方式 - 很多人喜欢将从功能分支,合并到他们更熟练的分支中,但那&#39 ;这是一个完全不同的问题)。要进行合并,Git:
git merge master
/ commit HEAD
); H
,提交master
); I
)。其中最后一个是合并的合并基础。然后Git会将合并基础与当前提交进行比较,看看我们做了什么:
G
然后运行第二次比较,看看他们做了什么:
git diff --find-renames <hash-of-G> <hash-of-H>
Git尽可能地结合这些更改,对合并基础内容应用两组更改。如果一切顺利(或者如果我们意外地解决了合并而没有解决任何问题),我们将得到一个合并提交,有两个父母。 第一个父级将是我们现在正在进行的提交,git diff --find-renames <hash-of-G> <hash-of-I>
; 第二个父级将是另一个提交,H
。 Git会使我们当前的分支名称指向新的提交I
(用于合并):
M
虽然我们不能很好地画出来,但即便如此,这第一和第二个父母观念也非常重要;我们马上就会再看到它。
正如您所发现的,...--E--F--G--I <-- master
\ \
H--M <-- develop (HEAD)
只是添加了一个新提交,其效果是取消之前提交的一些更改。让我们现在绘制提交,作为提交git revert
进行恢复:
R
当您运行...--E--F--G--I <-- master
\ \
H--M--R <-- develop (HEAD)
时,Git将从当前提交开始。我们的git log
已附加到我们的HEAD
,这意味着Git会按照develop
的箭头提交develop
。 Git向我们显示了提交R
,然后转到R
的父级R
。
Git现在向我们展示M
(M
即使git log
也不会在这里显示差异,因为有两个父母可能会有差异反对)。完成后,-p
需要向我们展示git log
的父母......但等等!有两个!应该显示哪一个?
Git目前所做的是将两个提交放入&#34;提交以显示&#34;的队列中。然后它从队列中挑出一个并显示它。它选择的那个取决于您运行M
时选择的排序选项。 (默认情况下按提交者时间戳排序。)在您的情况下,它选择的是git log
,I
的提示。这会将master
的父I
放入队列。
Git再次有两个提交可供选择(G
和G
)。它选择一个,向您显示,并将该提交的父级放入队列中。在我们的例子中,如果它选择H
,它会将H
放入队列,但G
已经在队列中,因此队列现在只有一个提交 - 并且在此指出行为再次变得简单(显示G
,然后G
,等等。)
在任何情况下,F
都会向您展示这种线性化的视图,这些视图本身就是不是线性的:合并git log
的父母可能会出现在订单,您可以提供可能更改该订单的M
排序选项。
git log
)表示法虽然您可以通过其原始哈希ID命名提交,但这通常是令人不愉快的类型。你可以缩短它们,但即便如此,它也有点棘手。您可以使用鼠标并剪切并粘贴它们,这样更好。但是有{em>许多替代方案,所有这些都在the gitrevisions documentation中列出。其中一个是HEAD^
后缀。
当你在提交说明符上使用hat-suffix时,你告诉Git:查找我给你的提交,然后找到它的父。你可以在帽子之后添加一个数字,并且如果你这样做,你告诉Git:查找提交,然后找到第n个父级。大多数提交只有一个父级,所以只有{{1没有任何意义,你可以省略数字。
因此,如果^
附加到^1
,HEAD
名称提交develop
,则字符串develop
表示提交R
,但字符串HEAD
表示 R
的第一个父级,当然是HEAD^
。请注意,如果您愿意,可以写R
来说明 M
的第一位家长,但当然只有父母一方。
通过合并,至少有两个父母,你可以有意义地选择两个父母中的一个。因此,我们可以将HEAD^1
写成:从R
开始,找到它的第一个父级,然后找到该提交的第二个父级。这将从{{1}开始从} HEAD^1^2
开始,然后从R
到R
。
M
,拼写更简单的方式M
。这告诉Git:从I
开始,找到它的第一个父HEAD^1^1
,然后找到它的第一个父HEAD^^
。所以这个名称提交R
,好像你在命令行输入了提交M
的哈希值。
当您运行H
时,您告诉Git,首先将H
部分解析为哈希ID,然后找到提交后,更改当前分支名称 - 附加一个H
,以便它指向该提交。
(因为Git总是向后工作,在之后提交选择的点变得很难找到,除非你还有一些其他的名字可以让你找到它们。它总是容易的向后工作:例如,帽子后缀就是这样做的,而且git reset --hard <commit-specifier>
也是这样做的。但是Git字面意思不能前进,除非它首先倒退并记住它是怎么回事当你使用<commit-specifier>
的一些更复杂的选项时,你最终可能会看到这一点。)
在您的情况下,您标记为HEAD
的提交与此处的git log
相同,标记为git rev-list
的提交相当于此处的a
,{{{ 1}}的第一个父级必须是您标记为R
的提交:
b
因此M
为b
而e
为...--e-----b--a
/
...--d--c
。
请注意,您可以使用a^
选择提交b
:从a^^
移至其第一个父e
,从那里移至第二个父d
},并从那里移动到它的第一个父a^^2^
。
这也适用于
a
波浪号后缀是此第一个父符号如此重要的部分原因(对于其余部分,请参阅b
)。像帽子/插入符号一样的波浪号可以跟随一个数字。实际上,此数字是重复c
操作的次数。给出:
d
名称git reset --hard HEAD~1
表示git log --first-parent
,从^1
开始,然后向前移动一次父母两次,到...--e-----b--a
/
...--d--c
。您不能以这种方式提交a~2
或a^^
,因为它们不在第一个母链上;但a
会找到e
的第一个(也可能是唯一的)父级,无论可能是什么,c
会找到其父级,依此类推。