Search code examples
gitgit-submodulesgit-rebasegit-subtree

How to rebase a `filter-branch` from a subproject on my main repository's subtree?


How to rebase a filter-branch from a subproject on my main repository's subtree?

To me this is a normal operation to be performed with submodules. I would just to do a normal rebase as described here: Rebasing everyone onto changed git history after filter-branch

The operation I did was on my subproject, on its own repository. This was the operation:

git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch "some_folder/path" -r -f' \
--prune-empty --tag-name-filter cat -- --all

After it I pushed the changes to the subproject own repository:

git push origin --force --all
git push origin --force --tags

Now I want to integrate the changes on my main repository, where my subproject is a subtree. So, this is not a normal repository. Why I do not see a rebase option on git subtree?

The solution to this would be only this: git subtree: possible to change subtree branch/path in a forked repository?, to delete it and readd it?

Tries

When doing:

git subtree pull --prefix=subprojectFolder subprojectRemoteRepository.git 

The output is:

 * branch            master     -> FETCH_HEAD
fatal: refusing to merge unrelated histories

Using git subtree merge:

git remote add subproject_remote subprojectRemoteRepository.git
git fetch --no-tags
git checkout master
git subtree merge --prefix=subprojectFolder subproject_remote/master

fatal: refusing to merge unrelated histories

How to make them related?

Doing --allow-unrelated-histories just results in:

error: unknown option `allow-unrelated-histories'
usage: git subtree add   --prefix=<prefix> <commit>
   or: git subtree add   --prefix=<prefix> <repository> <ref>
   or: git subtree merge --prefix=<prefix> <commit>
   or: git subtree pull  --prefix=<prefix> <repository> <ref>
   or: git subtree push  --prefix=<prefix> <repository> <ref>
   or: git subtree split --prefix=<prefix> <commit...>

Solution

  • I am applying this solution right now while there is not something more handy.

    Part 1

    1. To clone the subproject dedicated repository and checkout on it.
    2. To perform the filter-branch operations as I like them.
    3. To perform the git push origin --force --all and tags.

    Part 2

    1. To clone the main project repository and checkout on it.
    2. To perform the exactly the same filter-branch performed on Part 1, and also perform it removing the current subproject tree, or use the bfg-repo-cleaner with:

      git clone --mirror mainRepository.git
      java -jar bfg.jar --no-blob-protection --delete-folders 'subprojectFolder' mainRepository.git
      cd mainRepository.git
      git reflog expire --expire=now --all && git gc --prune=now --aggressive
      git push origin --force --all
      git push origin --force --tags
      cd ..
      rm -r mainRepository.git
      

    Part 3

    1. Finally readd the just cleaned subtree:

      git clone mainRepository.git
      cd mainRepository
      git checkout master
      git subtree add --prefix=subprojectFolder subproject.git master
      git push
      
    2. Tell everybody to Rebasing everyone onto changed git history after filter-branch your main project, as well as the subproject dedicated clone, if there exists one.

    End

    1. Note that using bfg-repo-cleaner for now, you will only be able to remove the folder if there are no other folders on the main project with the subproject name. This is because the BFG will remove all folders matching the subprojectFolder name. The ability to specify a folder path still in development: How to delete one folder / directory using BFG repo cleaner?. Otherwise you may use the filter-branch to remove the project's subtree.

    2. For completely erase things do also this from this other answer Why is my git repository so big?, after finished the filter-branch(s) on your repository:

      rm -Rf .git/refs/original && \
          git reflog expire --expire=now --all && \
          git gc --aggressive && \
          git prune
      
    3. To list the biggest files use:

      git rev-list --all --objects | \
          sed -n $(git rev-list --objects --all | \
          cut -f1 -d' ' | \
          git cat-file --batch-check | \
          grep blob | \
          sort -n -k 3 | \
          tail -n40 | \
          while read hash type size; do 
               echo -n "-e s/$hash/$size/p ";
          done) | \
          sort -n -k1
      
    4. From this other answer, How to remove unreferenced blobs from my git repo, you might want to do this after the filter-branch pull. Remember to readd the origin.

      git tag | xargs git tag -d && \
      git remote rm origin && \
      rm -rf .git/refs/original/ .git/refs/remotes/ .git/*_HEAD .git/logs/ && \
      git for-each-ref --format="%(refname)" refs/original/ | xargs -n1 --no-run-if-empty git update-ref -d && \
      git -c gc.reflogExpire=0 -c gc.reflogExpireUnreachable=0 -c gc.rerereresolved=0 \
          -c gc.rerereunresolved=0 -c gc.pruneExpire=now gc "$@"