Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preserve --no-ff merge commits with git filter-branch --subdirectory-filter

Tags:

git

I have a git repository with the following structure:

.git/
    project-root/
        abc/
        xyz/

And this is what I want:

project-root/
    .git
    abc/
    xyz/

I've been researching how to achieve this and this is what I've found so far: http://git-scm.com/docs/git-filter-branch

According to that, I should achieve what I want with the following command:

git filter-branch --subdirectory-filter project-root -- --all

This rewrites the commit history but most of the non-fast-forward merge commits are lost (see image below).

Is there any way to preserve those commits?

enter image description here

like image 375
miviclin Avatar asked Sep 02 '13 01:09

miviclin


1 Answers

As the docs point out here:

--subdirectory-filter <directory>

    Only look at the history which touches the given subdirectory. The result will 
    contain that directory (and only that) as its project root. Implies 
    [Remap_to_ancestor].

I believe the problem is that the merge commits do not actually touch the given subdirectory, meaning that subdirectory-filter doesn't even look at them, and thus can't preserve them. Thus, we must use another filter which examines each and every commit. tree-filter is perfect for this, but we must write a script to do what we want done to each commit.

Furthermore, my problem was actually a bit broader than the example, as my project-root folder had siblings which I wanted to remove. The subdirectory filter would remove them, but in order to do so with the tree filter it was helpful for find to come to the rescue.

It was convenient to put the commands to be run each time in a separate file.

for a repo structured like this:

.git/ 
someDirectory/
someFile.txt
otherDirectory/
dontDeleteThisOne/
project-root/
    xyz/
    abc/

This is what worked for me:

git filter-branch --tree-filter /absolute/path/to/filterScript.sh --tag-name-filter cat -- --all 

where /absolute/path/to/filterScript.sh is an executable script containing this:

#!/bin/bash

#save the folder dontDeleteThisOne so it won't get deleted
git mv -fk dontDeleteThisOne project-root && \
#remove everything not under project-root
find . -maxdepth 1 -mindepth 1 '(' -name project-root -o -name '.git' ')' -prune -o -exec git rm -rf --ignore-unmatch '{}' ';' && \
#move the contents of the project-root directory up to root
git mv -fk project-root/{,.[!.],..?}* .;

the resulting repo structure is then this:

.git/
dontDeleteThisOne/
xyz/
abc/

This result is equivalent to the result of git filter-branch --subdirectory-filter project-root, EXCEPT that merge commits are preserved in the history, as desired.

Of course, this takes MUCH longer than using the subdirectory filter...

like image 79
stochastic Avatar answered Nov 09 '22 05:11

stochastic