Search code examples
gitgit-historygit-history-rewrite

git replace --graft not truncating commit log


If I run git replace --graft SHA on a simple test repo, it seems to correctly truncate the repo (with respect to the provided SHA) when viewed via git log.

However on a more complicated repo when I try the same thing, it instead seems to 'replace' the original commit with another - and it keeps all the previous commits surprisingly. The only difference seems to be that the commit has a gpg signature:

git replace --graft af3b4b8a5f98db343b7fc05789aa9656e786d080
warning: the original commit 'af3b4b8a5f98db343b7fc05789aa9656e786d080' has a gpg signature
warning: the signature will be removed in the replacement commit!

Why is this happening? And how can I correctly make it truncate the commit log?

Surprisingly it correctly seems to show that the commit has no parent (if I try reference af3b4b8a5f98db343b7fc05789aa9656e786d080~1) yet git log still shows commits before it.

git log af3b4b8a5f98db343b7fc05789aa9656e786d080~1
fatal: ambiguous argument 'af3b4b8a5f98db343b7fc05789aa9656e786d080~1': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

The repo in question is https://github.com/eugenp/tutorials and the commit I'm grafting is af3b4b8a5f98db343b7fc05789aa9656e786d080.

So I'm basically doing:

git clone https://github.com/eugenp/tutorials
cd tutorials
git replace --graft af3b4b8a5f98db343b7fc05789aa9656e786d080
git log | tail -n 10 # this shows commits before af3b4b8a5f98db343b7fc05789aa9656e786d080 hence it's not truncating

Solution

  • git log af3b4b8a5f98db343b7fc05789aa9656e786d080 shows only a single commit. Your repository has multiple root commits after replacing with the graft commit; this happens if you do not graft/truncate all sides of a merge.

    The following graphs should help explaining this behavior.

    Original history:

    R--A--B--C---M--N--...   (HEAD -> master)
     \          /
      `--D--E--F
    

    D has one parent R, which has 2 children (A and D)

    After replacing E with E' (the grafted commit):

    R--A--B--C---M--N--...   (HEAD -> master)
                /
            E'-F
    

    As you can see, the "truncated" history now has two root commits (the new E' commit and the original R root commit). The old root commit is still reachable via the parent of the merge commit M.

    Running git log (which is identical to running git log HEAD) will show commits R, A..C, M, and E' (the grafted commit) and F.

    R is still reachable through one of its children that haven't been replaced with a graft.

    Everything is working as expected. If you want to fully truncate your history, you have to replace a non-merged commit or graft both sides of the merge (or all sides of the merge in case of octopus merges).