讀書筆記: 版本控制使用Git - 檔案管理、索引、送交

摘要自版本控制使用Git。因為在HackMD有另外整理筆較常用的指令,這篇只是筆記一些書上看到但實務不常用的指令和知識。


Git底層

Git基本物件型態

  1. Blobs
  2. Tree
  3. Commits
  4. Tags:Lightweight tags、Annotated tags

底層運作

  1. Git是內容追蹤系統。
  2. Git物件雜湊值/SHA1值/辨識碼

     // 以下是通常不會使用的一些低階指令
    
     $ git rev-parse 物件雜湊值前綴             // 回傳此物件完整的雜湊值(40字元)
    
     $ git cat-file -p 物件完整雜湊值(40字元)   // 還原雜湊函數所計算的物件儲存內容
    
  3. git add 檔案 只會產生包含檔案內容的物件、更新索引(Index),並不會立即產生Tree物件。
  4. 索引會追蹤檔案存放的路徑、檔案內容資訊。
  5. git write-tree 指令可以強迫Git根據目前索引狀態產生Tree物件,相同的索引狀態只會產生相同的Tree物件雜湊值。
  6. 如果產生Tree物件,可以用 git ls-files -s 查看目前Tree結構參照的檔案。
  7. 通常會用Lightweight tags標記Commit物件,並且套用此Tag到Commit物件指向的Tree物件中的所有檔案。

Git 檔案管理、索引

  1. 工作目錄(Working Directory)、索引(Index)、容器(Repository)
  2. git add 會將檔案加入容器內,且讓索引追蹤這些準備要被送交的檔案,這個動作可說是 被索引追蹤 或說 快取此檔案
  3. 送交(Commit):用 git commit 指令確認有被索引追蹤的檔案變更內容,並且真正地套用這些變更至容器內。
  4. 索引只是 參照(保存、記錄)檔案變更的集合,再強調一次索引參照的這些檔案變更只是準備被送交,在還沒使用 git commit之前,檔案變更不會真的被套用到容器
  5. 檔案被索引追蹤一次後就會持續被追蹤此檔案是否有變更,換句話說,只要有用過git add指令讓檔案被索引追蹤,此檔案後續只要有變更,就可省略git add 指令、直接用git commit 檔名套用此次變更到容器裡。
  6. 尚未被索引追蹤和送交的單一檔案,可以直接用 git commit 檔名 一步驟完成 新增檔案變更內容 (被索引追蹤) 和 送交檔案變更內容 的動作。(但移除檔案不行)
  7. 大多數的重要工作幾乎都在送交以前就完成了,也就是執行 git add 指令會完成這些動作:檔案被拷貝至Git物件、算出雜湊值和建立索引。
  8. git commit 會將索引追蹤變更後所建立的虛擬Tree物件轉成真正的Tree物件、然後建立新的commit物件,新的commit物件會指向剛剛轉換的Tree物件及前一個(父)commit物件。最後所在分支(branch)會參照(ref)新建立的commit物件。

.gitignore

  1. .gitignore 檔案可被放進容器中的任何目錄,但只會套用規則至所在目錄與所在目錄的子目錄。
  2. ! 可反向操作動作,使用範例如下:

     # ./.girignore
     # 不追蹤所有.o副檔名的檔案
     *.o
    
     # ./vendor_files/.gitignore
     # 但此目錄(vendor_files)底下的driver.o檔案會被追蹤 
     !driver.o
    


檔案狀態

  1. 檔案狀態可分為:被追蹤的(stage)、不被追蹤的(unstage)、被忽略的
  2. 如何辨識索引狀態和檔案是否被追蹤

     $ git status         // 查詢索引狀態
    
     $ git diff           // 顯示工作目錄中尚未被追蹤的變更檔案
     $ git diff --cache   // 顯示已被追蹤尚未被送交的變更檔案
    
     $ git add --interactive
     $ git commit --interactive
    
  3. 要小心 git commit --all 會讓Git遍歷目錄使得所有未被追蹤的檔案一起被追蹤+送交出去。

檔案移除

  1. Git 的移除主要是兩種:,
    • 一種是取消檔案被索引追蹤的狀態(stage):git rm --cached 檔名
    • 另一種是取消檔案被索引追蹤的狀態並從工作目錄中移除,但會先檢查此檔案的任何變更是否有被Git保留:git rm 檔名
    • 若想強迫移除檔案:git rm -f 檔名
  2. 確認檔案是否已不被追蹤:git ls-files --stage
  3. 恢復不小心連工作目錄也刪除的檔案:git checkout HEAD -- 檔名

檢視檔案變動歷史

Git其實會完整保留檔案的變動記錄,所以根據使用狀況提供以下兩種檔案變動歷史的檢視方式:

$ git log 檔名
$ git lig --follow 檔名   // 連同檔名變更前的歷史也一起檢視


送交

  1. Git真正記錄容器狀態的時間點發生在送交(commit)的那一刻,而且更精確地來說,送交只會記錄容器的索引(Index)狀態,意即容器那些有在工作目錄被修改、但是沒被 add 到索引的檔案是不會被送交的。
  2. Git會記錄每個送交的前/後一送交,可能會因為分支或合併的產生使得送交們之間彼此有複雜的結構和關係。
  3. $gitk 以視覺化方式顯示目前送交們的結構和關係。

檢視送交記錄

註:這篇只記錄奇怪的 git log 使用方式。

  1. git log HEAD~10..HEAD~8 表示只看 [HEAD~8, HEAD~10) 的送交。
  2. ^x表示排除X和所有X以前的送交,因此 git log HEAD^10 HEAD~8 等同 git log HEAD~10..HEAD~8
  3. git log -S 用來查看那些檔案包含指定字串,完整指令是git log -Sstring,string表示要查看的指定字串。
  4. git blame 查看檔案是被誰修改或是被哪一次送交所編輯。

尋找送交

如果不想使用難記的SHA-I值來找要處理的送交,可以試著使用相對的送交名稱。所謂使用相對的送交名稱通常是指要找的是 相對於某送交的前幾個送交

  1. HEAD:因為要比較送交記錄之間的相對順序,一般會以目前分支的最新送交作為基準點去找前幾次送交記錄,而 HEAD 即代表目前分支的最新送交。另外書中也介紹如下不同的HEAD:
    • HEAD:指向目前分支最新的送交
    • ORIG_HEAD:指向合併或重設前一版本
    • MERGE_HEAD:暫時記錄被合併分支的HEAD
    • FETCH_HEAD:操作遠端容器時會記錄 git fetch 指令抓取的HEAD。
  2. ^~
    • 通常如果要找HEAD的前一個送交,可以輸入 HEAD^1 或者縮寫 HEAD^;如果是HEAD的前二個送交則是 HEAD^2 或者縮寫 HEAD^^ 。另外有的網路文章也會介紹可輸入像是 HEAD~2 的語法。
    • 但這本書有指出 ^~ 的差別,以下重製書中的圖來比較^~的使用差異,其中圓形代表送交記錄,H表示HEAD。圖中一個HEAD會有許多前一送交(或說父送交)同時指向這個HEAD,是發生在分支曾有合併的情況。
  3. 還有一種作法是直接指定某個分支HEAD的前一個送交 master~1
  4. git rev-parse 則是可將任意形式的送交名稱轉換成 SHA-1值。

尋找壞掉的送交

git bisect 如果專案壞了,這項指令可以幫忙尋找可能造成專案bug原因的送交。Git反覆跟使用者確認、搜尋,最後分離出這個送交。通常壞掉的送交會是最新的那一次送交,可以設定從 HEAD 開始縮小範圍專案尋找壞掉的送交。

  1. 注意因為分離送交會改變工作目錄,所以整個工作要在一個不會影響專案的工作目錄進行。
  2. git bisect start
  3. git bisect bad 第一步用來設定壞掉的為HEAD、之後則是用來縮小範圍
  4. git bisect good commit 設定好的送交 (可以是相對送交名稱或tag)
  5. git bitsect visualize 查看目前匡列的好壞送交
    • 或者 git bitsect visualize --pretty=oneline
  6. git bisect replay 重新尋找 最後因為搜尋過程是在一個卸載的HEAD(detached HEAD)上,可用 git branchgit bisect reset 回到 master分支上。