3

When trying to revert to a previous commit (for instance 123abc) via git reset

git reset --hard 123abc 
git commit -a -m "revert to 123abc"

I cannot push this (I need to pull before and pulling moves me forward). I have come with this few lines:

for i in `git diff --name-only 123abc`; do git checkout 123abc $i; done
git commit -a -m "revert to 123abc"

Which works since now

 git diff --name-only 123abc

is empty

I was wondering if this is an hack or the git way to do it. In case it is not, how to accomplish this correctly?

jimifiki
  • 5,377
  • 2
  • 34
  • 60

3 Answers3

6

When trying to revert to a previous commit (for instance 123abc) ...

The emphasis I added here on the preposition "to", as in revert to, is crucial. The accepted answer at your linked question (How do I revert a Git repository to a previous commit?) calls this out:

This depends a lot on what you mean by "revert".

Running git revert on a single commit may not suffice. See the answers there about reverting multiple commits, if needed.

Running git reset --hard may suffice, but introduces the very problem you've encountered. The things to understand here are:

  1. A branch name simply lets Git find one particular commit. We say that the branch name points to the commit. Using git reset, we can change the one particular commit to which some branch name points. Nothing else happens in the repository yet, although depending on the kind of git reset you run, something else might happen before git reset finishes: the first step of this git reset is just to change one of your own branch names. Since you're considering (or have used) --hard, some more things will change in your repository, but they're less important at the moment.

  2. Every Git repository has its own branch names. Moving one of your branch names has no effect on any other repository.

  3. What you need to do is to get some other Git repository to change one of its branch names. That's where you run into this problem:

    I cannot push this (I need to pull before and pulling moves me forward).

    The git push command is how you ask—or, using --force, command—some other Git repository to change or create or delete some of its names (branch names, tag names, and other such names). But each Git repository is set up to easily accept new incoming commits, yet at the same time, resist the suggestion to throw away any existing commits.

When you use git reset to move one of your branch names "backwards", so as to revert to (not revert as in add-a-commit-that-backs-out) some previous commit, you're deliberately throwing away some existing commit(s). Since you control your own Git repository, you can definitely do this to your own repository. Since the git reset command is meant to do this kind of throwing-away,1 it does so without complaint. But git push isn't, and their Git complains.

As soon as you use git pull, their repository has you put back all the commits you snipped out of your own repository. Git is, after all, designed to add commits as easily as possible! That leaves you with the situation you're in.

You now have a choice:

  • Force the other Git repository to discard some commits. This requires sufficient permissions. It also means that everyone else who is using that other Git repository need to take action with any clones they have made, because their clones will enthusiastically put back all the commits you are attempting to prune. So this is kind of mean to those other people.

  • Or, use git revert or some other command to add some commit(s) to your repository that, in the end, result in putting the files back, but don't remove any old commits. The new commits simply add on to the old ones. The old ones are still there for anyone who would like to ask about them (git log) or use them (git switch --detach hash-id, for instance).

Adding new commit(s) is what Git is designed to do, so the latter is the way to go unless there's some really strong reason to ditch the old commits.

As the question you linked notes, all of this is a lot easier with unpublished commits: a commit that only you have, in your own private repository, is simply not in any other Git repository. If you shave those commits off your own branches, nobody will ever know. They won't be able to object that a git push shaves those commits off their branches, because those commits aren't on their branches. (Again, each branch name is local to each Git repository. One repository can show another one the commit hash ID stored in its branch name, but each repository takes responsibility in keeping its own hash IDs in its own branch names. The commits get shared; the branch names do not.)

Since the commits you're looking at are published you cannot use this short-cut.


1This makes git reset a very-high-powered tool, like some sort of flame-throwing chainsaw, or industrial steel-cutting laser, or something. This over-powered-ness is part of the reason that Git 2.23 now has git restore: some of the things you can do, that used to require using git reset, can now be done with the rather gentler git restore. Both commands will throw away in-progress work if you ask them, but git restore is more like a hand-saw, or bolt cutters, or something along these lines.

torek
  • 448,244
  • 59
  • 642
  • 775
  • This is a very nice and detailed explanation. I especially like the comparison of git reset to a flame-throwing chainsaw or industrial steel-cutting laser However, you did not say the command to force a git push to delete commits on origin. Is it git push --force? – Ethan Groat Aug 24 '21 at 16:43
  • @EthanGroat: yes, `git push --force` (which is the raw brute force without any double checking method), or `git push --force-with-lease` (which lets you insert double checking along the way), or the newfangled `--force-if-includes` (similar to `--force-with-lease`, introduced in case of background fetch updates with the new `git maintenance` suite). – torek Aug 25 '21 at 07:37
  • @torek What are the actions that the user users of the repo have to do after I `reset --hard` and `push --force`? – steffen May 11 '22 at 14:14
  • @steffen: they must check *their* Git repositories. You told some third-party repository (e.g., on GitHub) "discard a bunch of commits, because I don't like them." Those other users might *have* those commits. Their Git software will now think that those commits are new, made by them, and should be *added to* the GitHub repository. If that's wrong, *they* must take action in *their* repositories to make sure it does not happen. What actions they should take depends on what they want to do with those commits (do they want to keep them somewhere, or throw them away?). – torek May 11 '22 at 14:37
  • Git is built to *add* commits, not remove them. Therefore, whenever you remove some commits, you must make sure that nobody else who has them already will re-add them. Git software everywhere will gladly re-take the discarded commits, because "add commits" is a natural thing for a Git repository. "Remove commits" is the one that requires force. – torek May 11 '22 at 14:39
1

It is a hack. Use

git revert <commit hash>

but beware, you are reverting the changes applied by the <commit hash>, so to revert to previous commit, use

git revert HEAD

This will create another commit that is reverting a change caused by the last commit. Check this answer: https://stackoverflow.com/a/22683231/12118546

This above is a proper way how to do this with putting the reverting itself to the history also. If you are the only one working in the repo, you can also force push the branch with the latest commit removed: https://stackoverflow.com/a/31937298/12118546

Roman Pavelka
  • 3,736
  • 2
  • 11
  • 28
0

You should try using git revert <commit hash>. This will revert the selected commit and you can then push the changes

Joshua Zeltser
  • 488
  • 2
  • 9