gitfork

Is there a way to merge a multiple git repository inside another one even if some of us are forked?


I have to change our code base to a monorepo alternative. That's why I have to "merge" many project inside a new project. But we want to keep git history.

My problem is this :

I have got a repository A and a fork of this repository : the B.

There are many changes in both of the repositories, and the code of the A is not compatible with the B (and B not compatible with A).

The Repository C will be the merged repository with a directory A and a directory B. The code inside the repository A will be in directory A and B in B.

In A, I create a directory "A", move all files inside this directory and commit this change in a branch "monorepo-merging". I do the same with B but in a directory "B"

I create a new project C (git init), I add as a remote the repository A and I merge the "monorepo-merging" (git merge A/monorepo-merging --allow-unrelated-histories and git merge B/monorepo-merging --allow-unrelated-histories)

And it's not working as I was thinking. For each files existing in both projects there is a conflit inside directory A and directory B. It will take too much time to resolve all this one by one.

Is there a way to merge this two repository without lost git history of the two repositories and make it works easily ?


Solution

  • If I understand your question correctly, you have a repo A and a repo B, now you want to create a repo C and its structure will be like this

    C
    ├── A
    └── B
    

    Let's go back to the state where you have not created the directory A in A and the directory B in B. Suppose the branch in A is branchA and the branch in B is branchB and the branch in C is just master.

    #!/bin/bash
    
    git init C
    cd C
    git fetch /path/to/A branchA:branch_from_A
    git fetch /path/to/B branchB:branch_from_B
    
    tree_a=$(git rev-parse branch_from_A^{tree})
    tree_b=$(git rev-parse branch_from_B^{tree})
    
    tree_c=$(echo -e "040000 tree ${tree_a}\tA\n040000 tree ${tree_b}\tB" | git mktree)
    
    git reset --hard $(git commit-tree -p branch_from_A -p branch_from_B ${tree_c} -m "merge A and B as sub directories")
    

    /path/to/A and /path/to/B could be a local path or a URL to the repository.

    The echo -e part creates a tree file that describes the structure of C, where there are 2 folders A and B. The content of A is described by the tree object ${tree_a}, and the content of B by the tree object ${tree_b}. git mktree creates a tree object ${tree_c} for C from this tree file.

    The git commit-tree part creates a merge commit from ${tree_c}, whose 1st parent is the head of branch_from_A and 2nd parent is the head of branch_from_B. You can swap them if you want branch_from_B to be the 1st parent. Also the commit message is specified at the same time. You can modify it later with git commit --amend.

    After the commit is created, the git reset command makes master point at this commit and updates the directories and files in C.

    The histories of A and B are merged together by this merge commit. If you check out a commit before this merge commit, you can only see the content of A or B.