需要把一个大的git repo拆分成多个小的git repo并保留相关文件的提交记录
前言
最近在把一个大项目拆分成多个小项目,以方便维护,以往我是直接新建一个repo,然后把文件直接copy过去。后来发现这样有一个严重的问题,这些文件的提交记录会丢失。后来用fork的方式,但是这样就会有很多无关的历史提交记录。为了保留同事的工作成果记录(更方便的证明这个代码不是我写的),我重新翻了一下Pro Git,发现git filter-branch
这个命令有一定的操作空间。google的一些实践,发现可以通过更改git filter-branch + 更改git remote的方式实现。流程大概如下:
git filter-branch
这个git的子命令在Pro Git中被称为The Nuclear Option,也就是核弹级别的选项。因为使用这个命令,会覆写巨量的提交历史。所以在公共项目上要慎重使用,除非是你自己的项目或者像我一样要对原本的项目进行拆分。 git filter-branch的命令格式如下:
NAME
git-filter-branch - Rewrite branches
SYNOPSIS
git filter-branch [--setup <command>] [--subdirectory-filter <directory>]
[--env-filter <command>] [--tree-filter <command>]
[--index-filter <command>] [--parent-filter <command>]
[--msg-filter <command>] [--commit-filter <command>]
[--tag-name-filter <command>] [--prune-empty]
[--original <namespace>] [-d <directory>] [-f | --force]
[--state-branch <branch>] [--] [<rev-list options>...]
你会发现里面很多选项后面都跟着一个command,这些选项的意思是通过外部的命令,来对git repo做出修改,然后根据这个结果修改提交历史。例如git filter-branch --tree-filter "rm password.txt develop"
这个命令,会从develop分支所有的提交记录中删除password.txt这个文件的存在。如果同时使用--prune-empty
参数,那么在执行过程中产生的空提交就会被删除(例如某次提交只修改了password.txt,那么,这个修改在git执行filter-branch后会变成空提交)。
项目拆分实践
假设现在有这样一个项目,目录结构是这个样子的:
.
├── conf
│ └── default.conf
├── Dockerfile
├── docs
│ ├── api.md
│ └── tips.md
├── models
│ ├── machine.py
│ └── service.py
├── scripts
│ ├── get_collections.py
│ └── load.py
├── misc
│ ├── compose
│ │ └── docker-compose.yml
│ └── sources.list
而你需要把script目录拆分成一个单独的repo,可以使用以下命令,来保留提交记录。
git filter-branch --subdirectory-filter --prune-empty scripts {branch_name}
git remote set-url {remote_name} {new_repo_url}
git push {remote_name} {branch_name}
如果你需要保留全部分支,可以使用–all选项。
另一个例子,如果你希望能够保留单个文件,可以考虑使用--tree-filter
选项, 例如只保留load.py,其他都不要:
git filter-branch --tree-filter 'find ./* -type f -not -name "load.py" -not -name ".git*" -delete' --prune-empty {branch_name} # 配合find命令,删除无关文件,注意别把.git也删除
git remote set-url {remote_name} {new_repo_url}
git push {remote_name} {branch_name}
Tips
- 如果不小心操作失误,那么还有一次回滚的机会,执行
git filter-branch
的时候git
会对当前状态进行备份,存放在refs/original/refs/heads/{branch_name}
,使用:git reset --hard refs/original/refs/heads/{branch_name}
来进行恢复 - 如果需要对同一分支进行多次
git filter-branch
,会出现报错,这个可以使用-f
选项,但是这样做会覆盖备份,就无法回到从前了。
参考
Git Tools Rewriting History git filter branch refer github上的项目拆分指引 大佬的blog文章: 初次使用 git 的“核弹级选项”