Copy Files Across Git Repositories Keeping Commit History

Posted on 15/09/2022 in posts git


Sometimes we would like to move files between two git repositories, namely, repo1 to repo2. Here, both the repos do not share a commit history and may have a very different directory structure.

One use case is you are developing a standalone side project in repo1 and would like later merge into a larger project repo2.

One easy way is to simply copy files from repo1 to repo2 and then commit them in repo2. However, in this approach we loose all the commit history information of the files in repo1.

We can use a combination of git format-patch and git am --3way commands to achieve this.

The key idea is to first export each commit in repo1 as a patch file. Then apply these patches to repo2.

Demo

Setup Repositories

Let us setup 2 repositories.

~$ git config --global user.email "you@example.com"
~$ git config --global user.name "Your Name"
~$ mkdir -p repo1 repo2

Setup repo1.

~$ cd repo1/
~/repo1$ git init
Initialized empty Git repository in /home/user/repo1/.git/
~/repo1$ touch file1 file2
~/repo1$ echo "Hello World" > file1
~/repo1$ echo "Hello World again" > file2
~/repo1$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        file1
        file2

nothing added to commit but untracked files present (use "git add" to track)
~/repo1$ git add .
~/repo1$ git commit -am "Initial commit"
[master (root-commit) 6d58af8] Initial commit
 2 files changed, 2 insertions(+)
 create mode 100644 file1
 create mode 100644 file2
~/repo1$ git log
commit 6d58af8d140c86de700dd9206cb2c7512c39a74c (HEAD -> master)
Author: Your Name <you@example.com>
Date:   Fri Sep 16 02:48:01 2022 +0000

    Initial commit

Add few more commits:

~/repo1$ echo "New Lines Galore" >> file1
~/repo1$ cat file1
Hello World
New lines galore
~/repo1$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   file1

no changes added to commit (use "git add" and/or "git commit -a")
~/repo1$ git commit -am "Second commit"
[master 3f3c874] Second commit
 1 file changed, 1 insertion(+)
~/repo1$ git log
commit 3f3c874e3487d999c581cf2d2b75f49de2ab5784 (HEAD -> master)
Author: Your Name <you@example.com>
Date:   Fri Sep 16 02:48:49 2022 +0000

    Second commit

commit 6d58af8d140c86de700dd9206cb2c7512c39a74c
Author: Your Name <you@example.com>
Date:   Fri Sep 16 02:48:01 2022 +0000

    Initial commit 

Now setup repo2.

~/repo1$ cd ../repo2
~/repo2$ git init
Initialized empty Git repository in /home/user/repo2/.git/
~/repo2$ echo "This is file3 in new repo" > file3
~/repo2$ git add .
~/repo2$ git commit -am "File3"
[master (root-commit) ce65e35] File3
 1 file changed, 1 insertion(+)
 create mode 100644 file3
~/repo2$ git log
commit ce65e35e465dd5052c0c5f5a84e6ff35a856488d (HEAD -> master)
Author: Your Name <you@example.com>
Date:   Fri Sep 16 02:49:54 2022 +0000

    File3

Exporting commits as patches

Now lets prepare the move from repo1 to repo2 keeping repo1 file history.

First we will export each commit in repo1 as a patch in a temp directory called ~/repo1_patches

~/repo2$ cd ../repo1
~/repo1$ mkdir ../repo1_patches
~/repo1$ git format-patch -o ../repo1_patches --root ./
../repo1_patches/0001-Initial-commit.patch
../repo1_patches/0002-Second-commit.patch

Now we will see what ~/repo1_patches looks like.

~/repo1$ cd ../repo1_patches
~/repo1_patches$ ls -ltrh
total 2.0K
-rw-r--r-- 1 user user 367 Sep 16 02:50 0002-Second-commit.patch
-rw-r--r-- 1 user user 563 Sep 16 02:50 0001-Initial-commit.patch

These are the contents of ./0001-Initial-commit.patch

From 6d58af8d140c86de700dd9206cb2c7512c39a74c Mon Sep 17 00:00:00 2001
From: Your Name <you@example.com>
Date: Fri, 16 Sep 2022 02:48:01 +0000
Subject: [PATCH 1/2] Initial commit

---
 file1 | 1 +
 file2 | 1 +
 2 files changed, 2 insertions(+)
 create mode 100644 file1
 create mode 100644 file2

diff --git a/file1 b/file1
new file mode 100644
index 0000000..557db03
--- /dev/null
+++ b/file1
@@ -0,0 +1 @@
+Hello World
diff --git a/file2 b/file2
new file mode 100644
index 0000000..c3aaa9d
--- /dev/null
+++ b/file2
@@ -0,0 +1 @@
+Hello World again
-- 
2.25.1

These are the contents of ./0002-Second-commit.patch

From 3f3c874e3487d999c581cf2d2b75f49de2ab5784 Mon Sep 17 00:00:00 2001
From: Your Name <you@example.com>
Date: Fri, 16 Sep 2022 02:48:49 +0000
Subject: [PATCH 2/2] Second commit

---
 file1 | 1 +
 1 file changed, 1 insertion(+)

diff --git a/file1 b/file1
index 557db03..acab188 100644
--- a/file1
+++ b/file1
@@ -1 +1,2 @@
 Hello World
+New lines galore
-- 
2.25.1

Applying patches as commits in new repo

Now that our patches are created, let us apply them sequentially to our new repo:

~/repo1_patches$ cd ../repo2
~/repo2$ git status
On branch master
nothing to commit, working tree clean
~/repo2$ git log
commit ce65e35e465dd5052c0c5f5a84e6ff35a856488d (HEAD -> master)
Author: Your Name <you@example.com>
Date:   Fri Sep 16 02:49:54 2022 +0000

    File3
~/repo2$ git am --3way ../repo1_patches/*.patch
Applying: Initial commit
Applying: Second commit
~/repo2$ ls -ltrh
total 3.0K
-rw-r--r-- 1 user user 26 Sep 16 02:49 file3
-rw-r--r-- 1 user user 18 Sep 16 02:53 file2
-rw-r--r-- 1 user user 29 Sep 16 02:53 file1
~/repo2$ git log
commit 3de485c1e24c555d53ede02ab6d52102ec7a2093 (HEAD -> master)
Author: Your Name <you@example.com>
Date:   Fri Sep 16 02:48:49 2022 +0000

    Second commit

commit 483f95d8944ef3b79cbb536dd0b68d9459abbfca
Author: Your Name <you@example.com>
Date:   Fri Sep 16 02:48:01 2022 +0000

    Initial commit

commit ce65e35e465dd5052c0c5f5a84e6ff35a856488d
Author: Your Name <you@example.com>
Date:   Fri Sep 16 02:49:54 2022 +0000

    File3

As we can see the commit history from repo1 is now copied to repo2 along with the files.

Epilogue

This is an easy fix when repo1 is small and there is less likely to be a merge conflict between repo1 and repo2. For more complex, you can add additional commits to move the files around.