第 07 天:解析 Git 資料結構 - 索引結構

本書是 Will 保哥在 2013 第 6 屆 iT 邦幫忙鐵人賽的得獎作品


第 07 天:解析 Git 資料結構 - 索引結構

我們知道在 Git 裡兩個重要的資料結構,分別是「物件」與「索引」,這篇文章主要用來解說「索引」的細節。使用 Git 版本控管的過程中,或許你可以很輕易的了解 git 指令的用法,不過那很容易流於死記,無法靈活運用,連 Linus Torvalds 都在郵件清單 (Mailing List) 中提到:「在使用者了解索引的意義之前,是無法完整了解 Git 的能力的」,因此,了解「索引」的用途十分重要。

關於索引

簡單來說,「索引」的目的主要用來紀錄「有哪些檔案即將要被提交到下一個 commit 版本中」。

換句話說,「如果你想要提交一個版本到 Git 儲存庫,那麼你一定要先更新索引狀態,變更才會被提交出去。」

這裡的「索引」其實在國外很多文章裡曾經出現過很多別名,但其意思都是相同的,各位以後看到相關單字千萬不要被混淆了。

  • Index (索引)
  • Cache (快取)
  • Directory cache (目錄快取)
  • Current directory cache (當前目錄快取)
  • Staging area (等待被 commit 的地方)
  • Staged files (等待被 commit 的檔案)

舉個例子來說,指令 git diff --cached 就與 git diff --staged 是完全同義的。

操作索引的指令

由於「索引」對 Git 來說十分重要,在大多數的指令中都會有跟 Git 索引相關的參數可用,不過我們大致列出幾個直接與「索引」相關的指令來解說。

在解說指令之前,各位可以先看看以下示意圖,這說明了透過指令改變狀態的生命週期,事實上,這些改變的過程,都是在更新「索引檔」的過程:

image

首先,先介紹四種檔案狀態:

  • untracked (未追蹤的,代表尚未被加入 Git 儲存庫的檔案狀態)
  • unmodified (未修改的,代表檔案第一次被加入,或是檔案內容與 HEAD 內容一致的狀態)
  • modified (已修改的,代表檔案已經被編輯過,或是檔案內容與 HEAD 內容不一致的狀態)
  • staged (等待被 commit 的,代表下次執行 git commit 會將這些檔案全部送入版本庫)

git status

取得 工作目錄 (working tree) 下的狀態。

由於先前已經講過儲存庫、工作目錄、物件與索引之間的關係,我們用一句話說明這關係:

Git 儲存庫的運作,是將工作目錄裡的變化,透過更新索引的方式,將資料寫入成 Git 物件。

這裡的 git status 指令,目的是顯示出 目前最新版索引檔 之間的差異,這當中的差異包含了一些微妙的關係,我們用一個例子來解釋這層關係。

以下是執行 git status 的結果:

G:\git-demo>git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   c.txt
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   a.txt
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       b.txt

這裡你會看到有三種不同的分組,分別是:

  • 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 status 就是為了查出 目前最新版索引檔 之間的差異,最終只有 目前最新版索引檔 之間有差異的變更,才會真正儲存到下一個 commit 物件裡。

git add

git add 指令,是為了將目前「工作目錄」的變更寫入到「索引檔」裡。

使用 git add -u 則可以僅將「更新」或「刪除」的檔案變更寫入到「索引檔」中。

git rm

我們以 git rm 為例,當你直接在檔案系統中刪除一個檔案,這只是從「工作目錄」中刪除而已,並沒有更新到索引檔,你可以利用 git status 看到這層改變,不過若要真正把「刪除」的狀態寫進索引檔的話,則要靠 git rm filename 更新索引檔。

在執行 git rm filename 的時候,除了更新索引檔之外,連工作目錄下的檔案也會一併被刪除。若你只想刪除索引檔中的該檔,又要保留工作目錄下的實體檔案,那麼你可以在指令列加上 --cached 參數,就能做到,例如:

git rm --cached a.txt

git mv

使用 git mv oldname newname 可以將檔案更名,執行此命令會同時更新索引與變更工作目錄下的實體檔案。

git commit

這個指令,則是把「索引檔」與「目前最新版」中的資料比對出差異,然後把差異部分提交變更成一個 commit 物件。

git ls-files

在索引檔之中,預設就包含了 目前最新版 的所有檔案,外加你在工作目錄中新增檔案且透過 git add 更新索引檔後的那些檔案。透過 git ls-files 命令,可以列出所有目前已經儲存在「索引檔」中的那些檔案路徑。

從下圖範例,你應該可以看出這幾個指令之間的關係:

image

今日小結

Git 裡的「索引」是 Git 版控中最重要的觀念,有了這層觀念,也自然能得知,為什麼每次提交變更都要打一些指令把變更給加進去。當然,也有許多好用的 GUI 工具可以幫你少打許多指令,不過在我們正式開始使用 Git 的 GUI 工具之前,我們還是多靠指令把觀念給建立再說吧!

參考連結