I have a Git repository that looks like this:
A <- B <- C <- D <- HEAD
I want the head of the branch to point to A, i.e., I want B, C, D, and HEAD to disappear and I want head to be synonymous with A.
It sounds like I can either try to rebase (doesn't apply, since I've pushed changes in between), or revert. But how do I revert multiple commits? Do I revert one at a time? Is the order important?
19 Answers 19
Expanding what I wrote in a comment
The general rule is that you should not rewrite (change) history that you have published, because somebody might have based their work on it. If you rewrite (change) history, you would make problems with merging their changes and with updating for them.
So the solution is to create a new commit which reverts changes that you want to get rid of. You can do this using git revert command.
You have the following situation:
A <-- B <-- C <-- D <-- master <-- HEAD
(arrows here refers to the direction of the pointer: the "parent" reference in the case of commits, the top commit in the case of branch head (branch ref), and the name of branch in the case of HEAD reference).
What you need to create is the following:
A <-- B <-- C <-- D <-- [(BCD)-1] <-- master <-- HEAD
where [(BCD)^-1] means the commit that reverts changes in commits B, C, D. Mathematics tells us that (BCD)-1 = D-1 C-1 B-1, so you can get the required situation using the following commands:
$ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "the commit message for all of them"
Works for everything except merge commits.
Alternate solution would be to checkout contents of commit A, and commit this state. Also works with merge commits. Added files will not be deleted, however. If you have any local changes git stash them first:
$ git checkout -f A -- . # checkout that revision over the top of local files
$ git commit -a
Then you would have the following situation:
A <-- B <-- C <-- D <-- A' <-- master <-- HEAD
The commit A' has the same contents as commit A, but is a different commit (commit message, parents, commit date).
Alternate solution by Jeff Ferland, modified by Charles Bailey builds upon the same idea, but uses git reset. Here it is slightly modified, this way WORKS FOR EVERYTHING:
$ git reset --hard A
$ git reset --soft D # (or ORIG_HEAD or @{1} [previous location of HEAD]), all of which are D
$ git commit
34 Comments
git checkout -f A -- . Will not delete these, you will have to do it manually. I applied this strategy now, thanks Jakubgit checkout foo might mean checkout branch foo (switch to branch) or checkout file foo (from index). -- is used to disambiguate, e.g. git checkout -- foo is always about file.git revert --no-commit D C Bgit revert --no-commit B^..D. Harder to remember but let hashes to type!git revert didn't accept multiple commits; it is quite new addition.Clean way which I found useful
git revert --no-commit HEAD~3..
git commit -m "your message regarding reverting the multiple commits"
This command reverts last 3 commits with only one commit.
Also doesn't rewrite history, so doesn't require a force push.
The .. helps create a range. Meaning HEAD~3.. is the same as HEAD~3..HEAD
11 Comments
git commit from there will actually do the commit.HEAD~3.. is the same as HEAD~3..HEADFor doing so you just have to use the revert command, specifying the range of commits you want to get reverted.
Taking into account your example, you'd have to do this (assuming you're on branch 'master'):
git revert master~3..master
or git revert B...D or git revert D C B
This will create a new commit in your local with the inverse commit of B, C and D (meaning that it will undo changes introduced by these commits):
A <- B <- C <- D <- BCD' <- HEAD
6 Comments
git revert --no-commit HEAD~2.. is a slightly more idiomatic way to do it. If you're on master branch, no need to specify master again. The --no-commit option lets git try to revert all the commits at once, instead of littering the history with multiple revert commit ... messages (assuming that's what you want).--no-commit (so you get a separate commit for each revert), and then squash all of them together in an interactive rebase. The combined commit message will contain all of the SHAs, and you can arrange them however you like using your favorite commit message editor.--no-commit it does them one at a time and you have to keep typing git revert --continue over and over for each one...dang I was hoping git would have a friendly commit option like "do them all and list all the hashes for me in a single commit" but appears not :|git reset --hard a
git reset --mixed d
git commit
That will act as a revert for all of them at once. Give a good commit message.
9 Comments
HEAD to look like A then he probably want the index to match so git reset --soft D is probably more appropriate.git checkout A then git commit above did not work for me, but this answer did.git reset --mixed D required? Specifically why reset? Is it because, without resetting to D, that, HEAD would point at A, causing B, C, and D to be "dangling" and garbage-collected -- which is not what he wants? But then why --mixed? You already answered "--soft resetting doesn't move the index..." So by moving the index, this means index will contain D's changes, while Working Directory will contain A's changes -- this way a git status or git diff (which compares Index [D] to Working Directory [A]) will show the substance; that user is going from D back to A?Similar to Jakub's answer, this allows you to easily select consecutive commits to revert.
# Revert all commits from and including B to HEAD, inclusively
git revert --no-commit B^..HEAD
git commit -m 'message'
4 Comments
B^..HEAD, otherwise B is excluded.git revert --no-commit B^..HEAD or git revert --no-commit A..HEADI'm so frustrated that this question can't just be answered. Every other question is in relation to how to revert correctly and preserve history. This question says "I want the head of the branch to point to A, i.e. I want B, C, D, and HEAD to disappear and I want head to be synonymous with A."
git checkout <branch_name>
git reset --hard <commit Hash for A>
git push -f
I learned a lot reading Jakub's post, but some guy in the company (with access to push to our "testing" branch without a Pull-Request) pushed like five bad commits, trying to fix and fix and fix a mistake he made five commits ago. Not only that, but one or two pull requests were accepted, which were now bad. So forget it; I found the last good commit (abc1234) and just ran the basic script:
git checkout testing
git reset --hard abc1234
git push -f
I told the other five guys working in this repository that they better make note of their changes for the last few hours and wipe/rebranch from the latest testing. End of the story.
10 Comments
git push --force-with-lease, which will rewrite history only if no one else has committed to the branch after or within the range of the commits to vapourize. If other people have used the branch, then its history should never be rewritten, and the commit should simply be visibly reverted.First be sure that your working copy is not modified.
Then:
git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply
And then just commit. Don't forget to document what the reason is for the revert.
9 Comments
error: cannot apply binary patch to 'some/image.png' without full index line error: some/image.png: patch does not applygit diff --binary HEAD commit_sha_you_want_to_revert_to | git applygit revert A..Z you'd get error: commit X is a merge but no -m option was given.Using Git Restore
You can also use the restore command:
A <- B <- C <- D <- HEAD
Let's say you want HEAD to look exactly like A. Make sure you've pulled the latest main. Then cut a new branch.
git switch -c feature/flux-capacitor # synonymous with checkout -b
git restore --source A .
git add .
git commit
git push
The restore command changes everything (.) to what it was at the --source commit. Then you commit that to your local branch and push it to the origin. You can then open a PR against it.
This has the benefit of not changing any history that others might have based work on. It also leaves a useful history for folks in the future.
Docs: git restore
4 Comments
restore on your desired branch, just as you would with any of the other approaches mentioned in this list.This is an expansion of one of the solutions provided in Jakub's answer .
I was faced with a situation where the commits I needed to roll back were somewhat complex, with several of the commits being merge commits, and I needed to avoid rewriting history. I was not able to use a series of git revert commands because I eventually ran into conflicts between the reversion changes being added. I ended up using the following steps.
First, check out the contents of the target commit while leaving HEAD at the tip of the branch:
git checkout -f <target-commit> -- .
(The -- makes sure <target-commit> is interpreted as a commit rather than a file; the . refers to the current directory.)
Then, determine what files were added in the commits being rolled back, and thus need to be deleted:
git diff --name-status --cached <target-commit>
Files that were added should show up with an "A" at the beginning of the line, and there should be no other differences. Now, if any files need to be removed, stage these files for removal:
git rm <filespec>[ <filespec> ...]
Finally, commit the reversion:
git commit -m 'revert to <target-commit>'
If desired, make sure that we're back to the desired state:
git diff <target-commit> <current-commit>
There should be no differences.
The easy way to revert a group of commits on shared repository (that people use and you want to preserve the history) is to use git revert in conjunction with git rev-list. The latter one will provide you with a list of commits, the former will do the revert itself.
There are two ways to do that. If you want the revert multiple commits in a single commit use:
for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-commit $i; done
this will revert a group of commits you need, but leave all the changes on your working tree, you should commit them all as usual afterward.
Another option is to have a single commit per reverted change:
for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done
For instance, if you have a commit tree like
o---o---o---o---o---o--->
fff eee ddd ccc bbb aaa
to revert the changes from eee to bbb, run
for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done
2 Comments
git checkout <first commit> then git archive and then manually create a commit unzipping the archived version...None of those worked for me, so I had three commits to revert (the last three commits), so I did:
git revert HEAD
git revert HEAD~2
git revert HEAD~4
git rebase -i HEAD~3 # pick, squash, squash
Worked like a charm :)
1 Comment
In my opinion a very easy and clean way could be:
go back to A
git checkout -f A
point master's head to the current state
git symbolic-ref HEAD refs/heads/master
save
git commit
2 Comments
git checkout master; git reset --hard A ? Or if not could you explain a bit more about what this does?Probably less elegant than other approaches here, but I've always used get reset --hard HEAD~N to undo multiple commits, where N is the number of commits you want to go back.
Or, if unsure of the exact number of commits, just running git reset --hard HEAD^ (which goes back one commit) multiple times until you reach the desired state.
Comments
I found myself needing to revert a long range of commits and then re-revert them to help a team present a clear pull request without having to force-push over their target branch (which was committed directly to)
# checkout the branch that should be targeted
git checkout $branch_target
# revert the commits in $branch_target to some $count where
# $count is the number of commits to revert
# cut is used to slice just the commit hash field from each line of output
# xargs runs the command once for each line of input, reversing the commits!
git log --oneline -n $count | cut -d' ' -f1 | xargs git revert
# check out the branch which should be the source of the pull request
git checkout -b $branch_for_pull
# revert the revert commits
# $count is that same number of commits being reverted (again)
git log --oneline -n $count | cut -d' ' -f1 | xargs git revert
# push branches up and go off to create PR in whatever web UI
git push --set-upstream origin $branch_for_pull # it's new!
git checkout $branch_target
git push # if this branch wasn't pushed, just fix the issue locally instead..
Because this reverts all of the commits from HEAD to git log -n $count in reverse order, it'll work well and cleanly with any number of commits
View from $branch_target at this state
% git log --oneline origin/$branch_target
ffff006 (origin/$branch_target, $branch_target) Revert "first commit"
ffff005 Revert "second commit"
ffff004 Revert "third commit"
ffff003 third commit
ffff002 second commit
ffff001 first commit
View from $branch_for_pull at this state
% git log --oneline origin/$branch_for_pull
ffff009 (origin/$branch_for_pull, $branch_for_pull) Revert "Revert "third commit""
ffff008 Revert "Revert "second commit""
ffff007 Revert "Revert "first commit""
ffff006 (origin/$branch_target, $branch_target) Revert "first commit"
ffff005 Revert "second commit"
ffff004 Revert "third commit"
ffff003 third commit
ffff002 second commit
ffff001 first commit
If the intention was to create N branches with changesets, but they were all committed to the same branch, you can still revert all of them back to the base commit, then only revert the reverts needed as the changesets should be ordered logically (try saying that 5x fast)
Using a syntax like HEAD~7..HEAD~5 may help with describing the ranges to precisely split the revert-revert branches
Here, it would make sense when reverting the last 7 commits (git log -n 7), but restoring 5 with in one branch (git log -n 5) and 2 then the top-most 2 in another git log HEAD~12..HEAD~10 (12 is 7 commits + 5 commits, assuming the new PR branch is based off either the branch "before" it, or the result of a FF (non-squashed) merge the branch "before" it into the original target branch)
1 Comment
I really wanted to avoid hard resets, this is what I came up with.
A -> B -> C -> D -> HEAD
To go back to A (which is 4 steps back):
git pull # Get latest changes
git reset --soft HEAD~4 # Set back 4 steps
git stash # Stash the reset
git pull # Go back to head
git stash pop # Pop the reset
git commit -m "Revert" # Commit the changes
Comments
If you
- have a merged commit and
- you are not able to revert, and
- you don't mind squashing the history you are to revert,
then you can
git reset --soft HEAD~(number of commits you'd like to revert)
git commit -m "The stuff you didn't like."
git log
# copy the hash of your last commit
git revert <hash of your last (squashed) commit>
Then when you want to push your changes remember to use the -f flag because you modified the history
git push <your fork> <your branch> -f
Comments
Other way we can clone the branch
and then run below command.
git reset --hard commitID
git push -f branchname
Comments
I managed to solve this with the simplest solution
git revert D
git revert C
git revert B
git revert A
Note: D, C, B, A were not merge commits.
Comments
If you want to temporarily revert the commits of a feature, then you can use the series of following commands.
git log --pretty=oneline | grep 'feature_name' | cut -d ' ' -f1 | xargs -n1 git revert --no-edit
git push -f HEAD~4:master(assuming the remote branch is master). Yes, you can push any commit like that.git revert.