git的定義是一個內容尋址文件系統。內容、尋址、文件、系統,該來的總會來的…
本文旨在通過實踐來介紹.git文件夾中的目錄及文件功能,屬git基礎知識。但在此基礎上可解決各git使用過程中可能遇到的問題,如“.git文件夾占用空間大”,“git如何找回丟失的對象(提交)”,”git diff 對比依據是什么”等,
話不多說,擼起袖子就是干,來看看 .git 是個啥樣,這些個貨色都是干嘛的。
# 初始化git,查看內容 git init 產生一個.git隱藏文件夾 cd .git ls -F1 # 初始化時的.git長這樣 # HEAD # config # description # hooks/ # info/ # objects/ # refs/ # 日常工作中的.git大概長這樣 # COMMIT_EDITMSG # config # description # hooks/ # index # info/ # logs/ # objects/ # refs/
隨着之后的多種 git操作 ,還會存在如 MERGE_HEAD 、 MERGE_MODE等和 COMMIT_EDITMSG 類似功能的文件,branches(分支信息)、lost-found(存儲被懸掛起/丟失的提交對象)、packed-refs(壓縮后的refs記錄)等和logs、objects類似功能的文件夾。
以上面的.git為例做個簡單的介紹:
COMMIT_EDITMSG 最近一次的 commit edit message ;
# 編寫提交信息的兩種姿勢 git commit # 自動打開文件編輯,在文件中輸入信息即可 git commit -m msg # 信息都會被保存到 COMMIT_EDITMSG # 如需修改提交信息,不可直接編輯COMMIT_EDITMSG,可執行以下命令 git commit --amend # 自動打開文件編輯,在文件中修改信息即可
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
# 包含一個全局性排除(global exclude)文件,用以放置那些不希望被記錄在 .gitignore 文件中的忽略模式(ignored patterns); echo for git ignore > git-ignore echo for git exclude > git-exclude echo git-ignore > .gitignore git status # On branch test # Untracked files: # (use "git add <file>..." to include in what will be committed) # .gitignore # git-exclude # git-ignore已被忽略,還有2個untracked的文件 cd .git cd info/ vi exclude # 最后一行新增 git-exclude cd ../.. git status # On branch test # Untracked files: # (use "git add <file>..." to include in what will be committed) # .gitignore # git-exclude 已被忽略,只有.gitignore還是untracked
除exclude文件外,還可能會有refs、grafts,attributes等文件
hooks/ 文件夾包含客戶端或服務端的鈎子腳本(hook scripts),如pre-commit,post-receive等:
vi .git/hooks/pre-commit # 保存以下信息 第一行指定用什么執行 #!/bin/sh echo "Message for pre commit"; exit 1; # 開執行權限 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) touch commit-hook-test git add . git commit -m “pre-commit test“ # 你會看到以下信息 # Message for pre commit # 如果不exit,則繼續執行,詳情可參考.git/hooks/下的*.sample文件 # Message for pre commit # [test 038e6ec] pre-commit test # 1 file changed, 1 insertion(+), 1 deletion(-)
logs/ 放置git倉庫操作記錄的文件夾,包含HEAD文件 和 refs文件夾。
HAED 文件包含對 git分支 的操作記錄,如
vi HEAD # 99a10c283c33beed7f31c210a6c8b411d2a31085 5daf6094ea2cc60d17e947c0435096a4bdafe82d yeshou <yeshou@xxx.com> 1535082919 +0800 commit: rm files # 5daf6094ea2cc60d17e947c0435096a4bdafe82d b8e02a5f9c2bf44342d15f5ea1e60ffd9434765a yeshou <yeshou@xxx.com> 1535087285 +0800 checkout: moving from test to master # 先是刪除文件后提交了次,再是由test檢出到master
refs 文件夾包含 heads 文件夾,remote文件夾。heads 記錄本地相關的各 git分支 操作記錄,remote 記錄遠程倉庫相關的各 git分支 操作記錄
cd heads ls # master master分支操作記錄 # test test分支操作記錄 cd remote/origin ls # master 遠程master分支操作記錄 # 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記錄內部文件對應名稱的遠程倉庫分支的提交對象;
舉個例子:
cat .git/refs/heads/master # ce1fed3fdbaf12e816e3028055f9feee57b33b45 當前master的提交記錄 git checkout -b test # 檢出一個新分支 find .git # 多了個 .git/refs/heads/test 文件 git log # commit 63a85dcbc6978f2d43996f5bebc38993c2afadaa (HEAD -> test) # Author: yeshou <yeshou@xxx.com> # Date: Sat Aug 25 13:57:26 2018 +0800 # # branch test : edit test : add line : write d # commit ce1fed3fdbaf12e816e3028055f9feee57b33b45 (master) # Author: yeshou <yeshou@xxx.com> # Date: Sat Aug 25 12:40:35 2018 +0800 # # edit test : add line : write c cat .git/refs/heads/test # 63a85dcbc6978f2d43996f5bebc38993c2afadaa 當前test的最近提交記錄 cat .git/refs/heads/master # ce1fed3fdbaf12e816e3028055f9feee57b33b45 依然是當前master的最近提交記錄
另兩者亦然。
objects/ 文件夾用以存儲git倉庫中的所有數據內容。
一步步來...
先看看這個文件夾里是怎么存數據內容的,再理解這些數據內容又是什么。
# 為了清晰的看明白objects文件夾中文件的生成,初始化.git rm -rf .git git init cd .git find .git # 這時候objects文件夾下只有pack和info 兩個空文件夾 touch test vi test # 輸入 a ,保存,退出 git add test find .git # objects 下多了個文件夾,且里面有文件 # .git/objects/44 # .git/objects/44/2406aa9341668f9c43c2d5378a777ad69324a0
驗證下這個文件內容是什么,注意,這是個二進制球,呸…是文件,文明觀球,呸…是觀看。這里我們用傳說中git中的手術刀( git cat-file )來解剖git文件。
git cat-file -p 442406aa9341668f9c43c2d5378a777ad69324a0 # 輸出 a
9de29bb2d1d6434b8b29ae775ad8c2e48c5391 是個二進制文件,是git對象中的blob對象,它記錄了當前版本的該文件的數據內容,並以SHA-1計算產生一個40個字符的校驗和。 根據官方描述:這是一個 SHA-1 哈希值——一個將待存儲的數據外加一個頭部信息(header)一起做 SHA-1 校驗運算而得的校驗和。
繼續操作,修改test
vi test # 第二行輸入 b ,保存,退出 git add test find .git # objects 下又多了個文件夾,且里面也有文件 # .git/objects/bf # .git/objects/bf/daa0f1c3415c09d3080063911d155fd7259d18
這次的二進制文件的數據內容是 a (手動換行) b 。
繼續走下去:
git commit -m “add test” # wtf,怎么突然生出2個文件夾,來來來,看看內容 # .git/objects/3e # .git/objects/3e/5f95cd5c4f0ff429522b0fdfeda9369f92d89c # .git/objects/fd # .git/objects/fd/1332e4e95f8a64682c1516e175abb66b6f6325 git cat-file -t fd1332e4e95f8a64682c1516e175abb66b6f6325 # commit git cat-file -p fd1332e4e95f8a64682c1516e175abb66b6f6325 # tree對象、作者、提交者、提交日期、提交信息、父對象(有的話帶一個或多個父對象) # tree 3e5f95cd5c4f0ff429522b0fdfeda9369f92d89c # author yeshou <yeshou@xxx.com> 1535168447 +0800 # committer yeshou <yeshou@xxx.com> 1535168447 +0800 # add dir-test git cat-file -t 3e5f95cd5c4f0ff429522b0fdfeda9369f92d89c # tree git cat-file -p 3e5f95cd5c4f0ff429522b0fdfeda9369f92d89c # 100644 blob bfdaa0f1c3415c09d3080063911d155fd7259d18 test git cat-file -p bfdaa0f1c3415c09d3080063911d155fd7259d18 # 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 前者是以壓縮形式存儲之前記錄對象的文件,后者用以存儲訪問索引的文件。
舉個例子:
git gc # Counting objects: 12, done. # Delta compression using up to 4 threads. # Compressing objects: 100% (4/4), done. # Writing objects: 100% (12/12), done. # Total 12 (delta 0), reused 0 (delta 0) find .git # 發現少了很多objects里的文件夾和文件,多了以下2個文件 # .git/objects/pack/pack-2021ec3cb18c796fdfca8ef616fb6a20b1449ab1.pack # .git/objects/pack/pack-2021ec3cb18c796fdfca8ef616fb6a20b1449ab1.idx git verify-pack -v .git/objects/pack/pack-2021ec3cb18c796fdfca8ef616fb6a20b1449ab1.idx # 列出之前存在objects里的所有操作數據內容 # 655a12c9b83a029bb46fa852ea15e6affd1587d8 commit 167 117 510 # ... # 616dfdb2643c725fa1027ecef76d49d482d9e26d tree 32 43 670 # ... # bfdaa0f1c3415c09d3080063911d155fd7259d18 blob 5 14 853 # 也可以通過后面加 | grep keyword 來搜索所需的內容,如下列出所有commit記錄 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 來解析探索。
其間關系大致如下:
|- commit aaaaa… |- tree abbbb… |- blob acccc… (可能是這次修改的) |- blob adddd…(也可能是上次修改的) |- tree aeeee… |- blob affff… |- commit bbbbb…
關系圖的話,這個是git官網的… 和上面的結構是一樣的。
基於objects的介紹再回過頭來看看”內容、尋址、文件、系統”便比較清晰了:以git對象作為內容,通過唯一的校驗和尋址,文件形式存儲的一個版本控制系統。
了解完這些,主要還是希望能夠運用到實際生產中來解決問題。如 “項目中.git文件為什么這么大?怎么處理?"
可能的處理方案:
1. 執行 git gc ,如果壓縮后能達到預期效果,則不做過多處理
2.針對歷史記錄中對某些大文件的引用,則刪除對應引用的對象,操作如下
git gc git count-objects -v git verify-pack -v .git/objects/pack/pack-(SHA-1).idx | sort -k 3 -n | tail -5 # 前面用過了git verify-pack,可知第三列信息表示的文件大小,這里用tail取前5個較大文件的記錄 git rev-list --objects --all | grep (SHA-1) # 使用 git rev-list --objects -all 來查看指定 (SHA-1) 對象信息 # ce1fed3fdbaf12e816e3028055f9feee57b33b45 xxx.mp4 比如是個視頻文件 git log --pretty=oneline --branches -- xxx.mp4 # 找出哪些 commit 修改/操作了這個文件 # 94cbe08e... add xxx.mp4 git filter-branch --index-filter 'git rm --cached --ignore-unmatch xxx.mp4' -- 94cbe08e^.. # 刪除文件引用,rewrite 信息... rm -rf .git/refs/original rm -rf .git/logs/ git gc # 刪除 .git/refs/original 和 .git/logs/ ,處理其中對xxx.mp4文件仍存在的引用,之后repack倉庫 git count-objects -v # 檢查下操作后文件大小,或者直接在.git目錄下執行 du -h -d 1 查看1級目錄/文件的大小
3.若還是難處理,或者不好處理,或者不想刪除大文件的引用,則備份一份.git,然后初始化git倉庫,操作如下
# 除去備份操作,備份操作使用者自定義 rm -rf .git git init git remote add origin xxx.git # 重新指向新的遠程倉庫地址,也可根據上文所說修改config文件來指定
關於git hooks,參考Customizing-Git-Git-Hooks
關於更詳細的.git文件夾,參考 Gitrepository-layout-objectsinfo