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

Create a submodule repository from a folder and keep its git commit history

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

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


详细解决方案

See the note at the end of this answer (last paragraph) for a quick alternative to git submodules using npm ;)

在下面的答案中,您将知道如何从存储库中提取文件夹,并从中创建Git存储库,然后将其包含为子模块而不是文件夹。

灵感来源于GergBayer的文章,将文件从一个Git存储库移动到另一个,保留历史

一开始,我们有这样的东西:

1
2
3
4
5
<git repository A>
    someFolders
    someFiles
    someLib <-- we want this to be a new repo and a git submodule!
        some files

在下面的步骤中,我将把这个someLib称为

最后,我们将有如下内容:

1
2
3
4
5
6
7
8
<git repository A>
    someFolders
    someFiles
    @submodule --> <git repository B>

<git repository B>
    someFolders
    someFiles

号从其他存储库中的文件夹创建新的Git存储库步骤1

获取要拆分的存储库的新副本。

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

步骤2

当前文件夹将是新的存储库,因此请删除当前远程文件夹。

1
git remote rm origin

。步骤3

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

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

现在,您应该有一个Git存储库,其中包含来自directory 1的文件,该存储库位于repo的根目录中,包含所有相关的提交历史记录。

步骤4

创建在线存储库并推送新的存储库!

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

您可能需要设置upstream分支以进行第一次推送

1
git push --set-upstream origin master

。清洗(可选,见注释)

我们想从中删除的跟踪(文件和提交历史记录),因此该文件夹的历史记录只存在一次。

这是基于从GitHub中删除敏感数据。

转到新文件夹并

1
2
3
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

替换为要删除的文件夹。-r将在指定的目录中递归地执行它。现在用--force推到origin/master

1
git push origin master --force

。凸台阶段(见下文注释)

创建从的子模块

1
2
3
git submodule add <git repository B url>
git submodule update
git commit

核实是否一切按预期工作,并核实push

1
git push origin master

。注释

在做了所有这些之后,我意识到在我的案例中,使用NPM来管理自己的依赖关系更为合适。我们可以指定git URL和版本,将package.json git URL视为依赖项。

如果您这样做,您想要用作需求的存储库必须是一个NPM模块,因此它必须包含一个package.json文件,否则您会得到这个错误:Error: ENOENT, open 'tmp.tgz-unpack/package.json'

TLDR(替代方案)

您可能会发现使用NPM和使用Git URL管理依赖项更容易:

  • 将文件夹移动到新存储库
  • 在两个存储库中运行npm init
  • 在需要安装依赖项的地方运行npm install --save git://github.com/user/project.git#commit-ish


@gableroux的解决方案将压扁分支和相关提交。

克隆和保留所有这些额外分支和提交的简单方法:

1-确保您有这个git别名

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

2-克隆遥控器,拉所有分支,更改遥控器,筛选目录,推送

1
2
3
4
5
6
7
8
9
git clone [email protected]:user/existing-repo.git new-repo
cd new-repo
git clone-branches
git remote rm origin
git remote add origin [email protected]:user/new-repo.git
git remote -v
git filter-branch --subdirectory-filter my_directory/ -- --all
git push --all
git push --tags


除了使用git lfs并且在要分离的目录下有大文件之外,Gableroux的解决方案工作得很好。在这种情况下,在步骤3之后,所有的大文件将保留为指针文件,而不是真正的文件。我想这可能是因为在过滤分支过程中删除了.gitattributes文件。

意识到这一点,我发现以下解决方案适用于我:

1
cp .gitattributes .git/info/attributes

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

完成过滤器分支后,如果您仍想为新的存储库使用git-lfs,请不要忘记放回.gitattributes

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