Search code examples
gitgit-subtree

Git subtree -- sharing subtrees with contributors


In some of the repositories that I'm working with, I found git subtree does not behave as expected when sharing repos.

Here's the scenario:

  1. I own main repo X and push it to origin.
  2. My colleague pulls repo X from origin, adds a subtree pointing to another repo Y, makes the commit, and pushes X back to origin.
  3. I pull X and see the changes my colleague has made. However, in my local repo, the folder containing subtree Y is just another file. I can't pull changes from repo Y even after adding repo Y as a remote. Git subtree always says that the folder already exists whenever I do a git subtree add and says that it can not find the commit when I do git subtree merge.

Does anyone know the best way to deal with this situation? Ideally I would like to also be able to pull changes from repo Y even though I am not the user adding repo Y as a subtree of repo X. Is this possible? If not, what is the best way to share repositories having subtrees?


Solution

  • Use git subtree pull.

    git subtree add adds a history of another repository as a new subtree -- in your case, this has been done by your colleague, so it won't work

    git subtree merge merges the changes up to a given local commit into the subtree -- as you have not pulled those changes yet, it won't work either

    git subtree pull pulls the changes from another repo and then merges them into the subtree. Tested with the following command sequence (uses the 'master' branch of the subtree repository, adjust if needed).

    ~ $ mkdir subrepo
    ~ $ cd subrepo
    ~/subrepo $ git init
    Initialized empty Git repository in ~/subrepo/.git/
    ~/subrepo $ touch file
    ~/subrepo $ git add file
    [master (root-commit) 03a9f75] file added
     1 file changed, 0 insertions(+), 0 deletions(-)
     create mode 100644 file
    ~/subrepo $ cd ..
    ~ $ mkdir repo1
    ~ $ cd repo1
    ~/repo1 $ git init
    Initialized empty Git repository in ~/repo1/.git/
    ~/repo1 $ git commit --allow-empty -m "initial commit"
    [master (root-commit) ec7e1c5] initial commit
    ~/repo1 $ git subtree add --prefix=sub ../subrepo master
    git fetch ../subrepo master
    warning: no common commits
    remote: Counting objects: 3, done.
    remote: Total 3 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (3/3), done.
    From ../subrepo
     * branch            master     -> FETCH_HEAD
    Added dir 'sub'
    ~/repo1 $ cd ..
    ~ $ git clone repo1 repo2
    ~ $ cd subrepo
    ~/subrepo $ echo 'new version' > file
    ~/subrepo $ git add file
    ~/subrepo $ git commit -m "file changed"
    [master c72ac6e] file changed
     1 file changed, 1 insertion(+)
    ~/subrepo $ cd ../repo2
    ~/repo2 $ git subtree pull --prefix=sub ../subrepo master
    remote: Counting objects: 5, done.
    remote: Total 3 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (3/3), done.
    From ../subrepo
     * branch            master     -> FETCH_HEAD
    Merge made by the 'recursive' strategy.
     sub/file | 1 +
     1 file changed, 1 insertion(+)
    ~/repo2 $ cat sub/file
    new version