rebase 合并到 develop 分支后重复提交
Duplicate commits after rebase have been merged into the develop branch
我最近取消了另一个开发人员正在开发的远程分支,我们称之为 feature
。然后我错误地在 develop
- 主要工作分支 - 我现在知道这是你不应该做的事情。 feature
分支已合并到 develop
.
我现在遇到的问题是 develop
有一段奇怪的 Git 历史。来自 feature
的所有提交似乎都是重复的,它们出现了两次。但是,它们具有不同的提交 ID。
我的历史现在看起来有点像这样(ID 用于演示目的):
0007 commit from feature #3 <--- these commits are duplicated
0006 commit from feature #2
0005 commit from feature #1
0004 different commit from another branch #2
0004 different commit from another branch #1
0002 commit from feature #3
0002 commit from feature #2
0001 commit from feature #1
我犯了一个愚蠢的错误!我能做些什么吗?历史看起来很难看,但所有正确的代码似乎都在那里。我可以删除重复的提交吗?或者有没有其他方法可以清除历史记录?
请为经验不足的 Git 用户写下您的答案。
使用 git reflog
还原您的更改。
阅读 中的所有内容(如何恢复以前的头部/如何撤消更改):
怎么办?
键入 git reflog
并找出要返回的 "last good" sha-1。
运行
git reset <SHA-1> --hard
并且你回到了你犯错之前的上一次提交。
发生了什么事
"Copy commits" 正是 git rebase
所做的 。它复制一些提交,然后随机移动分支指针,以便 "forget" 或 "abandon" 原始提交。 (但见下文。)
下面是 git rebase
如何进行复制的说明。单个字母代表commit,右边的名字是branch names,其实只指向一个个commit,即"tip of the branch".每个提交都指向其父提交,即 A--B
连接线实际上是指向左的箭头(并且对角线方向的箭头也仍然指向左侧,指向较早的提交,后来的提交指向右侧):
C--D <-- branch1
/
A--B
\
E <-- branch2
这是 "before" 图片,您只有 "original" 次提交。您现在决定 git checkout branch1
和 git rebase branch2
,以便 C
和 D
在 E
之后 。但是 Git 实际上根本无法 改变 原来的 C--D
,所以它 复制 它们到新的副本, C'
和 D'
,新的略有不同:它们出现在 E
之后(并且还使用您在 E
中所做的任何代码更改):
C--D [abandoned]
/
A--B
\
E <-- branch2
\
C'-D' <-- branch1
在这里完全忘记原来的 C--D
是可以的,但是如果您最终认为这是个坏主意怎么办?变基将分支的原始值保留在 "reflogs" 中以记住它。它还使用特殊名称 ORIG_HEAD
。这更容易使用,但只有 一个 ORIG_HEAD
,而可能有无限数量的 reflog 条目。 Reflog 条目默认保留至少 30 天,让您有时间改变主意。回头看第二张图,想象添加了 ORIG_HEAD
。
现在,您遇到的问题发生了,因为它只是 记住以前提交的分支名称。每个提交 也 记住 它自己的 以前的提交,通过那些连接的、向左的箭头。因此,让我们看看如果有另一个名称或其他(合并)提交,记住 C
和 D
会发生什么。例如,如果我们有这个复杂得多的起始图怎么办:
.-----F <-- branch3
/ /
/ C--D <-- branch1
/ /
A--B
\
E <-- branch2
如果我们现在 "rebase" branch1
,我们得到:
.-----F <-- branch3
/ /
/ C--D [ORIG_HEAD and reflog]
/ /
A--B
\
E <-- branch2
\
C'-D' <-- branch1
提交F
是一个合并提交:它指向两个提交A
和提交D
。所以它保留了原来的D
,它保留了原来的C
,给我们带来了一些混乱。
F
可能是一个普通的普通提交,仅指向 D
,我们会看到同样的问题。但是,普通的普通提交更容易复制,所以如果 F
是 而不是 合并——如果我们的 F
仅指向 D
并且不要 A
——我们也可以小心地变基 branch3
,将 F
复制到 F'
,其中 F'
位于我们新的 D'
之后。也可以重新进行合并,但这有点棘手(并不是说正确复制 F
就那么简单——"get lost" 和复制 C--D
[=139 很容易=]再次 错误)。
发生这种情况时
每当你复制你或其他人所做的提交时,你都会遇到这个问题,和你和"someone else"(可能是"other you")也仍在使用原件。这发生在我们的提交 F
中,例如:我们仍在使用原始的 C--D
链。我们可以通过制作一个新的 F'
并使用它来解决这个问题, 只要 我们是唯一使用 branch3
的人。但是,如果 branch3
已 发布 ,或者就此而言,如果我们已发布 branch1
,那么其他人可能会将它们作为 origin/branch1
或 origin/branch3
,我们已经失去了对 C--D
.
原始副本的控制
因此,标准建议是仅对 private(未发布)提交进行变基,因为您知道谁在使用它们——当然只有您自己——并且您可以检查自己并确保您没有使用它们,或者可以复制它们,因为您还计划复制或以其他方式重新提交 F
.
如果您已经完成了变基——制作了副本——和 发布了它们(将它们推送到 origin
),你就有点卡住了。无论如何你都可以 "undo" 你的变基,并恳求所有共享 origin
的人确保 他们 不使用你的 C'-D'
类型复制任何东西,因为你要把原件放回去。
(对于更高级的用户组,您甚至可以同意某些分支会定期进行变基,并且您和他们都必须认识到这种情况何时发生,并且 所有然后你会注意切换到新的提交副本。但是,这可能不是你现在想要做的!)
撤消它
所以,如果你 (a) 可以 和 (b) 想要 "undo" 你的 rebase,现在 reflog ,还是存的ORIG_HEAD
,真的派上用场了。让我们再次以第二个例子为例,看看在我们忘记 branch3
仍然记得最初的 C-D
提交之后我们有什么:
.-----F <-- branch3
/ /
/ C--D [ORIG_HEAD and reflog]
/ /
A--B
\
E <-- branch2
\
C'-D' <-- branch1
现在,假设我们从底行删除名称 branch1
并写入一个新的 <-- branch1
指向提交 D
:
.-----F <-- branch3
/ /
/ C--D <-- branch1
/ /
A--B
\
E <-- branch2
\
C'-D' [abandoned]
既然我们已经放弃了C'-D'
,那就别看了。将此图与原始图进行比较,瞧!这就是你想要的!
"moves" 像这样任意方式的分支标签的命令是 git reset
(它移动 当前 分支,所以你必须在branch1
)。在 reflog 中查找 D
的原始提交哈希,或检查 ORIG_HEAD
是否正确,或使用 reflog 拼写来识别提交 D
。 (对于新手,我发现原始哈希的剪切和粘贴是可行的方法。)例如,尝试:
$ git log --graph --decorate --oneline ORIG_HEAD
看看 ORIG_HEAD
是否为您提供了正确的哈希值。如果没有,请尝试 git reflog branch1
(在此处查看 branch1
的特定 reflog)来查找哈希值,然后使用:
$ git log --graph --decorate --oneline branch1@{1}
(或剪切并粘贴原始哈希而不是使用 branch1@{1}
)。找到所需的 "original" 提交后,您可以:
$ git status # to make sure you're on the right branch
# and that everything is clean, because
# "git reset --hard" wipes out in-progress work!
$ git reset --hard ORIG_HEAD
(或输入 branch1@{1}
或原始哈希 ID,照常代替 ORIG_HEAD
)。1 移动当前分支(我们刚刚检查过)以便它指向给定的提交(branch1@{1}
,来自 reflog,或 ORIG_HEAD
或原始哈希 ID),让我们得到最终的图形绘制。 --hard
设置我们的 index/staging-area 和我们的工作树,以匹配我们刚刚重新指向分支的新提交。
1这里的一般想法,在Git中一直重复出现,是我们必须命名一些特定的提交,Git 从中找到 rest 的提交(如有必要)。任何名称都可以:分支名称、HEAD
之类的名称、master@{1}
之类的引用日志名称或原始提交哈希 ID。 Git 并不关心 你怎么说 "look at this here commit";最终,Git 将该名称解析为那些又大又丑的 SHA-1 哈希 ID,并使用它。
我最近取消了另一个开发人员正在开发的远程分支,我们称之为 feature
。然后我错误地在 develop
- 主要工作分支 - 我现在知道这是你不应该做的事情。 feature
分支已合并到 develop
.
我现在遇到的问题是 develop
有一段奇怪的 Git 历史。来自 feature
的所有提交似乎都是重复的,它们出现了两次。但是,它们具有不同的提交 ID。
我的历史现在看起来有点像这样(ID 用于演示目的):
0007 commit from feature #3 <--- these commits are duplicated
0006 commit from feature #2
0005 commit from feature #1
0004 different commit from another branch #2
0004 different commit from another branch #1
0002 commit from feature #3
0002 commit from feature #2
0001 commit from feature #1
我犯了一个愚蠢的错误!我能做些什么吗?历史看起来很难看,但所有正确的代码似乎都在那里。我可以删除重复的提交吗?或者有没有其他方法可以清除历史记录?
请为经验不足的 Git 用户写下您的答案。
使用 git reflog
还原您的更改。
阅读
怎么办?
键入 git reflog
并找出要返回的 "last good" sha-1。
运行
git reset <SHA-1> --hard
并且你回到了你犯错之前的上一次提交。
发生了什么事
"Copy commits" 正是 git rebase
所做的 。它复制一些提交,然后随机移动分支指针,以便 "forget" 或 "abandon" 原始提交。 (但见下文。)
下面是 git rebase
如何进行复制的说明。单个字母代表commit,右边的名字是branch names,其实只指向一个个commit,即"tip of the branch".每个提交都指向其父提交,即 A--B
连接线实际上是指向左的箭头(并且对角线方向的箭头也仍然指向左侧,指向较早的提交,后来的提交指向右侧):
C--D <-- branch1
/
A--B
\
E <-- branch2
这是 "before" 图片,您只有 "original" 次提交。您现在决定 git checkout branch1
和 git rebase branch2
,以便 C
和 D
在 E
之后 。但是 Git 实际上根本无法 改变 原来的 C--D
,所以它 复制 它们到新的副本, C'
和 D'
,新的略有不同:它们出现在 E
之后(并且还使用您在 E
中所做的任何代码更改):
C--D [abandoned]
/
A--B
\
E <-- branch2
\
C'-D' <-- branch1
在这里完全忘记原来的 C--D
是可以的,但是如果您最终认为这是个坏主意怎么办?变基将分支的原始值保留在 "reflogs" 中以记住它。它还使用特殊名称 ORIG_HEAD
。这更容易使用,但只有 一个 ORIG_HEAD
,而可能有无限数量的 reflog 条目。 Reflog 条目默认保留至少 30 天,让您有时间改变主意。回头看第二张图,想象添加了 ORIG_HEAD
。
现在,您遇到的问题发生了,因为它只是 记住以前提交的分支名称。每个提交 也 记住 它自己的 以前的提交,通过那些连接的、向左的箭头。因此,让我们看看如果有另一个名称或其他(合并)提交,记住 C
和 D
会发生什么。例如,如果我们有这个复杂得多的起始图怎么办:
.-----F <-- branch3
/ /
/ C--D <-- branch1
/ /
A--B
\
E <-- branch2
如果我们现在 "rebase" branch1
,我们得到:
.-----F <-- branch3
/ /
/ C--D [ORIG_HEAD and reflog]
/ /
A--B
\
E <-- branch2
\
C'-D' <-- branch1
提交F
是一个合并提交:它指向两个提交A
和提交D
。所以它保留了原来的D
,它保留了原来的C
,给我们带来了一些混乱。
F
可能是一个普通的普通提交,仅指向 D
,我们会看到同样的问题。但是,普通的普通提交更容易复制,所以如果 F
是 而不是 合并——如果我们的 F
仅指向 D
并且不要 A
——我们也可以小心地变基 branch3
,将 F
复制到 F'
,其中 F'
位于我们新的 D'
之后。也可以重新进行合并,但这有点棘手(并不是说正确复制 F
就那么简单——"get lost" 和复制 C--D
[=139 很容易=]再次 错误)。
发生这种情况时
每当你复制你或其他人所做的提交时,你都会遇到这个问题,和你和"someone else"(可能是"other you")也仍在使用原件。这发生在我们的提交 F
中,例如:我们仍在使用原始的 C--D
链。我们可以通过制作一个新的 F'
并使用它来解决这个问题, 只要 我们是唯一使用 branch3
的人。但是,如果 branch3
已 发布 ,或者就此而言,如果我们已发布 branch1
,那么其他人可能会将它们作为 origin/branch1
或 origin/branch3
,我们已经失去了对 C--D
.
因此,标准建议是仅对 private(未发布)提交进行变基,因为您知道谁在使用它们——当然只有您自己——并且您可以检查自己并确保您没有使用它们,或者可以复制它们,因为您还计划复制或以其他方式重新提交 F
.
如果您已经完成了变基——制作了副本——和 发布了它们(将它们推送到 origin
),你就有点卡住了。无论如何你都可以 "undo" 你的变基,并恳求所有共享 origin
的人确保 他们 不使用你的 C'-D'
类型复制任何东西,因为你要把原件放回去。
(对于更高级的用户组,您甚至可以同意某些分支会定期进行变基,并且您和他们都必须认识到这种情况何时发生,并且 所有然后你会注意切换到新的提交副本。但是,这可能不是你现在想要做的!)
撤消它
所以,如果你 (a) 可以 和 (b) 想要 "undo" 你的 rebase,现在 reflog ,还是存的ORIG_HEAD
,真的派上用场了。让我们再次以第二个例子为例,看看在我们忘记 branch3
仍然记得最初的 C-D
提交之后我们有什么:
.-----F <-- branch3
/ /
/ C--D [ORIG_HEAD and reflog]
/ /
A--B
\
E <-- branch2
\
C'-D' <-- branch1
现在,假设我们从底行删除名称 branch1
并写入一个新的 <-- branch1
指向提交 D
:
.-----F <-- branch3
/ /
/ C--D <-- branch1
/ /
A--B
\
E <-- branch2
\
C'-D' [abandoned]
既然我们已经放弃了C'-D'
,那就别看了。将此图与原始图进行比较,瞧!这就是你想要的!
"moves" 像这样任意方式的分支标签的命令是 git reset
(它移动 当前 分支,所以你必须在branch1
)。在 reflog 中查找 D
的原始提交哈希,或检查 ORIG_HEAD
是否正确,或使用 reflog 拼写来识别提交 D
。 (对于新手,我发现原始哈希的剪切和粘贴是可行的方法。)例如,尝试:
$ git log --graph --decorate --oneline ORIG_HEAD
看看 ORIG_HEAD
是否为您提供了正确的哈希值。如果没有,请尝试 git reflog branch1
(在此处查看 branch1
的特定 reflog)来查找哈希值,然后使用:
$ git log --graph --decorate --oneline branch1@{1}
(或剪切并粘贴原始哈希而不是使用 branch1@{1}
)。找到所需的 "original" 提交后,您可以:
$ git status # to make sure you're on the right branch
# and that everything is clean, because
# "git reset --hard" wipes out in-progress work!
$ git reset --hard ORIG_HEAD
(或输入 branch1@{1}
或原始哈希 ID,照常代替 ORIG_HEAD
)。1 移动当前分支(我们刚刚检查过)以便它指向给定的提交(branch1@{1}
,来自 reflog,或 ORIG_HEAD
或原始哈希 ID),让我们得到最终的图形绘制。 --hard
设置我们的 index/staging-area 和我们的工作树,以匹配我们刚刚重新指向分支的新提交。
1这里的一般想法,在Git中一直重复出现,是我们必须命名一些特定的提交,Git 从中找到 rest 的提交(如有必要)。任何名称都可以:分支名称、HEAD
之类的名称、master@{1}
之类的引用日志名称或原始提交哈希 ID。 Git 并不关心 你怎么说 "look at this here commit";最终,Git 将该名称解析为那些又大又丑的 SHA-1 哈希 ID,并使用它。