Igor Bukanov
Igor Bukanov

Reputation: 5142

Turn a sequence of unrelated git commits into a branch

I would like to turn a sequence of arbitrary unrelated commits A, B, C, ... into a branch A'->B'->C'->... where each commit X' matches the state of the tree for the corresponding commit X. Commits can be unrelated, with different files, submodules etc. I consider to use repeated git diff-apply-commit, like in:

git checkout A
git checkout -b commits-as-branch
git diff HEAD B | git apply; git commit -a -m 'Like B'
git diff HEAD C | git apply; git commit -a -m 'Like C'
...

But the repository is huge (Chromium tree), the sequence contain thousands of commits and the diff between commits can be hundreds of megabytes. So I am looking for something more scalable.

Upvotes: 2

Views: 91

Answers (1)

torek
torek

Reputation: 487755

I would like to turn a sequence of arbitrary unrelated commits A, B, C, ... into a branch A'->B'->C'->... where each commit X' matches the state of the tree for the corresponding commit X.

This is hard to do with user-oriented, normal everyday Git commands, and trivially easy to do with Git plumbing commands. Specifically you want to use git commit-tree to make the commits, with the trees involved being those from commits A, B, C, and so on, and the commit messages perhaps taken from those commits as well, but you want the parent of commit A' to be some chosen commit—in your setup that would be HEAD—and the parent of B' to be A', the parent of C' to be B', and so on.

(Note that this means you've drawn your commits the wrong way around. That is, Git internally works backwards. If you draw your commits the right way, that's the wrong way. 😀)

Once you are done making this chain of commits that ends at the hash ID produced by the last of your git commit-tree commands, you can create your branch name, pointing to this last commit. If you have the hash IDs of the commits you like in an input file, you would use, e.g.:

prev=HEAD    # could use prev=$(git rev-parse HEAD) but no need
newest=$prev
while read hash; do
    git log --no-walk --format=%B $hash > /tmp/commit-msg
    newest=$(git commit-tree -F /tmp/commitmsg -p $prev $hash^{tree})
    prev=$newest
done < commitlist

git checkout -b newbranch $newest

(written for clarity, rather than optimal performance: use pipes instead of temporary files, simplify away unneeded variables, and so on to improve it, and add error checking if/as needed).

Note: untested.

Upvotes: 4

Related Questions