160

In my git repo which is tracking a svn repo I have made a number of edits to a single file.

Now I want to revert those changes(like svn revert), but only portions of the file.

I want to be able to view the diffs on the file, discard(revert) the changes that I don't want and retain the changes I want.

the

git add -i 

command seems to have an option to do that but I don't want to stage this yet.

p.campbell
  • 98,673
  • 67
  • 256
  • 322
Pradeep
  • 4,099
  • 4
  • 31
  • 45

6 Answers6

321

I believe you can do it most simply with:

git checkout -p <optional filename(s)>

From the manpage:

   −p, −−patch
       Interactively select hunks in the difference between the <tree−ish>
       (or the index, if unspecified) and the working tree. The chosen
       hunks are then applied in reverse to the working tree (and if a
       <tree−ish> was specified, the index).
       This means that you can use git checkout −p to selectively discard
       edits from your current working tree.
Daniel Stutzbach
  • 74,198
  • 17
  • 88
  • 77
  • 1
    Also, if you do not wish to do this selectively, you can use "git checkout -- ..." to discard changes. – db42 Sep 07 '11 at 10:50
  • Does it deal with directories? – bbum Dec 09 '12 at 00:43
  • 1
    This seems to fail a lot for me when discarding changes ("error: patch failed ", "error: patch does not apply"). Strangely enough I'll receive those errors when using the `a` option in patch mode to discard an entire file, yet `git checkout -- ` works as expected. Anyone know why? – chrnola Nov 10 '14 at 19:48
  • I had that error a few times if the file had been patched already, and so the interactive patch was out of date with the current file's applied patches. It happened when using a gui tool like source tree if I discarded a hunk twice accidentally. The second time would produce that error. – phyatt Jul 05 '16 at 20:41
92

You can do that directly with git checkout -p. See Daniel Stutzbach's answer below.


Old answer (before checkout -p was introduced):

You can do it like this:

git add -i

(select the hunks you want to keep)

git commit -m "tmp"

Now you have a commit with only the changes you want to keep, and the rest is unstaged.

git reset --hard HEAD

At this point, uncommitted changes have been discarded, so you have a clean working directory, with the changes you want to keep committed on top.

git reset --mixed HEAD^

This removes the last commit ('tmp'), but keeps the modifications in your working directory, unstaged.

EDIT: replaced --soft with --mixed, to clean up the staging area.

Community
  • 1
  • 1
Paolo Capriotti
  • 4,052
  • 21
  • 25
  • this will commit the changes that I want to keep isn't it? I don't want to commit those changes yet. mm maybe I am taking commits too seriously. Maybe I should relax now since it is all in a local repo. btw what does git reset --soft HEAD^ do? – Pradeep Jul 10 '09 at 12:42
  • "git reset --soft HEAD^" undoes a commit in a sense that it keeps working directory and index like it was, and moves current branch one commit back. – Jakub Narębski Jul 10 '09 at 13:18
  • Thanks Jakub. Paolo why would a soft reset to head - 1 be required here? the partial commit and the hard reset should be enough isn't it to keep some changes and discard others? – Pradeep Jul 10 '09 at 13:27
  • 1
    Daniel's answer below is a lot simple and looks like the right way to do it. – Umang Jul 25 '12 at 22:19
3

You could run git diff on the file, save the resulting diff, edit it to remove the changes you do want to save, then run it through patch -R to undo the remaining diffs.

git diff file.txt >patch.tmp
# edit patch.tmp to remove the hunks you want to keep
patch -R <patch.tmp
Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • I had 2 hunks in my patch file and removed one. Not sure whats the reason but the patch -R keeps getting rejected with this. – Pradeep Jul 10 '09 at 12:40
  • 2
    You mention in another comment that `git diff` shows the whole file as changed. This usually happens when you've saved the file using different line endings from what it was previously. This would also cause `patch` to reject the patch file. Does that sound like what might have happened? – Greg Hewgill Jul 10 '09 at 12:50
  • you are right. the line endings are getting toggled whenever I use the patch command. I am on windows and using cream/vim. Need to sort this out first I think. – Pradeep Jul 10 '09 at 12:58
  • I was unable to resolve the issue with patch on windows. Though I am sure this works I prefer to use Paolo's recipe. For the love of those interactive commands to work with the diffs using git add -i. – Pradeep Jul 13 '09 at 06:59
3

Looks like you want

 git revert --no-commit $REVSISON 

You can then use

 git diff --cached

to see what change will be made before commiting ( as reverting is just a commit in a forwards direction that replicates the inverse of a change in the past )

If you were with a pure Git repository, you could possibly, depending on your goals, utilise interactive rebase (git rebase -i) to go back to the commit you didn't like and edit the commit retroactively so that the changes you don't like never happened, but thats generally only for if you KNOW you'll never want to see it again.

Kent Fredric
  • 56,416
  • 14
  • 107
  • 150
  • I did revert as you mentioned to a previous revision(is that correct?) and now git diff just shows the whole file as changed. It is supposed to show the edits that I had done? – Pradeep Jul 10 '09 at 12:47
1

Re-reading the question, it sounds like you want to revert changes that are in your working tree and not changes that have been previously committed but some of the other answers make it sound like my reading may be wrong. Can you clarify?

If the changes are just in your working copy then the easiest way to do this is to stage the changes you want to keep with:

git add -i <file>

Then throw away the changes that you don't want to keep by checking out the index version:

git checkout -- <file>

Then unstage the changes if you don't want them staged yet:

git reset -- <file>

This recipe only reverts selected changes to the file (or files that you specify) and doesn't create any temporary commit that then needs reverting.

If you want to selectively apply only some of the changes made in previous commits then you can reset a file to a previous committed state first:

git reset <commit_before_first_unwanted_change> -- <file>

Then you can follow the previous recipe of git add -i <file> to stage those changes that you want to keep, git checkout -- <file> to throw away the unwanted changes and git reset -- <file> to 'unstage' the changes.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
0

The command line options described in the answers here are handy when the file is on a server which I am accessing via a ssh terminal. However, when the file is on my local machine I prefer the following way:

Open the file in the netbeans editor (which comes with git support). Netbeans puts red/green/blue marks at line numbers to indicate where stuff was deleted/added/modified (respectively).

Right clicking any of these marks gives you an option to undo that change. In addition, you can right click on red and blue marks to find see the old version in a popup.

Abhishek Anand
  • 3,789
  • 2
  • 21
  • 29