To illustrate what's happening, let's say you have five commits.
A - B - C - D - E [master]
Then you reset back to C.
$ git reset --soft C
A - B - C [master]
\
D - E
The reverted commits are still there in your local repository. Resetting does not delete them, but nothing references them. If they're still unreferenced in a couple weeks they'll be garbage collected.
Then you make a new commit.
$ git commit
A - B - C - F [master]
\
D - E
Again, the old commits are still there.
Conceptually Git stores the entire changed file, not just the diff. If you make one tiny change to a large file .git
might grow by the size of the whole file as Git stores a new copy. But Git will eventually compress its database to reduce the size. If you're impatient you can run git gc
. Generally Git storage is extremely efficient.
Pushing has no effect on your local repository.
Those commits are not totally unreachable. You can still access them from the git reflog
and put a new tag or branch on them. For example, if you realized you made a mistake and want to go back you can move master
back to where it was.
$ git reset --hard E
A - B - C - F
\
D - E [master]
There's also ORIG_HEAD
. This is a special label that is set on where you moved from. Back in the original git reset --soft C
ORIG_HEAD
would still be on E.
$ git reset --soft C
A - B - C [master]
\
D - E [ORIG_HEAD]
And you an return there.
$ git reset ORIG_HEAD
A - B - C
\
D - E [master]
It works this way both for Git to be more efficient, disk is cheap and it doesn't have to optimize its storage on every change, and to allow you to change your mind.
If you want to get rid of all unreachable objects, you can run git gc --prune=all
. Don't do this unless you are really, really short on disk space. Usually running git gc
is sufficient to get git to compress and pack .git
.