115

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.

1

3 Answers 3

194

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.

2
  • If people already cloned bad pushes, and were notified - what should they do to have the same branch as in the remote? Do they have to do the same git reset --hard <last_good_commit>?
    – andrybak
    Commented Oct 21, 2015 at 17:43
  • 6
    If you are using commit tags, it's not <first_bad_commit>..<last_bad_commit>, but rather <last_good_commit>..<last_bad_commit>
    – Yerke
    Commented Feb 10, 2019 at 9:47
44

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 latest

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 broken 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.

3
  • +1 for git reset without the --hard flag, but I don't think most folks will understand that you're resetting both the working directory and HEAD, which will lead to ! [rejected] master -> master (non-fast-forward) on push. Also, if a lot of files have been changed, it makes for a really sweeping patch even if you force the commit. Commented May 28, 2012 at 8:01
  • @AD7Six: Thanks for that Better Way registered trademark add-on. Actually, branches are so cheap, that I though on that right after doing this stupid move. By knowing that no one else as touch the repository besides me, since I've done all this mess up, may help using the approach on my question update ?
    – MEM
    Commented May 28, 2012 at 8:12
  • @MEM it doesn't matter if nobody else has updated the repo - it matters if someone has updated their checkout since the "mistakes" were committed. Because if they have, when they pull, their checkout will still contain the commits you've forcefully removed, merged with any new changes. If you use git reset --hard in your feature branch, when you merge it you'll most likely get conficts as the commits you removed get reapplied - basically, no it's probably not a good idea to do that.
    – AD7six
    Commented May 28, 2012 at 8:27
0

For me the merging interim branch, got in to so many complexities like merge from other branches and revert commit(s). I ended up creating new branch and just merging branch(which has right changes) OR putting changes manually, where it was required.

Not the answer you're looking for? Browse other questions tagged or ask your own question.