Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to turn a set of amended commits (stored in reflog) into real commits

I was doing a refactoring, and just kept amending every time I made any decent progress. It has gone very well, and I realized it would be nice to be able to show people how I handled such a huge refactoring (lots of tiny steps, just nudging code around, with a green test suite at every point).

The commits are all in my reflog. Is there a way to turn each of those amended commits into its own real commit (sorry, don't know the terminology) so that users can see each step rather than just the aggregated ones on lines 81 and 57? I don't need unique messages for the commits or anything, just that they are captured in the history.

This is all still in my local repo.

like image 479
Joshua Cheek Avatar asked Oct 07 '22 11:10

Joshua Cheek


1 Answers

Reconstructing the reflog might not be as difficult as it first appears. You can regenerate the reflog's commits into distinct branches, or you can use git read-tree to generate a single newbranch containing a portion of the reflog.

One commit per branch solution:

Suppose you wanted distinct branches for each commit. First, copy your repository. You're going to change your reflog in this process, so you might as well use a throwaway version. Second, examine your reflog and find the commit with which you want to start. It's going to look like HEAD@{n}. Suppose it were HEAD@{49}. Then, try this script, replacing the head -50 with head -<n + 1>, whatever that is in your case:

#!/bin/sh
reflog=$(git reflog | head -50 | awk '{ print $1 }')
i=0
for ref in $reflog; do
    git checkout -B "reflog_$i" $ref
    i=$(expr $i + 1)
done

This is grabbing your reflog's commit history once, then iterating over it, generating reflog_$i branches along the way. You can cherry-pick, merge, or manipulate them however you want. If this is simply for a presentation, you could write a short script that executes git checkout on the branches, runs the test suite, and shows greens the whole way. Remember, reflog_1 represents the most current history; reflog_<n+1> the oldest.

#!/bin/sh
for i in $(seq 50 1); do
    git checkout "reflog_$i"
    ./test-suite.sh
done

Throw it up on a projector while you explain your commit method and it'd be a nice backdrop.

If you want to combine all the branches, you can run this ruby script to apply them in order (or create an equivalent in whatever language you're comfortable with). Let me reiterate that you should have your directory backed up, because this is rather destructive.

#!/usr/bin/env ruby
n = 50

n.downto 0 do |i|
  system "
    git read-tree reflog_#{i}
    git commit -m 'Refactoring #{n - i}'
    git checkout -- .
    git br -D refog_#{i}
  "
end

Putting all commits on the same branch using git read-tree

First, copy your repository. Then use a script like the one below. Per the discussion in the first solution, change the two <n + 1> to whatever depth you want in your reflog, plus one. Note the additional pipe to sed. You must use it or something like it, lest the reflog apply to newbranch in reverse chronological order. There's surely a way to do this without using both awk and sed, but this works:

#!/bin/sh
reflog=$(git reflog | head -<n + 1> | awk '{ print $1 }' | sed -n '1!G;h;$p')
git checkout -B newbranch HEAD@{<n + 1>}
for ref in $reflog; do
    git read-tree "$ref"
    git commit --no-verify -m "Adding commit $ref"
    git checkout -- .
done

The final result will be newbranch, which should contain all the commits between HEAD@{0} and HEAD@{n}, based upon commit HEAD@{n+1}.

like image 115
Christopher Avatar answered Oct 12 '22 20:10

Christopher