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


135

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

我不想分离一个子目录,而是要分离几个。例如,我当前的目录树如下所示:

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

我想要这样:

/apps
  /AAA
/libs
  /XXX

--subdirectory-filter给的说法git filter-branch是行不通的,因为它摆脱了一切除了给定目录的第一次的运行。我以为--index-filter所有不需要的文件都可以使用该参数(尽管很乏味),但是如果尝试多次运行它,则会收到以下消息:

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

有任何想法吗?TIA

Answers:


155

不必处理子外壳并使用ext glob(如kynan所建议的),请尝试这种简单得多的方法:

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

正如void.pointer在他/她的评论中提到的那样,这将删除当前存储库中apps/AAA和之外的所有内容libs/XXX

修剪空合并提交

这留下了许多空的合并。这些可以通过raphinesse在他的回答中所述的另一遍删除:

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

Warning️警告:上面的代码必须使用的GNU版本,否则将删除所有提交sedxargs否则将xargs失败。brew install gnu-sed findutils然后使用gsedgxargs

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

4
此外,--ignore-unmatch标志应该传递给git rm,否则对我来说第一次提交失败(否则,存储库是使用git svn clone创建的)
Pontomedon 2014年

8
假设你有标签的搭配,很可能需要添加--tag-name-filter cat到您的参数
Yonatan

16
您能否添加一些其他信息来解释此冗长的命令在做什么?
Burhan Ali

4
我很惊讶这在使用git bash的Windows上完美运行!

3
@BurhanAli对于历史记录中的每个提交,它将删除除您要保留的文件以外的所有文件。完成所有操作后,仅留下您指定的树的一部分以及该历史记录。
void.pointer

39

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

计划是将各个目录拆分成自己的存储库,然后将它们合并在一起。以下手动步骤未使用极客脚本,而是使用易于理解的命令,可以帮助将多余的N个子文件夹合并到另一个存储库中。

划分

假设您的原始仓库是:original_repo

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

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

通过合并应用程序和库来征服

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后面部分中的命令将按预期停止。

从用户的反馈中获取信息,a_file_and_make_a_commit您可以选择添加.gitignore或,而不是添加诸如的随机文件README.md

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"

现在,您应该在新存储库中看到apps目录。git log应该显示所有相关的历史提交消息。

注:克里斯的评论,为的git的新版本(> = 2.9)低于所指出的,你需要指定--allow-unrelated-historiesgit merge

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"

如果有两个以上的仓库要合并,请继续。

参考:使用git合并另一个存储库的子目录


4
从git 2.9开始,您需要在merge命令上使用--allow-unrelated-histories。否则,这似乎对我来说效果很好。
克里斯(Chris

1
天才!非常感谢你做的这些。我看过的最初答案是,在非常大的存储库上使用树过滤器,git预测将花费26个小时来完成git重写。使用这种简单但可重复的方法会更加愉快,并且已经成功地将4个子文件夹移动到具有所有预期提交历史记录的新存储库中。
shittsy

1
您可以将第一个提交用于添加.gitignoreREADME.md文件的“初始提交” 。
杰克·米勒

2
不幸的是,这种方法似乎破坏了在该git merge .. git read-tree步骤中添加的文件的跟踪历史记录,因为它将它们记录为新添加的文件,而我所有的git gui都未连接到其先前的提交。

1
@ksadjad,老实说,不知道。手动合并的中心点是选择目录以形成新的存储库并保留其提交历史记录。我不确定如何处理这样的情况,即提交将文件放入dirA,dirB,dirDrop且仅为新仓库选择dirA和dirB,提交历史应与原始记录有何关系。
chfw

27

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

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 ...)必要的ST extglob是由你的shell而不是索引过滤器,它使用评估sh内置eval(其中extglob不可用)。请参阅如何在git中启用shell选项?有关更多详细信息。


1
有趣的主意。我有一个类似的问题,但它不能去工作,看stackoverflow.com/questions/8050687/...
mANO1的

这几乎是我所需要的,尽管我在存储库中散布了文件和文件夹...谢谢:)
notlesh 2011年

1
嗯。即使打开了extglob,我也会在括号附近出现错误:意外标记'('附近的语法错误。我的命令看起来像:git filter-branch -f --index-filter“ git rm -r -f --cached- -ignore-unmatch src / css / themes /!(some_theme *)“ --prune-empty--所有带有src / css / themes /!的ls都会返回所有其他主题,因此extglob确实看起来像正在工作……
robdodson

2
@MikeGraf我认为不会产生预期的结果:转义将匹配文字“!”。等等。
凯南

1
@ david-smiley的(较新的)答案使用了非常相似的方法,但是具有仅依赖git命令的优势,因此ls不像@Bae 那样容易受到跨操作系统的解释方式的影响。
杰里米·卡尼

20

经过大量的反复试验后,在这里回答我自己的问题。

我管理这个使用的组合做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

现在,我不太确定如何/何时发生这种情况,但是在第一个checkout和之后pull,代码神奇地合并到了master分支中(对这里发生的事情有任何见解!)

一切似乎都按预期工作,但如果我仔细查看newRepo提交历史记录,则当变更集同时影响apps/AAA和时,会有重复项libs/XXX。如果有删除重复项的方法,那将是完美的。


您在这里找到的整洁工具。对“结帐”的了解:“ git pull”与“ git fetch && git merge”相同。“提取”部分是无害的,因为您是“本地提取”的。因此,我认为此签出命令与“ git merge master-B”相同,这更加不言而喻。参见kernel.org/pub/software/scm/git/docs/git-pull.html
2010年

1
不幸的是,由于目前的依赖关系不好,git-stitch-repo工具已损坏。
亨里克

@Henrik您到底遇到什么问题?它对我export PERL5LIB="$PERL5LIB:/usr/local/git/lib/perl5/site_perl/"有用,尽管我必须添加到bash配置中才能找到Git.pm。然后我用cpan安装了它。

可以用来git subtree add执行此任务。参见stackoverflow.com/a/58253979/1894803
laconbass,

7

我写了一个git过滤器来解决这个问题。它的名字叫git_filter,位于github上:

https://github.com/slobobaby/git_filter

它基于出色的libgit2。

我需要使用多个提交(〜100000)来拆分一个大型存储库,而基于git filter-branch的解决方案需要花几天的时间才能运行。git_filter花一分钟时间来做同样的事情。


7

使用'git splits'git扩展

git splits是一个bash脚本,它是git branch-filter基于jkeating的solution创建的git扩展程序的包装。

正是针对这种情况而制作的。对于您的错误,请尝试使用该git splits -f选项强制删除备份。由于git splits在新分支上运行,因此不会重写当前分支,因此备份是多余的。有关更多详细信息,请参见自述文件,并确保在回购的副本/克隆上使用它(以防万一!)

  1. 安装git splits
  2. 将目录拆分到本地分支 #change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
    #split multiple directories into new branch XYZ git splits -b XYZ apps/AAA libs/ZZZ

  3. 在某处创建一个空的仓库。我们假设我们已经xyz在GitHub上创建了一个空的仓库,该仓库具有path:git@github.com:simpliwp/xyz.git

  4. 推送到新的仓库。 #add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master

  5. 将新创建的远程仓库克隆到新的本地目录中
    #change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git


似乎无法将文件添加到拆分中并稍后进行更新,对吗?
亚历克斯(Alex)

这在我的回购上进行了大量提交似乎很慢
Shinta Smith

git-split似乎使用git --index过滤器,与--subdirectory-filter相比非常慢。对于某些存储库,它仍然可能是一个可行的选择,但是对于大型存储库(多个千兆字节,6位提交),即使在专用的云硬件上,索引过滤器也需要数周的时间才能有效运行。
JosteinKjønigsen18年

6
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

通读所有其他评论使我走上了正确的轨道。但是,您的解决方案就可以了。它导入所有分支,并与多个目录一起使用!大!
jschober

1
for循环值得确认,因为其他类似的答案未包含该循环。如果您的克隆中filter-branch没有每个分支的本地副本,则不会在重写过程中考虑它们,这可能会排除其他分支中引入的文件,但尚未与当前分支合并。(尽管也值得在git fetch您之前签出的任何分支上进行操作,以确保它们保持最新。)
Jeremy Caney

5

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

我遇到了类似的问题,在回顾了这里列出的各种方法之后,我发现了git-filter-repo。建议在此处使用 git-filter-branch替代git-filter-branch 。

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

git filter-repo --path <file_to_remove>

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

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

因此,要回答原始问题,使用git-filter-repo,您​​只需要以下命令:

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

这绝对是一个很好的答案。所有其他解决方案的问题是我无法提取目录的所有分支的内容。但是,git filter-repo从所有分支中检索了该文件夹,并完美地重写了历史记录,就像清理整个树上不需要的所有东西。
Teodoro

3

是的 通过-f在后续调用中使用该标志filter-branch来覆盖该警告,以强制覆盖备份。:)否则,我认为您已经找到了解决方案(即,一次清除一个不需要的目录filter-branch)。


-4

按照消息提示,删除refs / original中.git目录下的备份。该目录被隐藏。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.