How can I revert multiple Git commits (already pushed) to a published repository?

New to git, and already messing up.

I've commited and pushed some changes to a remote dev machine. I need to recover an older version, but keep the "bad progress" doing so far to keep working on a separate branch;

I was thinking doing it like this:

  1. Create a local branch named: "tested-thing"
  2. Revert local repository to the state where it worked (hopefully meaningful commits will help);
  3. Push to remote

  4. finish tests on tested-thing

  5. Merge "tested-thing" into dev
  6. Push to remote

Between steps 3 and 5 other developers may commit and push, and I'm afraid this may result on a "merge tragedy" - Anyway, may this be a proper way to go ?

UPDATE:

The main problem here resides on 2)

Here, on topic: "breaking work into a topic branch" http://learn.github.com/p/undoing.html

They suggest:

  1. $ git branch test
  2. $ git reset --hard a6b4c974

By doing so, other developers could still:

$ git commit (on the dev branch)

and I can checkout to test and work it out until merge time.

Despite all your options, this feels like to be a nice approach to follow. However, it's not stated if this can be done after we have pushed ?

Please note the following: Since I made those changes and I mess up the all thing, no one else have worked on the repository so far. So, if I revert the working directory, no one will notice.


Solution 1:

The Problem

There are a number of work-flows you can use. The main point is not to break history in a published branch unless you've communicated with everyone who might consume the branch and are willing to do surgery on everyone's clones. It's best not to do that if you can avoid it.

Solutions for Published Branches

Your outlined steps have merit. If you need the dev branch to be stable right away, do it that way. You have a number of tools for Debugging with Git that will help you find the right branch point, and then you can revert all the commits between your last stable commit and HEAD.

Either revert commits one at a time, in reverse order, or use the <first_bad_commit>..<last_bad_commit> range. Hashes are the simplest way to specify the commit range, but there are other notations. For example, if you've pushed 5 bad commits, you could revert them with:

# Revert a series using ancestor notation.
git revert --no-edit dev~5..dev

# Revert a series using commit hashes.
git revert --no-edit ffffffff..12345678

This will apply reversed patches to your working directory in sequence, working backwards towards your known-good commit. With the --no-edit flag, the changes to your working directory will be automatically committed after each reversed patch is applied.

See man 1 git-revert for more options, and man 7 gitrevisions for different ways to specify the commits to be reverted.

Alternatively, you can branch off your HEAD, fix things the way they need to be, and re-merge. Your build will be broken in the meantime, but this may make sense in some situations.

The Danger Zone

Of course, if you're absolutely sure that no one has pulled from the repository since your bad pushes, and if the remote is a bare repository, then you can do a non-fast-forward commit.

git reset --hard <last_good_commit>
git push --force

This will leave the reflog intact on your system and the upstream host, but your bad commits will disappear from the directly-accessible history and won't propagate on pulls. Your old changes will hang around until the repositories are pruned, but only Git ninjas will be able to see or recover the commits you made by mistake.

Solution 2:

If you've already pushed things to a remote server (and you have other developers working off the same remote branch) the important thing to bear in mind is that you don't want to rewrite history

Don't use git reset --hard

You need to revert changes, otherwise any checkout that has the removed commits in its history will add them back to the remote repository the next time they push; and any other checkout will pull them in on the next pull thereafter.

If you have not pushed changes to a remote, you can use

git reset --hard <hash>

If you have pushed changes, but are sure nobody has pulled them you can use

git reset --hard
git push -f

If you have pushed changes, and someone has pulled them into their checkout you can still do it but the other team-member/checkout would need to collaborate:

(you) git reset --hard <hash>
(you) git push -f

(them) git fetch
(them) git reset --hard origin/branch

But generally speaking that's turning into a mess. So, reverting:

The commits to remove are the lastest

This is possibly the most common case, you've done something - you've pushed them out and then realized they shouldn't exist.

First you need to identify the commit to which you want to go back to, you can do that with:

git log

just look for the commit before your changes, and note the commit hash. you can limit the log to the most resent commits using the -n flag: git log -n 5

Then reset your branch to the state you want your other developers to see:

git revert  <hash of first borked commit>..HEAD

The final step is to create your own local branch reapplying your reverted changes:

git branch my-new-branch
git checkout my-new-branch
git revert <hash of each revert commit> .

Continue working in my-new-branch until you're done, then merge it in to your main development branch.

The commits to remove are intermingled with other commits

If the commits you want to revert are not all together, it's probably easiest to revert them individually. Again using git log find the commits you want to remove and then:

git revert <hash>
git revert <another hash>
..

Then, again, create your branch for continuing your work:

git branch my-new-branch
git checkout my-new-branch
git revert <hash of each revert commit> .

Then again, hack away and merge in when you're done.

You should end up with a commit history which looks like this on my-new-branch

2012-05-28 10:11 AD7six             o [my-new-branch] Revert "Revert "another mistake""
2012-05-28 10:11 AD7six             o Revert "Revert "committing a mistake""
2012-05-28 10:09 AD7six             o [master] Revert "committing a mistake"
2012-05-28 10:09 AD7six             o Revert "another mistake"
2012-05-28 10:08 AD7six             o another mistake
2012-05-28 10:08 AD7six             o committing a mistake
2012-05-28 10:05 Bob                I XYZ nearly works

Better way®

Especially that now that you're aware of the dangers of several developers working in the same branch, consider using feature branches always for your work. All that means is working in a branch until something is finished, and only then merge it to your main branch. Also consider using tools such as git-flow to automate branch creation in a consistent way.