如果非得了解下 GIT 系统… – 实践篇

系统
本文旨在通过实践来介绍.git文件夹中的目录及文件功能,属git基础知识。但在此基础上可解决各git使用过程中可能遇到的问题,如“.git文件夹占用空间大”,“git如何找回丢失的对象(提交)”,”git diff 对比依据是什么”等,

git的定义是一个内容寻址文件系统。内容、寻址、文件、系统,该来的总会来的…

本文旨在通过实践来介绍.git文件夹中的目录及文件功能,属git基础知识。但在此基础上可解决各git使用过程中可能遇到的问题,如“.git文件夹占用空间大”,“git如何找回丢失的对象(提交)”,”git diff 对比依据是什么”等,

[[244047]]

话不多说,撸起袖子就是干,来看看 .git 是个啥样,这些个货色都是干嘛的。

  1. # 初始化git,查看内容 
  2. git init 产生一个.git隐藏文件夹 
  3. cd .git 
  4. ls -F1 
  5. # 初始化时的.git长这样 
  6. # HEAD 
  7. # config 
  8. # description 
  9. # hooks/ 
  10. # info/ 
  11. # objects/ 
  12. # refs/ 
  13. # 日常工作中的.git大概长这样 
  14. # COMMIT_EDITMSG 
  15. # config 
  16. # description 
  17. # hooks/ 
  18. index 
  19. # info/ 
  20. # logs/ 
  21. # objects/ 
  22. # refs/ 

随着之后的多种 git操作 ,还会存在如 MERGE_HEAD 、 MERGE_MODE等和 COMMIT_EDITMSG 类似功能的文件,branches(分支信息)、lost-found(存储被悬挂起/丢失的提交对象)、packed-refs(压缩后的refs记录)等和logs、objects类似功能的文件夹。

以上面的.git为例做个简单的介绍:

COMMIT_EDITMSG 最近一次的 commit edit message ;

  1. # 编写提交信息的两种姿势 
  2. git commit 
  3. # 自动打开文件编辑,在文件中输入信息即可 
  4. git commit -m msg 
  5. # 信息都会被保存到 COMMIT_EDITMSG 
  6. # 如需修改提交信息,不可直接编辑COMMIT_EDITMSG,可执行以下命令 
  7. git commit --amend  
  8. # 自动打开文件编辑,在文件中修改信息即可 

description 描述文件,打开后里面提醒该为git仓库创建个描述(Unnamed repository; edit this file ‘description’ to name the repository.)。

config 文件包含项目特有的配置选项,如一些常用项:

  • [core] ignorecase 是否忽略文件大小写;
  • [remote "origin"] url 配置远程仓库地址;
  • [remote "origin"] fetch 远程分支映射关系;
  • [user] name 用户名
  • [user] email 邮箱
  • [alias] 命令别名配置 : cmt = commit

抑或更多其他config参数…

全局配置文件在 ~/.gitconfig ,Windows应该是在Users/Administrator/.gitconfig。

info/ 文件夹用以存储一些有关git仓库的信息,如exclude

  1. # 包含一个全局性排除(global exclude)文件,用以放置那些不希望被记录在 .gitignore 文件中的忽略模式(ignored patterns); 
  2. echo for git ignore > git-ignore 
  3. echo for git exclude > git-exclude 
  4. echo git-ignore > .gitignore 
  5. git status 
  6.       
  7. On branch test 
  8. # Untracked files: 
  9. # (use "git add <file>..." to include in what will be committed
  10.   
  11. # .gitignore 
  12. # git-exclude 
  13.   
  14. # git-ignore已被忽略,还有2个untracked的文件 
  15.   
  16. cd .git 
  17. cd info/ 
  18. vi exclude 
  19.       
  20. # ***一行新增 git-exclude 
  21.       
  22. cd ../.. 
  23. git status 
  24. On branch test 
  25. # Untracked files: 
  26. # (use "git add <file>..." to include in what will be committed
  27.   
  28. # .gitignore 
  29.   
  30. # git-exclude 已被忽略,只有.gitignore还是untracked 

除exclude文件外,还可能会有refs、grafts,attributes等文件

hooks/ 文件夹包含客户端或服务端的钩子脚本(hook scripts),如pre-commit,post-receive等:

  1. vi .git/hooks/pre-commit 
  2. # 保存以下信息  ***行指定用什么执行 
  3. #!/bin/sh 
  4. echo "Message for pre commit"
  5. exit 1; 
  6. # 开执行权限 
  7. chmod +x .git/hooks/pre-commit   # user+group+other 执行x(1)权限  ,备注 r(4,read),w(2,write),x(1,execute),-(0,no permission),s(special) 
  8.       
  9. touch commit-hook-test 
  10. git add . 
  11. git commit -m “pre-commit test“ 
  12. # 你会看到以下信息 
  13. # Message for pre commit 
  14.   
  15. # 如果不exit,则继续执行,详情可参考.git/hooks/下的*.sample文件 
  16. # Message for pre commit 
  17. # [test 038e6ec] pre-commit test 
  18. # 1 file changed, 1 insertion(+), 1 deletion(-) 

logs/ 放置git仓库操作记录的文件夹,包含HEAD文件 和 refs文件夹。

HAED 文件包含对 git分支 的操作记录,如

  1. vi HEAD 
  2. # 99a10c283c33beed7f31c210a6c8b411d2a31085 5daf6094ea2cc60d17e947c0435096a4bdafe82d yeshou <yeshou@xxx.com> 1535082919 +0800       commit: rm files 
  3. # 5daf6094ea2cc60d17e947c0435096a4bdafe82d b8e02a5f9c2bf44342d15f5ea1e60ffd9434765a yeshou <yeshou@xxx.com> 1535087285 +0800        checkout: moving from test to master 
  4. # 先是删除文件后提交了次,再是由test检出到master 

refs 文件夹包含 heads 文件夹,remote文件夹。heads 记录本地相关的各 git分支 操作记录,remote 记录远程仓库相关的各 git分支 操作记录

  1. cd heads 
  2. ls 
  3. # master   master分支操作记录 
  4. # test     test分支操作记录 
  5.   
  6. cd remote/origin 
  7. ls 
  8. # master  远程master分支操作记录 
  9. # test    远程test分支操作记录 

HEAD 文件指示当前被检出(所在)的分支,如当前在test分支,文件内容则为ref: refs/heads/test。

index 文件是当前版本的文件索引,包含生成当前树(唯一确定的)对象的所虚信息,可用于快速比对工作树和其他提交树对象的差异(各commit和HEAD之间的diff),可用于存储单文件的多个版本以有效的解决合并冲突。可使用git ls-files 查看index文件内容。如:

  • git commit 的一次提交从index中的信息生成tree对象,将其存储在对象数据库中,并与本次新的commit做关联,产生本次commit的tree信息(下面的objects介绍中会提到commit和tree)。

refs/ (references) 文件夹存储指向数据(分支)的提交对象指针;其中 heads 文件夹记录内部文件对应名称的分支的提交对象;tags记录内部文件对应名称的标签的提交对象;remotes记录内部文件对应名称的远程仓库分支的提交对象;

举个例子:

  1. cat .git/refs/heads/master 
  2.  # ce1fed3fdbaf12e816e3028055f9feee57b33b45 当前master的提交记录 
  3.  git checkout -b test # 检出一个新分支 
  4.  find .git 
  5.  # 多了个 .git/refs/heads/test 文件 
  6.  git log 
  7.   
  8.  # commit 63a85dcbc6978f2d43996f5bebc38993c2afadaa (HEAD -> test) 
  9.  # Author: yeshou <yeshou@xxx.com> 
  10.  # Date:   Sat Aug 25 13:57:26 2018 +0800 
  11.  # 
  12.  # branch test : edit test : add line : write d 
  13.   
  14.  # commit ce1fed3fdbaf12e816e3028055f9feee57b33b45 (master) 
  15.  # Author: yeshou <yeshou@xxx.com> 
  16.  # Date:   Sat Aug 25 12:40:35 2018 +0800 
  17.  #  
  18.  # edit test : add line : write c 
  19.   
  20.  cat .git/refs/heads/test 
  21.  # 63a85dcbc6978f2d43996f5bebc38993c2afadaa   当前test的最近提交记录 
  22.  cat .git/refs/heads/master 
  23.  # ce1fed3fdbaf12e816e3028055f9feee57b33b45 依然是当前master的最近提交记录 

另两者亦然。

objects/ 文件夹用以存储git仓库中的所有数据内容。

一步步来…

先看看这个文件夹里是怎么存数据内容的,再理解这些数据内容又是什么。

  1. # 为了清晰的看明白objects文件夹中文件的生成,初始化.git 
  2. rm -rf .git 
  3. git init   
  4. cd .git  
  5. find .git # 这时候objects文件夹下只有pack和info 两个空文件夹 
  6. touch test 
  7. vi test 
  8. # 输入 a ,保存,退出 
  9. git add test 
  10. find .git 
  11. # objects 下多了个文件夹,且里面有文件 
  12. # .git/objects/44 
  13. # .git/objects/44/2406aa9341668f9c43c2d5378a777ad69324a0 

验证下这个文件内容是什么,注意,这是个二进制球,呸…是文件,文明观球,呸…是观看。这里我们用传说中git中的手术刀( git cat-file )来解剖git文件。

  1. git cat-file -p 442406aa9341668f9c43c2d5378a777ad69324a0 
  2. # 输出 a 

9de29bb2d1d6434b8b29ae775ad8c2e48c5391 是个二进制文件,是git对象中的blob对象,它记录了当前版本的该文件的数据内容,并以SHA-1计算产生一个40个字符的校验和。 根据官方描述:这是一个 SHA-1 哈希值——一个将待存储的数据外加一个头部信息(header)一起做 SHA-1 校验运算而得的校验和。

继续操作,修改test

  1. vi test 
  2. # 第二行输入 b ,保存,退出 
  3. git add test 
  4. find .git 
  5. # objects 下又多了个文件夹,且里面也有文件 
  6. # .git/objects/bf 
  7. # .git/objects/bf/daa0f1c3415c09d3080063911d155fd7259d18 

这次的二进制文件的数据内容是 a (手动换行) b 。

继续走下去:

  1. git commit -m “add test” 
  2. # wtf,怎么突然生出2个文件夹,来来来,看看内容 
  3. # .git/objects/3e 
  4. # .git/objects/3e/5f95cd5c4f0ff429522b0fdfeda9369f92d89c 
  5. # .git/objects/fd 
  6. # .git/objects/fd/1332e4e95f8a64682c1516e175abb66b6f6325 
  7. git cat-file -t fd1332e4e95f8a64682c1516e175abb66b6f6325 
  8. commit 
  9. git cat-file -p fd1332e4e95f8a64682c1516e175abb66b6f6325 
  10. # tree对象、作者、提交者、提交日期、提交信息、父对象(有的话带一个或多个父对象) 
  11. # tree 3e5f95cd5c4f0ff429522b0fdfeda9369f92d89c  
  12. # author yeshou <yeshou@xxx.com> 1535168447 +0800 
  13. # committer yeshou <yeshou@xxx.com> 1535168447 +0800 
  14. add dir-test     
  15. git cat-file -t 3e5f95cd5c4f0ff429522b0fdfeda9369f92d89c 
  16. # tree 
  17. git cat-file -p 3e5f95cd5c4f0ff429522b0fdfeda9369f92d89c 
  18. # 100644 blob bfdaa0f1c3415c09d3080063911d155fd7259d18    test 
  19. git cat-file -p bfdaa0f1c3415c09d3080063911d155fd7259d18 
  20. # a (换行) b 

3e5f95cd5c4f0ff429522b0fdfeda9369f92d89c 也是个二进制文件,是git对象中的tree对象,记录着blob标识符、路径名和在一个tree下的所有文件的元数据。

fd1332e4e95f8a64682c1516e175abb66b6f6325 又是个二进制文件,是git对象中的commit对象,它记录了当前版本的一次提交数据内容,包含tree对象、作者、提交者、提交日期、提交信息、父对象(有的话带一个或多个父对象)。

然后,pack文件夹是干嘛的?

随着objects文件夹下的文件夹和文件不断生成(也就是N多次的commit之后),objects文件夹明显会”长大”,这时开发者可以用 git gc 来对之前的操作的对象做整理压缩。

pack 文件夹内有2个文件 pack-(SHA-1).pack 和 pack-(SHA-1).idx 前者是以压缩形式存储之前记录对象的文件,后者用以存储访问索引的文件。

举个例子:

  1. git gc  
  2. # Counting objects: 12, done. 
  3. # Delta compression using up to 4 threads. 
  4. # Compressing objects: 100% (4/4), done. 
  5. # Writing objects: 100% (12/12), done. 
  6. # Total 12 (delta 0), reused 0 (delta 0) 
  7. find .git 
  8. # 发现少了很多objects里的文件夹和文件,多了以下2个文件 
  9. # .git/objects/pack/pack-2021ec3cb18c796fdfca8ef616fb6a20b1449ab1.pack 
  10. # .git/objects/pack/pack-2021ec3cb18c796fdfca8ef616fb6a20b1449ab1.idx 
  11. git verify-pack -v .git/objects/pack/pack-2021ec3cb18c796fdfca8ef616fb6a20b1449ab1.idx 
  12. # 列出之前存在objects里的所有操作数据内容 
  13. # 655a12c9b83a029bb46fa852ea15e6affd1587d8 commit 167 117 510 
  14. # ... 
  15. # 616dfdb2643c725fa1027ecef76d49d482d9e26d tree   32 43 670 
  16. # ... 
  17. # bfdaa0f1c3415c09d3080063911d155fd7259d18 blob   5 14 853 
  18. # 也可以通过后面加 | grep keyword 来搜索所需的内容,如下列出所有commit记录 
  19. git verify-pack -v .git/objects/pack/pack-2021ec3cb18c796fdfca8ef616fb6a20b1449ab1.idx | grep commit 

git gc ( garbage collect )命令将会收集所有松散对象并将它们存入 pack,合并这些 pack 进一个大的 pack,然后将不被任何 commit 引用并且已存在一段时间 (数月) 的对象删除,除此之外还会将所有引用 (references) 并入一个单独文件(上面有提到随着各种操作,.git下还会产生更多文件夹,.git中的packed-refs文件夹就是这时候产生的)。该命令可能通过修改配置中的 gc.auto 和 gc.autopacklimit 来调整操作阈值。注意:git gc 调用的也是 git prune ,如有需求也可关注这个命令。

至于”info文件夹是干嘛的?”这个问题还未知… 官网的描述也没看懂,也没查到或者在项目中实际出现这个文件夹有存在什么文件,要么等遇到再说?

至此git对象中的三个对象已经知道是咋回事了,还剩个tags对象,简单介绍下。

tags对象通常也是一个commit对象,指的是一个指定了开发者可读名称的一个特殊对象,如有需要也可通过 git cat-file 来解析探索。

其间关系大致如下:

  1. |- commit       aaaaa… 
  2.     |- tree       abbbb… 
  3.         |- blob       acccc… (可能是这次修改的) 
  4.         |- blob       adddd…(也可能是上次修改的) 
  5.         |- tree        aeeee…   
  6.             |- blob        affff… 
  7.   
  8. |- commit        bbbbb… 

关系图的话,这个是git官网的… 和上面的结构是一样的。

 

基于objects的介绍再回过头来看看”内容、寻址、文件、系统”便比较清晰了:以git对象作为内容,通过唯一的校验和寻址,文件形式存储的一个版本控制系统。

了解完这些,主要还是希望能够运用到实际生产中来解决问题。如 “项目中.git文件为什么这么大?怎么处理?”

可能的处理方案:

1. 执行 git gc ,如果压缩后能达到预期效果,则不做过多处理

2.针对历史记录中对某些大文件的引用,则删除对应引用的对象,操作如下

  1. git gc 
  2. git count-objects -v 
  3. git verify-pack -v .git/objects/pack/pack-(SHA-1).idx | sort -k 3 -n | tail -5 
  4. # 前面用过了git verify-pack,可知第三列信息表示的文件大小,这里用tail取前5个较大文件的记录 
  5. git rev-list --objects --all | grep (SHA-1) 
  6. # 使用 git rev-list --objects -all 来查看指定 (SHA-1) 对象信息 
  7. # ce1fed3fdbaf12e816e3028055f9feee57b33b45 xxx.mp4  比如是个视频文件 
  8. git log --pretty=oneline --branches -- xxx.mp4 
  9. # 找出哪些 commit 修改/操作了这个文件 
  10. # 94cbe08e... add xxx.mp4 
  11. git filter-branch --index-filter 'git rm --cached --ignore-unmatch xxx.mp4' -- 94cbe08e^.. 
  12. # 删除文件引用,rewrite 信息... 
  13. rm -rf .git/refs/original 
  14. rm -rf .git/logs/ 
  15. git gc 
  16. # 删除 .git/refs/original 和 .git/logs/ ,处理其中对xxx.mp4文件仍存在的引用,之后repack仓库 
  17. git count-objects -v 
  18. # 检查下操作后文件大小,或者直接在.git目录下执行 du -h -d 1 查看1级目录/文件的大小 

3.若还是难处理,或者不好处理,或者不想删除大文件的引用,则备份一份.git,然后初始化git仓库,操作如下

  1. # 除去备份操作,备份操作使用者自定义 
  2. rm -rf .git 
  3. git init 
  4. git remote add origin xxx.git  
  5.  
  6. # 重新指向新的远程仓库地址,也可根据上文所说修改config文件来指定 

参考

关于git hooks,参考Customizing-Git-Git-Hooks

https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks

关于更详细的.git文件夹,参考 Gitrepository-layout-objectsinfo

https://git-scm.com/docs/gitrepository-layout#gitrepository-layout-objectsinfo

责任编辑:武晓燕 来源: 博客园
相关推荐

2019-11-21 10:45:22

MyBatisSQL标签

2011-01-25 09:32:30

系统运维

2019-10-23 09:02:49

BIONIO单线程

2019-07-17 13:31:34

VLAN集中管理协议VCMP

2017-12-19 08:50:56

UIJavaAWS Lambda

2014-08-19 14:12:47

Windows

2016-08-03 15:32:50

GitLinux开源

2018-07-30 13:51:06

2018-10-09 14:34:58

开源KubernetesGit

2019-12-02 16:05:30

网盘硬盘移动

2019-05-29 23:01:15

云计算IT云技术

2015-10-23 17:09:27

Snappy Ubun

2020-07-07 07:34:29

RedisSDS数据结构

2021-01-04 09:00:00

JavaScript开发Web

2012-11-19 10:09:25

2022-09-08 11:12:09

ES6JavaScript

2014-07-28 14:43:14

git开源

2021-12-06 22:44:51

Windows 10Windows微软

2020-12-31 23:31:13

网络安全网络攻击漏洞

2017-12-15 21:05:13

点赞
收藏

51CTO技术栈公众号