What is the proper way to [go] back a few commits and basically delete old ones.
Generally speaking, the proper way is not to delete old commits. But that's Tim Biegeleisen's answer, which someone apparently does not like. So let's look at the alternative.
You literally can't delete old commits, at least not directly. What you can do is stop using them. Once they become disused, they eventually dry up, fall off, and disintegrate away as if they'd never existed. (There are some manual ways to speed this up, but it's usually best to leave this alone.)
The git reset
command that you used is a way to stop using some existing commits. This has the effect of moving your own branch name backwards. For (much) more about this, see my long answer to Git - how to remove local history entry? But it only moves your branch name: the commits that you have thus stripped from your (local) branch, but which still exist, are still there in some other Git repository. That Git repository is now "ahead" of your branch by as many commits as you have stripped out of your local branch.
Having, in effect, removed the 25 commits from your (local) branch—those 25 commits still stick around for multiple reasons, including the fact that your Git knows that their (the other) Git has them, plus the usual at-least-30-days thing I noted in that long answer—you must now convince their Git repository, over at origin
, to strip their copies of those 25 commits from their branch. Remember that Git is fundamentally a distributed version control system, in which many copies of various commits exist in many copies of some now-widely-scattered repository. In your case, at least two clones exist: yours, and one on some remote server (e.g., on GitHub).
The question you've asked, about using git push --force
,1 has to do with commanding another Git repository to do this: to "forget" (or strip out) some commits from some branch.
When you use an ordinary git push
, you tell your Git to call up some other Git, at some URL. Rather than typing out the full URL every time,2 your Git stores the URL of that other Git, under a short name. By convention, the first—and often only—short name in a Git repository, for the other Git repository you have it talk to, is origin
. So you use git push origin
to tell your Git that the other Git you want it to call up is the usual one.
After the word origin
, you would generally list the (local) branch names of one or more of your own branches. What your Git actually needs here is two items: it needs the hash ID of some commit(s) to push, and the names you want to ask the other Git to update. That other Git, being a Git repository, has its own branches. You're going to have your Git send to their Git, any new commits you made on your branch(es) that they don't already have, and then ask them to set, as their branch names, the last commit of each of those commit sequences. As I explained in the long answer, a branch name, in any Git repository, simply holds the hash ID of the last commit in the branch.
By running, e.g.:
git push origin develop
you therefore have your Git use your branch name develop
to find your last commit. Your Git then calls up their Git, checks to see if they have the necessary commits, and if not, sends over those commits. Then your Git asks, politely, if their Git would please set their develop
to that hash ID.
When you have used git reset
to strip out commits, their develop
is likely to have commits that you deliberately stripped out. This polite request will then fail. That's because Git hates to give up commits. Git loves to add commits! It is happy to add more, as long as it keeps all the old ones too. But you have decided that both your Git and their Git should drop commits—or at least, hide them away, and stop using them, until they really do fall off after some expiration period.
They will not do this unless you force them. So you can use --force
, which changes the final command your Git sends: instead of please, if it's OK, update ... your Git says update ...! do it now! They can still refuse, but if you have the right permissions, they will hop to it and do it now.
That's what the --force
flag is all about: turning the polite request into a command. Note that if they are going to obey this command, you had better know exactly what you are doing.
Now, as to the use of HEAD
in git push origin HEAD
(with or without --force
): as I noted in the long answer, HEAD
is a special name in Git. Normally, you will keep your HEAD
attached to your current branch. Whatever attachment it has is literally defined as the current branch, and you use git checkout
or git switch
to change this attachment. All that git push origin HEAD
means, then, is that your Git should use HEAD
to get the name of the current branch, and act as if you'd run git push origin that-name
. This works regardless of whether you've also included --force
.
1While git push
allows you to put the --force
flag towards the end, there's a sort of canonical ordering for commands where we write the command first, then any flags, then any non-flag arguments. Some commands allow both the canonical order and non-canonical orders, and other commands interpret things as flags up to some point, then have a literal or implied --
meaning nothing after this is a flag even if it resembles one, then have any remaining arguments. This allows you to, for instance, remove a file named -f
even though -f
looks like the short spelling of the --force
flag.
In general, it's usually a good idea to spell your commands out in canonical order, just in case it happens to be a command that requires that order. Most Git commands don't fall into this category, so you can be sloppy with Git, but it's still a bad habit.
2You can type out the full URL every time, if you want. If you do this, you give up a bunch of modern convenience features. Don't do that.