从文件夹创建子模块仓库并保留其Git提交历史记录

141

我有一个 Web 应用程序,以特定方式探索其他 Web 应用程序。 它包含一些 Web 演示,存储在 demos 文件夹中,其中一个演示现在应该拥有自己的存储库。 我想为此演示应用程序创建一个单独的存储库,并使其成为主存储库的一个子模块,而不会丢失其中的提交历史记录。

从存储库文件夹中保留提交历史记录并创建一个存储库,然后将其用作子模块,是否可行?


我一直在寻找如何将Git仓库A中的目录1移动到Git仓库B中。感谢提供文章链接。 - eQ19
4
重复的问题?https://dev59.com/tGcs5IYBdhLWcg3w3Hun - naught101
是的,这确实非常相似,解决方案有些不同,感谢分享。 - GabLeRoux
4个回答

239

详细解决方案

请查看本答案末尾的注释(最后一段)以了解使用npm快速替代git子模块的方法 ;)

在下面的回答中,您将了解如何从存储库中提取一个文件夹,并将其作为Git存储库包含为子模块,而不是一个文件夹。

受 Gerg Bayer 文章 Moving Files from one Git Repository to Another, Preserving History 的启发。

起初,我们有类似于这样的东西:

<git repository A>
    someFolders
    someFiles
    someLib <-- we want this to be a new repo and a git submodule!
        some files
在以下步骤中,我将将此someLib称为<directory 1>。 最终,我们将拥有类似于这样的东西:
<git repository A>
    someFolders
    someFiles
    @submodule --> <git repository B>

<git repository B>
    someFolders
    someFiles

从一个仓库中的文件夹创建新的git仓库

步骤1

获取要拆分的仓库的最新版本。

git clone <git repository A url>
cd <git repository A directory>

步骤2

当前文件夹将成为新的仓库,因此请移除当前远程。

git remote rm origin

步骤三

提取所需文件夹的历史记录并提交

git filter-branch --subdirectory-filter <directory 1> -- --all

现在您应该拥有一个git仓库,其中包含directory 1中的文件,所有相关提交历史都在仓库的根目录中。

第四步

创建您的在线仓库并将新的仓库推送!

git remote add origin <git repository B url>
git push

在您第一次推送之前,您可能需要设置upstream分支。

git push --set-upstream origin master

清理 <git 仓库 A> (可选,详见注释)

我们希望从 <git 仓库 A> 中删除 <git 仓库 B> 的痕迹(文件和提交历史),以使该文件夹的历史记录只留下一次。

此操作基于从 GitHub 上删除敏感数据

进入新文件夹并执行以下操作:

git clone <git repository A url>
cd <git repository A directory>
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch <directory 1> -r' --prune-empty --tag-name-filter cat -- --all

<directory 1>替换为您要删除的文件夹。使用-r可以递归地在指定目录内执行此操作:)。现在,使用--force推送到origin/master

git push origin master --force

最终BOSS阶段(见下面注释)

<git 代码库 B>创建一个子模块<git 代码库 A>

git submodule add <git repository B url>
git submodule update
git commit

验证一切是否按预期工作并执行push

git push origin master

注意

在做完所有这些之后,我意识到在我的情况下,更合适的是使用npm来管理自己的依赖关系。我们可以指定git url和版本,请参阅package.json git urls as dependencies

如果您以这种方式进行操作,则要使用作为要求的存储库必须是一个npm模块,因此它必须包含一个package.json文件,否则您将收到此错误:Error: ENOENT, open 'tmp.tgz-unpack/package.json'

tldr(另一种解决方案)

您可能会发现使用npm通过git urls管理依赖关系更容易:

  • 将文件夹移至新存储库
  • 在两个存储库中都运行npm init
  • 在您想要安装依赖项的位置运行npm install --save git://github.com/user/project.git#commit-ish

46
应避免执行“清除<Git 代码库 A>”这一步骤。这样做会导致您无法完全还原/检出历史版本/提交。您应该只需使用“git rm”命令删除文件夹并添加子模块,以确保在检出旧的提交时拥有一个完整可用的副本。 - Cybot
1
在第二步之前,你不应该执行 cd someLib 吗?你说“当前文件夹将成为新的存储库”,但实际上并不是这样;新的存储库(子模块)在那个文件夹内部。 - Jago
1
确认:是的,它适用于多个子模块。非常感谢你详细的回答。而且,我不需要使用npm。 - Breno Inojosa
2
我会在第三步中创建的refs/original/...添加信息 - Emile Bergeron
7
GitHub发表了一篇关于如何将文件夹分离到一个新仓库的文章:https://help.github.com/articles/splitting-a-subfolder-out-into-a-new-repository/ - jrobichaud
显示剩余8条评论

13

@GabLeRoux提供的解决方案会压缩分支和相关提交记录。

一个简单的方法是克隆并保留所有额外的分支和提交记录:

1 - 确保你有这个git别名

git config --global alias.clone-branches '! git branch -a | sed -n "/\/HEAD /d; /\/master$/d; /remotes/p;" | xargs -L1 git checkout -t'

2- 克隆远程仓库,拉取所有的分支,更改远程地址,过滤目录,推送

git clone git@github.com:user/existing-repo.git new-repo
cd new-repo
git clone-branches
git remote rm origin
git remote add origin git@github.com:user/new-repo.git
git remote -v
git filter-branch --subdirectory-filter my_directory/ -- --all
git push --all
git push --tags

它运行良好,除了 LFS(请参见下面 ls 的答案)和标签:在我的情况下,它会为整个父目录重新创建新存储库,因为标签是为整个父目录创建的。 我不需要那个。 - YaP

7
GabLeRoux的解决方案很好,但如果您使用git lfs并且在要分离的目录下有大型文件,则所有大型文件都将保留为指针文件而不是实际文件。我猜这可能是由于过滤器分支过程中删除了.gitattributes文件导致的。
认识到这一点后,我发现以下解决方案适用于我:
cp .gitattributes .git/info/attributes

将git lfs用于跟踪大文件的.gitattributes复制到.git/目录中,以避免被删除。

当filter-branch完成后,如果您仍然想为新存储库使用git lfs,请不要忘记重新放置.gitattributes:

mv .git/info/attributes .gitattributes
git add .gitattributes
git commit -m 'added back .gitattributes'

非常有用,很难找到! - YaP

2

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