[轉發]深入理解git,從研究git目錄開始


轉發學習的啦。

似乎很少有人在讀某個git快速教程的時候會說:“這個關於git的快速教程太酷了!讀完了用起git來超級舒服,並且我一點也不怕自己會破壞什么東西。”

 

對git的初學者來說,剛接觸git時就像進入了一個他不會聽/說當地語言的陌生國度。如果你知道自己在哪里並且知道怎么走,那就很好。但是如果你迷路了,那麻煩就大了。

 

市面上已經有很多關於學習git基本指令的文章,因此這篇文章不講這個。這里我們將試着從不同的角度來理解。

 

新手們通常都很害怕git,的確,git確實是一個很強大的工具,但它對使用者並不友好。大量的新概念,一個指令在不同的情況下做完全不同的事情,各種隱含的反饋……

 

我認為克服這個困難的一個可行方法就是在git的日常commit/puah之外再多做一點工作,如果花點時間來理解一下git是如何產生的,能幫我們避免很多麻煩。

 理解.git

當你建立了一個git repo,使用git init,git便創建了一個奇妙的目錄:.git

 

這個文件夾里包含了git工作時所需要的所有信息。需要明確的是,如果想從你的項目中移除git,但保留項目文件,只需要刪除.git文件夾即可。但是,為什么要這樣做呢?

|── HEAD

|── branches

|── config

|── description

|── hooks

| |── pre-commit.sample

| |── pre-push.sample

| └── ...

|── info

| └── exclude

|── objects

| |── info

| └── pack

└── refs

|── heads

└── tags

 

這是你第一次commit之前.git文件夾的樣子。

 

HEAD:這個我們稍后再說。

 

config:這個文件夾里是你的保存設置,這里將寫入遠程URL,比如你的郵箱、用戶名等。每一次在控制端使用“git config”,它都會在這里結束。

 

description:被gitweb (Github的原型)用來顯示對repo的描述。

 

hooks:這里有一個有趣的特性。Git有一套可以自動運行在任何一個有意義的git階段下的腳本,叫做hooks。hooks可以運行在commit/rebase/pull等等狀態的之前或之后。腳本的名稱決定了它什么時候被執行。一個有用的pre-push腳本的例子將會被運行以測試控制器(遠程控制)中的所有樣式規則保持一致。

 

info - exclude:可以將你不想被git處理的文件放到.gitignore文件夾里。被排除的文件可以做到相同的事情,除了它不能被共享。如果你不想將你的自定義IDE關聯到congif文件里,就可以使用這個。雖然大部分情況下,.gitignore就足夠了。

 commit的內部是什么?

每次你創建了一個文件並跟蹤它,git都會將其壓縮並存儲到自己的數據結構中。這個壓縮對象有一個獨特的名字、一個哈希碼,存儲在對象目錄下。

 

在研究對象目錄前,我們要問一個問題,什么是commit。這里commit可以看做是工作目錄的一種快照,但不僅僅是快照。

 

實際上當你commit時,git只做兩件事情來創造你工作目錄的快照:

  1. 如果文件沒有修改,git僅僅增加壓縮文件的名字(hash)到快照中。

  2. 如果文件被修改過,git就將其壓縮,再將壓縮后的文件存儲到對象文件夾中。然后再添加這個壓縮文件的文件名(hash)到快照中去。

 

這是一個簡化的描述,實際整個過程會復雜一點。

 

一旦快照文件被建立,它也會被壓縮並用hash文件命名。那么這些壓縮文件在什么地方呢?答案:對象文件夾。

|── 4c

| └── f44f1e3fe4fb7f8aa42138c324f63f5ac85828 // hash

|── 86

| └── 550c31847e518e1927f95991c949fc14efc711 // hash

|── e6

| └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 // hash

|── info 

└── pack 

當我創建了一個名為file_1.txt的空文件並commit之后,對象目錄應該是如上所看到的樣子。請注意如果你的文件的哈希碼是“89faaee…”,git將會吧這個文件儲存在名為“89”的子目錄下,並且命名這個文件為“faaee…”。

 

你看到右邊有3個hash字樣。一個對應的是我的file_1.txt文件,另一個對應的是我commit時所創建的快照文件。那么第三個呢?這是因為commit本身也是一個對象,它也會被壓縮並被存儲在對象文件夾中。

 

記住,一個commit指令實際上包含4件事:

  • 工作目錄的快照文件的名稱(哈希碼)

  • 注釋

  • 提交者信息

  • Parent commit的哈希碼

 

然后,你們可以自己看一下如果不壓縮commit文件時會發生什么:

 

// 查看歷史,你會很容易就找到你的commit哈希碼

// 你也不必粘貼全部的哈希碼,只要足夠就可以了

// 創造了獨特哈希碼的字符串

git cat-file -p 4cf44f1e3fe4fb7f8aa42138c324f63f5ac85828

得到結果:

tree 86550c31847e518e1927f95991c949fc14efc711

author Pierre De Wulf <test@gmail.com> 1455775173 -0500

committer Pierre De Wulf <test@gmail.com> 1455775173 -0500

commit A

你看,正如我們所期望的,我們得到了快照文件的哈希碼、作者信息、和我的commit信息。

 

這里有兩件很重要的事情:

  • 正如所期望的,那個名為“86550…”的快照哈希文件也是一個對象,並且能夠在對象文件夾中被找到。

  • 因為這是我的第一個commit,所以沒有Parent。

 

快照文件本質上是什么呢?

git cat-file -p 86550c31847e518e1927f95991c949fc14efc711

100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file_1.txt

這里,我們找到我們對象存儲器中之前存儲的最后一個對象,我們快照文件中僅有的對象。這是一個blob,但不是我們今天要講的。

 

分支、標簽、HEAD,它們都一樣。

 

所以,現在你明白了git中的任何事情都可以用一個正確的hash來得到。現在,讓我們來看一看HEAD。那么,HEAD中又有什么呢?

cat HEAD

ref: refs/heads/master

這不是一個哈希碼,但它有價值,因為HEAD可以被認為是一個指向你正在使用的分支的指針。現在,我們看一下refs/heads/master內部,我們會看到如下信息:

 

cat refs/heads/master

4cf44f1e3fe4fb7f8aa42138c324f63f5ac85828

是不是很眼熟?是的,這和我們第一次commit時的哈希碼是完全一樣的。這說明 branches和tags無非就是一個指向commit的指針。

 

這意味着你可以刪除所有你想刪除的branches和tags,而它們所指向的 commit會仍然保留在這里。僅僅是訪問commit更困難一點。

現在你應該明白,當你用一堆其它的指令來命令“壓縮”你目前的工作目錄並儲存在對象文件夾時,git實際上都做了些什么。如果你對這些工具足夠熟悉,你就應該完全知道哪些文件應該包含在commit中,而哪些文件不應該。

 

我的意思是commit並不真的是你工作目錄的快照,而是你想要commit的文件的快照。那么在你保存你想要commit的文件之前,git將它們保存在哪里呢?它將這些文件保存在索引文件。這里我們就不繼續往下深究了,感興趣的讀者請繼續研究。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM