How do I add a parent commit before all other commits in Git?

Something like this should work.

# Create a new branch with the old history
$ git checkout --orphan old-history    
$ git add <old-files>
$ git commit

# Rebase master on top of the branch with old-history
$ git checkout master
$ git pull --rebase . old-history

There are instructions in the git-filter-branch(1) man page for this:

   To set a commit (which typically is at the tip of another history) to be
   the parent of the current initial commit, in order to paste the other
   history behind the current history:

       git filter-branch --parent-filter 'sed "s/^\$/-p <graft-id>/"' HEAD

   (if the parent string is empty - which happens when we are dealing with
   the initial commit - add graftcommit as a parent). Note that this assumes
   history with a single root (that is, no merge without common ancestors
   happened).

Taking a suggestion from Kent, you might want to add --tag-name-filter cat to this command to rewrite tag names to the new commits. This is also documented in the man page, with a warning to back up the old tags first:

       The original tags are not deleted, but can be overwritten; use
       "--tag-name-filter cat" to simply update the tags. In this case, be
       very careful and make sure you have the old tags backed up in case
       the conversion has run afoul.

The man page also explains why signatures must be stripped when rewriting tags:

       Nearly proper rewriting of tag objects is supported. If the tag has
       a message attached, a new tag object will be created with the same
       message, author, and timestamp. If the tag has a signature
       attached, the signature will be stripped. It is by definition
       impossible to preserve signatures. The reason this is "nearly"
       proper, is because ideally if the tag did not change (points to the
       same object, has the same name, etc.) it should retain any
       signature. That is not the case, signatures will always be removed,
       buyer beware. There is also no support for changing the author or
       timestamp (or the tag message for that matter). Tags which point to
       other tags will be rewritten to point to the underlying commit.

After the single-root example, the man page continues with multiple-root instructions:

   If this is not the case, use:

       git filter-branch --parent-filter \
               'test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>" || cat' HEAD

   or even simpler:

       git replace --graft $commit-id $graft-id
       git filter-branch $graft-id..HEAD

Using git filter-branch is much cleaner than using git --rebase, which functions like a combination of diff and patch, applying the changes in each commit on top of another one. Instead, git filter-branch leaves the actual files in each commit untouched and changes the parent commit pointer directly.

Of course, the new commits will have different SHAs than the original ones – that is unavoidable since the parent commit is included in the SHA calculation.


You could also try Mark Lodato's git-reparent script, which appears to use git plumbing commands to achieve a similar result.

Tags:

Git