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

pri*_*ohn 125 git git-filter-branch

这个问题基于Detach子目录到单独的Git存储库中

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

/apps
  /AAA
  /BBB
  /CCC
/libs
  /XXX
  /YYY
  /ZZZ
Run Code Online (Sandbox Code Playgroud)

而我想这样做:

/apps
  /AAA
/libs
  /XXX
Run Code Online (Sandbox Code Playgroud)

--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
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?TIA

Dav*_*ley 143

不必处理子shell并使用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
Run Code Online (Sandbox Code Playgroud)

  • 您能否添加更多信息来解释这个冗长的命令在做什么? (14认同)
  • 假设你在混合中有标签,你应该在你的参数中添加`--tag-name-filter cat` (7认同)
  • 另外,--ignore-unmatch标志应该传递给git rm,它对我来说是第一次提交失败了(在我的情况下使用git svn clone创建了存储库) (4认同)
  • 令我惊喜的是,这在Windows上使用git bash完美运行,p! (4认同)
  • @BurhanAli对于历史记录中的每个提交,它将删除除您要保留的文件以外的所有文件。完成所有操作后,仅留下您指定的树的一部分以及该历史记录。 (3认同)
  • 如果给定提交中不存在其中一个路径,我会收到 `fatal: bad revision <path>` 并且命令中止。需要明确的是,我指定的是文件而不是目录。 (2认同)
  • 顾名思义,“index-filter”对索引进行操作,因此您需要“--cached”来让“git rm”也对索引进行操作(请参阅“git help rm”)。 (2认同)
  • 您应该在答案中提到环境变量 $GIT_COMMIT 是从特定提交获取文件夹,如果未设置/空则来自 HEAD (2认同)

chf*_*hfw 38

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

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

划分

让我们假设你的原始回购是:original_repo

1 - 拆分应用程序:

git clone original_repo apps-repo
cd apps-repo
git filter-branch --prune-empty --subdirectory-filter apps master
Run Code Online (Sandbox Code Playgroud)

2 - 拆分库

git clone original_repo libs-repo
cd libs-repo
git filter-branch --prune-empty --subdirectory-filter libs master
Run Code Online (Sandbox Code Playgroud)

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

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

3 - 准备全新的回购:

mkdir my-desired-repo
cd my-desired-repo
git init
Run Code Online (Sandbox Code Playgroud)

而且您需要至少进行一次提交.如果应该跳过以下三行,您的第一个仓库将立即显示在您的仓库的根目录下:

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"
Run Code Online (Sandbox Code Playgroud)

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

从用户的反馈以代替添加一个随机文件等a_file_and_make_a_commit,你可以选择添加.gitignore,或README.md

4 - 首先合并应用程序repo:

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"
Run Code Online (Sandbox Code Playgroud)

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

注:克里斯下面提到的意见,对新版本(> = 2.9)的git的,你需要指定--allow-unrelated-historiesgit merge

5 - 以相同的方式合并libs repo:

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"
Run Code Online (Sandbox Code Playgroud)

如果您有超过2个repos进行合并,请继续.

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

  • 从git 2.9开始,你需要在merge命令中使用--allow-unrelated-histories.否则这似乎对我有用. (4认同)
  • 不幸的是,这种方法似乎破坏了在 `git merge .. git read-tree` 步骤中添加的文件的跟踪历史记录,因为它将它们记录为新添加的文件,而我的所有 git gui 都没有连接到它们的较早的提交。 (2认同)

kyn*_*nan 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
Run Code Online (Sandbox Code Playgroud)

这应该摆脱不需要的子目录中的所有更改并保留所有分支和提交(除非它们仅影响已修剪的子目录中的文件--prune-empty) - 没有重复提交等问题.

执行此操作后,不需要的目录将被列为未跟踪git status.

$(ls ...)是必要的,extglob它由shell而不是索引过滤器评估,索引过滤器使用sh内置eval(在哪里extglob不可用).请参阅如何在git中启用shell选项?有关这方面的进一步细节.

  • @MikeGraf 我认为这不会给出预期的结果:转义将匹配文字“!” 等在你的道路上。 (2认同)

小智 26

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

我遇到了类似的问题,在查看了此处列出的各种方法后,我发现了git-filter-repo。在此处的官方 git 文档中,建议将其作为 git-filter-branch 的替代方案。

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

git filter-repo --path <file_to_keep>
Run Code Online (Sandbox Code Playgroud)

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

git filter-repo --path keepthisfile --path keepthisfolder/
Run Code Online (Sandbox Code Playgroud)

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

git filter-repo --path apps/AAA/ --path libs/XXX/
Run Code Online (Sandbox Code Playgroud)


pri*_*ohn 20

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

我设法使用git subtree和组合做到这一点git-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
Run Code Online (Sandbox Code Playgroud)

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

cd ..
mkdir newRepo
cd newRepo
git init
git-stitch-repo ../aaaRepo:apps/AAA ../xxxRepo:libs/XXX | git fast-import
Run Code Online (Sandbox Code Playgroud)

这创建了两个分支,master-A并且master-B每个分支都保存了一个拼接回购的内容.要将它们组合起来并进行清理:

git checkout master-A
git pull . master-B
git checkout master
git branch -d master-A 
git branch -d master-B
Run Code Online (Sandbox Code Playgroud)

现在我不太确定如何/何时发生这种情况,但是在第一次checkout和之后pull,代码会神奇地合并到主分支中(对此处发生的事情的任何见解都表示赞赏!)

一切似乎都按预期工作,除非我查看newRepo提交历史记录,当变更集影响到apps/AAA和时,都会有重复libs/XXX.如果有办法删除重复项,那么它将是完美的.


slo*_*aby 7

我写了一个git过滤器来解决这个问题.它有着名的git_filter,位于github:

https://github.com/slobobaby/git_filter

它基于优秀的libgit2.

我需要拆分一个包含许多提交的大型存储库(~100000),基于git filter-branch的解决方案需要几天才能运行.git_filter花了一分钟做同样的事情.


And*_*ewD 7

使用'git splits'git扩展名

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

这完全是为了这种情况.对于您的错误,请尝试使用该git splits -f选项强制删除备份.因为git splits在新分支上运行,所以它不会重写当前分支,因此备份是无关紧要的.有关更多详细信息,请参阅自述文件,并确保在repo的复制/克隆中使用它(以防万一!).

  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. 在某处创建一个空的repo.我们假设我们已经创建了一个xyz在GitHub上调用的具有路径的空仓库: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


Ric*_*ugh 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
Run Code Online (Sandbox Code Playgroud)