撤销远程分支上的一个提交

45

我在本地分支上添加了许多提交。

然后,我将其推送到远程暂存分支。

现在,我必须撤消已推送到远程暂存的最后一个提交,即我的本地分支合并到远程暂存的提交。

根据其他回答所理解的是,我必须使用还原而不是重置以简洁的方式实现它,是吗?

那么我要做的就是:

  1. 创建一个名为cleaning的新本地分支,其父项为master(落后于staging)
  2. 将远程staging拉入清理
  3. 使用git revert {staging中最后一个正确提交的哈希值}
  4. 现在,cleaning应处于正确的提交状态,并且与远程暂存在我的错误推送之前处于相同状态,是吗?
  5. 现在,我应该将cleaning推送到远程暂存,以使远程分支恢复。使用哪些参数?

我是正确的吗?因为git status在第4点告诉我,我已经更新到staging了。


几点需要注意:你只需要一个远程分支的本地副本;它与master的关系并不重要。git checkout -b cleaning remote/staging应该足以替代1)和2)。其次,git revert将你想要撤销的提交作为参数,而不是你想要保留的最后一个提交。我认为你想要类似于go revert {last good commit}..HEAD的东西来撤消最后一个好提交之后的所有内容。 - chepner
2个回答

80

不要把它变得复杂。

首先,您需要执行 git log 命令以查找要还原的提交 ID。例如,abc123 是该提交。如果您知道它是最后一个提交,您可以使用特殊标识符 "HEAD"。

然后,您需要在本地 "staging" 分支中先还原它:

git checkout staging
git revert abc123
注意:对于日志中的最新提交,您需要编写git revert HEAD
然后您需要更新您的远程“staging”:
git push

说明:在 Git 中,如果你有一个远程仓库,那么你将处理两个不同的仓库(本地和远程)。当你执行git checkout staging后,实际上会创建一个表示远程分支的独立本地名称。git revert并没有删除你的提交,而是在顶部创建一个新的提交,撤销所有更改(如果添加了文件,则新提交将删除它;如果删除了一行,则新提交将添加它回来等),即这是日志的一个补充,这就是为什么推送将是干净的。

如果你想真正让它消失,以便没有人能够责怪你,你可以自担风险执行以下操作:

git checkout staging
git reset --hard HEAD^
git push -f

重置行将本地的“暂存”分支重新定位到您顶部提交之前的提交。

通常,强制推送是一种不好的做法,但保留无用的提交和还原也不好,因此另一种解决方案是实际上创建一个新的“staging2”分支,并在该分支中进行测试:

git checkout staging
git checkout -b staging2
git reset --hard HEAD^
git push

OP 正在尝试还原一个已推送的合并,因此简单的还原可能不够。你需要使用 -m - jingx
撤销更改而不改变历史记录通常是最好的方法!如果出于某种原因您真的想要更改历史记录,那么这将不仅仅是“撤销” :) - superarts.org

9

TL;DR

记住,git revert 的真正含义是 回退更改,而不是 恢复到某个特定版本。虽然也有一些命令可以实现 恢复到 / 还原,但这些命令都比较棘手,其中包括 git checkoutgit read-tree。请参见 如何将 Git 存储库还原到以前的提交?在公共存储库中回滚到旧的 Git 提交(特别是 jthill's answer)。

这里唯一真正棘手的部分是“还原合并”。这相当于在合并提交上使用git revert -m 1,很容易运行;但这意味着之后,您不能重新合并,因为Git非常确定(并且正确)地认为您已经合并了所有工作,并且正确的结果已经存在(您只是稍后撤消了它)。要将其全部恢复,您可以撤消还原。
还原会创建一个新提交,就像每个提交一样,只是将新提交添加到当前分支。像往常一样,在自己的存储库中执行此操作。其余的工作只是将新提交推送到某个其他的Git存储库,将其添加到该其他Git的集合中,作为其中一个分支的新提示。
长(涉及许多细节)
首先,让我们回顾一下,因为短语“远程分支”什么也不表示,或者说对太多人来说意味着太多不同的事情-无论哪种情况,最终都会导致沟通失败。
Git确实有分支,但是即使是“分支”这个词也是含糊的(请参阅我们所说的“分支”到底是什么?)。我发现更好的方法是具体说明:我们有像masterstaging和你的建议中的cleaning这样的分支名称。 Git还有远程跟踪名称远程跟踪分支名称,如origin/staging,但令人困惑的是,Git将这些名称本地存储,即在您自己的存储库中(仅限于您的存储库)。 您的Git使用您的origin/staging来记住您的Git上一次与其Git通信并询问有关他们的staging时,您的Git看到了什么。
因此,这里的问题根源在于你的Git仓库是你自己的,但有第二个Git仓库却不是你自己的,你最终想要在那个其他的Git仓库上做一些事情。这里的限制是你的Git仅允许你在你自己的仓库中进行这些操作,之后你最终会运行git push。在此时,git push步骤将从你的Git仓库中传输一些提交到他们的Git仓库。此时,你可以要求他们的Git设置他们的名称-他们的staging,他们要么会说是的,我已经设置好了(你的Git现在将更新你的origin/staging以记住这一点),要么会说不,我拒绝设置,原因是:_____(在此处插入原因)
因此,在您的存储库中,您要做的是设置适当的步骤,以便他们的Git在他们的存储库中接受您git push提交,并更新他们的staging。请记住这一点,当我们按照以下步骤进行时。有多种方法可以做到这一点,但以下是我会使用的方法。
  1. Run git fetch. This command is always safe to run. You can give it the name of one specific remote, if you have more than one, but most people only have one, named origin, and if you only have one, there's no need to name it.

    (The name of the remote—origin—is mainly just a short-hand way of spelling the URL that your computer should use to reach the other Git repository.)

    This git fetch may do nothing, or may update some of your remote-tracking (origin/*) names. What git fetch did was call up the other Git, get from it a list of all its branches and which commit hashes go with them, and then bring over any commits they have that you don't. Your origin/staging now remembers their staging. It's now possible for you to add a new commit to this.

  2. Now that your origin/staging is in sync with the other Git's staging, create or update a local branch name as needed. The best name to use here is staging, but you can use cleaning if you like. Because this step is create or update, it has sub-steps:

    • If it's "create", you can use git checkout -b staging origin/staging to create a new staging whose upstream is origin/staging.

      You can shorten this to git checkout staging, which—since you don't have your own staging—will search through all of your origin/* names (and any other remote-tracking names, if you have more than one remote) to find which one(s) match. It then acts as though you used the longer command. (With only one remote, the only name that can match is origin/staging. If you had both origin and xyzzy as remotes, you could have both origin/staging and xyzzy/staging; then you'd need a longer command.)

    • Or, if it's "update", you will already have a staging that already has origin/staging set as its upstream, because you have done this before. In this case, just run:

      git checkout staging
      git merge --ff-only origin/staging
      

      to get your staging re-synchronized with origin/staging. If the fast-forward merge fails, you have some commit(s) they don't, and you'll need something more complex, but we'll assume here that this succeeds.

      You can abbreviate these commands a bit as well, but I'll leave them spelled out here. Note that the first command is the same as the short version for the first case above, and you can tell which one happened by the output from git checkout staging. (I'll leave the details for other questions or an exercise.)

    We can draw a picture of what you have now, in your own repository, and it looks something like this:

    ...--o--o--o---M   <-- staging (HEAD), origin/staging
             \    /
              o--o   <-- feature/whatever
    

    Each round o represents a commit. M represents the merge commit, whose result you don't like, but which is also present in the other Git at origin under their name staging, which is why your own Git has the name origin/staging pointing to commit M.

  3. You now want to create a commit that undoes the bad commit. This will likely use git revert, but remember, revert means undo or back out, not switch to old version. You tell Git which commit to undo, and Git undoes it by figuring out what you did, and doing the opposite.

    For instance, if the commit you say to revert says "remove the file README", the change will include "restore the file README in the form it had when it was removed." If the commit you say to revert says "add this line to Documentation/doc.txt", the change will include "remove that line from Documentation/doc.txt". If the commit you say to revert says "change hello to goodbye" in some third file, the change that revert will do is to change "goodbye" to "hello" in that third file, on the same line (with some magic to find the line if it moved).

    This means that git revert can undo any commit, even if it's not the latest commit. To do so, though, it must compare that commit to its immediate parent. If the commit you are attempting to revert is a merge commit, it has more than one parent and you will need to specify which parent Git should use.

    The correct parent to use is not always immediately obvious. However, for most merges, it's just "parent number 1". This is because Git places special emphasis on the first parent of a merge: it's the commit that was HEAD when you ran git merge. So this is everything that the merge brought in, that was not already present.

    When the git revert succeeds, it makes a new commit that undoes the effect of the merge:

                     W   <-- staging (HEAD)
                    /
    ...--o--o--o---M   <-- origin/staging
             \    /
              o--o   <-- feature/whatever
    

    Here, W represents this new commit: it's M turned upside down. All you have to do now is run git push origin staging to send your own new commit W to the other Git:

  4. git push origin staging: this calls up that other Git and offers it commit W—that's every commit we have that they don't; they have M and everything earlier (to the left), but not W.

    As long as there are no special restrictions, they will accept this new commit and change their staging to point to new commit W. Your Git will remember the change:

                     W   <-- staging (HEAD), origin/staging
                    /
    ...--o--o--o---M
             \    /
              o--o   <-- feature/whatever
    

    (There's no need to keep drawing W on a separate line, but I am using copy-paste here to keep the shape the same.)

如您所见,现在您已经完成了。您和他们都同意,您和他们的staging应该都指向撤销提交M的提交W。如果您愿意,现在可以安全地删除自己的staging名称:

git checkout <something-else>
git branch -d staging

这将产生:

...--o--o--o---M--W   <-- origin/staging
         \    /
          o--o   <-- feature/whatever

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接