I did by mistake more than 100 amend commits. How can i convert them to usual commits? Or at least to get git log with difference for each amend commit?
I can see only difference for all amend commits at once if i run gitk
or git log -p
now. i can see all amend commits hashes but only with reflog. I can also see difference with git diff amend_hash1 amend_hash2
but not in gitk
or git log -p
. They jump over these amends although they are linked correctly in .git/logs/refs/heads/master
and .git/logs/HEAD
i just ran git commit --amend
100 times, one time for each of 100 changes. Then I got one big commit for all 100 changes when i ran git commit without amend.
I found how to undo only one amend commit...
I think this is more explainable with pictures, although my ASCII-art ability runs out after just a few
--amends
. The trick is to realize that whatgit commit --amend
does, vsgit commit
without--amend
, is to change the parent hash stored in the new commit.A normal
git commit
freezes the index contents into a tree and makes the new commit using the new tree, you as the author and committer, your log message, and the current commit as the parent of the new commit:Then we make new commit
I
, after straightening out the drawing:and new commit J:
and so on.
Using
git commit --amend
, however, we make the new commitH
as usual except that the parent ofH
isF
instead ofG
:Then, making
I
, we make it as usual except that the parent ofI
isF
instead ofH
:and so on.
If you imagine running
git log
right at this point—with commitI
pointing back to commitF
—you will see commitI
, then commitF
, thenE
, and so on. CommitsG
andH
will be invisible togitk
orgit log
(but will show up ingit reflog
output, since that does not follow parent chains).After 100 such operations, I've run out of commit letters and cannot possibly draw the crazy fan of commits all pointing back to
F
, but you can imagine them; or I can draw just 9 such commits,G
throughO
:Each of these various commits has the tree and the message that you want. What is wrong with each of these commits, except for
G
itself, is that it has the wrong parent: you'd likeG
to haveF
as its parent (which it does), but then you would likeH
to haveG
as its parent (which it does not).This means you must copy the wrong commits. Let's start by copying
H
toH'
that hasG
as its parent, but otherwise uses the same tree and message and other metadata asH
:Now we need to copy
I
to a new commitI'
. The parent ofI'
is notG
but ratherH'
:We repeat for
J
toJ'
, usingI'
as the parent forJ'
, and so on until we have copied every "wrong" commit to a "right" one. Then we can set a branch name to point to the last such copied commit:Running
git log
while onrepaired
, orgitk --all
, will now show commitN'
leading back toM'
leading back toL'
and so on. Remember thatgit log
(andgitk
) follow the parent linkages backwards, without looking at the reflog at all.If you're willing to let some of the metadata (author and committer name, email, and timestamp) be clobbered, it's easy to make each of these commits with a shell script loop using
git commit-tree
. if you want to preserve that metadata, it's harder: you need to set a series of Git environment variables before callinggit commit-tree
each time, setting:GIT_AUTHOR_NAME
,GIT_AUTHOR_EMAIL
,GIT_AUTHOR_DATE
: for the authorGIT_COMMITTER_NAME
,GIT_COMMITTER_EMAIL
,GIT_COMMITTER_DATE
: similar but for the committerThe log message can be copied directly from the incorrect commits, using
sed
or similar to chop off everything up to and including the first blank line (note that this discards any i18n encoding data, andsed
may behave badly with unterminated final text lines in commit messages, but these may be tolerable; if not, extract the relevant code fromgit filter-branch
).Use
git reflog
to obtain the hash IDs of all commits to be copied, in the correct order (oldest-to-copy first, last-to-copy = newest last). Place these in a file, one entry per line. Then the following untested shell script will probably suffice:This creates a new branch name
repaired
to hold the newly built chain.