重写git历史记录

Category:
发表:
大纲
  1. 1. 案例
  2. 2. 方案
  3. 3. 额外的问题
  4. 4. 参考链接

大家在使用git时做版本管理时,有时候会遇到下面的场景,

  • 不小心将一个很大的文件提交到仓库中了,导致仓库臃肿,上传下载都非常消耗网络,追悔莫及!
  • 随着项目的推进,突然不想用git来管理某个文件了,将其放入.gitignore文件中,发现git依然能够探测到这个文件的改动,一脸懵逼!

本文将会就以上两个问题给你指条明路。

git filter-branch命令就是我们的主角。git filter-branch命令在git中号称大杀器命令,基本上可以用这个命令能够到达任何操作要求,注意,是任何

关于这个命令的文档我已经在上面的链接中给出了,一句话概括这个命令的作用:操作所有的git对象数据以重写历史记录。有兴趣掌握其原理的,可自行找虐。下面,我们将会用一个实际的案例来说明“如何重写git历史记录”。

案例

在前端开发中,npm是必不可少的包管理工具。在npm@5.0.0之后,npm引入了package-lock.json文件。关于这个package-lock.json是干什么用的,这个官方的说明文档

简单来说,这个package-lock.json文件是npm用来“锁版本”的。这里所谓的锁版本是指当在项目拥有package-lock.json文件时,npm会根据其自动解析出包依赖,不会出现在不同的环境场景下,安装了不同的包版本。

在开始使用npm@5.0.0的时候,执行完npm install后,有一个提示,

npm notice created a lockfile as package-lock.json. You should commit this file.

然后我就按照这句提示来做了。后来发现这玩意坑很多。所以我们现在想将这个package-lock.json文件放到.gitignore中,希望git仓库不再追踪这个文件。

方案

想做的事情已经很明确了,接下来我们来动手了。

首先明确一点,只要一个文件被git管理中(即被添加到git仓库中),那么无论是删除这个文件,还是将其添加到.gitignore文件中,都是没办法组织git继续对齐进行管理和追踪的。

所以,我们改写git仓库的历史记录。

step 1

git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch package-lock.json' --prune-empty --tag-name-filter cat -- --all

我们使用了git filter-branch命令,对所有的分支上的commit执行额外的命令操作。这个操作就是git rm --cached --ignore-unmatch package-lock.json,忽略对package-lock.json文件的追踪,从git仓库中将其移除,并同时重写每一条记录。

step 2

rm -rf .git/refs/original
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

现在历史记录中已经不包含对那个文件的引用了。不过reflog以及运行filter-branch时,git往.git/refs/original添加的一些refs中仍有对它的引用,因此需要将这些引用删除并对仓库进行repack操作。在进行repack前需要将所有对这些commits的引用去除。

Step 3

git push origin master --force

将本地重写的git仓库强制推送到远程origin。

额外的问题

如果之前签出了远程分支,比如在很早的时候,通过git checkout --track origin/a签出了分支a到本地。那么在重写历史之后,可能本地a分支会丢失对远程分支remote/a的追踪。造成这种现象的原因是在重写记录时,将origin/a分支上的记录重写了,导致本地a分支与远程remote/a分支不再匹配。

此时,我们应该删除本地游离的分支a,然后从remote/a上重新签出分支a到本地。

另外还可能会出现一个问题,就是我们是针对master分支进行重写记录的,如果团队中其他成员在自己的本地有尚未合并进master分支的开发分支,那么可能会出现本地开发分支与远程分支游离的情况。此时我们需要针对这个开发分支再做一个重写历史记录。

参考链接