[英]Move an misplaced commit to a new branch
Setup: I have commited a code change to local and also to remote, but in the wrong branch. 设置:我已将代码更改提交给本地和远程,但在错误的分支中。
My solution: 我的解决方案:
Question : Is this the way to go? 问题 :这是要走的路吗? If I google it, I get that people use revert and I can't understand why as it seems more complicated and more dangerous.. Why should I use revert?
如果我用google搜索,我会发现人们使用了还原,但为什么它看起来更复杂,更危险,我也无法理解为什么。为什么要使用还原?
When you revert
some commit you create commit that negates target commit. revert
某些提交时,您revert
创建否定目标提交的提交。 Result as with reset
but you do not have to force push
, you can do simple push
becouse you add to history not remove from it. 结果与
reset
但不必force push
,因为添加到历史记录而不是将其从中删除,所以可以执行简单的push
。 Another difference you can revert
commit that is not last in history. 您可以
revert
历史上最近一次提交的另一个区别。 You can't use reset
in this case becouse it leads to loosing all commits since target commit. 在这种情况下,您不能使用
reset
,因为它会导致目标提交后所有提交丢失。
Also you may look this question: What's the difference between Git Revert, Checkout and Reset? 您可能还会看到以下问题: Git还原,签出和重置之间有什么区别?
Instead of 3
I'd suggest rebase onto : 我建议将基准改为
3
而不是3
:
git rebase --onto COMMIT_BEFORE_WRONG WRONG_COMMIT branch_with_wrong_commit
git push --force-with-lease
This "cuts" the wrong commit. 这“削减”了错误的提交。
When adding -i
(for interactive ) you can check that the right commits are moved. 当添加
-i
(用于Interactive )时,您可以检查正确的提交是否被移动。
Same result would come with: 同样的结果将伴随着:
git rebase -i COMMIT_BEFORE_WRONG git rebase -i COMMIT_BEFORE_WRONG
and then changing the word pick
to drop
in the first line of the "todo" file presented. 然后改变字
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?
我[看到]人们使用还原,但我不明白为什么它看起来更复杂,更危险。为什么我应该使用还原?
Should is a strong word. 应该是一个有力的词。 (Not quite as strong as shall or must , but at least fairly strong. :-) )
(不如必须或必须要强大,但至少要相当强大。:-))
... Reset to the first correct commit and then force push it again to remove the faulty commit on remote branch
...重置为第一个正确的提交,然后再次强制将其删除,以删除远程分支上的错误提交
Whenever you have to use git push --force
or equivalent, you are moving a branch name in a way that other people might not expect. 每当必须使用
git push --force
或等效命令时,您就以其他人可能不希望的方式移动分支名称。 ( Might is weaker than should: in a partial ordering, I would say may < might < should < shall / must .) In particular, Git branch names naturally move in a progression in which commits get added to branches, because of the way that branches grow by making commits. ( 可能比应有的要弱:在局部排序中,我会说may <may <should <must / must 。)特别是,Git分支名称自然会在将提交添加到分支的过程中自然地移动,因为分支通过提交而增长。
Consider: 考虑:
$ git checkout <somebranch>
... work ...
$ git add <files> # or --all or . or whatever
$ git commit
The git checkout
step has the effect of attaching HEAD
to a branch and copying that branch's tip commit , as pointed-to by the branch name, into your Git's index and work-tree so that you can work on it: git checkout
步骤的作用是将HEAD
附加到分支上,并将分支名称提示的该分支的tip commit复制到Git的索引和工作树中,以便您可以对其进行处理:
... <-F <-G <-H <--branch (HEAD)
The branch named branch
—the reference whose full name is refs/heads/ branch
—stores the raw hash ID of some commit H
. 名为
branch
的分支(全名是refs/heads/ branch
引用)存储某些提交H
的原始哈希ID。 Commit H
itself stores the raw hash ID of its parent commit G
, which stores the raw hash ID of some commit F
, and so on. 提交
H
本身存储其父提交G
的原始哈希ID,该原始哈希ID存储某些提交F
的原始哈希ID,依此类推。 So we say that the name points to the tip commit H
, which points to the earlier commit G
, and so on. 因此,我们说该名称指向提示提交
H
,指向更早的提交G
,依此类推。
The git add
step updates your index / staging-area so that it is ready for the commit, and the git commit
step creates a new commit. git add
步骤将更新索引/暂存区,以便可以进行提交,而git commit
步骤将创建一个新的提交。 The new commit stores, as its parent hash ID, the hash ID of the currently-checked-out commit H
. 新提交将当前签出的提交
H
的哈希ID作为其父哈希ID存储。 (It stores as its snapshot the frozen snapshot made from the current index / staging-area, of course.) Then, as its final step, git commit
writes the new commit's hash ID into the branch name to which HEAD
is attached: (当然,它存储从当前索引/暂存区中创建的冻结快照作为快照作为快照 。)然后,作为最后一步,
git commit
将新提交的哈希ID写入与HEAD
相连的分支名称中:
... <-F <-G <-H <-I <--branch (HEAD)
This is how branches grow, one commit at a time, when people make commits. 这就是人们进行提交时分支的增长方式,一次提交一次。 When you merge in a series of commits en-masse, either as a real merge or as a fast-forward not-really-a-merge-at-all operation, the branch acquires new commits as well, maybe many at once and maybe in a nonlinear fashion, but the important thing is that the new commits always lead back to the existing commits:
当您合并一系列提交时,无论是作为真正的合并,还是作为一种快进而不是全部合并的操作,分支也将同时获取新的提交,也许一次很多,也许以一种非线性的方式,但是重要的是新的提交总是导致回到现有的提交:
...--F--G--H--I---M <-- master (HEAD)
\ /
J--K--L <-- develop
Adding merge commit M
to master
leaves commits H
and I
reachable from master
, because we can follow the backwards-pointing internal commit-to-commit arrows—rendered here as lines because arrows are just too hard to draw in text now—using the top-row arrow out of M
. 将归并提交
M
添加到master
提交将使提交H
和I
可以从master
到达 ,因为我们可以遵循向后指向的内部“提交提交”箭头(此处绘制为线,因为箭头现在太难绘制文本了)使用顶部M
行箭头。 (The left-and-down arrow from M
to L
allows us to ride from M
to, say, K
or J
as well. Think Like (a) Git has a nice analogy to the transit system in Portland, though any metropolitan train system is similar.) (从
M
到L
的左右箭头也使我们也可以从M
骑到K
或J
像这样思考:(a)Git与波特兰的公交系统很相似,尽管任何大都会火车系统很相似。)
But suppose we do this instead: 但是,假设我们改为这样做:
...--F--G--H--X <-- master (HEAD)
\
J--K <-- develop
and then realize that, oops, we meant to put commit X
on develop
. 然后认识到,哎呀,我们打算把犯下
X
上的develop
。 We use any means appropriate to copy X
to a new commit, such as cherry-pick or git rebase --onto
(both do the same job). 我们使用任何适合将
X
复制到新提交的方法,例如cherry-pick或git rebase --onto
(两者都做相同的工作)。 Then we use git checkout master; git reset --hard master~1
然后我们使用
git checkout master; git reset --hard master~1
git checkout master; git reset --hard master~1
to push X
out of the way, so that it's no longer on master
: git checkout master; git reset --hard master~1
将X
推开,所以它不再在master
:
X
/
...--F--G--H <-- master (HEAD)
\
J--K--L <-- develop
(Here L
is the copy of X
, put where we wanted it.) This kind of branch-name-motion leaves commit X
dangling without any way to find it—at least, no way in our repository. (这里
L
是X
的副本,放在我们想要的位置。)这种分支名称运动叶子使X
悬空而无法找到它-至少在我们的存储库中找不到。 But if we already use git push
to send commit X
somewhere else, some other Git has a name for it. 但是,如果我们已经使用
git push
将提交X
发送到其他地方,那么其他一些Git都有一个名称。 In fact, so do we: 实际上,我们也这样做:
X <-- origin/master
/
...--F--G--H <-- master (HEAD)
\
J--K--L <-- develop
Our origin/master
, which is our Git's way of remembering master
on origin
, still remembers that commit X
exists. 我们的
origin/master
是Git在origin
时记住master
的方式,但它仍然记得提交X
存在。 That means that origin
's Git remembers X
as being on their master
. 这意味着
origin
的Git记得X
在他们的master
。
That, in fact, is why we have to use git push --force origin master
: to tell the Git at origin
that it should discard its commit X
. 也就是说,实际上,这就是为什么我们要使用
git push --force origin master
:说的Git在origin
,它应该放弃其提交X
。 If we do this before anyone else—anyone who has access to that Git— also copies X
into their Git repository, we're fine: no one saw X
, so no one can be harmed by our removing X
. 如果我们在其他任何人(有权访问该Git的人)之前也这样做, 也将
X
复制到他们的 Git存储库中,那么我们很好:没有人看到X
,因此删除X
不会对任何人造成伤害。
The problems start to pile up if someone else did grab a copy off of the other Git. 如果其他人确实从另一个Git上获取了副本,则问题开始堆积。 Now there is some third Git repository that still has commit
X
, maybe in their master
. 现在,第三个Git存储库中仍存在提交
X
,也许在其 master
。 Maybe they have built new commits atop (their copy of) X
that they want to keep: 也许他们在要保留的
X
上(它们的副本)上建立了新的提交:
...--F--G--H--X--Y--Z <-- master (HEAD)
We're now going to tell them: Oh, forget X
, take it away from your repository too. 我们现在要告诉他们: 哦,忘记
X
,也要从您的存储库中删除它。 That requires them to do their own git rebase --onto
or similar, to copy their Y
and Z
to new commits that no longer lead back to X
. 这就要求他们做自己的
git rebase --onto
或类似的操作,将他们的Y
和Z
复制到不再导致回到X
新提交中。
In short, by removing X
from our Git and from origin
's Git, we put a burden on everyone else who shares these Git repositories: they, too, must all remove their X
, and handle any consequences. 简而言之,通过从Git和
origin
Git中删除X
,我们给共享这些Git存储库的其他所有人带来了负担:他们也必须全部删除X
并处理任何后果。
There are projects in which everyone agrees that this can happen—either at any time to any branch, or at any time to some specific subset(s) of branches. 在某些项目中,每个人都同意这是可能发生的–无论是在任何时间到任何分支,还是在任何时候到分支的某些特定子集。 In these projects, resetting branches and force-pushing is fine.
在这些项目中,重设分支机构和强制推动就可以了。 There are projects where there are no other users, or where you can force-push before anyone has a chance to pick up the mistake;
在某些项目中,没有其他用户,或者您可以在任何人有机会发现错误之前进行强制推送; in these cases, resetting and force-pushing is fine there too.
在这种情况下,也可以在其中进行复位和强制推动。 The problems occur when you start making a lot of work for people who are not prepared to do it.
当您开始为不准备这样做的人做大量工作时,就会出现问题。 In this case, making a new commit that simply un-does the work in
X
gives them a way to incorporate this new work in a way that they are prepared to accept. 在这种情况下,作出新的承诺,根本未确实在工作中
X
给他们的方式纳入的方式, 他们愿意接受这个新的工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.