Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Port feature of forked git repo to original without pulling in entire forked repo

Tags:

git

I'm just learning git and I have a bit of a conundrum...

There are two projects that I do not own:

  • Delila
  • Julio

Background

  • Delila was forked from Julio some time in the ancient distant past.
  • Julio has continued development apace.
  • I have a feature that is in Delila that I want to put into Julio.
  • For the owner of Julio, the feature makes no sense, as it is not generic enough (which is why the original author of Delila never bothered to offer a pull request).
  • Delila clearly doesn't want all the new stuff from Julio so offering a mammoth pull request with all the new stuff from Julio to Delila would not be sensible either.

Hairbrained Scheme

Now, I want to take one file from Delila and put it into Julio since Julio contains a heap of goodness I need and Delila contains that feature of wonderment that I could really do with.

So, pragmatically, I can do this:

  1. Fork Julio to a new project that I actually own (I'll call this one Benito)
  2. git clone Delila to get the old code
  3. git clone Benito to get my fork of the new code
  4. Copy the file I need from the directory containing Delila to Benito
  5. Tweak it a little bit so that it works with the new stuff around it, and git add it into Benito
  6. git commit Benito, git push it, go and make myself a well deserved cup of tea

Except the tea tastes sour. I've taken credit for someone else's work (if you look in the history of the file you see only me) and I've lost, potentitally useful historical information (why oh why oh why did that crazy loon do that..?).

So what would the correct thing be to do in this case?


Update

Thanks to @djechlin I have been playing with this, but I still cannot make it work. Here I have mocked up the situation by creating the two repositories Julio and Delila locally. So, here is the starting situation:

Julio

~/playing/Julio (master)
$ git log
commit f72960c18392d843d40adfd1c7ab943162005879
Author: xxxxx
Date:   Tue Sep 24 08:46:50 2013 +0200

    A change after Delila left the building

commit eca80d52acefcb02baae48e717bd8c2d98685c5e
Author: xxxxx
Date:   Tue Sep 24 08:31:15 2013 +0200

    initial commit from Julio

Delila

~/playing/Delila (master)
$ git log
commit 0e7c530246bc782dbf30fb4ac425e031d3626bbe
Author: xxxxx
Date:   Tue Sep 24 08:39:06 2013 +0200

    Added changes for Delila

commit eca80d52acefcb02baae48e717bd8c2d98685c5e
Author: xxxxx
Date:   Tue Sep 24 08:31:15 2013 +0200

    initial commit from Julio

You can see that Delila has been forked from Julio and some changes have happend to it. Specifically, the Added changes for Delila commit is the one that I want to maintain.

Now, starting in ~/playing I do this, as per the answer:

 ~/playing
$ mkdir me

 ~/playing
$ cd me

 ~/playing/me
$ git clone ../Delila
Cloning into 'Delila'...
done.

 ~/playing/me
$ cd Delila

 ~/playing/me/Delila (master)
$ git remote rm origin

 ~/playing/me/Delila (master)
$ git filter-branch --subdirectory-filter someFolder -- --all
Rewrite 0e7c530246bc782dbf30fb4ac425e031d3626bbe (2/2)
Ref 'refs/heads/master' was rewritten

 ~/playing/me/Delila (master)
$ mkdir someFolder

 ~/playing/me/Delila (master)
$ mv * someFolder
mv: cannot move `someFolder' to `someFolder/someFolder'

Now, I do not want the entire folder so I do this:

 ~/playing/me/Delila (master)
$ git rm theNewFeature
rm 'theNewFeature'

 ~/playing/me/Delila (master)
$ git rm anotherFileFromJulio
rm 'anotherFileFromJulio'

Is this the wrong thing to do?

Back to the answer:

 ~/playing/me/Delila (master)
$ git add .

 ~/playing/me/Delila (master)
$ git commit -m "filtered Delila"
[master b7fde89] filtered Delila
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename anotherFileFromJulio => someFolder/anotherFileFromJulio (100%)
 rename theNewFeature => someFolder/theNewFeature (100%)

Now I want to "fork" Julio so I do this:

 ~/playing/me
$ git clone ../Julio
Cloning into 'Julio'...
done.

 ~/playing/me
$ cd Julio

 ~/playing/me/Julio (master)
$ git remote rm origin

OK, back to the steps from the answer...

 ~/playing/me/Julio (master)
$ git remote add repo-A-branch ../Delila

 ~/playing/me/Julio (master)
$ git pull repo-A-branch master
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 8 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (8/8), done.
From ../Delila
 * branch            master     -> FETCH_HEAD
Merge made by the 'recursive' strategy.
 someFolder/theNewFeature | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 someFolder/theNewFeature

 ~/playing/me/Julio (master)
$ git remote rm repo-A-branch

That should be it, so let's look at the log of our special feature we have ported...

 ~/playing/me/Julio/someFolder (master)
$ git log theNewFeature
commit b7fde8940d761f7babe13d8b6cdfa12fe1685390
Author: xxxxx
Date:   Tue Sep 24 09:01:43 2013 +0200

    filtered Delila

sigh So what am I doing wrong? Where has the history gone?

like image 211
kmp Avatar asked Dec 10 '25 05:12

kmp


1 Answers

This is actually fully within the range of git's capabilities. In general, this is something you ought to be able to expect git to do. Commits are not just codebase snapshots, they are (roughly - merges create branching) linked list of codebase snapshots with a complete history. Git lets you rewrite history so it resembles what you think should have happened. In particular, you don't want to destroy the file's history, but you want to import that too.

Your issue is you don't want to pull in all of the forked repo. So just use filter-branch to import what you need. This accomplishes in one shot what you need: keep these files, keep their histories, but destroy all history that's not pertinent to these files.

I expect to make this doable for yourself, you will either need to 1) become facile with filter-branch or 2) isolate your relevant files in one directory.

This is described in this post here.

Use filter-branch to destroy everything in the original repo not in the directory/ies you care about. Yes, you want to clone and divorce a copy of the original repo first.

git clone <git repository A url>
cd <git repository A directory>
git remote rm origin
git filter-branch --subdirectory-filter <directory 1> -- --all
mkdir <directory 1>
mv * <directory 1>
git add .
git commit

And, simply, pull and merge from the new repo that only contains the things you care about. Keep in mind git repos don't have some ID that say what repo they are. This is a vanilla operation - there just happens to be a copy of repo B lying around that only contains new files.

git clone <git repository B url>
cd <git repository B directory>
git remote add repo-A-branch <git repository A directory>
git pull repo-A-branch master
git remote rm repo-A-branch
like image 52
djechlin Avatar answered Dec 12 '25 20:12

djechlin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!