使用git filter-branch拆分项目
@ wgjak47 | 星期六,十二月 29 日,2018 年 | 3 分钟阅读 | 更新于 星期六,十二月 29 日,2018 年

需要把一个大的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 的“核弹级选项”

git
保存为图片