「你知道 Git 是怎麼一回事嗎」
「你知道 Git 是怎麼一回事嗎」這是我在今年 ModernWeb 研討會上分享的講題。這個主題基本上算是一個科普等級的分享,主要是介紹關於 Git 一些你可能知道,也可能還不知道,或是以為已經知道但其實是不知道的東西。
適合對象
主要適合平常工作有在用 Git 或是曾經有用過 Git,或是大概略懂略懂 Git 的朋友,如果這是你第一次看到這三個英文字母的組合,有滿大的機會你可能會不知道我在說什麼。但也沒關係,既然都來了,就安心坐下來吧,就把我當做是天橋底下說書的,當故事聽也可以。
由於場地及投影設備不是非常理想,在場地中、後排的朋友應該看不太清楚台上在表演什麼,轉出來的簡報檔又都是靜態的,無法看到真正原本的樣子。再加上本回主辦單位似乎好像也沒有提供錄影服務,剛好晚上也醒著,於是我就自己愛現的再錄了一次,供大家參考:
有任何問題,都歡迎留言或來信討論 :)
Git 簡單嗎? 困難嗎?
大家覺得 Git 很簡單嗎? 還是很困難? 不就是 git add
,然後 git commit
再 git push
這樣而已嘛,沒什麼難的吧?
我想在座的各位的答案應該跟我差不多:「Git 不好學」,不然大家今天就不會坐在這裡聽我講故事了。Git 是一種看起來不太難但其實很難精通的一種工具,要真的好好的使用這個工具,正確的觀念不可少。
也許你曾經使用過 CVS 或 SVN 之類的版控軟體,但 Git 在設計上跟這些版控軟體有本質上的差異,所以如果各位只是把 Git 當做是 SVN 的加強版的話,那在學習上可能容易卡關,或是誤解。像是他們處理檔案的方式、使用分支的方式等等,都有很大的不同。
20/80 法則
在 Git 裡的指令,主要有分兩種,一種是比較底層的,稱之 Plumbing,另一種比較高階的,稱之 Porcelain,早期版本的 Git 大部份都是底層指令較不易上手,後來才慢慢的加入了比較高階的指令,例如 add、commit 等這些都是屬於高階的指令。還好,我們平常用到的指令雖然不多,但已經足以應付平日的工作了。
一定要認識一下的四大天王
在 Git 的世界裡,有 4 種物件一定要認識一下,分別是 Blob、Tree、Commit 以及 Tag 這四種物件。其中,Blob 物件主要是存放檔案內容,Tree 物件主要是存放目錄以及檔案資訊,Commit 物件,看名字就知道是存放 Commit 的資訊,而 Tag 物件,也就是存放跟 Tag 相關資訊的物件(更正確的說,其實是 Annotated Tag 而不是一般的 Tag)。
在使用 Git 開始進行版控後:
- 當開始把檔案加到 Git 的暫存區(或稱之索引區),Git 便會開始生出一些 Blob 物件。
- 當開始進行 Commit,Git 便會產生出一些 Tree 物件,指向剛剛那些 Blob 物件。或是可能會有其它的子目錄,所以 Tree 物件也可能會指向其它的 Tree 物件。
- Commit 的同時,也會產生出 Commit 物件,它會指向某一個 Tree 物件。而除了最一開始的 Commit 之外,所有的 Commit 物件也都會指向它的前一個 Commit 物件。
- 最後,Tag 物件則會指向某一個 Commit 物件。
這四種物件的關連圖大概長這樣。
其實 Git 不在乎你!
很重要的觀念:「Git 只在乎檔案的內容,不在乎目錄或檔案名稱」,相同的內容的檔案,不管有多少個,在 Git 裡面都只會存一份而已。而 Git 使用的 SHA1 雜湊演算法,正是使用「內容」來進行計算的。
其實,Git 只是個內容追蹤軟體,只是剛好它可以被拿來當版控軟體而已。
Git 的 SHA1 怎麼算出來的?
以 Blob 物件的 SHA1 計算公式為例:
- 「"blob" 字樣」
- 「1 個空白字元」
- 「輸入內容的長度」
- 「Null 結束符號」
- 「輸入內容」
用一段 Ruby 的程式碼來說明:
require "digest/sha1"
content = "Hello, 5xCampus"
input = "blob #{content.length}\0#{content}"
puts Digest::SHA1.hexdigest(input)
#=> "2f231f0c92c6266d35937e960bd17cfb79c9d102"
什麼是分支(Branch)?
有些人會把分支當做是「把檔案複製出來一份改,改完之後會再合併回去,所以在改的時候才不會影響到原來的分支」,但其實 Git 的分支不是這樣的。另外,為什麼大家常說在 Git 使用分支很便宜? 那是有多便宜?
因為,說白了,分支只是一個指向某個 Commit 的指標,所謂的分支,其實就只是一個有 40 個字元的檔案而已,不相信的話讓我們來看看:
$ cat ./git/refs/heads/master
0ea8463352e5fcac14b9bad84b691058cbbf8eb1
Git 的分支沒什麼,就只是這樣一個檔案而已! 如果你仔細觀察,便會發現這串 SHA1 值,正是某個 Commit 物件的 SHA1 值:
$ git log --oneline --graph
* 0ea8463 add c.html
* 632dca8 add abc
* e368aa4 init
注意到了嗎? 它正指向最後一次的 Commit 0ea8463
。
所以,為什麼說在 Git 使用分支很便宜? 因為所謂的分支,就說它只是一個 40 個字元的檔案罷了,所以當然不貴。
什麼是 HEAD?
HEAD 是一個指向某一個分支的指標,在分支切換(checkout)的時候,HEAD 會跟著指向切換過去的那個分支。
所以再回顧一下前面那張 4 個物件的圖,每一個分支,都會指向某個 Commit 物件,而 HEAD 則會指著某一個分支:
猜猜看,Git 是怎麼知道現在是哪一個分支?
在 .git 目錄裡有一個名為 HEAD
的檔案,它會記錄目前是在哪個分支的資訊:
$ git branch
cat
* master
$ cat .git/HEAD
ref: refs/heads/master
可以看得出來現在 HEAD 的內容是指向 master
分支的,接著切換到 cat
分支:
$ git checkout cat
Switched to branch 'cat'
$ cat .git/HEAD
ref: refs/heads/cat
看到了嗎? 這個 .git/HEAD
檔案就是記錄著目前所在的分支。
標籤(Tag)是什麼?
標籤其實是跟分支有點像的東西,差別只在於在 Commit 發生的時候,分支會隨著一起往前移動,但標籤一旦定下來之後就不會再移動了。大家也許看過「海角七號」電影,用電影裡一句很紅的台詞「留下來,或我跟你走」舉例,留下來的是標籤(Tag),跟你走的是分支(Branch)。
沒有蠢問題
有沒有人想過以下這些問題?
Q: 一定要有 Master 這個分支嗎?
A: 其實也不一定,分支就只是一個像貼紙或標籤一樣的東西,想叫什麼名字都可。
Q: 合併過的分支可以砍掉嗎? 為什麼?
A: 看你心情,你想砍就砍,或是想留做紀念也可以。分支也只是一張貼紙而已,砍掉分支只像是把這張貼紙撕起來而已,不會影響原來已經存在 .git
目錄裡的物件。
Q: Master 分支可以砍掉嗎?
A: 可啊,再次強調,分支就只像是一張貼紙一樣,想撕就撕,唯一的限制就是,你要撕這張貼紙的時候,不可以踩在這張貼紙上,不然撕掉之後要去哪裡?
這些問題可能看起來有點蠢,但其實只要觀念是正確的,這些問題根本也算不上是問題了。
得罪了方丈還想跑...
其實東西進了 Git,想要把它刪還不太容易,不管是檔案不小心被刪了,或是還沒合併但卻被刪掉的分支,甚至被 Hard Reset 的 Commit,一樣都救得回來。操作細節請見影片介紹。
工商服務 Part 1 - 為你自己學 Ruby on Rails
最近給出版社一本書,是關於 Ruby on Rails 的入門指南,這是我們家在培訓 Ruby on Rails 學員時候會用到的教材。目前正在校稿中,我看了一下,大概 400 多頁,我也不知道我是怎麼辦到的,就發揮了我囉嗦的個性,一直寫寫寫就寫了這麼多了。沒意外的話,大概九月會上市吧。
所以內容均放在網路上,可免費閱讀,有需要的可自取。
工商服務 Part 2 - 五倍學院上線啦
之前常有朋友提到因為時間無法配合,或是人在中南部的緣故,無法參加我們家舉辦的課程。於是,我們開始準備線上課程了,希望之後可以讓更多的朋友在線上參與我們的課程。
網址:五倍學院
課程預計將包括 Ruby/Ruby on Rails、Elixir/Phoenix、Git、CSS.. 等內容。