Coming from Subversion (and with
Plone collective repository
structure in mind), I’ve
recently moved all my tiny software projects
in a big standalone Git repository (named
kev-code
). Now that I figured out that GitHub allows you to create unlimited amount of repositories, as long as they are open-source public projects, it make sense to emancipate some of my projects to their own repository. How do I move a sub-tree to its own repository? That’s what I talk about in this article.
First, there is an automated way of performing this task with git-subtree . You should try it first. For some reasons I didn’t investigate, git-subtree didn’t worked for me. So I’ll explain now how I did it by hand.
The idea is to revisit the history of my bloated Git repository and massively delete everything that is not related to the sub-folder I’m looking to export. In this case, I try to make a dedicated repository for my e107 importer for WordPress .
Let’s start by getting a local copy of my source repository:
$ git clone [email protected]:kdeldycke/kev-code.git
$ cd scripts
Then I’ll use the
filter-branch
action with a combination of
find
and
rm
to remove everything except the source code of my plugin:
$ git filter-branch --prune-empty --tree-filter 'find ./ -maxdepth 1 -not -path "./e107*" -and -not -path "./wordpress-e107*" -and -not -path "./.git" -and -not -path "./" -print -exec rm -rf "{}" \;' -- --all
Instead of the command above, I could have use the
--subdirectory-filter
option (
as suggested by
jamessan
on Stack Overflow
):
$ git filter-branch --prune-empty --subdirectory-filter e107-importer -- --all
But this doesn’t work in my case as my e107 Importer plugin didn’t started its life straight in a dedicated folder. So this command squash some of the history I want to preserve.
At this point I’m left with this following history:
This looks pretty good, as all the history of my plugin is kept in order. But tags unrelated to my plugin are still there. Let’s remove them:
$ git tag -d coolkevmen-0.3 cool-blue-0.1 sapphire-0.1 sapphire-0.2 sapphire-0.3 sapphire-0.4
Now there is some commits polluting my history. These are left-overs of
git-modules
additions. I
tried to removed them
, but it didn’t worked. Also left in the history are unwanted merges and empty commits
from an old CVS import
. To clean this up, I started an interactive rebase:
$ git rebase --interactive init
There, using my text editor, I deleted the entries corresponding to these unrelated commits (namely
c21a840
,
0dc1d76
,
37473a8
and
c6f9f64
), and hoped Git will be smart enough to reconstruct a clean history:
Luckily, it worked for me. If Git complain about such abuse, you may ignore warnings and force it to continue:
$ git rebase --continue
Now that we only have a clean sub-tree, let’s create a dedicated local Git repository to receive our branch:
$ cd ..
$ mkdir e107-importer
$ cd e107-importer
$ git init
Add a temporary origin hooked on our source repository:
$ git remote add origin ../kev-code
And import the master branch we carefully crafted (including tags):
$ git pull --tags origin master
Now we can create on GitHub the new repository that will receive our exported project:
It’s time to push our changes. Let’s replace our temporary origin to the new GitHub repository we just created:
$ git remote rm origin
$ git remote add origin [email protected]:kdeldycke/e107-importer.git
$ git push origin master --force --tags
So now we have a copy of the sub-tree of my plugin into its own repository. That’s great, but there is still some stuff to clean-up.
First, we will rewrite the repository to look as if the
./e107-importer
sub-folder had been its project root since the beginning:
$ git filter-branch --tree-filter 'test -d ./e107-importer && mv ./e107-importer/* ./ || echo "No folder found"' -- --all
Then, I’ve altered some commit messages to fix inconsistencies due to sub-folder removal:
$ git filter-branch --msg-filter 'sed "s/Move the script to a dedicated folder/Rename script/g"' -- --all
Finally, at the bottom of the history, I still have my initial commit (a personal habit of mine when I
initialize my Git repositories
). But its date was updated by the first
filter-branch
call. Let’s set its date back to epoch:
$ git filter-branch --force --env-filter \
'if [ $GIT_COMMIT = a2a5c05aed893fdd10250b724eb6a54bc6e7f122 ]
then
export GIT_AUTHOR_DATE="Thu, 01 Jan 1970 00:00:00 +0000"
export GIT_COMMITTER_DATE="Thu, 01 Jan 1970 00:00:00 +0000"
fi' -- --all
We can now send our latest changes to the remote GitHub repository by forcing a push:
$ git push --force
Last thing we have to do, is to remove the plugin code from the fat source repository (I don’t like duplicates). But that’s another story for another article…