tags:

views:

37

answers:

2

I'm trying to learn git by applying it (retroactively) to a project where I have been tracking a remote codebase for a while. When I put everything into git, I simply made a remote branch for all the external versions and put my versions on master, so currently my repository looks like this:

master: A0---A1.0--A1.1--A2.0--A2.1-....
          \                
remote:    B1----------B2-----------....

My question is: how do I retroactively tell git about the merges that took place, to make the repository look like so (no code should be changed):

master: A0---A1.0--A1.1--A2.0--A2.1-....
          \ /           / 
remote:    B1----------B2-----------....

Standard git disclaimer: no published history will be affected by the above actions :)

EDIT: Below is how I did this using grafts as suggested by Kevin:

First, I manually created .git/info/grafts as follows (all entries are sha1's):

A1.0 A0 B1
A2.0 A1.1 B2

Then, after checking that things looked good (gitx), I ran git filter-branch with no arguments.

Filter-branch will make grafts permanent, and store refs to the original commits in refs/originals/... to allow you to back out via git reset --hard refs/originals/refs/heads/master. Since everything looked fine I removed all leftovers as follows:

rm .git/info/grafts
rm .git/refs/originals

If you have garbage collected, you need to do git update-ref -d refs/originals/refs/heads/master instead.

+2  A: 

Sounds like you want grafts. You can create grafts for the new merges you want (remember to include the original parent in the graft!), and when everything looks good, use git filter-branch (read the manpage!) to bake it in stone.

Kevin Ballard
Works like a charm -- I'll add pseuodocode to my question for reference (a bit hard to put in comments).
Janus
One thing looks funny after git filter-branch: `git branch -a` only show `master` and `remote` but gitx shows a dangling thing that looks like branch (from A1.0) with A1.1, A2.0 and A2.1 (which is tagged `refs/original/refs/heads/master`). What is this, and is there a simple way to delete it?
Janus
@Janus: That's filter-branch making sure you don't really really screw up. For every ref `refs/foo/bar` it rewrites, it creates a copy of the original in `refs/originals/refs/foo/bar`. If you realize you completely messed up, all you have to do is `git reset --hard refs/originals/refs/heads/master`. If you confirm that it all works, then you can remove them. If it's just one, use `git update-ref -d refs/original/refs/heads/master`. For multiple, a oneliner from the man page: `git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d`. See the manpage for more detail.
Jefromi
If you haven't garbage-collected yet, you can also delete them with `rm -rf .git/refs/original` (once you garbage-collect they'll be stored as so-called "packed refs" and you need to use update-ref to kill them).
Kevin Ballard
+1  A: 

You could totally fake this by doing an interactive rebase, specifying that you want to edit all those commits, and amend them with merge commits:

git rebase -i A0 master
# change the lines for A1.0 and A2.0 to "edit"
# git stops at A1.0
git merge --no-commit --strategy=ours B1     # the SHA1 of B1, of course
git commit --amend
git rebase --continue
# git stops at A2.0
git merge --no-commit --strategy=ours B2
git commit --amend
git rebase --continue

Each time you stop, you're technically merging, but with the ours strategy, which keeps all the contents of your current commit. The --no-commit tells git to stop just before committing. You then amend the current commit (which basically means replace) with the merge, instead of making the merge a separate commit as you usually would. Tada! A1.0 has been replaced with a new commit with an identical tree, but an additional parent. commit --amend gives you a chance to edit the message; you may want to do so, to leave some record of what you've done.

Jefromi
Thanks! I was thinking along these lines, but it hadn't occurred to me that you can do merges in the middle of a rebase. Now that I (think) I have learned this, I'll try the grafts approach just for kicks :)
Janus
I've marked Kevin's anwer as a solution: Yours taught me more about everyday work with git, but there is less information about grafts than interactive rebases around :)
Janus
@Janus: His answer was actually new to me - one of the upvotes is mine. Cool stuff.
Jefromi
@Janus: As for being able to do merges during a rebase... you can actually do just about anything. Interactive rebase just tries to apply the patches in order, stopping when you tell it, squashing when you tell it. Once you've got it stopped, you can do all kinds of crazy things to get whatever commit(s) you want there.
Jefromi