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

如何在保留子目录中文件的历史记录的同时执行此操作?

我想我可以制作一个克隆并删除每个克隆中不需要的部分,但我想这会在检查旧版本等时提供完整的树。这可能是可以接受的,但我更希望能够假装这两个存储库没有共享的历史。

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

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

但我想改为:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

更新:这个过程非常常见,git团队用一个新工具git子树简化了这个过程。请参阅此处:将子目录分离(移动)到单独的Git存储库中


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

要克隆本地存储库,请执行以下操作:gitclone/XYZ/ABC(注意:将使用硬链接克隆存储库,但这不是问题,因为硬链接文件本身不会被修改-将创建新的文件。)现在,让我们保留我们想要重写的有趣分支,然后删除源,以避免推到那里,并确保源不会引用旧的提交:cd/ABC对于分支1 br2 br3中的i;do git branch-t$i原点/$i;完成git远程rm源或对于所有远程分支:cd/ABC对于$中的i(git branch-r|sed“s/.*origin\///”);do git branch-t$i原点/$i;完成git远程rm源现在,您可能还想删除与子项目无关的标记;你也可以稍后再做,但你可能需要再次修剪你的repo。我没有这样做,并得到一个警告:Ref‘refs/tags/v0.1‘对于所有标签都是不变的(因为它们都与子项目无关);此外,在移除这些标签之后,将回收更多的空间。显然,gitfilter分支应该能够重写其他标记,但我无法验证这一点。如果要删除所有标记,请使用git-tag-l|xargs-git-tag-d。然后使用过滤器分支和重置来排除其他文件,以便可以对它们进行修剪。还让我们添加--tag-namefilter-cat--pruneempty以删除空提交并重写标记(注意,这将不得不去掉它们的签名):gitfilter branch--标记名filter cat--修剪空--子目录筛选器ABC----全部或者替代地,只重写HEAD分支并忽略标记和其他分支:gitfilter branch--标记名filter cat--修剪空--子目录筛选器ABC HEAD然后删除备份回流,以便真正回收空间(尽管现在操作是破坏性的)git重置--硬git for each ref--format=“%(refname)”refs/original/| xargs-n 1 git update ref-dgit reflog expire--expire=现在--全部git-gc--aggressive--prune=现在现在您有了ABC子目录的本地git存储库,并保留了其所有历史记录。

注意:对于大多数用途,gitfilter分支确实应该添加参数--all。是的,那真的是——空间——全部。这需要是命令的最后一个参数。正如Matli所发现的,这会将项目分支和标记保留在新的回购中。

编辑:以下评论中的各种建议都被纳入其中,以确保存储库实际上已经缩小(以前并非如此)。


Paul的回答创建了一个包含/ABC的新存储库,但没有从/XYZ中删除/ABC。以下命令将从/XYZ中删除/ABC:

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

当然,首先在“clone--no hardlinks”存储库中测试它,然后使用Paul列出的reset、gc和prune命令进行测试。


在垃圾收集之前,您可能需要像“gitreflog-expire--expire=now--all”这样的内容来实际清理文件。gitfilter分支只删除历史记录中的引用,但不删除保存数据的reflog条目。当然,先测试一下。

尽管我的初始条件有所不同,但我的磁盘使用量在这样做时大幅下降。也许--子目录过滤器否定了这种需要,但我对此表示怀疑。


为了补充Paul的答案,我发现为了最终恢复空间,我必须将HEAD推到一个干净的存储库中,这样可以缩小.git/objects/pack目录的大小。

i.e.

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

在gc修剪之后,还要执行以下操作:

$ git push ...ABC.git HEAD

那你就可以了

$ git clone ...ABC.git

并且减小了ABC/.git的大小

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

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

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

执行克隆和筛选:gitclone--没有硬链接foo bar;cd条gitfilter分支--子目录filter subdir/you/wente删除对旧历史的所有引用。“origin”是跟踪您的克隆,“original”是过滤器分支保存旧内容的位置:git远程rm源git update ref-d refs/original/refs/heads/mastergit reflog expire--expire=现在--全部即使是现在,您的历史记录也可能被保存在fsck不会触及的文件包中。将其撕成碎片,创建新的打包文件并删除未使用的对象:git重新打包-ad

过滤器分支手册中对此进行了解释。


更新:git子树模块非常有用,以至于git团队将其拉入核心并使其成为git子树。请参阅此处:将子目录分离(移动)到单独的Git存储库中

git子树可能对此有用

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/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

在以前的解决方案中,存储库大小约为100 MB。这一次将其降至1.7 MB。也许这对某人有帮助:)


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

!/bin/bash

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


clone=/tmp/${3}Clone
newN=/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 --all
git repack -ad
git gc --prune=now

值得一提的是,下面是如何在Windows机器上使用GitHub。假设您在C:\dir1中有一个克隆的repo。目录结构如下:C:\dir1\dir2\dir3。dir3目录是我希望成为一个新的独立存储库的目录。

github:

创建新存储库:MyTeam/mynewrepo

猛击提示:

$cd c:/Dir1$gitfilter分支--修剪空--子目录筛选器dir2/dir3 HEAD返回:Ref“refs/heads/master”已重写(fyi:dir2/dir3区分大小写。)$git远程添加some_namegit@github.com:我的团队/mynewrepo.gitgit远程添加原点等不起作用,返回“远程原点已存在”$git push--进度some_name master


原始问题希望XYZ/ABC/(*文件)变为ABC/ABC/“*文件”。在为我自己的代码实现了公认的答案后,我注意到它实际上将XYZ/ABC/(*文件)更改为ABC/(*)文件。过滤器分支手册页甚至说,

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

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

我对这个问题的回答是制作存储库的两个副本,然后手动删除每个副本中要保留的文件夹。手册页支持我:

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


把这个放到你的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/subdr/targetdir),这似乎可以很好地去除大约95%的提交(根据需要)。然而,还有两个小问题。

首先,过滤器分支完成了一项出色的工作,删除了引入或修改代码的提交,但显然,合并提交在Gitiverse的站点之下。

截图:合并疯狂!

这是一个我可能可以忍受的美容问题(他说……慢慢后退,眼睛转向)。

第二,剩下的几个提交几乎都是重复的!我似乎获得了第二个多余的时间线,它几乎涵盖了整个项目的历史。有趣的是(你可以从下面的图片中看到),我的三个本地分支并不都在同一个时间线上(这就是为什么它存在,而不仅仅是垃圾收集)。

尖叫:双双,Git过滤器分支样式

我唯一能想到的是,其中一个被删除的提交可能是过滤器分支实际删除的单个合并提交,并且创建了并行时间线,因为每个现在未合并的链都有自己的提交副本。(耸耸肩,我的TARDiS在哪里?)我很确定我能解决这个问题,尽管我真的很想知道它是怎么发生的。

对于疯狂的mergefest-O-RAMA,我很可能会把它单独放在一边,因为它在我的承诺历史中根深蒂固,每当我走近时,它都会威胁我——它似乎并没有真正引起任何非外观问题,因为在Tower.app中它非常漂亮。


简单的方法™

事实证明,这是一种非常普遍和有用的做法,Git的霸主们让它变得非常简单,但你必须有一个新版本的Git(>=1.7.11 May 2012)。有关如何安装最新的Git,请参阅附录。此外,下面的演练中有一个真实世界的示例。

准备旧回购cd<大回购>git子树拆分-P<文件夹名称>-b<新分支名称>

注意:<文件夹名称>不能包含前导或尾随字符。例如,名为subject的文件夹必须作为子项目传递,而不是/子项目/

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

创建新回购mkdir~/<new repo>&&cd~/<newrepo>初始化git pull</path/to/big repo><新分支的名称>将新回购链接到GitHub或任何地方git远程添加原点<git@github.com:user/new repo.git>git push-u原始主机如果需要,清理<big repo>内部gitrm-rf<文件夹名称>

注意:这会将所有历史引用保留在存储库中。如果您确实担心提交了密码或需要减小.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,它只有btoa的提交,我想创建一个新存储库。

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

接下来,我在GitHub或Bitbucket上创建一个新的repo,并将其添加为源代码

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

快乐的一天!

注意:如果您使用README.md、.gitignore和LICENSE创建了一个repo,则需要首先执行以下操作:

git pull origin master
git push origin master

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

git rm -rf btoa

附录

macOS上的最新Git

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

brew install git

Ubuntu上的最新Git

sudo apt-get update
sudo apt-get install git
git --version

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

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

如果仍然不起作用,请尝试

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

谢谢你的评论。

清除您的历史记录

默认情况下,从Git中删除文件并不会真正删除它们,它只是表明它们不再存在。如果您想要实际删除历史引用(即您提交了密码),则需要执行以下操作:

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

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

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

但是,您不能将删除内容“推送”到GitHub等。如果你尝试了,你会得到一个错误,你必须先得到pull,然后才能得到push,然后你就回到了你的历史中。

因此,如果你想从“源”中删除历史记录-意思是从GitHub、Bitbucket等中删除它-你需要删除回购,并重新推送一个经过修剪的回购副本。但等等-还有更多!-如果你真的担心删除密码或类似的东西,你需要删除备份(见下文)。

使.git变小

前面提到的delete history命令仍然会留下一堆备份文件,因为Git非常友好,可以帮助您避免意外破坏回购。它最终会在几天和几个月内删除孤立的文件,但它会在一段时间内将它们留在那里,以防您意识到您无意中删除了一些您不想删除的文件。

所以,如果你真的想清空垃圾箱以立即减少回购的克隆大小,你必须做所有这些非常奇怪的事情:

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

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

信用

http://psionides.eu/2010/02/04/sharing-code-between-projects-with-git-subtree/从git中永久删除目录http://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/如何从git repo中删除未引用的Blob


我确实遇到了这个问题,但所有基于gitfilter分支的标准解决方案都非常缓慢。如果你有一个小的存储库,那么这可能不是问题,这是我的问题。我基于libgit2编写了另一个git过滤程序,作为第一步,它为主存储库的每个过滤创建分支,然后作为下一步将这些分支推送到清理存储库。在我的存储库中(500Mb100000次提交),标准的gitfilter分支方法花费了几天时间。我的程序需要几分钟来完成相同的过滤。

它有一个神话般的名字git_filter,住在这里:

https://github.com/slobobaby/git_filter

在GitHub上。

我希望它对某人有用。


这不再那么复杂,您只需在repo的克隆上使用gitfilter branch命令,即可选择不需要的子目录,然后推送到新的远程。

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

正确的方法如下:

git filter branch--修剪空--子目录筛选器FOLDER_NAME[first_branch][another_branch]

GitHub现在甚至有关于此类案例的小文章。

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

所以你的算法应该是:

将远程存储库克隆到另一个目录使用gitfilter分支只在某些子目录下留下文件,推送到新的远程创建提交以从原始远程存储库中删除此子目录


更简单的方法

安装git拆分。我基于jkeating的解决方案将其创建为git扩展。将目录拆分为本地分支#更改到回购的目录cd/path/to/repo#检查分支机构数字结帐XYZ#将多个目录拆分为新的分支XYZ数字拆分-b XYZ XY1 XY2在某处创建空回购。我们假设在GitHub上创建了一个名为xyz的空repo,其路径为:git@github.com:simpliwp/xyz.git推送至新回购。#为空回购添加一个新的远程源,以便我们可以在GitHub上推送到空回购git远程添加origin_xyzgit@github.com:simpliwp/xyz.git#将分支推送到空回购的主分支git push origin_xyz xyz:master将新创建的远程存储库克隆到新的本地目录#将当前目录更改为旧的存储库cd/path/to/where/you/want/the/new/local/repo#克隆您刚刚推送到的远程存储库git克隆git@github.com:simpliwp/xyz.git


这里是对CoolAJ86的“简单方法”的一个小修改™回答,以便将多个子文件夹(假设sub1和sub2)拆分为一个新的git存储库。

简单的方法™ (多个子文件夹)

准备旧回购推送<大回购>gitfilter分支--树过滤器“mkdir<文件夹名称>;mv<sub1><sub2><文件夹名称>/”HEADgit子树拆分-P<文件夹名称>-b<新分支名称>邻苯二胺注意:<文件夹名称>不能包含前导或尾随字符。例如,名为subject的文件夹必须作为子项目传递,而不是/子项目/windows用户注意:当文件夹深度>1时,<文件夹名称>必须具有*nix样式的文件夹分隔符(/)。例如,名为path1\path2\subject的文件夹必须作为path1/path2/subject传递。此外,不要使用mvcommand,而是移动。最后一点:与基本答案的最大区别是脚本“gitfilter分支…”的第二行创建新回购mkdir<新回购>推送<新回购>初始化git pull</path/to/big repo><新分支的名称>将新回购链接到Github或任何地方git远程添加原点<git@github.com:我的用户/new repo.git>git推送原点-u主清理(如果需要)popd#退出<新回购>推送<大回购>gitrm-rf<文件夹名称>注意:这会将所有历史引用保留在存储库中。如果您确实担心提交了密码或需要减小.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,这非常容易。将要移动的项目与团队->断开连接,然后团队->将其共享到新位置。默认情况下,它将尝试使用旧的回购位置,但您可以取消选中使用现有选择并选择新位置来移动它。大家都来了。


这里的大多数答案似乎都依赖于某种形式的gitfilter分支——子目录筛选器及其类似的分支。这可能在“大多数情况下”有效,但在某些情况下,例如重命名文件夹时,例如:

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

ABC/
    /move_this_dir_renamed

如果您使用普通的git过滤器样式来提取“move_this_dir重命名”,则会丢失最初为“move_this_dir”(ref)时发生的文件更改历史记录。

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

在本地克隆多模块项目分支-检查有什么:gitbranch-a对要包含在拆分中的每个分支进行签出,以在您的工作站上获得本地副本:gitcheckout--trackorigin/branchABC在新目录中创建副本:cp-r oldmultimodsimple进入新项目副本:cd simple删除此项目中不需要的其他模块:git rm other模块1 other2 other3现在只剩下目标模块的子磁盘删除模块子目录,使模块根目录成为新的项目根目录git-mv模块Subdir1/*。删除遗迹子目录:rmdir moduleSubdir1随时检查更改:git状态创建新的git repo并复制其URL以将此项目指向其中:git远程设置url源http://mygithost:8080/git/our-分裂模块回购验证这是否正确:gitremote-v将更改推送到远程存储库:git Push转到远程回购并检查所有内容对所需的任何其他分支重复此操作:git checkout branch2

接下来是github文档“将子文件夹拆分为新存储库”的步骤6-11,以将模块推送到新存储库。

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


我推荐GitHub将子文件夹拆分为新存储库的指南。步骤与保罗的答案相似,但我发现他们的指示更容易理解。

我已经修改了说明,使其适用于本地存储库,而不是托管在GitHub上的存储库。


将子文件夹拆分到新存储库中打开Git Bash。将当前工作目录更改为要创建新存储库的位置。克隆包含子文件夹的存储库。git克隆OLD-REPOSTORY-FOLDER NEW-RPOSITORY-FOLDER将当前工作目录更改为克隆的存储库。cd REPOSTORY-NAME(维修人员姓名)要从存储库中的其余文件中筛选出子文件夹,请运行gitfilter branch,提供以下信息:FOLDER-NAME:项目中要从中创建单独存储库的文件夹。提示:Windows用户应使用/分隔文件夹。BRANCH-NAME:当前项目的默认分支,例如master或gh页面。gitfilter branch—修剪空—子目录筛选器FOLDER-NAME branch-NAME#筛选目录中的指定分支并删除空提交重写48dc599c80e20527ed902928085e7861e6b3cbe6(89/89)Ref“refs/heads/BRANCH-NAME”被重写


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

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

在此命令中,gitfilter branch--prune empty--子目录筛选器FOLDER-NAME branch-NAMEBRANCH-NAME是主如果由于保护问题提交时最后一步失败,请遵循以下步骤: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 here
git commit -m 'Remove ABC'

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

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

对于多个文件夹,可以使用逗号java-jar bfg.jar--删除文件夹“{ABC1,ABC2}”metric.git

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

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

5) 现在您有了没有ABC的干净存储库,所以把它推到新的原点

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

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

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


当使用更新版本的git(2.22+可能?)运行gitfilter分支时,它表示要使用这个新工具gitfilter repo。这个工具确实简化了我的工作。

使用过滤器回购进行过滤

根据原始问题创建XYZ回购的命令:

# create local clone of original repo in directory XYZ
tmp $ git clone git@github.com:user/original.git XYZ

# switch to working in XYZ
tmp $ 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 repo
XYZ $ git remote add origin git@github.com:user/XYZ.git

# push (and track) remote master
XYZ $ git push -u origin master

假设:*远程XYZ回购是新的,在推送之前是空的

过滤和移动

在我的例子中,我还想移动几个目录以获得更一致的结构。最初,我运行简单的filter repo命令,然后运行git mv dir进行重命名,但我发现使用--path重命名选项可以获得稍微“更好”的历史记录。我去年(在GitHub UI中)看到的新回购中移动文件的修改时间与原始回购中的修改时间相匹配,而不是5小时前的最后一次修改。

而不是

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

我最终跑了。。。

git filter-repo --path XY1 --path XY2 --path inconsistent --path-rename inconsistent:XY3
Notes:

我认为Git Rev News博客文章很好地解释了创建另一个回购过滤工具的原因。我最初尝试的路径是在原始存储库中创建一个与目标回购名称匹配的子目录,然后进行过滤(使用gitfilter repo--匹配新回购名称的子目录过滤器dir)。该命令正确地将该子目录转换为复制的本地repo的根目录,但它也只生成了创建子目录所需的三次提交的历史记录。(我没有意识到--路径可以多次指定;因此,不需要在源repo中创建子目录。)由于在我注意到我未能继续执行历史记录时,有人已经提交了源repo,所以我只在subdir move之前使用了git reset commit,在clone命令之后使用了,并在filter repo命令中添加了-force,以使其在稍微修改过的本地克隆上运行。

git clone ...
git reset HEAD~7 --hard      # roll back before mistake
git filter-repo ... --force  # tell filter-repo the alterations are expected

由于我不知道git的扩展模式,我在安装过程中遇到了困难,但最终我克隆了gitfilter repo并将其符号链接到$(git-exec路径):

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-folder
Created branch 'my-folder'
aecbdc3c8fe2932529658f5ed40d95c135352eff

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

2.创建新存储库

$ cd my-folder
$ git init
Initialized empty Git repository in /Users/adamwest/Projects/learngit/shop/my-folder/.git/
$ git add .
$ git commit -m "initial commit"
[master (root-commit) 192c10b] initial commit
 1 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 master
Enumerating 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 -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

我们在这里添加远离GitHub的新存储库,然后推送我们的第一次提交。

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

$ cd ../
$ git rm -rf my-folder
rm 'my-folder/file'
$ git commit -m "Remove old folder"
[master 56aedbe] remove old folder
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 my-folder/file
$ git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 4 threads
Compressing 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.git
   74dd8b3..56aedbe  master -> master

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