I put it off for so long. It seemed like a good idea at the time to commit my raw data input files (CSV) that I was using for my R analysis, because it was time consuming to generate them and so this seemed like a good way to back them up. But then as the analysis evolved some of the files became redundant and so I wanted to purge them from the repository (yes, with hindsight it is obvious they should have never gone in there), but I knew that required use of
git filter-branch and that looked kind of scary.
Turns out it isn't actually too tricky and you can do some nifty things with it - plus you can always test on a dummy branch or even a fresh clone of a repository.
git filter-branch --prune-empty --tree-filter 'find -type d -name "read" -print0 | xargs -0 rm -rf' HEAD
You can do lots of useful things with
find. Fortunately I had at least structured by repository sensible and all input files were in sub-directories called "read". So this
find command is looking for all directories (
-type d) called "read".
print0 is used to get the output into a format that can be piped into
xargs; deals with spaces in names, etc. So what this does is iterate through each commit, running the
find command each time, which in turn looks for any directories called "read" and on finding them deletes them and the contents. Lastly, the
--prune-empty option is used to remove any empty commits (i.e. those commits where all I had done was to add the input files). All that was left to do was force push this to the remote repository.
Now I was a bit more comfortable with
git filter-branch I used it to split some files out of an existing repository. Since I'm a bit of cheapskate I only have the Github Micro plan which means I have this "catch-all" repository for various bits of code and scripts. Unfortunately, this can result in the scenario where I start something off as a single file little script, which then grows into a multi-file programme, which then makes more sense in it's own repository, which means extracting it out of the "catch-all" repository. Also unfortunately, in this case it wasn't as simple as Splitting out a sub-directory into a new repo as I had multiple files in multiple sub-directories (my "catch-all" repo is organised by language, rather than project - since it isn't meant to be for projects). What I did have, however, was consistent naming. All the files I required had the text "ihateponies" in their filename (not really, but ...) and all of the files I didn't require, didn't. Therefore I could use a
find command that would filter out anything not required.
First I created a new clone of the repository. Then I ran this:
git filter-branch --prune-empty --tree-filter 'find ! -path "*ihateponies*" ! -path "*git*" -type f -delete' HEAD
Then I had my new repository!
find command looks for any path names (I'm sure when I tried I couldn't get the negation to work with
-name, but I can't seem to duplicate that problem now) that don't contain "ihateponies", where the type is a file (
-type f) and deletes them. To be on the safe side I also excluded pathnames that contained "git" since I didn't want the find script deleting the
.git directory. And again I used
--prune-empty to clean up the commit history.
For the original "catch-all" repository I just did a
git rm on the relevant files because I was fine with having the history of those files in there as well.
[EDIT: 2013-07-08] I just had another cause to use git filter-branch in merging two repositories. To keep within my Github micro plan stinginess I wanted to 'pull' in one of my private repositories (repo A) as a new sub-directory of another private repository (repo B). I.e. basically the reverse of everyone's favourite of splitting out a sub-directory as a separate repository.
To do this, within repo A I ran git filter-branch as follows to move all the files into a sub-directory structure matching what I required in repo B:
git filter-branch --tree-filter 'mkdir -p sub/directory/structure; mv a.file sub/directory/structure/; mv directory/ sub/directory/structure/' HEAD
In this example I'm using the
-p option on
mkdir to make the whole directory structure (
sub/directory/structure) each commit and I've then chained together a couple of
mv commands. In repo A I had one file (
a.file) and a sub-directory (
directory) so the two
mv commands just move this into the required new directory structure (I probably could have just use one
mv *.* command).
With this done, within repo B all I had to do was:
git pull /path/to/repoB
Job done! And with a freed up private repository on Github I can start on another shitty little "project" that I'm too embarrassed to have public just yet.