How to merge Git repositories
In Git, you usually merge pull requests consisting of a few commits worth of changes. Sometimes however you need to merge two separate repositories with unrelated histories. This usually happens when an experimental component is first developed in a separate repository and later promoted to core component developed in-tree. Such merges are a bit more complicated than the usual pull request merges.
There are some SO questions (1, 2) and articles on various sites and blogs (for example 1, 2, 3), but all of them are wrong in subtle ways. I am not even mentioning the ones that are completely wrong. I have therefore decided to write down canonical repository merge process.
First the gotchas to be aware of:
- Quick and dirty solution is to just copy files over. More complicated methods like this one are all about preserving history. History is valuable and it does not take much time to preserve it, so do it right.
- Repository usually cannot be merged as is, because that would cause a lot of conflicts in the root directory. Most repositories need some preparation. They often have to be moved into a subdirectory.
- Some people recommend rewriting history in various ways. Don't do it. Ordinary merge is adequate.
- Tags usually shouldn't be merged, because they are global and they will therefore cause conflicts, especially release tags (e.g. v1.0.0). There are ways to rename them, but that's rarely what you want.
- Only master branch is merged by default. If there are other branches you want to keep, you will have to repeat this process for every branch.
So here's the promised canonical process. We will be merging repository "extension" into repository "core".
Step 1: Add extension repository as a remote in the core repository and fetch it. The remote is only temporary. It can be later removed. We will disable tag import right here to avoid tag conflicts.
cd /path/to/core-directory git remote add --no-tags extension-remote /path/to/extension-directory git fetch extension-remote
Step 2: Promote remote branch to tracking branch, so that we can make changes before merge. The tracking branch is only temporary. It can be deleted later.
git checkout -b extension-branch extension-remote/master
Step 3: Perform the necessary changes and commit. In most cases, you would move extension code into a subdirectory and delete files already present in the core project. The goal is to avoid conflicts during merge.
mkdir extension mv src README.md extension/ git stage . git commit -m "Moved the extension into a subdirectory"
Step 4: Switch back to master and merge the extension branch.
git switch master git merge --allow-unrelated-histories --no-commit extension-branch git commit -m "Merged the extension"
Step 5: Clean up. Delete the extension branch and extension remote.
git branch -D extension-branch git remote remove extension-remote
And we are done. If you look at the history of the core repository now, you will see that the head revision merges the two repositories and that commits from both histories are preserved.