I want to remove one or more commits from a repository permanently, leaving the repository in a state as if the undesirable commits were never there. There is lots of references that point to git reset
for this job. In testing, it seems that git reset
leaves the old commits still available, even after garbage collection.
Here's an example that shows this (using git 2.25.1 tested with a posix shell script).
% cat dotest
tmpdir=$(mktemp -d $(pwd)/tmpdir.XXXXX)
cd $tmpdir
echo 'set up a repo with two commits...'
(set -x; git init)
echo 0 > x
(set -x
git add x
git commit -m 0 x)
sha0=$(git rev-parse HEAD)
echo 1 > x
(set -x; git commit -m 1 x)
sha1=$(git rev-parse HEAD)
(set -x; git log --graph --all --decorate --oneline)
echo; echo ===========================
echo 'remove second commit and switch working directory to first commit...'
(set -x
git reset --hard $sha0
git log --graph --all --decorate --oneline)
echo; echo ===========================
echo 'try to checkout the commit that was removed...'
(set -x
git checkout $sha1
git log --graph --all --decorate --oneline)
echo; echo ===========================
echo 'try to checkout the commit that was removed after forcing a garbage collect...'
(set -x
git reset --hard $sha0
git gc --prune=now
git checkout $sha1
git log --graph --all --decorate --oneline)
And the results:
% sh dotest
set up a repo with two commits...
+ git init
Initialized empty Git repository in /tmp/tmpdir.e4vrh/.git/
+ git add x
+ git commit -m 0 x
[master (root-commit) 1aba911] 0
1 file changed, 1 insertion(+)
create mode 100644 x
+ git commit -m 1 x
[master 6b9e4c1] 1
1 file changed, 1 insertion(+), 1 deletion(-)
+ git log --graph --all --decorate --oneline
* 6b9e4c1 (HEAD -> master) 1
* 1aba911 0
===========================
remove second commit and switch working directory to first commit...
+ git reset --hard 1aba911898c5d9b18fbc314c9df59f485318ea23
HEAD is now at 1aba911 0
+ git log --graph --all --decorate --oneline
* 1aba911 (HEAD -> master) 0
===========================
try to checkout the commit that was removed...
+ git checkout 6b9e4c115a92b9baeec8c41e63402c239ae46cc0
Note: switching to '6b9e4c115a92b9baeec8c41e63402c239ae46cc0'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 6b9e4c1 1
+ git log --graph --all --decorate --oneline
* 6b9e4c1 (HEAD) 1
* 1aba911 (master) 0
===========================
try to checkout the commit that was removed after forcing a garbage collect...
+ git reset --hard 1aba911898c5d9b18fbc314c9df59f485318ea23
HEAD is now at 1aba911 0
+ git gc '--prune=now'
+ git checkout 6b9e4c115a92b9baeec8c41e63402c239ae46cc0
Previous HEAD position was 1aba911 0
HEAD is now at 6b9e4c1 1
+ git log --graph --all --decorate --oneline
* 6b9e4c1 (HEAD) 1
* 1aba911 (master) 0
This shows that the commit that I tried to delete using git reset
is still available even after the garbage collection was run. And the "garbage" is preserved in a new clone made from this repo.
Note: This is similar to the Permanently remove git commit history question, but more general so I didn't hijack that question. But the basic issue is still the same. The 'Create a new orphan branch' answer to that question has the same problem as well - you can still get at the old "deleted" commits, even after GC.