git 恢复远程分支上的提交

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/50473587/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 05:17:03  来源:igfitidea点击:

Revert a commit on remote branch

git

提问by lellefood

I've worked on a local branch adding many commits.
Then i've pushed it to the remote stagingbranch.

我曾在一个本地分支上工作,添加了许多提交。
然后我把它推到了remote staging分支。

Now I have to undo the last commit already pushed to remote stagingthat is the merge of my local branch to remote staging

现在我必须撤消已经推送到的最后一次提交,remote staging这是我本地分支的合并remote staging

What I've understood looking on the other answers is that I have to use revert and not reset to do it in a clean way, isn't it?

我在其他答案中的理解是,我必须使用还原而不是重置才能以干净的方式完成它,不是吗?

So what I have to do is:

所以我要做的是:

  1. create a new local branch called for example cleaninghaving masteras parent (that is behind staging)
  2. pull the remote staginginto cleaning
  3. use git revert {last good commit hash in staging}
  4. now cleaningshould be in the good commit and in the same state of remote stagingbefore my bad push, isn't it?
  5. now I should push cleaninginto remote stagingto having the remote branch reverted. With which flags?
  1. 创建一个新的本地分支,例如cleaning具有master作为父级(即在暂存之后)
  2. 将遥控器拉staging入清洁状态
  3. git revert {last good commit hash in staging}
  4. 现在cleaning应该处于良好的提交状态并且处于与remote staging我的错误推送之前相同的状态,不是吗?
  5. 现在我应该推cleaningremote staging到其恢复远程分支。用哪些标志?

Am I correct? Because git statusbeing at point 4 tells me that i'm up to date with staging

我对么?因为git status在第 4 点告诉我我是最新的staging

回答by battlmonstr

Don't make it complicated.

不要让它复杂。

First you need to do a git logto find out which commit ID you want to revert. For example it is commit abc123. If you know that it's the last one, you can use a special identifier "HEAD".

首先,您需要执行 agit log来找出要还原的提交 ID。例如它是 commit abc123。如果您知道这是最后一个,则可以使用特殊标识符“HEAD”。

Then you first revert it locally in your local "staging" branch:

然后,您首先在本地“暂存”分支中将其本地还原:

git checkout staging
git revert abc123

Note: for the last commit in the log you would write git revert HEAD.

注意:对于日志中的最后一次提交,您将编写git revert HEAD.

And then you update your remote "staging":

然后你更新你的远程“登台”:

git push

Explanation: In git if you have a remote you are dealing with 2 distinct repositories (local and remote). After you do git checkout staging, you actually create a distinct local name that represents the remote branch. git revertactually doesn't delete your commit, but it creates a new commit on top, that undoes all the changes (if you added a file - the new commit will remove it, if you removed a line - the new commit will add it back etc.), i.e. it is an addition to the log, that's why the push will be clean.

说明:在 git 中,如果您有一个远程仓库,您将处理 2 个不同的存储库(本地和远程)。完成之后git checkout staging,您实际上创建了一个代表远程分支的不同本地名称。git revert实际上不会删除您的提交,但它会在顶部创建一个新提交,这会撤消所有更改(如果您添加了文件 - 新提交将删除它,如果您删除一行 - 新提交会将其添加回来等.),即它是对日志的补充,这就是为什么推送将是干净的。

If you want to really make it gone, so that nobody can blame you, you can do at your risk:

如果你真的想让它消失,这样就没有人可以责怪你了,你可以自己承担风险:

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

(the reset line retargets the local "staging" branch so that it points to a commit that is right before your top commit)

(重置行重新定位本地“暂存”分支,以便它指向在您的最高提交之前的提交)

In general the forced push is a bad practice, but keeping useless commits and reverts around is not nice as well, so another solution would be to actually create a new branch "staging2" and do your testing in that branch instead:

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

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

回答by torek

TL;DR

TL; 博士

Remember that git revertreally means back out a change, notrevert to, i.e., restore, some particular version. It is possible to achieve revert to / restore, but the commands that do this are git checkoutand git read-tree(both of these are mildly tricky, for different reasons). See How to revert Git repository to a previous commit?and Rollback to an old Git commit in a public repo(specifically jthill's answer).

请记住,这git revert实际上意味着取消更改而不是恢复到(即恢复)某个特定版本。可以实现revert to / restore,但是执行此操作的命令是git checkoutand git read-tree(由于不同的原因,这两个命令都有些棘手)。请参阅如何将 Git 存储库恢复到以前的提交?回滚到公共存储库中的旧 Git 提交(特别是jthill 的回答)。

The only really tricky part here is "revert a merge". This amounts to using git revert -m 1on the merge commit, which is easy enough to run; but it means that afterward, you cannot re-merge as Git is quite certain (and correct) that you already merged all of that workand that the correct result is in place (which it is, you just undid it later). To put it all back, you can revert the revert.

这里唯一真正棘手的部分是“还原合并”。这相当于git revert -m 1在合并提交上使用,这很容易运行;但这意味着之后,您无法重新合并,因为 Git 非常确定(并且正确)您已经合并了所有这些工作并且正确的结果已经到位(确实如此,您稍后才将其撤消)。要将其全部还原,您可以还原还原。

Reverting makes a new commit, which like every commit, just adds a new commit to the current branch. You do this in your own repository as always. The rest of the job is just to push the new commit to some otherGit repository, adding it to that other Git's collection as the new tip of one of theirbranches.

恢复会产生一个新的提交,就像每次提交一样,只是向当前分支添加一个新的提交。您像往常一样在自己的存储库中执行此操作。剩下的工作就是将新提交推送到某个其他Git 存储库,将其添加到其他 Git 的集合中作为分支之一的新提示。

Long (goes into lots of detail)

长(涉及很多细节)

First, let's back up a bit, because the phrase remote branchmeans nothing, or rather, means too many different things to too many different people—in either case, it winds up resulting in failed communication.

首先,让我们备份一下,因为短语远程分支没有任何意义,或者更确切地说,对太多不同的人意味着太多不同的事情——无论哪种情况,最终都会导致通信失败。

Git does have branches, but even the word "branch" is ambiguous (see What exactly do we mean by "branch"?). I find that it is better to be specific: we have branch nameslike masterand stagingand, in your suggestion, cleaning. Git also has remote-tracking namesor remote-tracking branch nameslike origin/staging, but confusingly, Git stores those names locally, i.e., in your own repository (only). Your Git uses yourorigin/stagingto remember what your Git saw on theirGit, when your Git was last talking with their Git and asked them something about theirstaging.

Git 确实有分支,但即使是“分支”这个词也是模棱两可的(请参阅我们所说的“分支”到底是什么意思?)。我发现最好是具体的:在您的建议中,我们有像和这样的分支名称。Git 也有远程跟踪名称远程跟踪分支名称,例如,但令人困惑的是,Git 将这些名称存储在本地,即存储在您自己的存储库中(仅限)。你的 Git 使用你的来记住你的 Git 在他们的 Git 上看到了什么,当你的 Git 上次与他们的 Git 交谈并询问他们关于他们的.masterstagingcleaningorigin/stagingorigin/stagingstaging

Hence, the root of the problem here is that your Git repository is yours, but there is a second Git repository that is notyours, and you ultimately would like to do something on that otherGit repository. The constraint here is that your Git only lets you do these things in yourrepository, after which you will eventually run git push. The git pushstep will transfer some commit or commits from your Git repository over to their Git repository. At this time, you can ask theirGit to set theirname—theirstaging—and they will either say Yes, I have set that(your Git will now update your origin/stagingto remember this), or No, I refuse to set that, for the following reason: _____ (insert reason here).

因此,这里问题的根源在于您的 Git 存储库是您的,但是还有一个不属于您的Git 存储库,而您最终希望在另一个Git 存储库上执行某些操作。这里的约束是你自己的Git只允许你在做这些事情资料库,之后,你最终会运行git push。该git push步骤会将一些提交或提交从您的 Git 存储库转移到他们的 Git 存储库。这时,你可以让他们的Git 设置他们的名字——他们的staging——他们要么说是,我已经设置了(你的 Git 现在会更新你origin/staging的记住这一点),或者不,我拒绝设置,原因如下:_____(在此处插入原因)

Hence, what you are going to do in yourrepository is set up the appropriate steps so that theirGit, in theirrepository, will accept the commit you git pushand will update their staging. Keep that in mind as we go through these steps. There are multiple ways to do this, but here are the ones I would use.

因此,你会在做什么你的仓库已建立适当的步骤,使自己的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 fetchmay do nothing, or may update some of your remote-tracking (origin/*) names. What git fetchdid 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/stagingnow remembers their staging. It's now possible for you to add a new commit to this.

  2. Now that your origin/stagingis 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 cleaningif 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/stagingto create a new stagingwhose 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 originand xyzzyas remotes, you could have both origin/stagingand xyzzy/staging; then you'd need a longer command.)

    • Or, if it's "update", you will already have a stagingthat already has origin/stagingset 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 sameas 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 orepresents a commit. Mrepresents the merge commit, whose result you don't like, but which is also present in the other Git at originunder theirname staging, which is why your own Git has the name origin/stagingpointing to commit M.

  3. You now want to create a commit that undoesthe bad commit. This will likely use git revert, but remember, revert means undoor 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 revertcan 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 mergecommit, it has more than one parentand 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 firstparent of a merge: it's the commit that was HEADwhen you ran git merge. So this is everything that the merge brought in, that was not already present.

    When the git revertsucceeds, 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, Wrepresents this new commit: it's Mturned upside down. All you have to do now is run git push origin stagingto send your own new commit Wto 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 Mand everything earlier (to the left), but not W.

    As long as there are no special restrictions, they will accept this new commit and change theirstagingto 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 Won a separate line, but I am using copy-paste here to keep the shape the same.)

  1. 运行git fetch。此命令始终可以安全运行。您可以给它指定一个特定遥控器的名称,如果您有多个,但大多数人只有一个, named origin,如果您只有一个,则无需命名。

    (远程的名称origin——主要是拼写你的计算机应该用来访问另一个 Git 存储库的 URL 的简写方式。)

    git fetch可能什么都不做,或者可能会更新您的某些远程跟踪 ( origin/*) 名称。是什么git fetch做的是调用其他的Git,从中获得其所有分支机构的名单和提交哈希和他们一起去,然后带过来,他们有你没有任何承诺。你origin/staging现在记得他们的staging. 您现在可以为此添加新的提交。

  2. 现在您origin/staging已与其他 Git 同步,请staging根据需要创建或更新本地分支名称。此处使用的最佳名称是staging,但您可以根据需要使用cleaning。因为这一步是create 或 update,所以它有子步骤:

    • 如果是“创建”,则可以使用git checkout -b staging origin/staging创建一个staging上游为origin/staging.

      您可以将其缩短为git checkout staging,因为您没有自己stagingorigin/*姓名,它将搜索您的所有姓名(以及任何其他远程跟踪姓名,如果您有多个遥控器)以查找匹配的姓名。然后它就像您使用了更长的命令一样。(只有一个遥控器,唯一可以匹配的名称是origin/staging。如果您同时拥有originandxyzzy作为遥控器,则可以同时拥有origin/stagingand xyzzy/staging;那么您需要更长的命令。)

    • 或者,如果它是“更新”,您将已经有一个staging已经origin/staging设置为其上游的,因为您之前已经这样做了。在这种情况下,只需运行:

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

      使您的登台与origin/staging. 如果快进合并失败,您有一些他们没有的提交,并且您需要更复杂的东西,但我们在这里假设这会成功。

      您也可以稍微缩写这些命令,但我会在此处将它们拼写出来。请注意,第一个命令上述第一种情况的简短版本相同,您可以通过git checkout staging. (我会把细节留给其他问题或练习。)

    我们可以在您自己的存储库中绘制您现在拥有的内容的图片,它看起来像这样:

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

    每轮o代表一次提交。 M代表着合并提交,其结果是你不喜欢,但也存在于其他的蠢货origin他们的名字staging,这就是为什么你自己的Git有名称origin/staging指向承诺M

  3. 您现在想要创建一个撤销错误提交的提交。这可能会使用git revert,但请记住,还原意味着撤消退出,而不是切换到旧版本。你告诉 Git 撤消哪个提交,Git 通过弄清楚你做了什么来撤消它,然后做相反的事情。

    例如,如果您说要还原的提交表示“删除文件 README”,则更改将包括“以删除时的形式恢复文件 README”。如果您说要还原的提交说“将此行添加到 Documentation/doc.txt”,则更改将包括“从 Documentation/doc.txt 中删除该行”。如果您说要还原的提交在第三个文件中说“将 hello 改为 goodbye”,则 revert 所做的更改是将第三个文件中的“goodbye”更改为“hello”,在同一行(用一些魔法找到线,如果它移动)。

    这意味着git revert可以撤消任何提交,即使它不是最新的提交。但是,要这样做,它必须将该提交与其直接父项进行比较。如果您尝试还原的提交是合并提交,则它有多个父级,您需要指定 Git 应使用哪个父级。

    要使用的正确父级并不总是很明显。但是,对于大多数合并,它只是“父编号 1”。这是因为 Git 特别强调合并的第一个父级:它是HEAD您运行git merge. 所以这就是合并带来的一切,还没有出现。

    git revert成功时,它会进行一个新的提交来撤销合并的效果:

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

    在这里,W代表这个新的提交:它M被颠倒了。您现在要做的就是运行git push origin staging将您自己的新提交W发送到另一个 Git:

  4. git push origin staging:这会调用另一个 Git 并提供它提交W——这是我们拥有但他们没有的每个提交;他们有M和一切更早(到左边),但没有W

    只要没有特殊的限制,他们将接受这一新的承诺,改变staging指向新的提交W。你的 Git 会记住这个变化:

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

    (没有必要继续W在单独的线上绘图,但我在这里使用复制粘贴来保持形状相同。)

As you can see, you're now done. You and they both agree that your and their stagingshould both point to commit Wthat has the effect of undoing commit M. It's now safe to delete your own stagingname, if you like:

如您所见,您现在已完成。你和他们都同意你和他们staging都应该指向W具有撤销 commit 效果的 commit Mstaging如果您愿意,现在可以安全地删除您自己的名字:

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

which produces:

它产生:

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