在 Git 裡,主要可以分成「工作目錄(Working Directory)」、「暫存區(Staging Area)」以及「儲存庫(Repository)」三個區塊,透過不同的 Git 指令,可以把檔案移往不同的區域:
了解儲存庫
當我們在任意的資料夾輸入 git
,底下資料夾便可以看到 . git
,而在 .git
目錄下儲存完整的Git 儲存庫,未來的所有版本變更都會自動存在此資料夾中。
了解工作目錄
我們之後準備開發的專案檔案,都會和git
同一個目錄底下,也就是我們會在此工作目錄底下進行新增、修改、刪除與更改檔案名稱。
由於在使用 Git 版本控管時,會遭遇到很多分支的狀況,所以工作目錄會在不同的分支之間進行切換,有些 git 指令在執行的時候,會一併更新工作目錄下的檔案。例如當你使用 git checkout
切換到不同分支時,由於目前分支與想要切換過去的分支的目錄結構不太一樣,所以很有可能會將你目前工作目錄下的檔案進行更新,好讓目前的工作目錄下的這些目錄與檔案,都與另一個要切換過去的分支下的目錄與檔案一樣,因此適時的保持工作目錄的乾淨是板控的基本原則。
了解 Git 的資料結構
在 Git 裡有兩個重要的資料結構,分別是「物件」與「索引」結構。
關於物件
用來保存版本庫中所有檔案與版本紀錄,
物件的產生是將檔案中內容取出並產生一組 SHA1
雜湊值,並依照 SHA1
雜湊值命名一個檔案。
物件的類型
在這些「物件資料庫」裡面,又包含了 4 種物件類型,分別是:
- blob 物件:當你執行
git add
指令的同時,這些新增檔案的內容就會立刻被寫入成為blob
物件,並把原本的「檔案內容」當成blob 檔案
的內容 (注意:blob 物件
其實就是一個實體檔案),內容進行SHA1
雜湊運算後產生的一個hash id
,再把這個hash id
當成blob
檔案的檔名。而像是檔案時間、原本的檔名或檔案的其他資訊,都會儲存在tree
物件。 - tree 物件:這類物件會儲存特定目錄下的所有資訊,包含該目錄下的檔名、對應的
blob
物件名稱、檔案連結(symbolic link) 或其他tree
物件等等。由於tree
物件可以包含其他tree
物件,所以瀏覽tree
物件的方式其實就跟檔案系統中的「資料夾」沒兩樣。簡單來說,tree
物件這就是在特定版本下某個資料夾的快照(Snapshot)。 - commit 物件:用來記錄有哪些
tree
物件包含在版本中,一個 commit 物件代表著 Git 的一次提交,記錄著特定提交版本有哪些tree
物件、以及版本提交的時間、紀錄訊息等等,通常還會記錄上一層的 commit 物件名稱 (只有第一次 commit 的版本沒有上層 commit 物件名稱。) - tag 物件:是一個容器,通常用來關聯特定一個 commit 物件 (也可以關聯到特定
blob
、tree
物件),並額外儲存一些額外的參考資訊(metadata),例如: tag 名稱。使用 tag 物件最常見的情況是替特定一個版本的 commit 物件標示一個易懂的名稱,可能是代表某個特定發行的版本,或是擁有某個特殊意義的版本。
在使用Git進行版控的過程,所有進行控管的目錄與檔案都會區分「目錄資訊」與「檔案內容」,我們個別稱為 tree物件
與 blob物件
。
物件結構的優點
- 有效率的處理大型專案
- 不僅僅是完整的版本庫會複製(clone)一份在本機,由於所有的 blob 物件都是透過「內容」來做定址的 (content addressable),因此,若在不同版本之間找尋相同的內容,效率是非常高的。
- 歷史紀錄保護
- Git 版控的過程,每次提交變更都會產生一個 commit 物件,而這個 commit 物件的名稱又是透過 commit 物件的內容產生。再者,commit 物件會關連到 tree 物件,tree 物件的名稱又是透過 tree 物件的內容所產生。tree物件又會關聯到 blob 與 tree物件,這些物件的名稱也是透過內容產生。就這樣一層一層的關聯下去,如果你今天真的想竄改某個版本的歷史紀錄,困難度也是挺高的!
- 由於 Git 儲存庫經常會被 clone 或 fork,只要是被 clone 過的儲存庫,來源的儲存庫只要任何一個物件被修改,這些 clone 出去的儲存庫就很難再合併回來,所以你幾乎不可能任意竄改版本紀錄。
- 定期的封裝物件
- 我們在 Git 中提到的 “物件” 其實就是代表版本庫中的一個檔案。而在版本異動的過程中,專案中的程式碼或其他檔案會被更新,每次更新時,只要檔案內容不一樣,就會建立一個新的 “物件”,這些不同內容的檔案全部都會保留下來。
- 你應該可以想像,當一個專案越來越大、版本越來越多時,這個物件會越來越多,雖然每個檔案都可以各自壓縮讓檔案變小,不過過多的檔案還是會檔案存取變得越來越沒效率。因此 Git 的設計有個機制可以將一群老舊的 “物件” 自動封裝進一個封裝檔(packfile)中,以改善檔案存取效率。
- 那些新增的檔案還是會以單一檔案的方式存在著,也代表一個 Git 版本庫中的 “檔案” 就是一個 Git “物件”,但每隔一段時間就會需要重新封裝(repacking)。
- 照理說 Git 會自動執行重新封裝等動作,但你依然可以自行下達指令執行。例如: git gc
- 如果你要檢查 Git 維護的檔案系統是否完整,可以執行以下指令: git fsck
關於索引
用來保存當下要進版本庫之前的目錄狀態。 主要用來紀錄「有哪些檔案即將要被提交到下一個 commit 版本中」
所謂的「索引」是一個經常異動的暫存檔,這個檔案通常位於 .git
目錄下的一位名為 index 的檔案。主要用來紀錄「有哪些檔案即將要被提交到下一個 commit 版本中」。換句話說,如果你想要提交一個版本到 Git 儲存庫,那麼你一定要先更新索引狀態,變更才會被提交出去。
這個索引檔,通常保存著 Git 儲存庫中特定版本的狀態,這個狀態可以由任意一個 commit 物件
,以及 tree 物件
所表示。
檔案的各種狀態
首先要知道檔案的各種狀態
- untracked (未追蹤的,代表尚未被加入 Git 儲存庫的檔案狀態)
- unmodified (未修改的,代表檔案第一次被加入,或是檔案內容與 HEAD 內容一致的狀態)
- modified (已修改的,代表檔案已經被編輯過,或是檔案內容與 HEAD 內容不一致的狀態)
- staged (等待被 commit 的,代表下次執行 git commit 會將這些檔案全部送入版本庫)
了解工作目錄的狀態
通常目前 最新版 與 索引檔 之間的差異
git status
通常你會看到不同的結果:
- Changes to be committed (準備提交的變更)
- 這區有個
new file: c.txt
檔案,代表 c.txt 是一個新檔案,而且已經被標示可提交。 - 這代表著幾件事:
- 目前最新版 並沒有 c.txt 這個檔案
- 索引檔 已經加入了這個 c.txt 檔案
- 所以該檔案會在執行 git commit 之後被存入下一個版本
- 這區有個
- Changes not staged for commit (尚未準備提交的變更)
- 這區有個
modified: a.txt
檔案,代表 a.txt 已經被變更,但尚未標示可提交。 (not staged) - 這代表著幾件事:
- 目前最新版 也有 a.txt 這個檔案
- 索引檔 尚未加入 a.txt 這個檔案
- 所以該檔案就算執行了 git commit 也不會在下一版中出現
- 這區有個
- Untracked files (未追蹤的變更)
- 這區有個
b.txt
檔案,代表 b.txt 尚未被追蹤。(untracked) - 這代表著幾件事:
- 目前最新版 沒有 b.txt 這個檔案
- 索引檔 也沒有 b.txt 這個檔案
- 所以該檔案就算執行了 git commit 也不會在下一版中出現
- 這區有個
索引檔案的操作
我們通常不會直接去編輯
.git\\index
這個二進位檔,而是透過標準的 git 指令去操作這個索引檔,對於索引檔的操作指令大概有以下幾個:
- git add
- 將目前「工作目錄」的變更寫入到「索引檔」裡。
- git mv
- 使用
git mv oldname newname
可以將檔案更名,執行此命令會同時更新索引與變更工作目錄下的實體檔案。
- 使用
- git rm
- 從「工作目錄」中刪除檔案
- 真正把「刪除」的狀態寫進索引檔的話,則要靠
git rm filename
更新索引檔。
- git status
- git commit
- 把「索引檔」與「目前最新版」中的資料比對出差異,然後把差異部分提交變更成一個 commit 物件。
- git ls-files
- 列出所有目前已經儲存在「索引檔」中的那些檔案路徑。
Git 的「索引」是一個介於「物件儲存區」 (object storage) 與「工作目錄」 (working directory) 之間的媒介。
Git 創建步驟總和
- 要使用 Git 版本控管,你必須先建立「工作目錄」與「版本庫」。(mkdir, git init)
- 要先在「工作目錄」進行開發,你可能會建立目錄、建立檔案、修改檔案、刪除檔案、… 等操作。
- 然後當你想提交一個新版本到 Git 的「儲存庫」裡,一定要先更新「索引」狀態。(git add, git mv, …)
- 然後 Git 會依據「索引」當下的狀態,決定要把那些檔案提交到 Git 的「儲存庫」裡。(git status)
- 最後提交變更時 (git commit),才會把版本資訊寫入到「物件儲存區」當中 (此時將會寫入 commit 物件)。
參考資料:
ithelp.ithome.com.tw/articles/10133653
https://gitbook.tw/chapters/using-git/working-staging-and-repository
https://github.com/doggy8088/Learn-Git-in-30-days/blob/master/zh-tw/07.md