Sin
In a
Nutshell
Git 的一些奇技淫巧

Git 的一些奇技淫巧

July 18, 2018 /专业打杂一百年

在你面前的,也许是一个有些年头但一直频繁迭代的代码仓库。 虽然不可能像 Google 几十亿行那么恐怖,但也可能有个几百万行代码,数万个提交了。 这时下面这些 git 小技巧也许可以派上一些用场,让生活稍微美好一点。

一个仓库只签出部分文件

你的仓库里囊括了个很多个项目,占据了好多个 G 的磁盘空间。 而你可能只负责某个项目里的某几个模块,希望可以只关注自己负责的那些文件。 这个时候,可以使用 sparseCheckout 稀疏签出,只签出仓库里的部分文件。

这样做的好处一是节省存储空间(SSD 的价格什么时候才能降回来啊摔), 二是当拉取或者切换分支时,只会处理指定的那些文件,可以大大提高操作速度。

首先,执行下面的命令将这个选项开启:

git config core.sparsecheckout true

然后,将你希望签出的文件写在目录下的 .git/info/sparse-checkout 文件里,可以使用与 .gitignore 文件一样的通配符。 举个例子:

cat <<EOF > .git/info/sparse-checkout
your-project/src
your-project/*.js
!your-project/src/android
!your-project/src/ios
EOF

最后,重新签出文件并检查下是否达成了目的:

git checkout
git ls-files -v

可以看到类似这样的输出。 前面的 S 表示 skip-worktree 跳过,这个文件就不会被真正签出了。

H your-project/src/index.js
S your-project/doc/index.html

一个仓库签出多个分支的文件

你们的业务发展很快,可能有多个大特性需要并行开发,所以需要经常在多个分支间切换。 你一气之下给把每个分支都单独克隆了一份出来。 这时瞅了一眼剩余磁盘空间,发现即使用了稀疏签出,抠门公司配的 120G SSD 也还是快被塞满了。

你发现每个克隆出来文件夹下都有一个保存仓库信息和所有历史的 .git 目录(可能有好几个 G),它们都是完全一样的。 可不可以共用同一个 .git 目录呢?

这就是 git 的 worktree 功能了。 它可以让你在只留一份仓库信息的情况下,同时签出多个分支的文件来进行工作。

操作很简单,给需要的分支(假设已经有一个分支叫 another-branch)创建一个 worktree:

cd path/to/your/repo
git worktree add ../another-branch-of-your-repo another-branch

这样就在原仓库的平级创建了一个新目录 another-branch-of-your-repo,包含了 another-branch 分支的文件。 这个目录下没有 .git 目录,取而代之的是一个 .git 文件,包含了对应真正的 .git 目录路径。

可以通过 git worktree list 来查看当前有哪些工作树。 要删除工作树的话,直接删掉那个目录,然后执行 git worktree prune 就好了。

拆分 git 仓库

公司终于发现把所有项目都放在一个仓库里给大家带来的痛苦,于是决定将它们拆到不同的仓库,并将这个光荣的任务交给了你。 那么,让我们来大展伸手吧~

拆分单个文件夹到新的 git 仓库

拆分一个文件夹非常简单,直接使用 git 的 subtree split 命令就可以了:

git subtree split --prefix=path/to/sub-project -b split

这样会创建一个新的分支 split,包含了 path/to/sub-project 这个子目录的所有历史提交记录。 然后,我们把这个分支推到自己的仓库就搞定了。可以先在本地建一个仓库试下:

mkdir ../sub-project
git init ../sub-project --bare
git push ../sub-project split

拆分多个文件夹到新的 git 仓库

如果你的目标是多个子文件夹,那事情就变得有点复杂。 粗暴地将它们拷贝到一个目录下再通过 subtree split 拆分出来可能会导致历史记录的丢失。

这个时候,就需要祭出大杀器 filter-branch 了:

git filter-branch --index-filter 'git rm --cached -qr --ignore-unmatch -- . && git reset -q $GIT_COMMIT -- path/to/sub-folder1 path/to/sub-folder2' --prune-empty -- --all

这条命令会重写所有的历史记录,只保留子文件夹相关的内容(也会产生很多空的合并提交,暂未找到解决方案)。 杀伤力巨大,操作前务必要备份哟~