「你知道 Git 是怎麼一回事嗎」

「你知道 Git 是怎麼一回事嗎」這是我在今年 ModernWeb 研討會上分享的講題。這個主題基本上算是一個科普等級的分享,主要是介紹關於 Git 一些你可能知道,也可能還不知道,或是以為已經知道但其實是不知道的東西。

適合對象

主要適合平常工作有在用 Git 或是曾經有用過 Git,或是大概略懂略懂 Git 的朋友,如果這是你第一次看到這三個英文字母的組合,有滿大的機會你可能會不知道我在說什麼。但也沒關係,既然都來了,就安心坐下來吧,就把我當做是天橋底下說書的,當故事聽也可以。

由於場地及投影設備不是非常理想,在場地中、後排的朋友應該看不太清楚台上在表演什麼,轉出來的簡報檔又都是靜態的,無法看到真正原本的樣子。再加上本回主辦單位似乎好像也沒有提供錄影服務,剛好晚上也醒著,於是我就自己愛現的再錄了一次,供大家參考:

投影片連結

有任何問題,都歡迎留言或來信討論 :)

Git 簡單嗎? 困難嗎?

大家覺得 Git 很簡單嗎? 還是很困難? 不就是 git add,然後 git commitgit 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 開始進行版控後:

  1. 當開始把檔案加到 Git 的暫存區(或稱之索引區),Git 便會開始生出一些 Blob 物件。
  2. 當開始進行 Commit,Git 便會產生出一些 Tree 物件,指向剛剛那些 Blob 物件。或是可能會有其它的子目錄,所以 Tree 物件也可能會指向其它的 Tree 物件。
  3. Commit 的同時,也會產生出 Commit 物件,它會指向某一個 Tree 物件。而除了最一開始的 Commit 之外,所有的 Commit 物件也都會指向它的前一個 Commit 物件。
  4. 最後,Tag 物件則會指向某一個 Commit 物件。

這四種物件的關連圖大概長這樣。

Git Object

其實 Git 不在乎你!

很重要的觀念:「Git 只在乎檔案的內容,不在乎目錄或檔案名稱」,相同的內容的檔案,不管有多少個,在 Git 裡面都只會存一份而已。而 Git 使用的 SHA1 雜湊演算法,正是使用「內容」來進行計算的。

其實,Git 只是個內容追蹤軟體,只是剛好它可以被拿來當版控軟體而已。

Git 的 SHA1 怎麼算出來的?

以 Blob 物件的 SHA1 計算公式為例:

  1. 「"blob" 字樣」
  2. 「1 個空白字元」
  3. 「輸入內容的長度」
  4. 「Null 結束符號」
  5. 「輸入內容」

用一段 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 Object

猜猜看,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 多頁,我也不知道我是怎麼辦到的,就發揮了我囉嗦的個性,一直寫寫寫就寫了這麼多了。沒意外的話,大概九月會上市吧。

所以內容均放在網路上,可免費閱讀,有需要的可自取。

網址:https://railsbook.tw

工商服務 Part 2 - 五倍學院上線啦

之前常有朋友提到因為時間無法配合,或是人在中南部的緣故,無法參加我們家舉辦的課程。於是,我們開始準備線上課程了,希望之後可以讓更多的朋友在線上參與我們的課程。

網址:五倍學院

課程預計將包括 Ruby/Ruby on Rails、Elixir/Phoenix、Git、CSS.. 等內容。