利用git-filter-repo无缝迁移git项目


背景
正所谓天下大事合久必分,分久必合。实际工作中的项目也类似,有的项目越来越大,或者有时候需要把没有前后端分离的项目代码拆分到两个仓库里,就涉及到对已有git项目的迁移操作。
如果只是简单的把代码拆开并不难,可以选择直接下载git项目源代码压缩包,拆分后push到新建的git仓库里就可以了。
但是现实世界往往没有那么简单,比如正在进行的开发分支还没有合并,或者我们想保留提交记录和分支关系。如此种种原因导致无法简单粗暴地把源代码扔到一个新建的git仓库里,还涉及对git记录进行必要的裁剪。
本文就介绍一个好用的工具来进行无损的git仓库迁移。
真实案例
在实际工作中,我们有一个项目,其项目目录结构很简单,是一个既含有前端代码,也含有服务端代码的仓库,如下所示:
server/
foo.c
bar.c
webapp/
app.tsx
components/
index.tsx
zebra.jpg
我们希望将前端和服务端代码拆分成两个独立的git仓库,但是因为开发同学还有正在开发的功能分支没有合并,因此我们希望能够同时迁移代码和git提交历史以及分支。最终希望得到的结果是:
git repo A: ./server/
git repo B: ./webapp/
git-filter-branch命令
通过查看git文档,首先考虑使用
git filter-branch
命令来进行迁移。简单来说该命令可以用来操作目录树,同时修改历史提交记录。
在我还没来得及完全理解这个命令之前,就看到文档中有这样一段
warning
git filter-branch has a plethora of pitfalls that can produce non-obvious manglings of the intended history rewrite (and can leave you with little time to investigate such problems since it has such abysmal performance). These safety and performance issues cannot be backward compatibly fixed and as such, its use is not recommended. Please use an alternative history filtering tool such as git filter-repo . If you still need to use git filter-branch , please carefully read SAFETY (and PERFORMANCE ) to learn about the land mines of filter-branch, and then vigilantly avoid as many of the hazards listed there as reasonably possible.
这里提到了
filter-branch
命令由于有可能产生杂乱的提交历史,以及惨不忍睹的执行效率,所以最终推荐了一个第三方工具
git filter-repo
。接下来,就该今天的主角登场了。
git-filter-repo
在
github
首页上,关于
git-filter-repo
有这样的描述
git-filter-repo
可以胜任很多需要修改提交历史的场景,虽然它与git-filter-branch
命令功能有些许重合,但摒弃了git-filter-branch
那令人抓狂的执行效率。
在功能方面,git-filter-repo
的人机交互设计让其面对简单的修改更加游刃有余,同时仍然可以像复杂的git-filter-branch
命令一样完成庞杂的任务。
接下来我们考虑如何利用这个强大的工具来进行git项目的迁移。
首先,需要定义成功迁移的标准:
- 删减server目录,仅保留webapp目录,并让webapp成为新的根目录
- 推向新的git仓库
- 确认在新的git仓库中仍然保留所有与webapp相关的分支
- 确认迁移之后保留的分支历史记录应该与老仓库的历史记录一致
相关命令简介
查看
git-filter-repo
的文档可以看到有不少简单的示例,很幸运有一些例子正好可以解决我们的问题。
git-filter-repo
的命令选项 (flag) 主要用来操作目录树,根据操作的目录树自动判断需要修改的git提交历史信息。
比如我们需要保留webapp目录,删除server目录,那么仅需执行:
git filter-repo --path 'webapp/'
这样仓库中的目录结构就会变为:
webapp/
app.tsx
components/
index.tsx
可以看到,已经没有server文件夹了。
再比如如果我们希望删除.DS_Store文件以及其提交历史:
git filter-repo --invert-paths --path '.DS_Store'
当然通常我们需要删除根目录下所有.DS_Store文件及其历史,那么可以加上
--use-base-name
选项,表示匹配文件名,而不是匹配完整路径:
git filter-repo --invert-paths --path '.DS_Store' --use-base-name
其中
--path
选项后跟需要操作的路径或者文件名,
--invert-paths
选项字面意思是反向,因此该标记表示的是删除操作。
以上都是删除某些文件或者保留某些文件的操作,其目录结构仍然会保留原始仓库的结构,但我们需要的是仅保留webapp目录下的所有文件,并将其中的内容移动到根目录下。针对这种场景
git-filter-repo
提供了一个叫做
--subdirectory-filter
的选项,接下来就进行实际操作。
实际操作
⚠️
git-filter-repo
需要在一个干净的,刚刚clone下来的仓库中进行操作,否则会提示操作并停止操作
接下来再看一下老的仓库目录结构
server/
foo.c
bar.c
webapp/
app.tsx
components/