将分叉的git仓库的端口功能移植到原始仓库,而无需拉取整个分叉仓库

3

我正在学习git,但遇到了一个难题...

有两个项目不是我的:

  • Delila
  • Julio

背景

  • Delila在很久以前从Julio分叉出来。
  • Julio一直在快速发展。
  • 我有一个在Delila中的功能,我想把它放到Julio中。
  • 对于Julio的所有者而言,该功能没有意义,因为它不够通用(这就是为什么Delila的原始作者从未提供拉取请求的原因)。
  • Delila显然不想要来自Julio的所有新东西,因此提供一个包含来自Julio的所有新东西的巨大拉取请求给Delila也不明智。

荒唐的方案

现在,我想从Delila中取出一个文件并将其放入Julio中,因为Julio包含了我需要的大量好东西,而Delila则包含了我真正需要的奇妙功能。

因此,实际上,我可以这样做:

  1. 将Julio分叉到一个我实际拥有的新项目中(我将称之为Benito)
  2. git clone Delila以获取旧代码
  3. git clone Benito以获取我fork的新代码
  4. 从包含Delila的目录中复制我需要的文件到Benito中
  5. 微调它,使其与周围的新内容一起工作,并将其添加到Benito中
  6. git commit Benito,git push它,然后喝上一杯应得的茶

但是茶味道很酸。我把别人的功劳归于自己(如果你查看该文件的历史记录,只会看到我),而且我失去了潜在有用的历史信息(为什么那个疯子要这样做...?)。

那么在这种情况下正确的做法是什么呢?


更新

感谢@djechlin,我已经尝试过这个问题,但我仍然无法解决它。在此,我通过本地创建两个存储库Julio和Delila来模拟该情况。所以,这是起始情况:

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

你可以看到Delila已经从Julio分叉出来,并进行了一些更改。具体而言,我想保留的是“添加Delila更改”的提交。
现在,按照答案,在~/playing目录下执行以下操作:
 ~/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'

现在,我不想要整个文件夹,所以我这样做:
 ~/playing/me/Delila (master)
$ git rm theNewFeature
rm 'theNewFeature'

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

这样做是否不正确?

回到答案:

 ~/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%)

现在我想要“分叉”Julio,因此我这样做:
 ~/playing/me
$ git clone ../Julio
Cloning into 'Julio'...
done.

 ~/playing/me
$ cd Julio

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

好的,回到答案中的步骤...

 ~/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

那就是这样了,让我们来看一下我们移植的特殊功能的日志...
 ~/playing/me/Julio/someFolder (master)
$ git log theNewFeature
commit b7fde8940d761f7babe13d8b6cdfa12fe1685390
Author: xxxxx
Date:   Tue Sep 24 09:01:43 2013 +0200

    filtered Delila

叹气,那我做错了什么?历史记录去哪儿了?


第四步可以通过使用 git cherry-pick 进行改进。但是,如果没有进行完整的仓库合并,我不确定如何在两个模糊相关的仓库之间执行此操作。请记住,文件的历史记录是指提交,您正在尝试避免移植这些提交。 - djechlin
相关 - http://gbayer.com/development/moving-files-from-one-git-repository-to-another-preserving-history/ - djechlin
2个回答

2
这实际上完全在git的能力范围之内。一般来说,你应该期望git能够做到这一点。提交不仅是代码库快照,它们(粗略地说-合并创建分支)是带有完整历史记录的代码库快照的链接列表。Git允许您重写历史,使其类似于您认为应该发生的事情。特别是,您不想销毁文件的历史记录,但您也想将其导入。
您的问题是您不想拉取所有派生存储库。因此,只需使用filter-branch导入所需内容即可。这可以一次性完成您需要的任务:保留这些文件,保留它们的历史记录,但销毁与这些文件无关的所有历史记录。
我预计要使自己能够做到这一点,您将需要1)熟练掌握filter-branch或2)将相关文件隔离在一个目录中。 此帖子中描述了这一点。 使用filter-branch销毁原始存储库中不在您关心的目录/文件中的所有内容。是的,您需要先克隆并分离原始存储库的副本。
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

只需要从仅包含您关注的内容的新存储库中拉取并合并即可。请记住,Git 存储库没有某些 ID 来说明它们是哪个存储库。这是一种普通操作 - 只是偶然地有一个仅包含新文件的存储库 B 的副本。

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

谢谢你的回答 - 当我执行 git pull repo-A-branch master 后,我会 cd 到包含文件的目录并执行 git logs,但我只看到了我的注释 - 没有完整的历史记录 - 你有什么想法我做错了什么吗? - kmp

0

好的,所以我已经找到了一种方法来做到我期望的效果。正如您从我的更新中可以看到的那样,我认为在过滤之后历史记录没有被保留。实际上,它是有保留的,我只需要将 --follow 传递给参数...

顺便说一下,不要对我进行负面评价,这只是我作为一个试图使用 git 完成工作并感到一些痛苦的人的个人意见:就我个人而言,我觉得这并不是最优的解决方案,因为在一个我不知道的存储库中,我总是需要添加额外的 follow 参数才能获取文件的完整历史记录(所以现在我会创建一个别名来代替仅使用 git log)。此外,github 在其美观的 Web UI 中默认不执行 --follow,我怀疑这是许多人寻找历史记录的地方。

我意识到 github 等可以改进,但从最终用户的角度来看,我只希望 git mv 可以保留历史记录。在我执行 git mv 后,我希望执行 git log 并查看之前的所有内容。

但我只是一个用户,我又懂得什么呢。

无论如何,这就是我所做的,基于Git:从另一个存储库复制文件或目录并保留历史记录

git clone Delila
mkdir ~/mypatches
cd Delila/folderWithFile
export reposrc=fileToKeepHistory
git format-patch -o ~/mypatches $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc

... Fork Julio ...

cd ~/
git clone JulioForked
cd Julio/folderWithFile    
git am ~/mypatches/*.patch

...Make my changes...

git commit -m "my changes"
git push origin master

太好了!现在我在github上看到了那个让我想要保留历史记录的麻烦文件,它以其美妙的光辉呈现在那里!

真是松了一口气。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接