tags:

views:

68

answers:

3

I have a git repo in /foo/bar with a large commit history and multiple branches.

I now want /foo/baz to be in the same repo as /foo/bar, which (I think) means that I need to create a new repo in /foo. However, I want to preserve history of changes that I've made to /foo/bar.

I first thought of git format-patch followed by apply, but commit messages aren't preserved.

Edit: This is still an open question. Help still appreciated.

A: 

You could create a git repo in foo and reference both baz and bar through git submodules.

Then both bar and baz coexist with their full history preserved.


If you really want only one repo (foo), with both bar and baz history in it, then some grafts technique or subtree merge strategy are in order.

VonC
+2  A: 

Rather than create a new repository, move what's in your current repository into the right place: create a new directory bar in your current directory and move the current content in (so your code is in /foo/bar/bar). Then create a baz directory next to your new bar directory (/foo/bar/baz). mv /foo /foo2; mv /foo2/bar /foo; rmdir /foo2 and you're done :).

Git's rename tracking means that your history will still work and Git's hashing of content means that even though you've moved things around, you're still referencing the same objects in the repository.

Andrew Aylett
I can't move things at will. I initialized my repo in the wrong place, but the file hierarchy is correct as it is. Thank you for suggest though.
masonk
I'm not suggesting that the end result look any different than you start with, just that moving things around as an intermediate step might help you get what you need. Sorry if my answer wasn't very clear on that. Note that the filter-branch answer also winds up with the repository that should be in `/foo` being in `/foo/bar` and needing to be moved.
Andrew Aylett
+1  A: 

What you want is git filter-branch, which can move a whole repository into a subtree, preserving history by making it look as if it's always been that way. Back up your repository before using this!

Here's the magic. In /foo/bar, check out each branch and run:

git filter-branch --commit-filter '
    TREE="$1";
    shift;
    SUBTREE=`echo -e 040000 tree $TREE"\tbar" | git mktree`
    git commit-tree $SUBTREE "$@"'

That will make the /foo/bar respository have another 'bar' subdirectory with all its contents throughout its whole history. Then you can move the entire repo up to the foo level and add baz code to it.

Update:

Okay, here's what's going on. A commit is a link to a "tree" (think of it as a SHA representing a whole filesystem subdirectory's contents) plus some "parent" SHA's and some metadata link author/message/etc. The git commit-tree command is the low-level bit that wraps all this together. The parameter to --commit-filter gets treated as a shell function and run in place of git commit-tree during the filter process, and has to act like it.

What I'm doing is taking the first parameter -- the original tree to commit -- and building a new "tree object" that says it's in a subfolder via git mktree, another low-level git command. To do that, I have to pipe into it something that looks like a git tree -- a set of (mode SP type SP SHA TAB filename) lines; thus the echo command. The output of mktree is then substituted for the first parameter when I chain to the real commit-tree; "$@" is a way to pass all the other parameters intact, having stripped the first off with shift. See git help mktree and git help commit-tree for info.

So, if you need multiple levels, you have to nest a few extra levels of tree objects (this isn't tested but is the general idea):

git filter-branch --commit-filter '
    TREE="$1"
    shift
    SUBTREE1=`echo -e 040000 tree $TREE"\tbar" | git mktree`
    SUBTREE2=`echo -e 040000 tree $SUBTREE1"\tb" | git mktree`
    SUBTREE3=`echo -e 040000 tree $SUBTREE2"\ta" | git mktree`
    git commit-tree $SUBTREE3 "$@"'

That should shift the real contents down into a/b/bar (note the reversed order).

Walter Mundt
Hi, I ran into a snag by oversimplifying the situation. The repo I want to move to /foo is not /foo/bar; it is actually /foo/a/b/bar. I think the basic idea behind your approach still works, but I can't rewrite a nested path:git filter-branch --commit-filter ' TREE="$1"; shift; SUBTREE=`echo -e 040000 tree $TREE"\ta/b/bar" | git mktree` git commit-tree $SUBTREE "$@"'Rewrite 27814f8d447ca9a4b61ed17a33912bee41e2e00e (1/68)fatal: path a/b/bar contains slashAlso, I can't figure out what your script does...could you explain? Thanks for your help so far.
masonk
I figured it out. Nope, nevermind, I didn't figure it out.
masonk
In particular, I don't understand this part:tree $TREE"\tbar"As I am interpreting the filter-branch manpage,$TREE should hold a SHA1 commit (I'm assuming that's what TREE_ID is, though I've never seen "ID" anywhere else in git terminology). The bash command tree "<SHA1>\tfoldername" does not yield a tree. Thus we're not piping anything useful into git-mktree.
masonk
Okay, I've added a section explaining what's going on, and providing a way to do what you really want, more or less.
Walter Mundt
Beautiful. It's starting to seep through now. Better yet - it worked as advertised! Thank you _very_ much.
masonk