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 能在特定的重要动作发生时触发自己定义脚本。 有两组这种钩子:client的和server端的。
client钩子由诸如提交和合并这种操作所调用,而server端钩子作用于诸如接收被推送的提交这种联网操作。
钩子都被存储在.git/hooks文件夹下,默认以.sample结尾。

假设你想启用它们,得先移除这个后缀。
图 钩子1.png

3.1 client钩子

client钩子可分为:提交工作流钩子电子邮件工作钩子其它钩子


(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) 其它client钩子

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

服务端钩子在推送到server之前和之后执行

  1. pre-receive
    处理来自client的推送操作时,假设它以非零值退出,全部的推送内容都不会被接受。能够用这个钩子阻止对引用进行非快进(non-fast-forward)的更新,或者对该推送所改动的全部引用和文件进行訪问控制。
  2. update 和pre-receive 脚本十分相似。不同之处在于它会为每个准备更新的分支各执行一次。

  3. post-receive 在整个过程完结以后执行,能够用来更新其它系统服务或者通知用户。

八、Git与其它系统

假设你的代码眼下不在Git上。可是想迁移到Git。能够參考以下两个地址:
Git与其它系统 - 作为client的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/          # 包括client或服务端的钩子脚本
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

简化版的Git数据模型.png

(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 值。就是近期一次与server通信时本地 master 分支所相应的 SHA-1 值
$ cat .git/refs/remotes/origin/master
1b63b62c89014812fb7d00c6c47b80abcec286e0

远程引用和分支(位于 refs/heads 文件夹下的引用)之间最基本的差别在于,远程引用是仅仅读的。尽管能够git checkout 到某个远程引用,可是Git并不会将HEAD引用指向该远程引用。因此,你永远不能通过commit命令来更新远程引用。Git将这些远程引用作为记录远程server上各分支最后已知位置状态的书签来管理。

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命令,或者你向远程server执行推送时,Git都会这样做。

# 对对象进行打包
$ git gc

图 gc.png

# 再查看 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

图 verify-pack.png

5. 引用规格

(1)引用规格
引用规格的格式由一个可选的 + 号和紧随其后的 <src>:<dst> 组成,当中 <src> 是一个模式(pattern),代表远程版本号库中的引用;<dst> 是那些远程引用在本地所相应的位置。

+ 号告诉 Git 即使在不能快进的情况下也要(强制)更新引用。
默认情况下,引用规格由 git remote add 命令自己主动生成, Git 获取server中 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

所以,假设server上有一个 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分支推送到远程server的 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 不仅仅是一个版本号控制系统。它同一时候是一个很强大且易用的工具。

posted @ 2017-08-21 10:26  wzjhoutai  阅读(705)  评论(0编辑  收藏  举报