简体   繁体   English

将放错位置的提交移动到新分支

[英]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: 我的解决方案:

  1. Have checked out master and created a branch where the code change should be 已检出master并创建了一个分支,应在其中更改代码
  2. Cherry-picked the commit from the wrong branch checkin and commited to new branch. Cherry从错误的分支签入中选择了提交,然后提交到新分支。
  3. Check out the wrong branch. 签出错误的分支。 Reset to the first correct commit and then force push it again to remove the faulty commit on remote branch. 重置为第一个正确的提交,然后再次强制将其删除以除去远程分支上的错误提交。

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. 然后改变字pickdrop中提出的“待办事项”文件的第一行。

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提交将使提交HI 可以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.) (从ML的左右箭头也使我们也可以从M骑到KJ像这样思考:(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~1X推开,所以它不再在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. (这里LX的副本,放在我们想要的位置。)这种分支名称运动叶子使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或类似的操作,将他们的YZ复制到不再导致回到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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM