将错位的提交移动到新分支
Move an misplaced commit to a new branch
设置:
我已经提交了对本地和远程的代码更改,但是在错误的分支中。
我的解决方案:
- 已经检查了 master 并创建了一个分支,代码更改应该是
- 樱桃从错误的分支签入中挑选提交并提交到新分支。
- 检查错误的分支。重置为第一个正确的提交
然后强制再次推送它以删除远程上的错误提交
分支.
问题:这是要走的路吗?如果我 google 它,我知道人们使用 revert 我不明白为什么它看起来更复杂和更危险..我为什么要使用 revert?
当您 revert
一些提交时,您创建了否定目标提交的提交。结果与 reset
一样,但你不必 force push
,你可以做简单的 push
因为你添加到历史记录而不是从中删除。您可以 revert
提交的另一个差异在历史上并不是最后一次。在这种情况下你不能使用 reset
因为它会导致丢失自目标提交以来的所有提交。
你也可以看看这个问题:What's the difference between Git Revert, Checkout and Reset?
而不是 3
我建议 rebase 到 :
git rebase --onto COMMIT_BEFORE_WRONG WRONG_COMMIT branch_with_wrong_commit
git push --force-with-lease
此"cuts" 错误提交。
添加 -i
时(对于 interactive),您可以检查是否移动了正确的提交。
同样的结果会出现:
git rebase -i COMMIT_BEFORE_WRONG
然后将 "todo" 文件第一行中的单词 pick
更改为 drop
。
I [see] that people use revert and I can't understand why as it seems more complicated and more dangerous.. Why should I use revert?
应该是一个强词。 (不如 shall 或 must 强,但至少相当强。:-) )
... Reset to the first correct commit and then force push it again to remove the faulty commit on remote branch
每当您必须使用 git push --force
或等价物时,您正在以其他人 可能 不期望的方式移动分支名称。 (Might 在部分排序中弱于 should:,我会说 may < might < should < shall / must.) 特别是,Git 分支名称自然会随着提交 添加 到分支的进程而移动,因为分支通过提交增长的方式。
考虑:
$ git checkout <somebranch>
... work ...
$ git add <files> # or --all or . or whatever
$ git commit
git checkout
步骤具有将 HEAD
附加到分支并复制该分支的 提示提交 的效果,正如分支名称所指向的, 到你的 Git 的索引和工作树中,这样你就可以处理它了:
... <-F <-G <-H <--branch (HEAD)
名为branch
的分支——引用全称refs/heads/<em>branch</em>
——存储一些提交的原始哈希 ID H
。提交 H
本身存储其父提交 G
的原始哈希 ID,后者存储某些提交 F
的原始哈希 ID,依此类推。所以我们说名称 指向 提示提交 H
,它指向更早的提交 G
,依此类推。
git add
步骤更新您的索引/暂存区,以便为提交做好准备,git commit
步骤创建一个新的提交。新提交将当前签出的提交 H
的哈希 ID 存储为其父哈希 ID。 (当然,它将当前索引/暂存区域制作的冻结快照存储为其 快照 。)然后,作为最后一步,git commit
写入新提交的哈希ID 到 附加 HEAD
的分支名称:
... <-F <-G <-H <-I <--branch (HEAD)
这就是分支增长的方式,一次一个提交,当人们进行提交时。当您合并一系列提交时,无论是作为真正的合并还是作为快进的非真正合并操作,分支也会获得新的提交,可能一次获得很多,也可能以非线性方式,但重要的是新提交 总是 导致现有提交:
...--F--G--H--I---M <-- master (HEAD)
\ /
J--K--L <-- develop
将合并提交 M
添加到 master
使提交 H
和 I
可访问 来自 master
,因为我们可以遵循向后指向的内部提交到提交箭头——这里呈现为线,因为箭头现在很难在文本中绘制——使用 M
中的顶行箭头。 (从 M
到 L
的左下箭头让我们也可以从 M
骑到 K
或 J
。Think Like (a) Git 与波特兰的交通系统有一个很好的类比,尽管任何大都市的火车系统都是相似的。)
但假设我们这样做:
...--F--G--H--X <-- master (HEAD)
\
J--K <-- develop
然后意识到,糟糕,我们打算将提交 X
放在 develop
上。我们使用任何适合 copy X
到新提交的方式,例如 cherry-pick 或 git rebase --onto
(两者做同样的工作)。然后我们使用 git checkout master; git reset --hard master~1
将 X
推开,使其不再位于 master
:
X
/
...--F--G--H <-- master (HEAD)
\
J--K--L <-- develop
(这里 L
是 X
的副本,放在我们想要的地方。)这种分支名称运动使提交 X
悬而未决,无法找到它——至少,在 我们的 存储库中没有办法。但是如果我们已经使用 git push
将提交 X
发送到其他地方,一些 other Git 就有了它的名字。事实上,我们也是:
X <-- origin/master
/
...--F--G--H <-- master (HEAD)
\
J--K--L <-- develop
我们的 origin/master
,这是我们 Git 在 origin
上记住 master
的方式,仍然记得提交 X
存在。这意味着 origin
的 Git 记得 X
是 在 他们的 master
.
事实上,这就是 为什么 我们必须使用 git push --force origin master
:告诉 origin
的 Git 它应该 丢弃 它的提交 X
。如果我们在其他任何人之前执行此操作——任何有权访问该 Git 的人——也 将 X
复制到 他们的 Git 存储库,我们很好:没有人看到 X
,因此我们删除 X
.
不会伤害任何人
如果其他人从另一个Git那里抢了一个副本,问题就会开始堆积起来。现在有一些第三个 Git 存储库仍然有提交 X
,可能在 他们的 master
中。也许他们已经在X
(他们的副本)之上建立了新的提交,他们想要保留:
...--F--G--H--X--Y--Z <-- master (HEAD)
我们现在要告诉他们:哦,忘了 X
,也把它从你的存储库中拿走。 这需要他们自己做 git rebase --onto
或类似的,将它们的 Y
和 Z
复制到不再导致返回 X
.
的新提交
简而言之,通过从我们的 Git 和 origin
的 Git 中删除 X
,我们给其他 的人增加了负担共享 这些 Git 个存储库:他们也必须全部删除 X
,并处理任何后果。
在某些项目中,每个人都同意这可能发生——任何时候任何分支,或任何特定分支的某个或多个子集。在这些项目中,重置分支和强制推送就可以了。有些项目没有其他用户,或者你可以在任何人有机会发现错误之前强制推送;在这些情况下,重置和强制推动也可以。当您开始为不准备这样做的人做大量工作时,就会出现问题。在这种情况下,进行 new 提交,简单地 un-does X
中的工作给了他们一种合并这项新工作的方法以他们准备接受的方式。
设置: 我已经提交了对本地和远程的代码更改,但是在错误的分支中。
我的解决方案:
- 已经检查了 master 并创建了一个分支,代码更改应该是
- 樱桃从错误的分支签入中挑选提交并提交到新分支。
- 检查错误的分支。重置为第一个正确的提交 然后强制再次推送它以删除远程上的错误提交 分支.
问题:这是要走的路吗?如果我 google 它,我知道人们使用 revert 我不明白为什么它看起来更复杂和更危险..我为什么要使用 revert?
当您 revert
一些提交时,您创建了否定目标提交的提交。结果与 reset
一样,但你不必 force push
,你可以做简单的 push
因为你添加到历史记录而不是从中删除。您可以 revert
提交的另一个差异在历史上并不是最后一次。在这种情况下你不能使用 reset
因为它会导致丢失自目标提交以来的所有提交。
你也可以看看这个问题:What's the difference between Git Revert, Checkout and Reset?
而不是 3
我建议 rebase 到 :
git rebase --onto COMMIT_BEFORE_WRONG WRONG_COMMIT branch_with_wrong_commit
git push --force-with-lease
此"cuts" 错误提交。
添加 -i
时(对于 interactive),您可以检查是否移动了正确的提交。
同样的结果会出现:
git rebase -i COMMIT_BEFORE_WRONG
然后将 "todo" 文件第一行中的单词 pick
更改为 drop
。
I [see] that people use revert and I can't understand why as it seems more complicated and more dangerous.. Why should I use revert?
应该是一个强词。 (不如 shall 或 must 强,但至少相当强。:-) )
... Reset to the first correct commit and then force push it again to remove the faulty commit on remote branch
每当您必须使用 git push --force
或等价物时,您正在以其他人 可能 不期望的方式移动分支名称。 (Might 在部分排序中弱于 should:,我会说 may < might < should < shall / must.) 特别是,Git 分支名称自然会随着提交 添加 到分支的进程而移动,因为分支通过提交增长的方式。
考虑:
$ git checkout <somebranch>
... work ...
$ git add <files> # or --all or . or whatever
$ git commit
git checkout
步骤具有将 HEAD
附加到分支并复制该分支的 提示提交 的效果,正如分支名称所指向的, 到你的 Git 的索引和工作树中,这样你就可以处理它了:
... <-F <-G <-H <--branch (HEAD)
名为branch
的分支——引用全称refs/heads/<em>branch</em>
——存储一些提交的原始哈希 ID H
。提交 H
本身存储其父提交 G
的原始哈希 ID,后者存储某些提交 F
的原始哈希 ID,依此类推。所以我们说名称 指向 提示提交 H
,它指向更早的提交 G
,依此类推。
git add
步骤更新您的索引/暂存区,以便为提交做好准备,git commit
步骤创建一个新的提交。新提交将当前签出的提交 H
的哈希 ID 存储为其父哈希 ID。 (当然,它将当前索引/暂存区域制作的冻结快照存储为其 快照 。)然后,作为最后一步,git commit
写入新提交的哈希ID 到 附加 HEAD
的分支名称:
... <-F <-G <-H <-I <--branch (HEAD)
这就是分支增长的方式,一次一个提交,当人们进行提交时。当您合并一系列提交时,无论是作为真正的合并还是作为快进的非真正合并操作,分支也会获得新的提交,可能一次获得很多,也可能以非线性方式,但重要的是新提交 总是 导致现有提交:
...--F--G--H--I---M <-- master (HEAD)
\ /
J--K--L <-- develop
将合并提交 M
添加到 master
使提交 H
和 I
可访问 来自 master
,因为我们可以遵循向后指向的内部提交到提交箭头——这里呈现为线,因为箭头现在很难在文本中绘制——使用 M
中的顶行箭头。 (从 M
到 L
的左下箭头让我们也可以从 M
骑到 K
或 J
。Think Like (a) Git 与波特兰的交通系统有一个很好的类比,尽管任何大都市的火车系统都是相似的。)
但假设我们这样做:
...--F--G--H--X <-- master (HEAD)
\
J--K <-- develop
然后意识到,糟糕,我们打算将提交 X
放在 develop
上。我们使用任何适合 copy X
到新提交的方式,例如 cherry-pick 或 git rebase --onto
(两者做同样的工作)。然后我们使用 git checkout master; git reset --hard master~1
将 X
推开,使其不再位于 master
:
X
/
...--F--G--H <-- master (HEAD)
\
J--K--L <-- develop
(这里 L
是 X
的副本,放在我们想要的地方。)这种分支名称运动使提交 X
悬而未决,无法找到它——至少,在 我们的 存储库中没有办法。但是如果我们已经使用 git push
将提交 X
发送到其他地方,一些 other Git 就有了它的名字。事实上,我们也是:
X <-- origin/master
/
...--F--G--H <-- master (HEAD)
\
J--K--L <-- develop
我们的 origin/master
,这是我们 Git 在 origin
上记住 master
的方式,仍然记得提交 X
存在。这意味着 origin
的 Git 记得 X
是 在 他们的 master
.
事实上,这就是 为什么 我们必须使用 git push --force origin master
:告诉 origin
的 Git 它应该 丢弃 它的提交 X
。如果我们在其他任何人之前执行此操作——任何有权访问该 Git 的人——也 将 X
复制到 他们的 Git 存储库,我们很好:没有人看到 X
,因此我们删除 X
.
如果其他人从另一个Git那里抢了一个副本,问题就会开始堆积起来。现在有一些第三个 Git 存储库仍然有提交 X
,可能在 他们的 master
中。也许他们已经在X
(他们的副本)之上建立了新的提交,他们想要保留:
...--F--G--H--X--Y--Z <-- master (HEAD)
我们现在要告诉他们:哦,忘了 X
,也把它从你的存储库中拿走。 这需要他们自己做 git rebase --onto
或类似的,将它们的 Y
和 Z
复制到不再导致返回 X
.
简而言之,通过从我们的 Git 和 origin
的 Git 中删除 X
,我们给其他 的人增加了负担共享 这些 Git 个存储库:他们也必须全部删除 X
,并处理任何后果。
在某些项目中,每个人都同意这可能发生——任何时候任何分支,或任何特定分支的某个或多个子集。在这些项目中,重置分支和强制推送就可以了。有些项目没有其他用户,或者你可以在任何人有机会发现错误之前强制推送;在这些情况下,重置和强制推动也可以。当您开始为不准备这样做的人做大量工作时,就会出现问题。在这种情况下,进行 new 提交,简单地 un-does X
中的工作给了他们一种合并这项新工作的方法以他们准备接受的方式。