Extracting git Changes to a Separate Repository

Clayton Carter •

“I really need to extract all changes (ever) to this file and move them into a different repository.”
– pretty much nobody ever

But what if

In my case, I had written a micro-library as part of one project (repo) and then later realized that I’d like it to be separated out into it’s own repo. This included ~10 commits to ~3 files, over the course of over a year. Here’s how I did it…

1. Create the new repo, with backdating

 mkdir jx
cd jx

 git new
...

 git commit --amend --allow-empty --date '<paste first commit date>'
...
  • new is an alias to create a new repo with an empty commit (or whatever contents are already present)
  • the date was taken from git show --name-only <commit> in the old project, or from the first patch file

2. Find the commits to extract

 git lol -- public/js/jx.js
23ecb5d (1 year, 9 months ago) feat(js): add jx micro-library - Clayton Carter
330c719 (1 year, 9 months ago) feat(jx): add computed props - Clayton Carter
e59b878 (1 year, 9 months ago) feat(jx): add support for reactive data objects - Clayton Carter
3a80ffa (1 year, 9 months ago) feat(jx): add comments - Clayton Carter
eba1567 (1 year, 4 months ago) test(jx): add test for jx-data-class - Clayton Carter
2f00146 (9 months ago) feat(jx): add jx-init attribute - Clayton Carter
d8f3e6f (8 months ago) refactor(jx): remove dead code - Clayton Carter
  • lol is an alias for log --one-line or something like that

3. Extract the commits as patches

You can probably tell by the dates above, but the first 4 commits where a consecutive series, and the rest were individuals.

 git format-patch 23ecb5d~..3a80ffa
0001-feat-js-add-jx-micro-library.patch
0002-feat-jx-add-computed-props.patch
0003-feat-jx-add-support-for-reactive-data-objects.patch
0004-feat-jx-add-comments.patch

 git format-patch -1 --start-number=5 eba1567
0005-test-jx-add-test-for-jx-data-class.patch

 git format-patch -1 --start-number=6 2f00146
0006-feat-jx-add-jx-init-attribute.patch

 git format-patch -1 --start-number=7 d8f3e6f
0007-refactor-jx-remove-dead-code.patch
  • note the ~ (first parent operator) in that first line: I wanted the commit range to include commit 23ecb5d, but .. excludes the given commit

4. Move the patches to the new project, and clean them up

 mv 00*patch ~/src/jx

 rp 00\* website/public/js/ '' -w

 rp 00\* website/resources/js/ '' -w

This is the heavy lifting, but rp makes it not very heavy! rp is an alias for rep, a tool to make find and replace easy and fast from the command line. I use it all the time and love it! But what is it really doing?

In the original project, the files were located in different directories than where I want them to be in the new project. These calls to rp tell it find occurances of website/public/js/ in all 00* files (glob matched, not regex matched) and remove them (replace them with an empty string). In other words:

  • website/public/js/jx.jsjx.js
  • website/resources/js/tests/jx.spec.jstests/jx.spec.js

This is roughly equivalent to sed -i s/website\/public\/js\///g <file> for each file.

But why? The original patches each have sections that look like this:

diff --git a/website/public/js/jx.js b/website/public/js/jx.js
index f79ce90..55a077b 100644
--- a/website/public/js/jx.js
+++ b/website/public/js/jx.js
@@ -1,83 +1,125 @@
... followed by diff contents

By editing these patches, we are basically just moving/renaming the files before they’re even created in the new repo. The edited patch will end up looking like this:

diff --git a/jx.js b/jx.js
index f79ce90..55a077b 100644
--- a/jx.js
+++ b/jx.js
@@ -1,83 +1,125 @@
... followed by diff contents

Which will put the new jx.js file will in the root of the new repo.

5. Remove unwanted changes (optional)

In my case, one of the commits (patches) included a small edit to a file that I don’t want or need in the new project. The patch looked like this:

+ ... preceded by other diff contents
diff --git a/website/webpack.mix.js b/website/webpack.mix.js
index c60ff49..34927f3 100644
--- a/website/webpack.mix.js
+++ b/website/webpack.mix.js
@@ -38,6 +38,7 @@ const jsAssets = [
     'fedco',
     'form-spinner',
     '../vendor/big',
+    'jx',
 ];

 const cssAssets = [
--
2.48.1

I deleted everything from diff through (including) the line that includes const cssAssets, just above the -- line. (I don’t know if those last 2 lines are important, but I didn’t want to find out, so I left them. That last one looks like my git version.)

The edited diff looked like this:

+ ... preceded by other diff contents
--
2.48.1

Presumably, if the diff-to-delete wasn’t at the end of the patch, you would just delete up to the next line that starts with diff.

6. Apply the Patches

Now just run git am < 0001-feat-js-add-jx-micro-library.patch for every patch, or do a batch patch with for patch in 00*; git am < $patch; done.

7. Done!

Check your work:

❯ tree
.
├── jx.js
└── tests
    └── jx.spec.js

1 directory, 2 files

❯ git lol
042090b (1 year, 9 months ago) Start project; initial commit - Clayton Carter
5c6d869 (1 year, 9 months ago) feat(js): add jx micro-library - Clayton Carter
c322339 (1 year, 9 months ago) feat(jx): add computed props - Clayton Carter
76bc184 (1 year, 9 months ago) feat(jx): add support for reactive data objects - Clayton Carter
85e982a (1 year, 9 months ago) feat(jx): add comments - Clayton Carter
dec3324 (1 year, 4 months ago) test(jx): add test for jx-data-class - Clayton Carter
c5cb813 (9 months ago) feat(jx): add jx-init attribute - Clayton Carter
aa71f87 (8 months ago) refactor(jx): remove dead code - Clayton Carter (HEAD -> main, origin/main, origin/HEAD)

That’s it! Yak: shaved.