now | writings | rss | github | twitter | contact

merging a git repo into a subdirectory

posted to writings on jan 27th, 2010 with tags git and nerd, last updated on apr 3rd, 2011 and commented on twice

update: this behavior is now supported natively in git with subtree merging.

for a project i'm working on, i need to host a local copy of a remote git repository inside of my project's git tree, but rooted in a subdirectory. it wasn't as straightforward as i was hoping, so i'm writing this so someone else can find it.

i needed to do this because the two codebases need to be bundled together while still allowing me to keep local changes to that remotely-maintained code in my own tree, yet still occasionally merge upstream changes from it as well. i want to retain all of the upstream repository metadata, so that individual commits are tracked and so on, without having added history to each of those files showing that they were renamed.

if i didn't need to keep it in a subdirectory, a normal git pull ... would work, and basically merge in the tree on top of mine. but since it has to be in a different path inside my tree, it involves rewriting all of the change history in order to make it look as if the files were always in that subdirectory from the remote repository. to do this requires a bit of work, so i have a shell script doing it for me now:

#!/bin/sh

cd ~/code/ && \
rm -rf php-activerecord && \
git clone git://github.com/kla/php-activerecord.git && \
cd php-activerecord && \
git filter-branch --index-filter 'git ls-files -s | sed "s,\t,&lib/php-activerecord/," |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD

note: that is a literal tab character in place of the \t in the sed expression.

this clones the remote repository into a fresh directory (~/code/php-activerecord/), then rewrites the history by renaming each file from its normal path to its path with lib/php-activerecord/ prepended to it. when it's done, all of the files have been moved to ~/code/php-activerecord/lib/php-activerecord.

then from my original project tree i just do a git pull --no-commit ~/code/php-activerecord master and it merges that repo into the lib/php-activerecord/ directory of my project. i use --no-commit because i want to change the auto-generated commit message to say it was merged from the original repo location, not ~/code/php-activerecord (otherwise it will auto commit and git commit --amend is needed).

Comments? Contact me via Twitter or e-mail.

2 Comments

benoitc (not authenticated) on february 1st, 2010 at 05:30:48:

Why not using submodules ?

joshua (authentic) on february 1st, 2010 at 06:11:48:

i didn't know about submodules, but seeing how they work, they seem kind of brittle and laborious.