Consider the following sequence of commands:
git replace --graft <sha> <new_parent_sha>
new_commit_sha="$(git show-ref refs/replace/<sha> | awk '{print $1}')"
git update-ref -d refs/replace/<sha>
The end result is that we have a commit (new_commit_sha) with new parents, that is identical to the original commit (same tree, same message, same author and committer, author date, commit date) except that the parents are different.
Is there a more direct way to achieve the same effect of duplicating a commit with replaced parents without first creating a replace
ref and then deleting it?
I looked at git-commit-tree
but did not see an option to take most commit properties from a source commit.
I also played with git cherry-pick
but that tries to apply a patch which can run into conflicts and results in a different tree if the parent is different. I am trying to produce a commit with exactly the same tree (and other properties).
There's an easier way to get the replace ref's sha, and a more direct way to do the whole thing but it's more verbose.
Easier:
git replace --graft $sha $parent1 $parent2 $etc
newcommit=$(git rev-parse refs/replace/$sha)
git replace -d $sha
More direct, but you have to say the exact alterations you want, the idea is Git's commit format is intentionally straightforward plain text, edit it to replace the parents then write the result as a new commit:
newcommit=$(
git cat-file commit $sha | sed -f $youredits | git hash-object -w -t commit --stdin
)
so for instance the above with two parents and without bothering with the replace-ref tango on a GNU system is
newcommit=$(
exec {sedpgm}<<EOD
1,/^ *$/ { /^parent/d; } # delete existing parents in the header; add new ones:
1aparent $parent1
1aparent $parent2
EOD
git cat-file commit $sha | sed -f /dev/fd/$sedpgm | git hash-object -w -t commit --stdin
#exec {sedpgm}<&- # not needed in a subshell that's going to exit anyway
)
If you don't have a GNU sed or shell you get to construct the edit instructions to suit your own toolset; it's easier, likely even a bit cheaper, and probably clearer to just do the git replace
, it's got the parent-rewiring dance down cold.
p.s. on a side tangent using more shell readability features, maybe this is getting too deep in the weeds here but if you do have the tools you can get good use of the shell's line-at-a-time readin to make it clearer. (This unfortunately horribly confuses SO's builtin syntax coloring, and even emacs's, but unsurprisingly not vim's.) Markdown eats tabs but the shell can use them to make inline data easier on the eyes, here's what I really used for my smoketest on a two-parent merge here, all leading whitespace is tabs:
sha=$(git rev-parse @)
newcommit=$(
git cat-file commit $sha | sed -f /dev/fd/3 3<<-EOD| git hash-object -w -t commit --stdin
1,/^ *$/ { /^parent/d; } # delete existing parents in the header; add new ones:
1aparent $(git rev-parse @^1)
1aparent $(git rev-parse @^2)
EOD
)
or even
sha=$(git rev-parse @)
newcommit=$(
git cat-file commit $sha | sed -f /dev/fd/3 3<<-EOD \
| git hash-object -w -t commit --stdin
1,/^ *$/ { /^parent/d; } # delete existing parents in the header; add new ones:
1aparent $(git rev-parse @^1)
1aparent $(git rev-parse @^2)
EOD
)