在使用Git的过程中,经常会遇到需要直接在本地代码库进行编译、执行或配置文件修改的情况,但我们不希望这些配置被Git所记录,甚至被误上传到远端仓库。本文章即讲解如何在Git中忽略对本地特定已入库文件的修改。

0x01

通常我们会使用.gitignore文件来避免Git追踪本地的文件或目录,但.gitignore只能忽略本地有,而远端没有的文件或目录。

举例说明,如果我们需要clone一个PHP程序,对其进行修改与调试,在调试过程中往往需要编辑配置文件,以实现数据库的连接等操作,而对于配置文件的修改不能传递到远端仓库上。

这个时候我们就需要使用git update-index命令来对git本地索引进行定制。

0x02

在了解如何使用该命令前,首先我们需要了解什么是仓库索引。

当我们执行git clone命令时,会在对应目录里看到我们clone下来的代码文件,但其实我们所看到的文件只是根据当前索引建立出来的『镜像』,即通常所述的工作区。

索引(index)是一个存放了排好序的路径的二进制文件,包含了对所有文件及其所在文件对象SHA1值(即git独特的块文件存储结构)的映射。我们可以通过git ls-files --stage命令看到仓库中每一个文件及其所对应的文件对象。

# git ls-files --stage
100644 d717f9f65d8c51afe074d2f9c799e3436ad172fd 0       .gitignore
100644 7a4eb6572137f46011ca256aba514cb71878f73a 0       CHANGELOG.md
100644 68b67f7fc44d81128dfc8a99014428fb05ed32b4 0       FEATURE.md
100644 a63406631f85124383ca9ba4e9747d083b79faf2 0       OPS.md
100644 05c51f61bcb956bfbac2d06deb9eba09e08b983b 0       README.md
100644 40e4959d91e379b7f431af8fbe9a484478083c48 0       app/app.go
100644 6476d24fc6409be665aceb0c06df822223023661 0       app/cache.go
100644 12ff45a896fa60eb5592be721bff638a8c7a5caf 0       app/image-process.go
100644 d52448fbf2a80337b6b502966ba8a29137d2acf4 0       app/image.go
100644 8e403530dd9b4b5edee1995d1863890c1d0c1fa4 0       app/log.go
100644 e19ce6e2ae4e8211330e9e48f7b71eecdf4c3536 0       config.yaml
100755 04ace730b8623af1c0c6645220cf885637f9efc0 0       core/server.go
100644 e6c7b45f3ade37c2a79c7539a237fa4219181b70 0       db/db.go
100644 601306cb229cf3e76621016f798e87157850b1a2 0       db/mysql.go
100644 9a144a00fa8bc238eb26ac02c28520101b4656bf 0       db/sqlite.go
......

当我们在当前工作区进行修改后执行git status,就会触发Git对当前工作区与索引内文件的比对(基于修改时间、状态与文件的SHA1值),并输出所有修改但未提交的的文件。

0x03

上一节我们大概了解了索引的概念,而本篇文章内我们要实现的其实就是在执行git status的时候忽略指定的文件或目录。

好在Git提供了大量关于索引的命令,大部分都在git update-index指令下。

在已经安装了Git的电脑上,我们可以使用man git-update-index查阅相关手册。可以发现,该命令包含了大量对仓库索引的配置,例如可以直接添加一个文件到索引中、从索引中删除一个文件,或是忽略对某个子模块的更新等等。

我们所需要使用到的参数是--[no-]assume-unchanged,即『(取消)假定不变』。该参数可以实现在上文所述比对过程中忽略某个文件。

0x04

首先我们来看相关的文档:

--[no-]assume-unchanged
           When this flag is specified, the object names recorded for the paths are not updated. Instead, this
           option sets/unsets the "assume unchanged" bit for the paths. When the "assume unchanged" bit is on, the
           user promises not to change the file and allows Git to assume that the working tree file matches what is
           recorded in the index. If you want to change the working tree file, you need to unset the bit to tell
           Git. This is sometimes helpful when working with a big project on a filesystem that has very slow
           lstat(2) system call (e.g. cifs).

从文档中可以看到,该命令可以设置/取消路径的『假定不变』位,以实现对指定路径更新的跟踪与否。

如果我们需要对config.yaml文件设置『假定不变』,即避免提交其变更,我们可以直接执行git update-index --assume-unchanged config.yaml

0x05

那如果我们真的需要对config.yaml作出修改(例如更新其字段等),需要怎么做呢?

首先我们可能需要备份config.yaml到其他目录以保持配置文件不被覆盖,例如执行cp config.yaml ../,可以将其备份到上一目录。

然后我们使用索引中的文件对象,恢复该文件到索引中的状态:git reset HEAD config.yaml

接着我们就可以对该文件进行修改。修改完毕后我们再执行git update-index --no-assume-unchanged config.yaml取消其『假定不变』位,这时候就可以在git status中看到对该文件的修改,并可以正常的commit与push。

如果读者只是需要满足该需求,读到这里就可以关闭浏览器,继续自己的工作了。但如果想继续深入,欢迎阅读接下来的章节。

0x06

我们来扩展一下思维:在拿到一个本地代码库的时候,怎么知道其中哪些文件被标记为『假定不变』呢?

还记得#0x02我们提到的git ls-files命令吗?我们来看看它的手册:

NAME
       git-ls-files - Show information about files in the index and the working tree
......
 -v: Similar to -t, but use lowercase letters for files that are marked as assume unchanged (see git-update-index(1)).
 ......

可以看到,其所包含的-v参数可以列举出所有的文件,并在被标记为『假定不变』的文件名前使用小写字母。

因此,我们只需要使用如下的命令就可以输出所有被标记的路径了:

# git ls-files -v | grep "^[a-z]"
h config.yaml
h static/db/example.db

0x07

这一节我们来看看索引文件到底长什么样子。

篇幅所限,请参考我写的文章《深入Git索引》继续阅读。