首页 > 代码库 > Git Pro深入浅出(三)

Git Pro深入浅出(三)

七、自定义Git

前面已经阐述了Git基本的运作机制和使用方式,介绍了许多Git提供的工具来帮助你简单且有效地使用它。本部分将演示如何借助Git的一些重要的配置方法和钩子机制,来满足自定义的需求。

1. 配置Git

(1)配置Git
可以用git config 配置Git。

$ git config --global user.name "ligang"
$ git config --global user.email "ligang@example.com"

**快速回忆下:**Git使用一系列配置文件来保存你自定义的行为。

  • 如果传递 –system 选项给git config,它就会读写 /etc/gitconfig 文件;
  • 如果传递 –global 选项给git config,会查找每个用户的 ~/.gitconfig 文件;
  • 如果不传递任何选项给git config,会查找你正在操作的版本库所对应的Git目录下的.git/config配置文件。

以上三个层次中每层的配置(系统、全局、本地)都会覆盖掉上一层次的配置:本地 > 全局 > 系统
(2)Git中的着色
Git会自动着色大部分输出内容,但如果你不喜欢花花绿绿,也可以关掉。

# 这个设置的默认值是auto
$ git config --global color.ui false
# 具体设置某项,diff的输出信息以蓝色前景、黑色背景和粗体显示
$ git config --global color.diff.meta "blue black bold"

(3)格式化与多余的空白字符
如你正在 Windows 上写程序,而你的同伴用的是其他系统(或相反),你可能会遇到CRLF问题。这是因为Windows使用回车(CR)和换行(LF)两个字符来结束一行,而 Mac 和 Linux 只使用换行(LF)一个字符。 虽然这是小问题,但它会极大地扰乱跨平台协作。
如果是在Windows系统上,把它设置成 true,这样在检出代码时,换行会被转换成回车和换行:

$ git config --global core.autocrlf true

如果使用以换行作为行结束符的 Linux 或 Mac,设置成input来告诉Git在提交时把回车和换行转换成换行,检出时不转换:

$ git config --global core.autocrlf input

如果,所有人使用相同的系统开发,则可以取消该功能:

$ git config --global core.autocrlf false

Git 预先设置了一些选项来探测和修正多余空白字符问题。 它提供了六种处理多余空白字符的主要选项。

选项 说明 是否默认开启
blank-at-eol 查找行尾的空格 默认开启
blank-at-eof 盯住文件底部的空行 默认开启
space-before-tab 警惕行头tab前面的空格 默认开启
indent-with-non-tab 揪出以空格而非tab开头的行 默认关闭
tab-in-indent 监视在行头表示缩进的 tab 默认关闭
cr-at-eol 告诉 Git 忽略行尾的回车 默认关闭

多个选项以逗号分割,通过逗号分割的链中去掉选项或在选项前加-来关闭。

# 开启indent-with-non-tab,cr-at-eol模式
$ git config --global core.whitespace "indent-with-non-tab,cr-at-eol"
# 在打上补丁前自动修正此问题
$ git apply --whitespace=fix <patch>

2. Git属性

你也可以针对特定的路径配置某些设置项,这样Git就只对特定的子目录或子文件集运用它们。这些基于路径的设置项被称为Git属性,可以在你的目录下的 .gitattributes 文件内进行设置(通常是你的项目的根目录)。如果不想让这些属性文件与其它文件一同提交,你也可以在 .git/info/attributes 文件中进行设置。通过使用属性,你可以对项目中的文件或目录单独定义不同的合并策略,让Git知道怎样比较非文本文件,或者让Git在提交或检出前过滤内容。
(1)导出版本库
当归档的时候,可以设置Git不导出某些文件和目录。如果你不想在归档中包含某个子目录或文件,但想把它们纳入项目的版本管理中,你可以在export-ignore属性中指定它们。

# 在项目导出的压缩包中,不包含test/目录下文件
test/ export-ignore

(2)合并策略
通过Git属性,你还能对项目中的特定文件指定不同的合并策略。一个非常有用的选项就是,告诉Git当特定文件发生冲突时不要尝试合并它们,而是直接使用你这边的内容。

# 合并分支时,确保database.xml选择我们的,不被覆盖
database.xml merge=ours
# 定义一个虚拟的合并策略,叫做ours
$ git config --global merge.ours.driver true
$ git merge topic
Auto-merging database.xml
Merge made by recursive.

3. Git钩子

Git 能在特定的重要动作发生时触发自定义脚本。 有两组这样的钩子:客户端的和服务器端的。
客户端钩子由诸如提交和合并这样的操作所调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。
钩子都被存储在.git/hooks目录下,默认以.sample结尾。如果你想启用它们,得先移除这个后缀。
技术分享

3.1 客户端钩子

客户端钩子可分为:提交工作流钩子电子邮件工作钩子其他钩子
(1)提交工作流钩子

  1. pre-commit 钩子在键入提交信息前运行。它用于检查即将提交的快照。例如,检查是否有所遗漏,确保测试运行,以及核查代码。
    如果该钩子以非零值退出,Git将放弃此次提交,不过你可以用 git commit --no-verify 来绕过这个环节。
  2. prepare-commit-msg
    钩子在启动提交信息编辑器之前,默认信息被创建之后运行。你可以结合提交模板来使用它,动态地插入信息。
  3. commit-msg
    钩子接收一个参数,此参数即上文提到的,存有当前提交信息的临时文件的路径。可以对提交信息是否遵循指定的模板校验。
  4. post-commit 钩子在整个提交过程完成后运行。该钩子一般用于通知之类的事情。

(2)电子邮件工作流钩子
都是由 git am 命令调用的。

applypatch-msg --> pre-applypatch --> post-applypatch 

(3) 其它客户端钩子

  1. pre-rebase 钩子运行于变基之前
  2. post-rewrite 钩子替换提交记录的命令调用
  3. post-checkout 钩子checkout 成功运行后调用
  4. post-merge 钩子merge 成功运行后调用
  5. pre-push 钩子push 运行后调用
3.2 服务端钩子

服务端钩子在推送到服务器之前和之后运行

  1. pre-receive
    处理来自客户端的推送操作时,如果它以非零值退出,所有的推送内容都不会被接受。可以用这个钩子阻止对引用进行非快进(non-fast-forward)的更新,或者对该推送所修改的所有引用和文件进行访问控制。
  2. update 和pre-receive 脚本十分类似,不同之处在于它会为每一个准备更新的分支各运行一次。
  3. post-receive 在整个过程完结以后运行,可以用来更新其他系统服务或者通知用户。

八、Git与其他系统

如果你的代码目前不在Git上,但是想迁移到Git,可以参考下面两个地址:
【Git与其他系统 - 作为客户端的Git】
【Git与其他系统 - 迁移到Git】

九、Git内部原理

了解Git内部原理对于理解Git的用途和强大至关重要。
Git是一个内容寻址(content-addressable)文件系统,并在此之上提供了一个版本控制系统的用户界面。

1. 底层命令和高层命令

之前章节讲述的友好的命令,基本都是“高层”命令。
底层命令得以让你窥探Git内部的工作机制,也有助于说明Git是如何完成工作的,以及它为何如此运作。多数底层命令并不面向最终用户:它们更适合作为新命令和自定义脚本的组成部分。

当在一个新目录或已有目录执行git init时,Git会创建一个.git目录。这个目录包含了几乎所有Git存储和操作的对象。如若想备份或复制一个版本库,只需把这个目录拷贝至另一处即可。

$ cd .git
$ ls -F1
HEAD            # 指示目前被检出的分支
index           # 保存暂存区信息
config*         # 包含项目特有的配置选项
description     # 仅供GitWeb程序使用,我们无需关心
hooks/          # 包含客户端或服务端的钩子脚本
info/           # 包含一个全局性排除文件
objects/        # 存储所有数据内容
refs/           # 存储指向数据(分支)的提交对象的指针

2. Git对象

Git是一个内容寻址文件系统。Git的核心部分是一个简单的键值对数据库(key-value data store)。你可以向该数据库插入任意类型的内容,它会返回一个键值,通过该键值可以在任意时刻再次检索(retrieve)该内容。

2.1 数据对象

(1)向Git中存入内容

# 所以数据内容都存在objects中
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
# 往Git数据库存入一些文本
$ echo ‘test content‘ | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4 

显示40个字符的校验和:前两个字符用于命名子目录,余下的38个字符则用作文件名。

  • -w 选项指示 hash-object 命令存储数据对象;若不指定此选项,则该命令仅返回对应的键值。
  • –stdin 选项则指示该命令从标准输入读取内容;若不指定此选项,则须在命令尾部给出待存储文件的路径。
# 查看objects中的文件
$ find .git/objects -type f
.git/objects//d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
# 查看文件内容
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
  • -p 选项可指示该命令自动判断内容的类型,并为我们显示格式友好的内容

(2)从Git中取出内容

# 创建一个新文件并将其内容存入数据库
$ echo ‘version 1‘ > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
# 接着,向文件里写入新内容,并再次将其存入数据库
$ echo ‘version 2‘ > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
$ find .git/objects -type f
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
# 把内容恢复到第一个版本或第二个版本
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt 
version 2

(3)查看文件存储对象的类型

$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob # 数据对象
2.2 树对象

使用“数据对象”存储,其记住文件的每一个版本所对应的SHA-1值并不现实,且文件名并没有被保存,仅保了文件的内容。
“树对象”能解决文件名保存的问题,也允许我们将多个文件组织到一起。
所有内容均以树对象和数据对象的形式存储:树对象对应目录项,数据对象则大致上对应inodes或文件内容。

$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859      README
100644 blob 8f94139338f9404f26296befa88755fc2598c289      Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0      lib

master^{tree} 语法表示 master 分支上最新的提交所指向的树对象。
lib子目录并不是一个数据对象,而是一个指针,其指向的是另一个树对象。

$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b      simplegit.rb

技术分享

(1)创建自己的树对象

$ echo ‘new file‘ > new.txt
$ git update-index test.txt
$ git update-index --add new.txt
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

3. Git引用

3.1 Git引用

我们可以借助类似于 git log 1a410e 这样的命令来浏览完整的提交历史,但为了能遍历那段历史从而找到所有相关对象,你仍须记住1a410e是最后一个提交。我们需要一个文件来保存SHA-1值,即“引用(references,或缩写为 refs)”

$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/heads/master
.git/refs/remotes
.git/refs/remotes/origin
.git/refs/remotes/origin/master
.git/refs/tags
# 只查看文件
$ find .git/refs type -f
.git/refs/heads/master
.git/refs/remotes/origin/master

$ git log --oneline master
21b61e0 Second commit
1b63b62 First commit
$ more .git/refs/heads/master 
21b61e04b11b0bb6e8aedd8d21132974a23630be

(1)更新某个引用

# 方式一
$ echo "1b63b62c89014812fb7d00c6c47b80abcec286e0" > .git/refs/heads/master
# 方式二
$ git update-ref refs/heads/master 1b63b62c89014812fb7d00c6c47b80abcec286e0

注意:不提倡直接编辑引用文件!!!

(2)根据某提交建立分支(好!!!)

# 在第一次提交上创建一个分支“test”
$ git update-ref refs/heads/test 1b63b62c89014812fb7d00c6c47b80abcec286e0

$ git log --oneline master
21b61e0 Second commit
1b63b62 First commit
$ git log --oneline test
1b63b62 First commit

注意:master分支并未收到任何影响!!!

3.2 HEAD引用

HEAD 文件是一个符号引用(symbolic reference),指向目前所在的分支。所谓符号引用,意味着它并不像普通引用那样包含一个SHA-1值,它是一个指向其他引用的指针。

$ cat .git/HEAD
ref: refs/heads/master
# 切换到test分支
$ git checkout test
$ cat .git/HEAD
ref: refs/heads/test

当我们执行git commit时,该命令会创建一个提交对象,并用HEAD文件中那个引用所指向的 SHA-1值设置其父提交字段。

# 查看HEAD引用对应的值
$ git symbolic-ref HEAD
refs/heads/master
# 设置HEAD引用的值 
$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
refs/heads/test
# 查看当前分支
$ git branch -a  
  master
* test
  remotes/origin/master

通过上述方式,也达到了切换分支的目的!!

3.3 标签引用

标签对象(tag object)非常类似于一个提交对象——它包含一个标签创建者信息、一个日期、一段注释信息,以及一个指针。主要的区别在于,标签对象通常指向一个提交对象,而不是一个树对象。它像是一个永不移动的分支引用——永远指向同一个提交对象,只不过给这个提交对象加上一个更友好的名字罢了。
(1)创建一个轻量标签

$ git update-ref refs/tags/v1.0.0 21b61e04b11b0bb6e8aedd8d21132974a23630be

(2)查看创建标签对象的SHA-1值

$ cat .git/refs/tags/v1.0.0
21b61e04b11b0bb6e8aedd8d21132974a23630be
3.4 远程引用

添加了一个远程版本库并对其执行过推送操作,Git 会记录下最近一次推送操作时每一个分支所对应的值,并保存在 refs/remotes 目录下。

# origin 远程版本库的 master 分支所对应的 SHA-1 值,就是最近一次与服务器通信时本地 master 分支所对应的 SHA-1 值
$ cat .git/refs/remotes/origin/master
1b63b62c89014812fb7d00c6c47b80abcec286e0

远程引用和分支(位于 refs/heads 目录下的引用)之间最主要的区别在于,远程引用是只读的。虽然可以git checkout 到某个远程引用,但是Git并不会将HEAD引用指向该远程引用。因此,你永远不能通过commit命令来更新远程引用。Git将这些远程引用作为记录远程服务器上各分支最后已知位置状态的书签来管理。

4. 包文件

# 查看生成的树对象
$ git cat-file -p master^{tree}
100644 blob eea6e07591843cfb79d894c44b978519af8f07df    .gitignore
040000 tree 61621bd52b3ac5ddf5277f06755c13ed6fe76c50    .idea
100644 blob 83c831f0b085c70509b1fbb0a0131a9a32e691ac    README.md
100644 blob f73f3093ff865c514c6c51f867e35f693487d0d3    new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a    test.txt
# 查看某个对象大小
$ git cat-file -s f73f3093ff865c514c6c51f867e35f693487d0d3
5
# 修改文件
$ echo ‘testing‘ >> new.txt
$ git commit -am "modified new.txt"
$ git cat-file -p master^{tree}
100644 blob eea6e07591843cfb79d894c44b978519af8f07df    .gitignore
040000 tree 61621bd52b3ac5ddf5277f06755c13ed6fe76c50    .idea
100644 blob 83c831f0b085c70509b1fbb0a0131a9a32e691ac    README.md
100644 blob 76e35462261c5d088d07de3c936458ecd20f4514    new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a    test.txt

new.txt对应一个与之前完全不同的数据对象,这意味着,虽然你只是在一个文件后面加入一行新内容,Git也会用一个全新的对象来存储新的文件内容:

$ git cat-file -s 76e35462261c5d088d07de3c936458ecd20f4514
13

你的磁盘上现在有两个几乎完全相同的对象。如果Git只完整保存其中一个,再保存另一个对象与之前版本的差异内容,岂不更好?

事实上Git可以那样做。Git最初向磁盘中存储对象时所使用的格式被称为“松散(loose)”对象格式。但是,Git会时不时地将多个这些对象打包成一个称为“包文件(packfile)”的二进制文件,以节省空间和提高效率。当版本库中有太多的松散对象,或者你手动执行git gc命令,或者你向远程服务器执行推送时,Git都会这样做。

# 对对象进行打包
$ git gc

技术分享

# 再查看 objects 目录,你会发现大部分的对象都不见了,与此同时出现了一对新文件
$ find .git/objects -type f
.git/objects/8c/6c1746924fa81ab1e7a7bc79c0c2e9cd34ffd5
.git/objects/info/packs
.git/objects/pack/pack-9032188022086c91c3810adf1900ec5d2014e7d3.idx     # 索引
.git/objects/pack/pack-9032188022086c91c3810adf1900ec5d2014e7d3.pack    # 包文件

包文件包含了刚才从文件系统中移除的所有对象的内容。 索引文件包含了包文件的偏移信息,我们通过索引文件就可以快速定位任意一个指定对象。通过打包对象可以减少大约 ? 的磁盘占用空间。

**Git是如何做到这点的?**Git打包对象时,会查找命名及大小相近的文件,并只保存文件不同版本之间的差异内容。你可以查看包文件,观察它是如何节省空间的。

# 查看已打包的内容
$ git verify-pack -v .git/objects/pack/pack-9032188022086c91c3810adf1900ec5d2014e7d3.idx

技术分享

5. 引用规格

(1)引用规格
引用规格的格式由一个可选的 + 号和紧随其后的 <src>:<dst> 组成,其中 <src> 是一个模式(pattern),代表远程版本库中的引用;<dst> 是那些远程引用在本地所对应的位置。 + 号告诉 Git 即使在不能快进的情况下也要(强制)更新引用。
默认情况下,引用规格由 git remote add 命令自动生成, Git 获取服务器中 refs/heads/ 下面的所有引用,并将它写入到本地的 refs/remotes/origin/ 中。

$ git remote add origin https://github.com/381510688/test.git

$ cat .git/config 
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = https://github.com/381510688/test.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master

所以,如果服务器上有一个 master 分支,我们可以在本地通过下面这种方式来访问该分支上的提交记录:

$ git log origin/master
$ git log remotes/origin/master
$ git log refs/remotes/origin/master

注意:上面的三个命令作用相同,因为 Git 会把它们都扩展成 refs/remotes/origin/master

如果想让 Git 每次只拉取远程的 master 分支,而不是所有分支,可以把(引用规格的)获取那一行修改为:

fetch = +refs/heads/master:refs/remotes/origin/master

注意:这仅是针对该远程版本库的 git fetch 操作的默认引用规格。

也可以在配置文件中指定多个用于获取操作的引用规格。 如果想在每次获取时都包括 master 和 experiment 分支,添加如下两行:

[remote "origin"]
    url = https://github.com/schacon/simplegit-progit
    fetch = +refs/heads/master:refs/remotes/origin/master
    fetch = +refs/heads/experiment:refs/remotes/origin/experiment

注意:不能在模式中使用部分通配符,所以像下面这样的引用规格是不合法的

fetch = +refs/heads/qa*:refs/remotes/origin/qa*     # 不合法
fetch = +refs/heads/*:refs/remotes/origin/*         # 合法

(2)引用规格推送
把master分支推送到远程服务器的 qa/master 分支上

$ git push origin master:refs/heads/qa/master

如果他们希望 Git 每次运行 git push origin 时都像上面这样推送,可以在他们的配置文件中添加一条 push 值:

[remote "origin"]
    url = https://github.com/schacon/simplegit-progit
    fetch = +refs/heads/*:refs/remotes/origin/*
    push = refs/heads/master:refs/heads/qa/master

(3)删除引用

$ git push origin :topic

因为引用规格(的格式)是 <src>:<dst>,所以上述命令把 <src> 留空,意味着把远程版本库的 topic 分支定义为空值,也就是删除它。

6. 维护与数据恢复

(1)维护
Git会不定时地自动运行一个叫做“auto gc”的命令。大多数时候,这个命令并不会产生效果。然而,如果有太多松散对象(不在包文件中的对象)或者太多包文件,Git会运行一个完整的git gc命令。“gc” 代表垃圾回收,这个命令会做以下事情:收集所有松散对象并将它们放置到包文件中,将多个包文件合并为一个大的包文件,移除与任何提交都不相关的陈旧对象。

# 手动执行自动垃圾回收
$ git gc --auto

就像上面提到的,这个命令通常并不会产生效果。大约需要7000个以上的松散对象或超过50个的包文件才能让Git启动一次真正的gc命令。你可以通过修改gc.autogc.autopacklimit的设置来改动这些数值。
(2)数据恢复
在你使用Git的时候,你可能会意外丢失一次提交。通常这是因为你强制删除了正在工作的分支,但是最后却发现你还需要这个分支;亦或者硬重置了一个分支,放弃了你想要的提交。如果这些事情已经发生,该如何找回你的提交呢?

$ git log --pretty=oneline
f991403ee78279300170ee9d192931668d2645d3 modified new.txt
21b61e04b11b0bb6e8aedd8d21132974a23630be Second commit
1b63b62c89014812fb7d00c6c47b80abcec286e0 First commit
# 将master分支硬重置到第二次提交
$ git reset --hard 21b61e04b11b0bb6e8aedd8d21132974a23630be
HEAD is now at 21b61e0 Second commit
$ git log --pretty=oneline
21b61e04b11b0bb6e8aedd8d21132974a23630be Second commit
1b63b62c89014812fb7d00c6c47b80abcec286e0 First commit

现在顶部的提交已经丢失了-没有分支指向这些提交。你需要找出最后一次提交的SHA-1然后增加一个指向它的分支。窍门就是找到最后一次的提交的SHA-1-但是估计你记不起来了,对吗?最方便,也是最常用的方法,是使用一个名叫git reflog 的工具。

$ git reflog
21b61e0 HEAD@{0}: reset: moving to 21b61e04b11b0bb6e8aedd8d21132974a23630be
f991403 HEAD@{1}: commit: modified new.txt
21b61e0 HEAD@{2}: commit: Second commit
1b63b62 HEAD@{3}: commit: First commit

为了使显示的信息更加有用,我们可以执行 git log -g

$ git branch recover-branch f991403
$ git checkout recover-branch
$ git log --pretty=oneline
f991403ee78279300170ee9d192931668d2645d3 modified new.txt
21b61e04b11b0bb6e8aedd8d21132974a23630be Second commit
1b63b62c89014812fb7d00c6c47b80abcec286e0 First commit

(3)移除对象
git clone 会下载整个项目的历史,包括每一个文件的每一个版本。如果所有的东西都是源代码那么这很好,因为Git被高度优化来有效地存储这种数据。然而,如果某个人在之前向项目添加了一个大小特别大的文件,即使你将这个文件从项目中移除了,每次克隆还是都要强制的下载这个大文件。之所以会产生这个问题,是因为这个文件在历史中是存在的,它会永远在那里。
所以,你必须找到并移除这些大文件。警告:这个操作对提交历史的修改是破坏性的。它会从你必须修改或移除一个大文件引用最早的树对象开始重写每一次提交。如果你在导入仓库后,在任何人开始基于这些提交工作前执行这个操作,那么将不会有任何问题;否则,你必须通知所有的贡献者他们需要将他们的成果变基到你的新提交上。

# 添加一个大文件到仓库中
curl https://github.com/381510688/javascript_test.git > git.tgz
$ git add git.tgz 
$ git commit -m "add big file"
# 移除这个大文件
$ git rm git.tgz 
$ git commit -m "oops - remove big file"
# 执行 gc 来查看数据库占用了多少空间
$ git gc
Counting objects: 31, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (20/20), done.
Writing objects: 100% (31/31), done.
Total 31 (delta 5), reused 26 (delta 4)
# 也可以执行 count-objects 命令来快速的查看占用空间大小
$ git count-objects -v
count: 1
size: 4
in-pack: 31
packs: 1
size-pack: 4
prune-packable: 0
garbage: 0
size-garbage: 0
# 找出哪个文件或哪些文件占用了如此多的空间,然后对输出内容的第三列(即文件大小)进行排序
git verify-pack -v .git/objects/pack/pack-d3e9607420577f00bdd580919f4d70835010c9a9.idx | sort -k 3 -n | tail -3;
7246fb5e4a10799d0f37c296a0de4d89d907aef4 blob   178 121 2435
9b8415079b0023a650395f070be3556d96d1e9d1 commit 227 155 12
7246fb5e4a10799d0f37c296a0de4d89d907aef4 commit 256 171 768

# 找出数据对象的名字
git rev-list --objects --all | grep 7246fb5e4a10799d0f37c296a0de4d89d907aef4
7246fb5e4a10799d0f37c296a0de4d89d907aef4 git.tgz

# 从过去所有的树中移除这个文件
$ git log --oneline --branches -- git.tgz
9b84150 oops - remove big file
5a17b55 add big file
$ git filter-branch --index-filter   ‘git rm --ignore-unmatch --cached git.tgz‘ -- 5a17b55^..

历史中将不再包含对那个文件的引用。 不过,你的引用日志和你在 .git/refs/original 通过 filter-branch 选项添加的新引用中还存有对这个文件的引用,所以你必须移除它们然后重新打包数据库。 在重新打包前需要移除任何包含指向那些旧提交的指针的文件:

$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc

这个大文件还在你的松散对象中,并没有消失;但是它不会在推送或接下来的克隆中出现,这才是最重要的。 如果真的想要删除它,可以通过有 –expire 选项的 git prune 命令来完全地移除那个对象:

$ git prune --expire now

作为一套内容寻址文件系统,Git 不仅仅是一个版本控制系统,它同时是一个非常强大且易用的工具。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Git Pro深入浅出(三)