将许多子目录分离到一个新的、独立的 Git 存储库中

这个问题是基于 将子目录分离到单独的 Git 存储库中

我不想分离单个子目录,而是想分离两个子目录。例如,我的工作目录树看起来像这样:

/apps
/AAA
/BBB
/CCC
/libs
/XXX
/YYY
/ZZZ

我想说的是:

/apps
/AAA
/libs
/XXX

git filter-branch--subdirectory-filter参数不起作用,因为它在第一次运行时除了给定的目录之外,删除了所有内容。我认为对所有不需要的文件使用 --index-filter参数会有用(尽管很乏味) ,但是如果我尝试多次运行它,我会得到以下信息:

Cannot create a new backup.
A previous backup already exists in refs/original/
Force overwriting the backup with -f

- 有什么想法吗?-TIA

49876 次浏览

是的。通过在随后对 filter-branch的调用中使用 -f标志来重写该警告,从而强制重写备份。:)否则,我认为您有解决方案(即,使用 filter-branch一次消除一个不需要的目录)。

在这里回答我自己的问题... 经过了很多的尝试和错误。

我设法使用 git subtreegit-stitch-repo的组合来做到这一点。这些说明是基于:

首先,我把我想要保存在它们各自独立存储库中的目录拿出来:

cd origRepo
git subtree split -P apps/AAA -b aaa
git subtree split -P libs/XXX -b xxx


cd ..
mkdir aaaRepo
cd aaaRepo
git init
git fetch ../origRepo aaa
git checkout -b master FETCH_HEAD


cd ..
mkdir xxxRepo
cd xxxRepo
git init
git fetch ../origRepo xxx
git checkout -b master FETCH_HEAD

然后,我创建了一个新的空存储库,并将最后两个导入/拼接到其中:

cd ..
mkdir newRepo
cd newRepo
git init
git-stitch-repo ../aaaRepo:apps/AAA ../xxxRepo:libs/XXX | git fast-import

这创建了两个分支,master-Amaster-B,每个分支都持有一个缝合的回购协议的内容。把它们结合起来清理:

git checkout master-A
git pull . master-B
git checkout master
git branch -d master-A
git branch -d master-B

现在我不太确定这是如何/何时发生的,但是在第一个 checkoutpull之后,代码神奇地合并到主分支中(任何关于这里发生的事情的见解都是值得赞赏的!)

一切似乎都按预期工作,除了如果我查看 newRepo提交历史,当变更集同时影响 apps/AAAlibs/XXX时会出现重复。如果有办法去除副本,那就太完美了。

为什么要多次运行 filter-branch?您可以在一次扫描中完成所有操作,因此不需要强制执行(注意,您需要在您的 shell 中启用 extglob才能执行此操作) :

git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch $(ls -xd apps/!(AAA) libs/!(XXX))" --prune-empty -- --all

这应该可以去除不需要的子目录中的所有更改,并保留所有的分支和提交(除非它们只影响被剪裁的子目录中的文件,通过 --prune-empty)——重复提交没有问题等等。

此操作之后,不需要的目录将被 git status列为取消跟踪。

$(ls ...)是必需的。 extglob由 shell 而不是索引过滤器来评估,索引过滤器使用 sh内建的 eval(其中 extglob不可用)。请参阅 如何在 git 中启用 shell 选项?了解更多细节。

不必处理 subshell 并使用 ext globb (如 Kynan 建议) ,尝试这种更简单的方法:

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- apps/AAA libs/XXX' --prune-empty -- --all

正如 指针的注释所提到的,这将从当前存储库中删除除 apps/AAAlibs/XXX之外的所有内容。

修剪空的合并提交

这留下了许多空洞的合并。如 拉菲尼斯在其 回答中所述,这些可以通过另一种方法去除:

git filter-branch --prune-empty --parent-filter \
'sed "s/-p //g" | xargs -r git show-branch --independent | sed "s/\</-p /g"'

Something 警告 : 上面必须使用 sedxargs的 GNU 版本,否则当 xargs失败时将删除所有提交。然后使用 gsedgxargs:

git filter-branch --prune-empty --parent-filter \
'gsed "s/-p //g" | gxargs git show-branch --independent | gsed "s/\</-p /g"'

我已经编写了一个 git 过滤器来解决这个问题。 它有一个很棒的名字 git _ filter,位于这里的 github:

Https://github.com/slobobaby/git_filter

它是基于优秀的 libgit2。

我需要用许多次提交(约100000次)拆分一个大型存储库,基于 git 过滤器分支的解决方案需要几天时间才能运行。Git _ filter 需要花一分钟来做同样的事情。

使用‘ git splits’git 扩展

git splits 是一个 bash 脚本,它是我基于 开玩笑的解决方案作为 git 扩展创建的 git branch-filter的包装器。

它就是为这种情况而制造的。对于您的错误,请尝试使用 git splits -f选项强制删除备份。因为 git splits在一个新的分支上运行,所以它不会重写当前的分支,所以备份是无关的。有关更多细节和 一定要把它用在复制/克隆你的回购文件上(以防万一!),请参见自述文件。

  1. 安装 git splits
  2. 将目录拆分为本地分支 # 转到你的回购目录 Cd/path/to/repo # 检查树枝 Git 结帐
    # 将多个目录分割成新的分支 XYZ Git splits-b XYZ apps/AAA libs/ZZZ

  3. 在某个地方建立一个空的回收站。我们假设在 GitHub 上创建了一个名为 xyz的空回购,其路径为 git@github.com:simpliwp/xyz.git

  4. 推到新的回收站。 # 为空回购添加一个新的远程原点这样我们就可以在 GitHub 上推送空回购了 Git remote add source _ xyz git@github.com: simliwp/xyz.git # 把树枝推向空空的回购主树枝 Git push source _ XYZ XYZ: master

  5. 将新创建的远程回购复制到一个新的本地目录中
    # 把工作目录换掉 Cd/path/to/where/you/want/the/new/local/repo # 克隆你刚刚推到的远程回购 Git 克隆 git@github.com: simliwp/xyz.git

像消息建议的那样,删除 refs/atom 目录下. git 目录下的备份。目录是隐藏的。

使用简单的 git 命令的手动步骤

该计划是将个别目录分割成自己的回购协议,然后将它们合并在一起。下面的手动步骤没有使用极客使用的脚本,而是使用易于理解的命令,可以帮助将额外的 N 个子文件夹合并到另一个存储库中。

分开

让我们假设您的原始回购是: 原始资料

1-拆分应用程序:

git clone original_repo apps-repo
cd apps-repo
git filter-branch --prune-empty --subdirectory-filter apps master

2-劈腿

git clone original_repo libs-repo
cd libs-repo
git filter-branch --prune-empty --subdirectory-filter libs master

如果您有2个以上的文件夹,请继续。现在您将有两个新的和临时的 git 存储库。

通过合并 app 和 libs 来征服

3-准备全新的回购:

mkdir my-desired-repo
cd my-desired-repo
git init

你至少要做出一个承诺。如果跳过以下三行,你的第一个回购将立即出现在你的回购的根目录下:

touch a_file_and_make_a_commit # see user's feedback
git add a_file_and_make_a_commit
git commit -am "at least one commit is needed for it to work"

提交临时文件后,后面部分中的 merge命令将按预期停止。

根据用户的反馈,您可以选择添加 .gitignoreREADME.md等,而不是添加像 a_file_and_make_a_commit这样的随机文件。

4-合并应用程序首先回购:

git remote add apps-repo ../apps-repo
git fetch apps-repo
git merge -s ours --no-commit apps-repo/master # see below note.
git read-tree --prefix=apps -u apps-repo/master
git commit -m "import apps"

现在您应该看到新存储库中的 应用程序目录。

注意: 正如 Chris 在下面的评论中指出的,对于 git 的更新版本(> = 2.9) ,您需要使用 git merge指定 --allow-unrelated-histories

5-以同样的方式合并即兴回购:

git remote add libs-repo ../libs-repo
git fetch libs-repo
git merge -s ours --no-commit libs-repo/master # see above note.
git read-tree --prefix=libs -u libs-repo/master
git commit -m "import libs"

如果你有超过2个回购合并继续。

参考资料: 将另一个存储库的子目录与 git 合并

git clone git@example.com:thing.git
cd thing
git fetch
for originBranch in `git branch -r | grep -v master`; do
branch=${originBranch:7:${#originBranch}}
git checkout $branch
done
git checkout master


git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- dir1 dir2 .gitignore' --prune-empty -- --all


git remote set-url origin git@example.com:newthing.git
git push --all

一个简单的解决方案: git-filter-repo

我遇到过类似的问题,在回顾了这里列出的各种方法之后,我发现了 Git-filter-repo。在官方 git 文档 给你中,推荐将其作为 git 过滤器分支的替代方法。

要从现有存储库中的目录子集创建新存储库,可以使用以下命令:

git filter-repo --path <file_to_keep>

通过链接过滤多个文件/文件夹:

git filter-repo --path keepthisfile --path keepthisfolder/

因此,对于 回答原来的问题,使用 git-filter-repo,您只需要以下命令:

git filter-repo --path apps/AAA/ --path libs/XXX/

我认为导出触及这些路径的提交更容易:

git log --pretty=email --patch-with-stat --reverse --full-index --binary -- /apps/{AAA,BBB,CCC} /libs/{XXX,YYY,ZZZ} > subdir.patch

然后将这些承诺导入新的回购协议:

git am < subdir.patch

如果你有合并提交,不能重新基础,你可能想尝试与 -m --first-parent:

git log --pretty=email --patch-with-stat --reverse --full-index --binary -m --first-parent -- <your paths>