Post

Git - Moving Commits Between Repositories


I use a local git repository as my scratch/temp workspace when playing around with and developing scripts. I developed this habit a couple of years ago and have stuck with it fairly successfully.

A simple PowerShell script I had been toying with recently, ended up evolving into something that can be used in another project. The script, along with commit history, was in my non-public personal repository. I needed to move it to a public, project related, repository. Preferrably, along with commit history.

If you were to run a quick google search, it would turn up a couple of ways to accomplish this seemingly simple task.

  1. Add the source repo as a remote, fetch, cherry-pick, merge, etc. You can find a comprehensive example of this approach describe by Andrew Marcinkevičius
  2. Export desired commits as patches and import them into the destination repositories.

After playing around with both, I found the second approach to be simpler and dummy-proof.

Exporting Commits

git provides format-patch command, which can export an entire commit in a .patch file. The file is unix mailbox format and contains everything related to the commit, including

Excerpt from documentation:

Name:

git-format-patch - Prepare patches for e-mail submission

Description:

Prepare each commit with its patch in one file per commit, formatted to resemble UNIX mailbox format. The output of this command is convenient for e-mail submission or for use with git am.

There are two ways to specify which commits to operate on.

  1. A single commit, , specifies that the commits leading to the tip of the current branch that are not in the history that leads to the to be output.
  2. Generic expression (see "SPECIFYING REVISIONS" section in gitrevisions(7)) means the commits in the specified range.

Syntax:

1
git format-patch --output-directory "../patches" FIRST_COMMIT_SHA1~..LAST_COMMIT_SHA1 

This command will export all commits between FIRST_COMMIT_SHA1 and LAST_COMMIT_SHA1, inclusive, and create one .patch file per commit in the ../patches directory. The .. and ~.. are standard operators that can be used to specify sets of revisions in git. This can be any of supported methods of specifying ranges. In this case, the ~.. ensures that the revision set will include the starting commit along with the rest.

You can get more information on revision sets and their usage by typing in the following command:

1
git help revisions

Importing Commits

Applying the patch to a different repo is straight forward. The following command will accomplish this task:

1
git am 0001-Example-Patch-File.patch

You can import multiple patches at once by using wildcards, e.g.

1
git am *.patch

Full Example

I’m working within powershell for the following exmaple, but it should work similarly in batch/command prompt.

Create a couple of test repos and a directory for patches

1
2
$ cd ~
~$ mkdir git_test git_test/repo1 git_test/repo2 git_test/patches

Initialize first repo.

1
2
3
4
5
~/git_test$ cd ~/git_test/repo1
~/git_test/repo1$ git init

#OUTPUT
Initialized empty Git repository in /home/nz/git_test/repo1/.git/

Initialize second repo.

1
2
3
4
5
~/git_test/repo1$ cd ~/git_test/repo2
nz@APOLLO:~/git_test/repo2$ git init

#OUTPUT
Initialized empty Git repository in /home/nz/git_test/repo2/.git/

Create a few commits in both repos:

1
2
3
4
5
6
7
8
9
10
11
12
13
# repo 1
$ cd ~/git_test/repo1
~/git_test/repo1$ for i in {0..10}; do   file=$(printf "file_%02d.md" "$i");   echo "## Test File - $i" > "$file";   git add "$file";   git commit -m "Add file $i"; done

#OUTPUT
[main (root-commit) fe39390] Add file 0
 1 file changed, 1 insertion(+)
 create mode 100644 file_00.md
[main b476fe7] Add file 1
 1 file changed, 1 insertion(+)
 create mode 100644 file_01.md
 # ...
 # ...
1
2
3
4
5
6
7
8
9
10
11
12
13
# repo 2
$ cd ~/git_test/repo2
~/git_test/repo2$ for i in {50..55}; do   file=$(printf "file_%02d.md" "$i");   echo "## Test File - $i" > "$file";   git add "$file";   git commit -m "Add file $i"; done

#OUTPUT
[main (root-commit) bf0905d] Add file 50
 1 file changed, 1 insertion(+)
 create mode 100644 file_50.md
[main dca535a] Add file 51
 1 file changed, 1 insertion(+)
 create mode 100644 file_51.md
# ...
# ...

Export patches for file 3 through 6 from repo1:

1
2
3
4
5
6
7
8
$ cd ~/git_test/repo1
~/git_test/repo1$ git format-patch --output-directory=../patches a4fe3b5..cb36189

#OUTPUT
../patches/0001-Add-file-3.patch
../patches/0002-Add-file-4.patch
../patches/0003-Add-file-5.patch
../patches/0004-Add-file-6.patch

Import into repo2:

1
2
3
4
5
6
7
8
$ cd ~/git_test/repo2
~/git_test/repo2$ for file in ~/git_test/patches/*.patch; do git am "$file"; done

#OUTPUT
Applying: Add file 3
Applying: Add file 4
Applying: Add file 5
Applying: Add file 6

Repo2 now has the files 3 through 6, along with original commit date, commit message, and the content.

You can validate with git log

1
2
3
4
5
6
7
8
9
10
11
12
13
~/git_test/repo2$ git log --oneline

#OUTPUT
06c1d8a (HEAD -> main) Add file 6
eb041bc Add file 5
61ef926 Add file 4
9861199 Add file 3
9611cfe Add file 55
ff4a967 Add file 54
2ff6b33 Add file 53
26f7707 Add file 52
dca535a Add file 51
bf0905d Add file 50

Please note that commit hash will change when the patch is applied. You will not retain the original commit hash from the source repo.