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
Revert a commit on remote branch
提问by lellefood
I've worked on a local branch adding many commits.
Then i've pushed it to the remote staging
branch.
我曾在一个本地分支上工作,添加了许多提交。
然后我把它推到了remote staging
分支。
Now I have to undo the last commit already pushed to remote staging
that 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:
所以我要做的是:
- create a new local branch called for example
cleaning
havingmaster
as parent (that is behind staging) - pull the remote
staging
into cleaning - use
git revert {last good commit hash in staging}
- now
cleaning
should be in the good commit and in the same state ofremote staging
before my bad push, isn't it? - now I should push
cleaning
intoremote staging
to having the remote branch reverted. With which flags?
- 创建一个新的本地分支,例如
cleaning
具有master
作为父级(即在暂存之后) - 将遥控器拉
staging
入清洁状态 - 用
git revert {last good commit hash in staging}
- 现在
cleaning
应该处于良好的提交状态并且处于与remote staging
我的错误推送之前相同的状态,不是吗? - 现在我应该推
cleaning
入remote staging
到其恢复远程分支。用哪些标志?
Am I correct? Because git status
being 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 log
to 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 revert
actually 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 revert
really 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 checkout
and 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 checkout
and git read-tree
(由于不同的原因,这两个命令都有些棘手)。请参阅如何将 Git 存储库恢复到以前的提交?并回滚到公共存储库中的旧 Git 提交(特别是jthill 的回答)。
The only really tricky part here is "revert a merge". This amounts to using git revert -m 1
on 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 master
and staging
and, 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/staging
to 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 交谈并询问他们关于他们的.master
staging
cleaning
origin/staging
origin/staging
staging
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 push
step 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/staging
to 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 push
and 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
。在我们执行这些步骤时请记住这一点。有多种方法可以做到这一点,但这里是我会使用的方法。
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, namedorigin
, 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. Whatgit 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. Yourorigin/staging
now remembers theirstaging
. It's now possible for you to add a new commit to this.Now that your
origin/staging
is in sync with the other Git'sstaging
, create or update a local branch name as needed. The best name to use here isstaging
, but you can usecleaning
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 newstaging
whose upstream isorigin/staging
.You can shorten this to
git checkout staging
, which—since you don't have your ownstaging
—will search through all of yourorigin/*
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 isorigin/staging
. If you had bothorigin
andxyzzy
as remotes, you could have bothorigin/staging
andxyzzy/staging
; then you'd need a longer command.)Or, if it's "update", you will already have a
staging
that already hasorigin/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 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
o
represents a commit.M
represents the merge commit, whose result you don't like, but which is also present in the other Git atorigin
under theirnamestaging
, which is why your own Git has the nameorigin/staging
pointing to commitM
.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 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 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
HEAD
when you rangit 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'sM
turned upside down. All you have to do now is rungit push origin staging
to send your own new commitW
to the other Git:git push origin staging
: this calls up that other Git and offers it commitW
—that's every commit we have that they don't; they haveM
and everything earlier (to the left), but notW
.As long as there are no special restrictions, they will accept this new commit and change their
staging
to point to new commitW
. 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.)
运行
git fetch
。此命令始终可以安全运行。您可以给它指定一个特定遥控器的名称,如果您有多个,但大多数人只有一个, namedorigin
,如果您只有一个,则无需命名。(远程的名称
origin
——主要是拼写你的计算机应该用来访问另一个 Git 存储库的 URL 的简写方式。)这
git fetch
可能什么都不做,或者可能会更新您的某些远程跟踪 (origin/*
) 名称。是什么git fetch
做的是调用其他的Git,从中获得其所有分支机构的名单和提交哈希和他们一起去,然后带过来,他们有你没有任何承诺。你origin/staging
现在记得他们的staging
. 您现在可以为此添加新的提交。现在您
origin/staging
已与其他 Git 同步,请staging
根据需要创建或更新本地分支名称。此处使用的最佳名称是staging
,但您可以根据需要使用cleaning
。因为这一步是create 或 update,所以它有子步骤:如果是“创建”,则可以使用
git checkout -b staging origin/staging
创建一个staging
上游为origin/staging
.您可以将其缩短为
git checkout staging
,因为您没有自己staging
的origin/*
姓名,它将搜索您的所有姓名(以及任何其他远程跟踪姓名,如果您有多个遥控器)以查找匹配的姓名。然后它就像您使用了更长的命令一样。(只有一个遥控器,唯一可以匹配的名称是origin/staging
。如果您同时拥有origin
andxyzzy
作为遥控器,则可以同时拥有origin/staging
andxyzzy/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
。您现在想要创建一个撤销错误提交的提交。这可能会使用
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: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 staging
should both point to commit W
that has the effect of undoing commit M
. It's now safe to delete your own staging
name, if you like:
如您所见,您现在已完成。你和他们都同意你和他们staging
都应该指向W
具有撤销 commit 效果的 commit M
。staging
如果您愿意,现在可以安全地删除您自己的名字:
git checkout <something-else>
git branch -d staging
which produces:
它产生:
...--o--o--o---M--W <-- origin/staging
\ /
o--o <-- feature/whatever