How do you merge two Git repositories?

后端 未结 23 3250
耶瑟儿~
耶瑟儿~ 2020-11-21 05:45

Consider the following scenario:

I have developed a small experimental project A in its own Git repo. It has now matured, and I\'d like A to be part of larger projec

相关标签:
23条回答
  • 2020-11-21 06:11

    I wanted to move a small project to a subdirectory of a larger one. Since my small project did not have many commits, I used git format-patch --output-directory /path/to/patch-dir. Then on the larger project, I used git am --directory=dir/in/project /path/to/patch-dir/*.

    This feels way less scary and way more cleaner than a filter-branch. Granted, it may not be applicable to all cases.

    0 讨论(0)
  • 2020-11-21 06:14

    I know it's long after the fact, but I wasn't happy with the other answers I found here, so I wrote this:

    me=$(basename $0)
    
    TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
    echo 
    echo "building new repo in $TMP"
    echo
    sleep 1
    
    set -e
    
    cd $TMP
    mkdir new-repo
    cd new-repo
        git init
        cd ..
    
    x=0
    while [ -n "$1" ]; do
        repo="$1"; shift
        git clone "$repo"
        dirname=$(basename $repo | sed -e 's/\s/-/g')
        if [[ $dirname =~ ^git:.*\.git$ ]]; then
            dirname=$(echo $dirname | sed s/.git$//)
        fi
    
        cd $dirname
            git remote rm origin
            git filter-branch --tree-filter \
                "(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
            cd ..
    
        cd new-repo
            git pull --no-commit ../$dirname
            [ $x -gt 0 ] && git commit -m "merge made by $me"
            cd ..
    
        x=$(( x + 1 ))
    done
    
    0 讨论(0)
  • 2020-11-21 06:16

    In my case, I had a my-plugin repository and a main-project repository, and I wanted to pretend that my-plugin had always been developed in the plugins subdirectory of main-project.

    Basically, I rewrote the history of the my-plugin repository so that it appeared all development took place in the plugins/my-plugin subdirectory. Then, I added the development history of my-plugin into the main-project history, and merged the two trees together. Since there was no plugins/my-plugin directory already present in the main-project repository, this was a trivial no-conflicts merge. The resulting repository contained all history from both original projects, and had two roots.

    TL;DR

    $ cp -R my-plugin my-plugin-dirty
    $ cd my-plugin-dirty
    $ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
    $ cd ../main-project
    $ git checkout master
    $ git remote add --fetch my-plugin ../my-plugin-dirty
    $ git merge my-plugin/master --allow-unrelated-histories
    $ cd ..
    $ rm -rf my-plugin-dirty
    

    Long version

    First, create a copy of the my-plugin repository, because we're going to be rewriting the history of this repository.

    Now, navigate to the root of the my-plugin repository, check out your main branch (probably master), and run the following command. Of course, you should substitute for my-plugin and plugins whatever your actual names are.

    $ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
    

    Now for an explanation. git filter-branch --tree-filter (...) HEAD runs the (...) command on every commit that is reachable from HEAD. Note that this operates directly on the data stored for each commit, so we don't have to worry about notions of "working directory", "index", "staging", and so on.

    If you run a filter-branch command that fails, it will leave behind some files in the .git directory and the next time you try filter-branch it will complain about this, unless you supply the -f option to filter-branch.

    As for the actual command, I didn't have much luck getting bash to do what I wanted, so instead I use zsh -c to make zsh execute a command. First I set the extended_glob option, which is what enables the ^(...) syntax in the mv command, as well as the glob_dots option, which allows me to select dotfiles (such as .gitignore) with a glob (^(...)).

    Next, I use the mkdir -p command to create both plugins and plugins/my-plugin at the same time.

    Finally, I use the zsh "negative glob" feature ^(.git|plugins) to match all files in the root directory of the repository except for .git and the newly created my-plugin folder. (Excluding .git might not be necessary here, but trying to move a directory into itself is an error.)

    In my repository, the initial commit did not include any files, so the mv command returned an error on the initial commit (since nothing was available to move). Therefore, I added a || true so that git filter-branch would not abort.

    The --all option tells filter-branch to rewrite the history for all branches in the repository, and the extra -- is necessary to tell git to interpret it as a part of the option list for branches to rewrite, instead of as an option to filter-branch itself.

    Now, navigate to your main-project repository and check out whatever branch you want to merge into. Add your local copy of the my-plugin repository (with its history modified) as a remote of main-project with:

    $ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
    

    You will now have two unrelated trees in your commit history, which you can visualize nicely using:

    $ git log --color --graph --decorate --all
    

    To merge them, use:

    $ git merge my-plugin/master --allow-unrelated-histories
    

    Note that in pre-2.9.0 Git, the --allow-unrelated-histories option does not exist. If you are using one of these versions, just omit the option: the error message that --allow-unrelated-histories prevents was also added in 2.9.0.

    You should not have any merge conflicts. If you do, it probably means that either the filter-branch command did not work correctly or there was already a plugins/my-plugin directory in main-project.

    Make sure to enter an explanatory commit message for any future contributors wondering what hackery was going on to make a repository with two roots.

    You can visualize the new commit graph, which should have two root commits, using the above git log command. Note that only the master branch will be merged. This means that if you have important work on other my-plugin branches that you want to merge into the main-project tree, you should refrain from deleting the my-plugin remote until you have done these merges. If you don't, then the commits from those branches will still be in the main-project repository, but some will be unreachable and susceptible to eventual garbage collection. (Also, you will have to refer to them by SHA, because deleting a remote removes its remote-tracking branches.)

    Optionally, after you have merged everything you want to keep from my-plugin, you can remove the my-plugin remote using:

    $ git remote remove my-plugin
    

    You can now safely delete the copy of the my-plugin repository whose history you changed. In my case, I also added a deprecation notice to the real my-plugin repository after the merge was complete and pushed.


    Tested on Mac OS X El Capitan with git --version 2.9.0 and zsh --version 5.2. Your mileage may vary.

    References:

    • https://git-scm.com/docs/git-filter-branch
    • https://unix.stackexchange.com/questions/6393/how-do-you-move-all-files-including-hidden-from-one-directory-to-another
    • http://www.refining-linux.org/archives/37/ZSH-Gem-2-Extended-globbing-and-expansion/
    • Purging file from Git repo failed, unable to create new backup
    • git, filter-branch on all branches
    0 讨论(0)
  • 2020-11-21 06:17

    Given command is the best possible solution I suggest.

    git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
    
    0 讨论(0)
  • 2020-11-21 06:18

    I had a similar challenge, but in my case, we had developed one version of the codebase in repo A, then cloned that into a new repo, repo B, for the new version of the product. After fixing some bugs in repo A, we needed to FI the changes into repo B. Ended up doing the following:

    1. Adding a remote to repo B that pointed to repo A (git remote add...)
    2. Pulling the current branch (we were not using master for bug fixes) (git pull remoteForRepoA bugFixBranch)
    3. Pushing merges to github

    Worked a treat :)

    0 讨论(0)
提交回复
热议问题