将子目录分离(移动)到单独的Git存储库中

我有一个git存储库,其中包含许多子目录。现在我发现其中一个子目录与另一个子目录无关,应该分离到一个单独的存储库中。

如何在保留子目录中文件的历史记录的同时做到这一点?

我想我可以做一个克隆并删除每个克隆的不需要的部分,但我想这会给我一个完整的树,当检查出一个较旧的版本等,这可能是可以接受的,但我更愿意能够假装两个存储库没有共享的历史。

为了清楚起见,我有以下结构:

XYZ/.git/XY1/ABC/XY2/

但我更喜欢这个:

XYZ/.git/XY1/XY2/ABC/.git/ABC/
303001 次浏览

更新:这个过程非常普遍,以至于git团队使用一个新工具git subtree使其变得更简单。请参阅此处:将子目录分离(移动)到单独的Git存储库中


您想克隆您的存储库,然后使用git filter-branch标记除您希望在新存储库中进行垃圾收集的子目录之外的所有内容。

  1. 要克隆您的本地存储库:

    git clone /XYZ /ABC

    (注意:存储库将使用硬链接克隆,但这不是问题,因为硬链接文件本身不会被修改-将创建新的文件。)

  2. 现在,让我们保留我们想要重写的有趣的分支,然后删除原点以避免在那里推送,并确保旧的提交不会被原点引用:

    cd /ABCfor i in branch1 br2 br3; do git branch -t $i origin/$i; donegit remote rm origin

    或者对于所有远程分支:

    cd /ABCfor i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; donegit remote rm origin
  3. Now you might want to also remove tags which have no relation with the subproject; you can also do that later, but you might need to prune your repo again. I did not do so and got a WARNING: Ref 'refs/tags/v0.1' is unchanged for all tags (since they were all unrelated to the subproject); additionally, after removing such tags more space will be reclaimed. Apparently git filter-branch should be able to rewrite other tags, but I could not verify this. If you want to remove all tags, use git tag -l | xargs git tag -d.

  4. Then use filter-branch and reset to exclude the other files, so they can be pruned. Let's also add --tag-name-filter cat --prune-empty to remove empty commits and to rewrite tags (note that this will have to strip their signature):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all

    或者,只重写HEAD分支并忽略标签和其他分支:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
  5. Then delete the backup reflogs so the space can be truly reclaimed (although now the operation is destructive)

    git reset --hardgit for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -dgit reflog expire --expire=now --allgit gc --aggressive --prune=now

    现在您有了ABC子目录的本地git存储库,并保留了其所有历史记录。

注意:对于大多数用途,git filter-branch确实应该添加参数-- --all。是的,那真的是--空间--all。这需要是命令的最后一个参数。正如Matli发现的那样,这将项目分支和标签保留在新存储库中。

编辑:来自下面评论的各种建议被合并,以确保,例如,存储库实际上是缩小的(以前并不总是这样)。

保罗的回答创建一个包含 /ABC的新存储库,但不从 /XYZ.中删除 /ABC以下命令将从 /XYZ中删除 /ABC:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

当然,首先在“clone-no-hardLink”存储库中测试它,然后使用Paul列表中的重置、gc和修剪命令进行测试。

在垃圾回收机制之前,您可能需要类似于“git reflg特效-特效-现在-所有”的东西来实际清理文件。git filter-分支只是删除历史记录中的引用,但不会删除保存数据的reflg条目。当然,先测试一下。

这样做使我的磁盘使用率急剧下降,尽管我的初始条件有些不同。也许--subtrader-filter否定了这种需求,但我对此表示怀疑。

为了添加到保罗的回答,我发现为了最终恢复空间,我必须将HEAD推送到一个干净的存储库,并减少. git/对象/包目录的大小。

$ mkdir ...ABC.git$ cd ...ABC.git$ git init --bare

在gc修剪之后,还要做:

$ git push ...ABC.git HEAD

然后你可以做

$ git clone ...ABC.git

ABC/. git的大小减小

实际上,一些耗时的步骤(例如git gc)不需要推送到清理存储库,即:

$ git clone --no-hardlinks /XYZ /ABC$ git filter-branch --subdirectory-filter ABC HEAD$ git reset --hard$ git push ...ABC.git HEAD

我发现,为了从新存储库中正确删除旧的历史记录,您必须在filter-branch步骤之后做更多的工作。

  1. 执行克隆和过滤器:

    git clone --no-hardlinks foo bar; cd bargit filter-branch --subdirectory-filter subdir/you/want
  2. Remove every reference to the old history. “origin” was keeping track of your clone, and “original” is where filter-branch saves the old stuff:

    git remote rm origingit update-ref -d refs/original/refs/heads/mastergit reflog expire --expire=now --all
  3. Even now, your history might be stuck in a packfile that fsck won’t touch. Tear it to shreds, creating a new packfile and deleting the unused objects:

    git repack -ad

There is an explanation of this in the manual for filter-branch.

更新:git-subtree模块非常有用,以至于git团队将其拉入核心并使其成为git subtree。在这里看到:将子目录分离(移动)到单独的Git存储库中

git-subtree可能对此有用

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt(已弃用)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/

使用此过滤器命令删除子目录,同时保留您的标签和分支:

git filter-branch --index-filter \"git rm -r -f --cached --ignore-unmatch DIR" --prune-empty \--tag-name-filter cat -- --all

编辑:添加了Bash脚本。

这里给出的答案对我来说只是部分有效;许多大文件仍保留在缓存中。最后起作用的是(在freenode上的#git中几个小时后):

git clone --no-hardlinks file:///SOURCE /tmp/blubbcd blubbgit filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --allgit clone file:///tmp/blubb/ /tmp/bloohcd /tmp/bloohgit reflog expire --expire=now --allgit repack -adgit gc --prune=now

使用以前的解决方案,存储库大小约为100 MB。这个将其降低到1.7 MB。也许它对某人有帮助:)


以下bash脚本自动执行任务:

!/bin/bash
if (( $# < 3 ))thenecho "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"echoecho "Example: $0 /Projects/42.git first/answer/ firstAnswer"exit 1fi

clone=/tmp/${3}ClonenewN=/tmp/${3}
git clone --no-hardlinks file://$1 ${clone}cd ${clone}
git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all
git clone file://${clone} ${newN}cd ${newN}
git reflog expire --expire=now --allgit repack -adgit gc --prune=now

为了它的价值,以下是如何在Windows机器上使用GitHub。假设你有一个克隆的存储库驻留在C:\dir1中。目录结构如下所示:C:\dir1\dir2\dir3dir3目录是我想成为一个新的单独存储库的目录。

Github:

  1. 创建您的新存储库:MyTeam/mynewrepo

Bash提示符:

  1. $ cd c:/Dir1
  2. $ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
    返回:Ref 'refs/heads/master' was rewritten(fyi:迪尔2/迪尔3区分大小写。)

  3. $ git remote add some_name git@github.com:MyTeam/mynewrepo.git
    git remote add origin etc.不起作用,返回“remote origin already exists

  4. $ git push --progress some_name master

最初的问题希望XYZ/ABC/(*文件)成为ABC/ABC/(*文件)。在为我自己的代码实现了可接受的答案后,我注意到它实际上将XYZ/ABC/(*文件)更改为ABC/(*文件)。过滤器分支手册页甚至说,

结果将包含该目录(并且仅包含该目录)作为其项目根。”

换句话说,它将顶级文件夹“向上”提升了一级。这是一个重要的区别,因为,例如,在我的历史中,我重命名了一个顶级文件夹。通过将文件夹“向上”提升一级,git在我重命名的提交处失去了连续性。

过滤分支后我失去了连续性

那么我对这个问题的回答是制作存储库的2个副本并手动删除您要保留在每个文件夹中的文件夹。手册页用以下内容支持我:

[…]如果一个简单的单一提交足以解决您的问题,请避免使用[此命令]

将其放入您的gitconfig中:

reduce-to-subfolder = !sh -c 'git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter cookbooks/unicorn HEAD && git reset --hard && git for-each-ref refs/original/ | cut -f 2 | xargs -n 1 git update-ref -d && git reflog expire --expire=now --all && git gc --aggressive --prune=now && git remote rm origin'

作为我的上述,我不得不使用相反的解决方案(删除所有未触及我的dir/subdir/targetdir的提交),这似乎很好地删除了大约95%的提交(如所需)。

第一filter-branch在删除引入或修改代码的提交方面做得很好,但显然,合并提交在Gitiverse中的地位之下。

这是一个化妆品问题,我可以忍受(他说…慢慢后退,避开视线)

第二剩下的少数提交几乎是所有重复的!我似乎获得了第二个,冗余的时间线,几乎跨越了项目的整个历史。有趣的是(你可以从下面的图片中看到),我的三个本地分支并不都在同一个时间线上(这当然是它存在的原因,而不仅仅是垃圾收集)。

我唯一能想象的是,其中一个被删除的提交可能是filter-branch确实删除了的单个合并提交,并且创建了并行时间线,因为每个现在未合并的链都有自己的提交副本。

在疯狂的mergefest-O-RAMA的情况下,我可能会把那个单独留下,因为它在我的提交历史中如此牢固地根深蒂固-每当我靠近时都会威胁我-它似乎实际上并没有造成任何非美容问题,因为它在Tower.app.中非常漂亮

简单的方法™

事实证明,这是一个如此常见和有用的做法,以至于Git的霸主让它变得非常容易,但你必须有一个较新版本的Git(>=1.7.11 May 2012)。有关如何安装最新的Git,请参阅附录。此外,下面的走查中有真实例子

  1. 准备旧repo

     cd <big-repo>git subtree split -P <name-of-folder> -b <name-of-new-branch>

备注:<name-of-folder>不能包含前导或尾随字符。例如,名为subproject的文件夹必须作为subproject传递,而不是./subproject/

Windows用户注意事项:当您的文件夹深度>1时,<name-of-folder>必须具有*nix样式的文件夹分隔符(/)。例如,名为path1\path2\subproject的文件夹必须作为path1/path2/subproject传递

  1. 创建新的仓库

     mkdir ~/<new-repo> && cd ~/<new-repo>git initgit pull </path/to/big-repo> <name-of-new-branch>
  2. 将新存储库链接到GitHub或任何地方

     git remote add origin <git@github.com:user/new-repo.git>git push -u origin master
  3. 清理内部<big-repo>如果需要

     git rm -rf <name-of-folder>

说明:这将所有历史引用保留在存储库中。如果您实际上担心提交密码或需要减小.git文件夹的文件大小,请参阅下面的附录


走查

这些是与上述相同的步骤,但遵循我的存储库的确切步骤,而不是使用<meta-named-things>

这是我在node中实现JavaScript浏览器模块的项目:

tree ~/node-browser-compat
node-browser-compat├── ArrayBuffer├── Audio├── Blob├── FormData├── atob├── btoa├── location└── navigator

我想将单个文件夹btoa拆分到一个单独的Git存储库中

cd ~/node-browser-compat/git subtree split -P btoa -b btoa-only

我现在有一个新的分支,btoa-only,它只有btoa的提交,我想创建一个新的存储库。

mkdir ~/btoa/ && cd ~/btoa/git initgit pull ~/node-browser-compat btoa-only

接下来,我在GitHub或比特桶上创建一个新的存储库,并将其添加为origin

git remote add origin git@github.com:node-browser-compat/btoa.gitgit push -u origin master

快乐的一天!

备注:如果你用README.md.gitignoreLICENSE创建了一个repo,你需要先拉取:

git pull origin mastergit push origin master

最后,我想从更大的存储库中删除该文件夹

git rm -rf btoa

附录

macOS上的最新Git

要使用自制获取最新版本的Git:

brew install git

Ubuntu上的最新Git

sudo apt-get updatesudo apt-get install gitgit --version

如果这不起作用(你有一个非常旧的Ubuntu版本),请尝试

sudo add-apt-repository ppa:git-core/ppasudo apt-get updatesudo apt-get install git

如果还是不行,试试看

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.shsudo ln -s \/usr/share/doc/git/contrib/subtree/git-subtree.sh \/usr/lib/git-core/git-subtree

感谢评论中的rui.araujo

清理你的历史

默认情况下,从Git中删除文件实际上并没有删除它们,它只是提交它们不再存在。如果你想实际删除历史引用(即你提交了密码),你需要这样做:

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

之后,您可以检查您的文件或文件夹是否不再显示在Git历史记录中

git log -- <name-of-folder> # should show nothing

然而,你无法将删除“推送”到GitHub之类的。如果你尝试,你会得到一个错误,你必须在git push之前git pull-然后你又回到了你历史上的一切。

因此,如果你想从“源”中删除历史记录——意味着从GitHub、比特桶等中删除它——你需要删除存储库并重新推送存储库的修剪副本。但是等待-还有更多!-如果你真的担心摆脱密码或类似的东西,你需要修剪备份(见下文)。

使.git更小

前面提到的删除历史命令仍然会留下一堆备份文件——因为Git在帮助您避免意外破坏存储库方面太好了。它最终会在几天或几个月内删除孤立的文件,但它会将它们留在那里一段时间,以防您意识到您不小心删除了您不想删除的内容。

因此,如果你真的想立即从倒垃圾减小克隆大小的回购,你必须做所有这些非常奇怪的事情:

rm -rf .git/refs/original/ && \git reflog expire --all && \git gc --aggressive --prune=now
git reflog expire --all --expire-unreachable=0git repack -A -dgit prune

也就是说,我建议不要执行这些步骤,除非你知道你需要-以防万一你确实修剪了错误的子目录,你知道吗?当您推送存储库时,备份文件不应该被克隆,它们只会在您的本地副本中。

信贷

我确实遇到了这个问题,但所有基于git filter-分支的标准解决方案都非常慢。如果你有一个小存储库,那么这可能不是问题,这是对我来说的。我编写了另一个基于libgit2的git过滤程序,该程序首先为主存储库的每个过滤创建分支,然后将这些分支推送到干净的存储库,作为下一步。在我的存储库(500Mb 100000次提交)上,标准的git filter-分支方法需要几天时间。我的程序做同样的过滤需要几分钟。

它的名字叫git_filter,住在这里:

https://github.com/slobobaby/git_filter

在github。

我希望它对某人有用。

这不再那么复杂,您可以在您的repo克隆上使用git过滤分支命令来剔除您不想要的子目录,然后推送到新的远程目录。

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> mastergit push <MY_NEW_REMOTE_URL> -f .

现在正确的方法如下:

git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]

GitHub现在甚至有小文章关于这种情况。

但请务必先将原始存储库克隆到单独的目录(因为它会删除所有文件和其他目录,您可能需要使用它们)。

所以你的算法应该是:

  1. 将您的远程存储库克隆到另一个目录
  2. 使用git filter-branch只留下某个子目录下的文件,推送到新的远程
  3. 创建提交以从原始远程存储库中删除此子目录

更简单的方法

  1. 安装#0。我将其创建为基于jkeating解的git扩展。
  2. 将目录拆分为本地分支#切换到你的repo目录cd /path/to/repo#检查分支git签出XYZ
    #将多个目录拆分为新的分支XYZgit拆分-b XYZ XY1 XY2

  3. 在某处创建一个空仓库。我们假设我们已经在GitHub上创建了一个名为xyz的空仓库,其路径为:git@github.com:simpliwp/xyz.git

  4. 推送到新存储库。#为空仓库添加一个新的远程原点,这样我们就可以推送到GitHub上的空仓库git远程添加origin_xyzgit@github.com:简化wp/xyz.git#将分支推到空repo的master分支Git推送origin_xyzXYZ: master

  5. 将新创建的远程仓库克隆到一个新的本地目录
    #从旧存储库中更改当前目录cd /path/to/where/you/want/the/new/local/repo#克隆你刚刚推送到的远程存储库git clonegit@github.com:简化wp/xyz.git

这是对CoolAJ86“简单的方式”答案的一个小修改,以便将多个子文件夹(假设sub1sub2)拆分为一个新的git存储库。

简易方式™(多个子文件夹)

  1. 准备旧repo

    pushd <big-repo>git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEADgit subtree split -P <name-of-folder> -b <name-of-new-branch>popd

    备注:<name-of-folder>不能包含前导或尾随字符。例如,名为subproject的文件夹必须作为subproject传递,而不是./subproject/

    Windows用户注意事项:当您的文件夹深度>1时,<name-of-folder>必须具有*nix样式的文件夹分隔符(/)。例如,名为path1\path2\subproject的文件夹必须作为path1/path2/subproject传递。此外,不要使用mv命令,而是使用move

    最后说明:与基本答案的独特而巨大的区别是脚本的第二行“git filter-branch...

  2. 创建新的仓库

    mkdir <new-repo>pushd <new-repo>
    git initgit pull </path/to/big-repo> <name-of-new-branch>
  3. Link the new repo to Github or wherever

    git remote add origin <git@github.com:my-user/new-repo.git>git push origin -u master
  4. Cleanup, if desired

    popd # get out of <new-repo>pushd <big-repo>
    git rm -rf <name-of-folder>

    说明:这将所有历史引用保留在存储库中。如果您实际上担心提交密码或需要减小.git文件夹的文件大小,请参阅原始答案中的附录

查看git_split项目https://github.com/vangorra/git_split

将git目录转换为它们自己位置的存储库。没有子树有趣的业务。此脚本将获取您的git存储库中的现有目录,并将该目录转换为自己的独立存储库。在此过程中,它将复制您提供的目录的整个更改历史记录。

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>src_repo  - The source repo to pull from.src_branch - The branch of the source repo to pull from. (usually master)relative_dir_path   - Relative path of the directory in the source repo to split.dest_repo - The repo to push to.

我确信git子树很好,但是我想移动的git托管代码的子目录都在eclipse中。所以如果你使用egit,这是非常容易的。以您要移动的项目为例,然后team->断开它,然后team->将其共享到新位置。默认情况下会尝试使用旧的存储库位置,但您可以取消选中use现有选择并选择新位置来移动它。万岁!

这里的大多数(所有?)答案似乎都依赖于某种形式的git filter-branch --subdirectory-filter及其同类。然而,对于某些情况,这可能在“大多数时候”有效,例如当您重命名文件夹时的情况,例如:

 ABC//move_this_dir # did some work here, then renamed it to
ABC//move_this_dir_renamed

如果您使用普通的git过滤器样式来提取“move_this_dir_renamed”,您将丢失最初“move_this_dir”时发生的文件更改历史记录(参考)。

因此,似乎真正保留所有更改历史记录的唯一方法(如果你的情况是这样的),本质上是复制存储库(创建一个新的存储库,将其设置为源),然后核其他所有内容并将子目录重命名为父目录,如下所示:

  1. 在本地克隆多模块项目
  2. 分支-检查那里有什么:git branch -a
  3. 对要包含在拆分中的每个分支进行签出,以在您的工作站上获取本地副本:git checkout --track origin/branchABC
  4. 在新目录中复制:cp -r oldmultimod simple
  5. 进入新项目副本:cd simple
  6. 删除此项目中不需要的其他模块:
  7. git rm otherModule1 other2 other3
  8. 现在只剩下目标模块的子目录
  9. 去掉模块子目录,使模块根成为新的项目根
  10. git mv moduleSubdir1/* .
  11. 删除遗迹子目录:rmdir moduleSubdir1
  12. 随时检查更改:git status
  13. 创建新的git repo并复制其URL以将此项目指向其中:
  14. git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
  15. 验证这是好的:git remote -v
  16. 将更改推送到远程存储库:git push
  17. 去远程仓库看看都在哪里
  18. 对任何其他需要的分支重复:git checkout branch2

这遵循github文档“将子文件夹拆分到新的存储库”步骤6-11将模块推送到新的存储库。

这不会为您节省. git文件夹中的任何空间,但即使跨重命名,它也会保留这些文件的所有更改历史记录。如果没有“大量”历史丢失等,这可能不值得。但至少你可以保证不会丢失旧的提交!

我推荐GitHub将子文件夹拆分到新存储库的指南。这些步骤与保罗的回答类似,但我发现它们的说明更容易理解。

我已经修改了说明,以便它们申请本地存储库,而不是托管在GitHub上的存储库。


将子文件夹拆分到一个新的存储库

  1. 打开Git Bash。

  2. 将当前工作目录更改为要创建新存储库的位置。

  3. 克隆包含子文件夹的存储库。

git clone OLD-REPOSITORY-FOLDER NEW-REPOSITORY-FOLDER
  1. 将当前工作目录更改为克隆的存储库。

cd REPOSITORY-NAME
  1. 要从存储库中的其余文件中过滤掉子文件夹,请运行git filter-branch,提供以下信息:
    • FOLDER-NAME:您要从项目中创建单独存储库的文件夹。
      • 提示:Windows用户应使用/来分隔文件夹。
    • BRANCH-NAME:当前项目的默认分支,例如mastergh-pages

git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME  BRANCH-NAME# Filter the specified branch in your directory and remove empty commitsRewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89)Ref 'refs/heads/BRANCH-NAME' was rewritten

您可以轻松尝试https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/

这对我有用。我在上面给出的步骤中遇到的问题是

  1. 在这个命令中git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAMEBRANCH-NAME大师

  2. 如果由于保护问题导致提交时最后一步失败,请关注-https://docs.gitlab.com/ee/user/project/protected_branches.html

我找到了非常直接的解决方案,这个想法是复制存储库,然后删除不必要的部分。它是如何工作的:

1)克隆你想拆分的存储库

git clone git@git.thehost.io:testrepo/test.git

2)移动到git文件夹

cd test/

2)删除不必要的文件夹并提交

rm -r ABC/git add .enter code heregit commit -m 'Remove ABC'

3)使用BFG删除不必要的文件夹表单历史记录

cd ..java -jar bfg.jar --delete-folders "{ABC}" testcd test/git reflog expire --expire=now --all && git gc --prune=now --aggressive

对于乘法文件夹,您可以使用逗号

java -jar bfg.jar --delete-folders "{ABC1,ABC2}" metric.git

4)检查历史记录是否包含您刚刚删除的文件/文件夹

git log --diff-filter=D --summary | grep delete

5)现在您有了没有ABC的干净存储库,把它推到新的地方

remote add origin git@github.com:username/new_repogit push -u origin master

就是这样。您可以重复步骤以获得另一个存储库,

只需删除XY1,XY2并在步骤3中重命名XYZ->ABC

当使用更新版本的git2.22+也许?)运行git filter-branch时,它说使用这个新工具git-filter-repo开发完成。这个工具对我来说确实简化了事情。

使用Filter-repo过滤

从原始问题创建XYZ存储库的命令:

# create local clone of original repo in directory XYZtmp $ git clone git@github.com:user/original.git XYZ
# switch to working in XYZtmp $ cd XYZ
# keep subdirectories XY1 and XY2 (dropping ABC)XYZ $ git filter-repo --path XY1 --path XY2
# note: original remote origin was dropped# (protecting against accidental pushes overwriting original repo data)
# XYZ $ ls -1# XY1# XY2
# XYZ $ git log --oneline# last commit modifying ./XY1 or ./XY2# first commit modifying ./XY1 or ./XY2
# point at new hosted, dedicated repoXYZ $ git remote add origin git@github.com:user/XYZ.git
# push (and track) remote masterXYZ $ git push -u origin master

假设:*远程XYZ存储库在推送之前是新的和空的

过滤和移动

在我的情况下,我还想移动几个目录以获得更一致的结构。最初,我运行了简单的filter-repo命令,然后是git mv dir-to-rename,但我发现使用--path-rename选项可以获得稍微“更好”的历史记录。而不是在新存储库中移动的文件上看到最后修改的5 hours ago,我现在看到last year(在GitHub UI中),它与原始存储库中的修改时间相匹配。

而不是…

git filter-repo --path XY1 --path XY2 --path inconsistentgit mv inconsistent XY3  # which updates last modification time

我最终跑了…

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
备注:
  • 我认为Git Rev新闻博客文章很好地解释了创建另一个存储库过滤工具背后的原因。
  • 我最初尝试了在原始存储库中创建与目标存储库名称匹配的子目录的路径并然后过滤(使用git filter-repo --subdirectory-filter dir-matching-new-repo-name)。该命令将该子目录正确转换为复制的本地存储库的根目录,但它也导致创建子目录所需的三次提交历史记录。(我没有意识到--path可以多次指定;因此,避免了在源存储库中创建子目录的需要。)因为当我注意到我没有继承历史记录时,有人已经提交了源存储库,我只是在clone命令之后使用git reset commit-before-subdir-move --hard,并将--force添加到filter-repo命令以使其对稍微修改的本地克隆进行操作。
git clone ...git reset HEAD~7 --hard      # roll back before mistakegit filter-repo ... --force  # tell filter-repo the alterations are expected
  • 我在安装时遇到了困难,因为我不知道git的扩展模式,但最终我克隆了git-filter-repo开发完成并将其符号链接到$(git --exec-path)
ln -s ~/github/newren/git-filter-repo/git-filter-repo $(git --exec-path)

发现这篇精彩的文章原始参考很容易理解。在这里记录它,以防它无法访问。

1.准备当前存储库

$ cd path/to/repository$ git subtree split -P my-folder -b my-folderCreated branch 'my-folder'aecbdc3c8fe2932529658f5ed40d95c135352eff

文件夹的名称必须是相对路径,从存储库的根目录开始。

2.创建新的存储库

$ cd my-folder$ git initInitialized empty Git repository in /Users/adamwest/Projects/learngit/shop/my-folder/.git/$ git add .$ git commit -m "initial commit"[master (root-commit) 192c10b] initial commit1 file changed, 0 insertions(+), 0 deletions(-)create mode 100644 file

在这里,我们只需要cd到新文件夹,初始化新存储库,并提交任何内容。

3.添加新的远程仓库并推送

$ git remote add origin git@github.com:robertlyall/my-folder.git$ git push origin -u masterEnumerating objects: 3, done.Counting objects: 100% (3/3), done.Writing objects: 100% (3/3), 199 bytes | 199.00 KiB/s, done.Total 3 (delta 0), reused 0 (delta 0)To github.com:robertlyall/my-folder.git* [new branch]      master -> masterBranch 'master' set up to track remote branch 'master' from 'origin'.

我们在此处添加远离GitHub的新存储库,然后将我们的第一次提交推送到它。

4.从主存储库中删除文件夹并推送

$ cd ../$ git rm -rf my-folderrm 'my-folder/file'$ git commit -m "Remove old folder"[master 56aedbe] remove old folder1 file changed, 0 insertions(+), 0 deletions(-)delete mode 100644 my-folder/file$ git pushEnumerating objects: 3, done.Counting objects: 100% (3/3), done.Delta compression using up to 4 threadsCompressing objects: 100% (2/2), done.Writing objects: 100% (2/2), 217 bytes | 217.00 KiB/s, done.Total 2 (delta 1), reused 0 (delta 0)remote: Resolving deltas: 100% (1/1), completed with 1 local object.To github.com:robertlyall/shop.git74dd8b3..56aedbe  master -> master

最后,我们cd回到根目录,从我们的主存储库中删除该文件夹,然后提交并推送更改。现在,我们在主存储库中拥有该文件夹,但链接到一个完全独立的存储库,可以跨多个项目重用。