Skip to main content

How to Merge Git Repositories While Preserving History

· Git, Version Control, Repository Management

Introduction

If you manage multiple Git repositories and want to consolidate them into a single repository, you need to choose the right merge strategy.

This guide covers two main approaches:

Both methods preserve complete commit history, but they are suited for different use cases.

Why Merge Repositories?

There are several reasons to merge repositories:

Requirements

Before starting, make sure:

A backup can be as simple as:

git clone /path/to/repo /path/to/repo-backup

Method 1: Remote Merge (Flat Structure)

This method merges two repositories at the root level.

The result is a single branch with both histories combined.

When to Use Remote Merge

Use this method when:

How Remote Merge Works

# In the target repository
cd /path/to/target-repo

# Add source repo as a remote
git remote add source-repo /path/to/source-repo

# Fetch commits from source
git fetch source-repo

# Merge with allow-unrelated-histories flag
git merge source-repo/master --allow-unrelated-histories -m "merge: add source repo"

# Clean up the remote
git remote remove source-repo

Why --allow-unrelated-histories

By default, Git refuses to merge repositories with no common ancestor.

The --allow-unrelated-histories flag tells Git to proceed anyway.

This is safe when you intentionally want to combine two independent repos.

Handling Conflicts

If both repositories have files with the same name, Git will report a conflict.

You need to choose which version to keep:

# Use the version from the source repo
git checkout --theirs conflicting-file.md

# Use the version from the target repo
git checkout --ours conflicting-file.md

# Stage the resolved file
git add conflicting-file.md

# Complete the merge
git commit

Example: Merging a build-scripts repo into a project repo

Suppose you have:

You can merge build-scripts into project-repo:

cd ~/repos/project-repo

git remote add build /home/user/repos/build-scripts
git fetch build
git merge build/main --allow-unrelated-histories -m "merge: add build scripts"

# If there is a conflict in README.md
git checkout --theirs README.md
git add README.md
git commit -m "merge: add build scripts"

git remote remove build

Result:

*   a1b2c3d merge: add build scripts
|\
| * e4f5g6h docs: update build instructions
| * h7i8j9k feat: add Docker build
* k0l1m2n feat: add user authentication
* n3o4p5q fix: resolve login bug

Both histories are preserved and merged at a single point.

Method 2: Subtree Merge (Directory Structure)

This method imports a repository into a subdirectory.

It keeps the imported repo organized and separated from the main repo content.

When to Use Subtree Merge

Use this method when:

How Subtree Merge Works (Without Squashing)

# In the target repository
cd /path/to/target-repo

# Add repo as a subtree under a specific path
git subtree add --prefix="libs/module-name" /path/to/source-repo main

This preserves all individual commits from the source repo.

How Subtree Merge Works (With Squashing)

# Add repo as a subtree with squashed history
git subtree add --prefix="libs/module-name" /path/to/source-repo main --squash

This condenses the entire history into a single commit.

With or Without Squashing?

AspectWithout --squashWith --squash
Commit countFull history preservedSingle commit
Use caseImportant historyLarge external libs
ClarityDetailed timelineClean summary

Use squashing when:

Skip squashing when:

Example: Organizing multiple repos into a monorepo

Suppose you have:

You want to organize them under a single monorepo:

cd ~/repos/monorepo

# Add frontend under packages/frontend
git subtree add --prefix="packages/frontend" ~/repos/web-app main

# Add backend under packages/backend
git subtree add --prefix="packages/backend" ~/repos/api-server main

# Add utilities under packages/utils with squashed history
git subtree add --prefix="packages/utils" ~/repos/shared-utils main --squash

Result:

monorepo/
├── packages/
│   ├── frontend/  (full commit history from web-app)
│   ├── backend/   (full commit history from api-server)
│   └── utils/     (single squashed commit from shared-utils)

The commit graph shows separate branches for each subtree:

*   x9y8z7 Add 'packages/utils/' from commit 'a1b2c3'
|\
| * a1b2c3 Squashed 'packages/utils/' content
*   w6v5u4 Add 'packages/backend/' from commit 't3s2r1'
|\
| * t3s2r1 feat: add user routes
| * q0p9o8 fix: database connection
*   n7m6l5 Add 'packages/frontend/' from commit 'k4j3i2'
|\
| * k4j3i2 feat: add login page
| * h1g0f9 feat: setup React app
* e8d7c6 docs: initial monorepo setup

Moving Files After Subtree Merge

After importing a repo with subtree, you may want to reorganize files.

Use git mv to preserve history:

# Remove old version if it exists
git rm old-location/file.txt

# Move files with git mv
git mv source-dir/file.txt target-dir/file.txt

# Move all files in a directory
for item in source-dir/*; do
  git mv "$item" target-dir/
done

# Remove empty directory
rmdir source-dir

# Commit the reorganization
git commit -m "refactor: reorganize project structure"

Using Remote URLs Instead of Local Paths

All examples above use local paths, but you can also use remote URLs:

# Remote merge with GitHub URL
git remote add source git@github.com:username/repo.git
git fetch source
git merge source/main --allow-unrelated-histories

# Subtree merge with GitHub URL
git subtree add --prefix="vendor/lib" git@github.com:username/lib.git main

If you encounter authentication issues with HTTPS, use SSH instead:

# HTTPS (may require password)
git@github.com:username/repo.git

# SSH (uses SSH keys)
git@github.com:username/repo.git

Comparison Table

FeatureRemote MergeSubtree (No Squash)Subtree (Squashed)
Directory structureFlat (root)Organized (subdir)Organized (subdir)
Commit historyFull, interleavedFull, separateSingle commit
Best forCombining similar projectsImporting modulesImporting large libs
ComplexitySimpleModerateSimple

Troubleshooting

Error: refusing to merge unrelated histories

Add --allow-unrelated-histories:

git merge source/main --allow-unrelated-histories

Error: Authentication failed

Use SSH instead of HTTPS:

git@github.com:user/repo.git

Conflict in binary files

Choose one version explicitly:

git checkout --theirs file.pdf
git add file.pdf
git commit

Subtree command not found

Make sure you are using a recent version of Git:

git --version

git subtree is available in Git 1.7.11+.

Best Practices

Before merging repositories, follow these best practices:

Example cleanup:

git remote remove temp-remote

Example commit message:

git commit -m "merge: consolidate frontend and backend repos for easier maintenance"

Conclusion

Merging Git repositories while preserving history is straightforward once you understand the two main methods:

Choose the method based on your project structure and whether individual commit history matters.

Always test merges locally first, handle conflicts carefully, and document the reasoning behind your merge decisions.