,很容易运行;但这意味着之后,您不能重新合并,因为Git非常确定(并且正确)地认为您已经合并了所有工作,并且正确的结果已经存在(您只是稍后撤消了它)。要将其全部恢复,您可以撤消还原。
还原会创建一个新提交,就像每个提交一样,只是将新提交添加到当前分支。像往常一样,在自己的存储库中执行此操作。其余的工作只是将新提交推送到某个其他的Git存储库,将其添加到该其他Git的集合中,作为其中一个分支的新提示。
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.
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
.
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:
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.)
master
的关系并不重要。git checkout -b cleaning remote/staging
应该足以替代1)和2)。其次,git revert
将你想要撤销的提交作为参数,而不是你想要保留的最后一个提交。我认为你想要类似于go revert {last good commit}..HEAD
的东西来撤消最后一个好提交之后的所有内容。 - chepner