182

I want to revert a particular commit in git. Unfortunately, our organization still uses CVS as a standard, so when I commit back to CVS multiple git commits are rolled into one. In this case I would love to single out the original git commit, but that is impossible.

Is there an approach similar to git add --patch that would allow me to selectively edit diffs to decide which parts of a commit to revert?

user229044
  • 232,980
  • 40
  • 330
  • 338
skiphoppy
  • 97,646
  • 72
  • 174
  • 218
  • More solutions [here](http://stackoverflow.com/q/5669358/470844), but focusing on limiting partial revert to specific files. – ntc2 Aug 26 '14 at 23:02

6 Answers6

275

Use the --no-commit (-n) option to git revert, then unstage the changes, then use git add --patch:

$ git revert -n $bad_commit    # Revert the commit, but don't commit the changes
$ git reset HEAD .             # Unstage the changes
$ git add --patch .            # Add whatever changes you want
$ git commit                   # Commit those changes

Note: The files you add using git add --patch are the files you want to revert, not the files you want to keep.

mipadi
  • 398,885
  • 90
  • 523
  • 479
  • 15
    May be worth adding the final required command, for those not so familiar with git: after committing, `git reset --hard` to discard the other changes which you didn't want to revert. – tremby Sep 26 '12 at 21:03
  • 20
    `git reset --hard` is dangerous for newbies, as it might loose wanted edits. Instead get used to `git status`, this hints for `git checkout -- FILE..` to revert things more safely. – Tino Jul 16 '13 at 10:30
  • 3
    What a gaping omission in `git`; `git revert` should just take a `--patch` argument. – Kaz Mar 31 '17 at 14:33
  • @Kaz: `git revert` is used for reverting whole commits. You can use `git checkout -p` to interactively select bits to revert. – mipadi Mar 31 '17 at 18:52
  • @mipadi That only works because `checkout` has `-p/--patch`. Someone had to recognize that as a gaping omission and code it. Also, why is `git revert` only for whole commits, whereas `git reset` has about six functionalities overloaded into it? ;) – Kaz Mar 31 '17 at 20:56
  • @Kaz: poor taste? :-) Seriously, I have never been pleased at the way Git front end commands are lumped together based on underlying mechanism, rather than based on goal. Mercurial is so much better about separating goals and mechanisms... – torek Mar 31 '17 at 21:36
  • 1
    I would also like to add the (perhaps) obvious, which is **first save your work**. Either `commit` first, or `stash`, then try `revert`. – Felipe Alvarez Aug 09 '17 at 02:44
52

I have used the following successfully.

First revert the full commit (puts it in index) but don't commit.

git revert -n <sha1>  # -n is short for --no-commit

Then interactively remove the reverted GOOD changes from the index

git reset -p          # -p is short for --patch  

Then commit reverse diff of the bad changes

git commit -m "Partially revert <sha1>..."

Finally the reverted GOOD changes (which have been unstaged by the reset command) are still in the working tree. They need to be cleaned up. If no other uncommitted changes are left in the working tree, this can be done by

git reset --hard
chtenb
  • 14,924
  • 14
  • 78
  • 116
user1338062
  • 11,939
  • 3
  • 73
  • 67
  • 5
    Is this not a superior alternative to the accepted answer (which uses `reset HEAD .`), because it does not require the final cleanup of working dir? – Steven Lu May 21 '15 at 15:04
  • 2
    This answer is superior, because `reset -p` is shorter than `reset HEAD` followed by `add -p`. But it still requires the clean up, since the "good" hunks that have been reset are still in the working directory after the commit. – chtenb Mar 09 '16 at 14:24
  • This answer is not superior because interactively removing the changes you **want** is often confusing and error-prone --- especially if any of them require an edit. – Kaz Mar 31 '17 at 14:32
8

Solution:

git revert --no-commit <commit hash>
git reset -p        # every time choose 'y' if you want keep the change, otherwise choose 'n'
git commit -m "Revert ..."
git checkout -- .   # Don't forget to use it.
  • 1
    It would help people if you said how it is different than the accepted solution – CharlesB Aug 11 '14 at 13:01
  • 3
    @Krzysztof Why is the checkout at the end important and why is this solution different from that of user1338062? – martin Aug 11 '14 at 13:08
6

Personally, I prefer this version, which reuses the auto-generated commit message and gives the user the opportunity to edit and stick the word "Partially" in before finally committing.

# generate a revert commit
# note the hash printed to console on success
git revert --no-edit <hash to revert>

# undo that commit, but not its changes to the working tree
# (reset index to commit-before-last; that is, one graph entry up from HEAD)
git reset HEAD~1

# interactively add reversions
git add -p

# commit with pre-filled message
git commit -c <hash from revert commit, printed to console after first command>

# reset the rest of the current directory's working tree to match git
# this will reapply the excluded parts of the reversion to the working tree
# you may need to change the paths to be checked out
# be careful not to accidentally overwrite unsaved work
git checkout -- .
jeffcook2150
  • 4,028
  • 4
  • 39
  • 51
6

Another alternative (if your current version of a file isn't too far from the version you're trying to revert) is to checkout interactively. Look at git log and get the hash of the commit you wish to undo, or the one immediately before it. Your command becomes either:

git checkout --patch HASH_OF_COMMIT_TO_REVERT^ -- file/you/want/to/fix.ext

(where ^ means parent commit of) or:

git checkout --patch HASH_PRECEDING_COMMIT_TO_REVERT -- file/you/want/to/fix.ext

You can then choose hunks to revert, or parts thereof by choosing the s (split) option when patching.

This does change the files in your working tree but creates no commits, so if you really stuff up you can just start again with git reset --hard -- file/you/want/to/fix.ext.

Walf
  • 8,535
  • 2
  • 44
  • 59
1

You can use git-revert -n, and then use add --patch to select hunks.

William Pursell
  • 204,365
  • 48
  • 270
  • 300