You can use the low-level commit-tree
command and the reflog to construct a new commit with your current head commit's tree, but the original (pre-amend) commit as parent. The branch is forwarded by resetting both working tree and index:
git reset --soft "$(git commit-tree HEAD^{tree} -p HEAD@{1} -m 'Commit message of new commit')"
Why does this work? Let's have a look at some ASCII drawings:
C t:W (HEAD@{1}) original commit with tree W
B t:V
A t:U
This is where we start, but after amending the commit, we are left with:
C' t:X (branch; HEAD) accidentally amended commit with tree X
| C t:W (HEAD@{1}) original commit with tree W
|/
B t:V
A t:U
Commit C is no longer reachable from the branch, but it still exists in Git's database and is listed in the reflog as HEAD@{1}
.
What you want is to create the following new commit with the tree of the amended commit:
D t:X (branch; HEAD) separate commit with tree X
C t:W original commit with tree W
B t:V
A t:U
and git commit-tree HEAD^{tree} -p HEAD@{1}
does exactly that: it creates commit D with tree X (same tree as of amended commit C'), but sets the parent to C (the commit before being amended). git reset
is required to move the branch from the erroneous, amended commit to the correct, separate commit.
git log --reflog -p -- {{name-of-the-dir-or-file-in-question}}
. It shows both the actual changes and the commit messages for each action.