了解儲存庫、工作目錄、物件與索引之間的關係

Share This Post

在 Git 裡,主要可以分成「工作目錄(Working Directory)」、「暫存區(Staging Area)」以及「儲存庫(Repository)」三個區塊,透過不同的 Git 指令,可以把檔案移往不同的區域:

了解儲存庫

當我們在任意的資料夾輸入 git,底下資料夾便可以看到 . git,而在 .git 目錄下儲存完整的Git 儲存庫,未來的所有版本變更都會自動存在此資料夾中。

了解工作目錄

我們之後準備開發的專案檔案,都會和git同一個目錄底下,也就是我們會在此工作目錄底下進行新增、修改、刪除與更改檔案名稱。

由於在使用 Git 版本控管時,會遭遇到很多分支的狀況,所以工作目錄會在不同的分支之間進行切換,有些 git 指令在執行的時候,會一併更新工作目錄下的檔案。例如當你使用 git checkout切換到不同分支時,由於目前分支與想要切換過去的分支的目錄結構不太一樣,所以很有可能會將你目前工作目錄下的檔案進行更新,好讓目前的工作目錄下的這些目錄與檔案,都與另一個要切換過去的分支下的目錄與檔案一樣,因此適時的保持工作目錄的乾淨是板控的基本原則。

了解 Git 的資料結構

在 Git 裡有兩個重要的資料結構,分別是「物件」與「索引」結構。

關於物件

用來保存版本庫中所有檔案與版本紀錄,

物件的產生是將檔案中內容取出並產生一組 SHA1 雜湊值,並依照 SHA1 雜湊值命名一個檔案。

物件的類型

在這些「物件資料庫」裡面,又包含了 4 種物件類型,分別是:

  1. blob 物件:當你執行 git add指令的同時,這些新增檔案的內容就會立刻被寫入成為 blob物件,並把原本的「檔案內容」當成 blob 檔案 的內容 (注意: blob 物件其實就是一個實體檔案),內容進行 SHA1雜湊運算後產生的一個 hash id,再把這個 hash id 當成 blob檔案的檔名。而像是檔案時間、原本的檔名或檔案的其他資訊,都會儲存在 tree 物件。
  2. tree 物件:這類物件會儲存特定目錄下的所有資訊,包含該目錄下的檔名、對應的 blob物件名稱、檔案連結(symbolic link) 或其他 tree 物件等等。由於 tree 物件可以包含其他 tree 物件,所以瀏覽 tree 物件的方式其實就跟檔案系統中的「資料夾」沒兩樣。簡單來說, tree 物件這就是在特定版本下某個資料夾的快照(Snapshot)。
  3. commit 物件:用來記錄有哪些 tree 物件包含在版本中,一個 commit 物件代表著 Git 的一次提交,記錄著特定提交版本有哪些 tree 物件、以及版本提交的時間、紀錄訊息等等,通常還會記錄上一層的 commit 物件名稱 (只有第一次 commit 的版本沒有上層 commit 物件名稱。)
  4. tag 物件:是一個容器,通常用來關聯特定一個 commit 物件 (也可以關聯到特定 blobtree 物件),並額外儲存一些額外的參考資訊(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 是一個新檔案,而且已經被標示可提交。
    • 這代表著幾件事:
      1. 目前最新版 並沒有 c.txt 這個檔案
      2. 索引檔 已經加入了這個 c.txt 檔案
      3. 所以該檔案會在執行 git commit 之後被存入下一個版本
  • Changes not staged for commit (尚未準備提交的變更)
    • 這區有個 modified: a.txt 檔案,代表 a.txt 已經被變更,但尚未標示可提交。 (not staged)
    • 這代表著幾件事:
      1. 目前最新版 也有 a.txt 這個檔案
      2. 索引檔 尚未加入 a.txt 這個檔案
      3. 所以該檔案就算執行了 git commit 也不會在下一版中出現
  • Untracked files (未追蹤的變更)
    • 這區有個 b.txt 檔案,代表 b.txt 尚未被追蹤。(untracked)
    • 這代表著幾件事:
      1. 目前最新版 沒有 b.txt 這個檔案
      2. 索引檔 也沒有 b.txt 這個檔案
      3. 所以該檔案就算執行了 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 創建步驟總和

  1. 要使用 Git 版本控管,你必須先建立「工作目錄」與「版本庫」。(mkdir, git init)
  2. 要先在「工作目錄」進行開發,你可能會建立目錄、建立檔案、修改檔案、刪除檔案、… 等操作。
  3. 然後當你想提交一個新版本到 Git 的「儲存庫」裡,一定要先更新「索引」狀態。(git add, git mv, …)
  4. 然後 Git 會依據「索引」當下的狀態,決定要把那些檔案提交到 Git 的「儲存庫」裡。(git status)
  5. 最後提交變更時 (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

訂閱研究文章

Get updates and learn from the best

More To Explore

Scroll to Top

hurry up !

軟體工程師培訓

限時免費報名中

藉由與「真實世界軟體專案」相同的技術、工具與開發流程,化簡成與商業機密無關、門檻較低更容易上手的「模擬專案」,讓你有機會在職場前輩的陪伴下,完成真槍實彈的練習,動手解決真實的問題,快速累積個人的經驗與作品,而不只是「學習技術」而已。