Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Undo change in git (not rewriting history)

Tags:

git

git-revert

I made a change in a script and committed it. Then I made a few other changes, and pushed them to a remote repository and such.

Then I realised that first change I mentioned was stupid, and want to undo it.. Can I "unapply" that commit, without manually copy/pasting the diff?

As an example: I have two files, a.py and b.py:

Commit 1: I delete a function in a.py  Commit 2: I change a few lines in b.py  Commit 3: I change the docstring in a.py 

Can I undo that function deletion, and make it appear as "commit 4" (rather than deleting commit 1)

like image 934
dbr Avatar asked Mar 13 '09 11:03

dbr


People also ask

Does git revert rewrite history?

The git revert command is a forward-moving undo operation that offers a safe method of undoing changes. Instead of deleting or orphaning commits in the commit history, a revert will create a new commit that inverses the changes specified. Git revert is a safer alternative to git reset in regards to losing work.


2 Answers

Yes, you can use git revert for this. See the git manual section on this for more information.

The gist is that you can say:

git revert 4f4k2a 

Where 4f4k2a is the id of the commit you'd like to undo, and it will try to undo it.

like image 50
Jesse Rusak Avatar answered Oct 03 '22 23:10

Jesse Rusak


Just a comment:

git revert aCommit 

does revert the all commit (as in "all the files part of the commit" ):
it computes a reverse patch, applies it on HEAD and commit.

So two problems here (the first one is easily solved):

  • it does always commit, so you may want to add -no-commit option: "git revert --no-commit aCommit": this is useful when reverting more than one commits' effect to your index in a row.
  • it does not apply for a specific file (what if you a.py was part of a commit with 1000 other changes that you may not want to revert) ?
    For that, if you want to extract specific files as they were in another commit, you should see git-checkout, specifically the git checkout <commit> <filename> syntax (that is not exactly what you need in this case though)

Easy Git (Elijah Newren) tried to bring a more "complete revert" to the Git Mailing list; but without much success:

People occasionally want to "revert changes".

Now, this may be:

  • the changes between 32 and 29 revisions ago,
  • it might be all changes since the last commit,
  • it could be the changes since 3 commits ago, or
  • it could be just one specific commit.
  • The user may want to subset such reversions to just specific files,

(eg revert is documented here, but I am not sure it is part of the current distribution of eg though)

but it all boils down to "reverting changes" in the end.

eg revert --since HEAD~3  # Undo all changes since HEAD~3 eg revert --in HEAD~8     # much like git revert HEAD~8, but nocommit by default eg revert --since HEAD foo.py  # Undo changes to foo.py since last commit eg revert foo.py               # Same as above eg revert --in trial~7 bar.c baz.  # Undo changes made in trial~7 to bar.[ch] 

Are these kinds of "reverting data" really so different that there should need to be different commands, or that some of these operations shouldn't be supported by the simple revert command?
Sure, most users most of the time will probably use the "eg revert FILE1 FILE2..." form, but I didn't see the harm in supporting the extra capabilities.

Also...is there anything fundamental that would keep core git from adopting such behavior?

Elijah

Note: commits by default don't make sense for the generalized revert command, and "git revert REVISION" would error out with instructions (telling the user to add the --in flag).


Lets say you have, out of 50 committed, 20 files you realize that old commit X introduced changes that should not have taken place.
A little plumbing is in order.
What you need is a way to list all the specific files you need to revert
(as in "to cancel changes made in commit X while keeping all subsequent changes"),
and then, for each of them:

git-merge-file -p a.py X X^ 

The issue here is to recover the lost function without obliterating all subsequent changes in a.py you might want to keep.
That technique is sometime called "negative merging".

Since git merge-file <current-file> <base-file> <other-file> means:
incorporates all changes that lead from the <base-file> to <other-file> into <current-file>, you can restore the deleted function by saying you want to incorporate all changes.)

  • from: X (where the function has been deleted)
  • to: X^ (the previous commit before X, where the function was still there)

Note: the '-p' argument which allows you to review first the changes without doing anything on the current file. When you are sure, remove that option.

Note: the git merge-file is not that simple: you can not reference previous versions of the file just like that.
(you would have over and over the frustrating message: error: Could not stat X)
You have to:

git cat-file blob a.py > tmp/ori # current file before any modification git cat-file blob HEAD~2:a.py > tmp/X # file with the function deleted git cat-file blob HEAD~3:a.py > tmp/F # file with the function which was still there  git merge-file a.py tmp/X tmp/F # basically a RCS-style merge                                  # note the inversed commit order: X as based, then F                                  # that is why is is a "negative merge" diff -u a.py tmp/ori # eyeball the merge result git add a.py  git commit -m "function restored" # and any other changes made from X are preserved! 

If this is to be done for a large number of files within a previous commit... some scripting is in order ;)

like image 40
VonC Avatar answered Oct 04 '22 01:10

VonC