views:

1844

answers:

3

I've asked before about how to squash the first two commits in a git repository.

While the solutions are rather interesting and not really as mind-warping as some other things in git, they're still a bit of the proverbial bag of hurt if you need to repeat the procedure many times along the development of your project.

So, I'd rather go through pain only once, and then be able to forever use the standard interactive rebase.

What I want to do, then, is to have an empty initial commit that exists solely for the purpose of being the first. No code, no nothing. Just taking up space so it can be the base for rebase.

My question then is, having an existing repository, how do I go about inserting a new, empty commit before the first one, and shifting everyone else forward?


PS:

  • Please don't ask why anyone would want to do such a terrible thing.
  • Yes, a shell script could automate the pain too.
  • I guess Stack Overflow has made me a bit defensive.
A: 

Start a new repository.

Set your date back to the start date you want.

Do everything the way you wish you'd done it, adjusting the system time to reflect when you'd wished you'd done it that way. Pull files from the existing repository as needed to avoid a lot of needless typing.

When you get to today, swap the repositories and you're done.

If you're just crazy (established) but reasonably intelligent (likely, because you have to have a certain amount of smarts to think up crazy ideas like this) you will script the process.

That will also make it nicer when you decide you want the past to have happened some other way a week from now.

MarkusQ
I have bad feelings about a solution that requires you to mess around with the system date, but you did give me an idea, which I developed a bit and, alas, it worked. So, thanks.
kch
So make it as accepted and we move on?
MarkusQ
+2  A: 

Well, here's what I came up with:

# Just setting variables on top for clarity.
# Set this to the path to your original repository.
ORIGINAL_REPO=/path/to/original/repository

# Create a new repository…
mkdir fun
cd fun
git init
# …and add an initial empty commit to it
git commit --allow-empty -m "The first evil."

# Add the original repository as a remote
git remote add previous $ORIGINAL_REPO
git fetch previous

# Get the hash for the first commit in the original repository
FIRST=`git log previous/master --pretty=format:%H  --reverse | head -1`
# Cherry-pick it
git cherry-pick $FIRST
# Then rebase the remainder of the original branch on top of the newly 
# cherry-picked, previously first commit, which is happily the second 
# on this branch, right after the empty one.
git rebase --onto master master previous/master

# rebase --onto leaves your head detached, I don't really know why)
# So now you overwrite your master branch with the newly rebased tree.
# You're now kinda done.
git branch -f master
git checkout master
# But do clean up: remove the remote, you don't need it anymore
git remote rm previous
kch
+8  A: 

Here’s a cleaner implementation of the same solution, in that it works without the need to create an extra repository, futz around with remotes, and correct a detached head:

# first you need a new empty branch; let's call it `newroot`
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

# then you apply the same steps
git commit --allow-empty -m 'root commit'
git cherry-pick $(git rev-list --reverse master | head -1)
git rebase --onto newroot newroot master
git branch -d newroot

Voila, you’ve ended up on master with its history rewritten to include an empty root commit.

Aristotle Pagaltzis
Nice. Looks like you missed the initial empty commit, or am I missing something? Also, I tried a bit without creating a new repository, but rebase went berserk with changesets dealing with submodules. You noticed any of that?
kch
Thanks for the pointer – yes, I did miss the empty commit. Fixed. Haven’t used submodules, so I don’t know how that turns out.
Aristotle Pagaltzis
I have now used this solution with submodules and didn't have a problem with it. As with everything: not to say there isn't any, just that I didn't hit any.
kch
That `--onto newroot` option is redundant; you can do without it because the argument you pass it, `newroot`, is the same as the upstream argument -- `newroot`.
wilhelmtell