随着工程的代码量的增加, 大家可能需要对工程进行拆分, 并且拆分的同时还需要保留子工程的历史提交版本. 这时就需要用到git filter-branch
命令了.
filter-branch
是一个非常强大的git命令, 用来重写版本历史. 通过他, 我们可以实现一些高级功能.
例如:
- 删除所有历史版本中的某个文件
- 工程拆分
- 更具预先定义的规则修改历史提交信息
等等
filter-branch
执行成功后, 会产生与原来不同的提交, 所以对分支修改后, 不可再push到正在使用的已发布分支.
1. 场景
原工程1
2
3
4
5saas-app
├─core
│ ├─user-api
│ └─user-svc
└─common
拆分成两个工程user
和common
:1
2
3
4user
├─user-api
└─user-svc
common
2. 拆分方法
以user为例:
编写处理脚本
新建index-filter.sh
, 每个index均会执行该该脚本1
2
3
4
git read-tree --empty
git read-tree --prefix=user-api/ ${GIT_COMMIT}:core/user-api/
git read-tree --prefix=user-svc/ ${GIT_COMMIT}:core/user-svc/
说明: 对于每一个index, 首先清空index, 并将core/user-api/路径改为user-api/index-filter.sh
需要有+x权限
路径不能以/
开头, 必须以/
结尾
调用filter-branch命令
1 | git filter-branch -f --index-filter /s/index-filter.sh -- --all |
-f
会强制执行, 忽略上一次filter-branch操作的备份/filter.sh
是处理脚本, 会对每次index执行该脚本, 脚本路径为绝对路径--
用于分割--all
针对所有的分之, tags
此过程执行时间较长, 且IO很高, 建议在内存盘中执行(tmpfs).
清理空提交
如果有多个提交1
git filter-branch -f --commit-filter 'git_commit_non_empty_tree "$@"' -- --all
分出子版本库
可清除git版本库中无法触及的提交1
2
3
4
5git init --bare ../user.git
git push ../user.git refs/*
cd ..
git clone user.git user
简单拆分
对于简单的拆分(某一个子目录直接作为子工程, 例如本利中的common)
可用使用下述命令1
git filter-branch --subdirectory-filter common -- --all
该命令执行的同时会自动清除无用提交
3. 预备知识
git read-tree
git read-tree用于将制定提交读入用户的index(暂存区). 也就是.git/index文件. 可通过git ls-files –stage查看暂存区中存储的内容.
git read-tree <commit-id>
可读取指定提交到当前index.
(我的理解git的快照就是index, 历史版本就是一个个的index想连, commit操作相当于将当前index压入版本库, 新建一个新的index)git read-tree --empty
清除当前的index.
(这样我们在暂存区会看到文件被删除的信息, 因为git拿当前index与最后一次提交的快照进行对比, 由于当前index全清空了, 故会提示文件被删除)git read-tree --prefix=user-svc/ <commit-id>:core/user-svc/
将制定提交的指定文件读入当前index, 并且文件路径添加user-svc前缀.
(–prefix中的user-svc/的/
不可省略)
引用git帮助文档中的一张图:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 commit-tree
commit obj
+----+
| |
| |
V V
+-----------+
| Object DB |
| Backing |
| Store |
+-----------+
^
write-tree | |
tree obj | |
| | read-tree
| | tree obj
V
+-----------+
| Index |
| "cache" |
+-----------+
update-index ^
blob obj | |
| |
checkout-index -u | | checkout-index
stat | | blob obj
V
+-----------+
| Working |
| Directory |
+-----------+
git filter-branch
用于重写分支的历史
- –index-filter
每次回调, git会设置这些环境变量
GIT_AUTHOR_NAME
GIT_AUTHOR_EMAIL
GIT_AUTHOR_DATE
GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL
GIT_COMMITTER_DATE
对于–index-filter, 如果在修改过程中修改了当前的index, 则回调结束后, git会将当前的index作为新的提交.
4. 其他
可用该脚本生成一个demo的仓库1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
gitCommit(){
git add -A
git commit -m $1
}
mkdir saas-app
cd saas-app
git init
mkdir -p core/user-api
mkdir -p core/user-svc
mkdir -p common
echo "commit1" > core/user-api/code
echo "commit1" > core/user-svc/code
echo "commit1" > common/code
gitCommit "commit1"
echo "commitApi" >> core/user-api/code
gitCommit "commitApi"
echo "commitSvc" >> core/user-svc/code
gitCommit "commit1"
echo "commitCommon" >> common/code
gitCommit "commitCommon"
git checkout -b user
echo "user" >> core/user-api/code
echo "user" >> core/user-svc/code
gitCommit "user"
git checkout master
echo "commitCommon" >> common/code
gitCommit "common"
git merge user --no-edit
git branch -D user
echo "commit2" >> core/user-api/code
echo "commit2" >> core/user-svc/code
echo "commit2" >> common/code
gitCommit "commit2"