← 上一章:新增、初始 Repository 下一章:工作區、暫存區與儲存庫 →


把檔案交給 Git 控管

上個章節我們對目錄進行了 Git 的初始化,讓 Git 開始可以管理這個目錄,接下來,我們就來看看 Git 是怎麼操作的。

把檔案交給 Git

在開始之前,我想先介紹 git status 這個指令。這個指令的用途是用來查詢現在這個目錄的「狀態」,先在剛剛建立的 git-practice 目錄下執行這個指令:

$ git status
On branch master

Initial commit

nothing to commit (create/copy files and use "git add" to track)

在這個目錄裡,現在除了 Git 幫你產生的那個 .git 隱藏目錄外什麼都沒有,所以上面這段訊息就是要跟你說「現在沒東西可以提交(nothing to commit)」。接下來,在這個目錄裡透過系統指令建立一個內容為 “hello, git” 並命名為 welcome.html 的檔案:

$ echo "hello, git" > welcome.html

這個步驟要用一般的文字編輯器或檔案管理員來完成也沒關係,總之就是在這個目錄裡建立一個叫做 welcome.html 的檔案就行了。接著,我們再次使用 git status 指令,來看一下這個目錄的狀態:

$ git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	welcome.html

nothing added to commit but untracked files present (use "git add" to track)

或是使用 SourceTree 來看:

image

這時候的狀態跟剛才一開始不太一樣了,現在可以看到這個 welcome.html 檔案目前的狀態是 Untracked files,意思是這個檔案尚未被加到 Git 版控系統裡,還沒開始正式被 Git「追蹤」,它只是剛剛才加入這個目錄而已。

把檔案交給 Git

既然目前這個檔案是 Untracked,接下來就是要把 welcome.html 這個檔案交給 Git,讓 Git 開始「追蹤」它,使用的指令是 git add 後面加上檔案名稱

$ git add welcome.html

在終端機執行這個指令不會有任何的輸出結果,如果是使用 SourceTree,可以在那個檔案上按滑鼠右鍵,然後選擇「Add to index」:

image

就可以把這個檔案交給 Git 來管控了。再次使用 git status 指令看一下目前的狀態:

$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

	new file:   welcome.html

有注意到嗎?剛才那個檔案從 Untracked 變成 new file 狀態了。這個表示這個檔案已經被安置到暫存區(Staging Area),等待稍後跟其它檔案一起被存到儲存庫裡。

而這個暫存區也稱之索引(index),所以在上圖中,SourceTree 的選單才會用「Add to index」字樣。基本上暫存區跟索引是一樣的意思。為了讓大家比較方便理解,本書將統一使用「暫存區」的用法。至於為什麼要有這樣的設計,請見下一章「工作區、暫存區與儲存庫」的介紹。

小提示

其實這個 add 指令看起來很簡單,但背後 Git 其實幫你做了不少事,詳情請參閱「【超冷知識】在 .git 目錄裡有什麼東西?Part 1」章節說明。

如果你覺得 git add welcome.html 這樣一次只加一個檔案有點麻煩,你也可以使用萬用字元(wildcard character):

$ git add *.html

就可把所有附檔名是 html 的檔案全部都加到暫存區。而如果想要一口氣把全部的檔案加到暫存區,可直接使用 --all 參數:

$ git add --all

【狀況題】如果在 git add 之後又修改了那個檔案的內容?

想像一下這個情境:

  1. 你新增了一個檔案叫做 abc.txt
  2. 然後,執行 git add abc.txt 把檔案加至暫存區。
  3. 接著編輯 abc.txt 檔案。

完成編輯後,接著你可能會想要進行 Commit,把剛剛修改的內容存下來。這是新手可能會犯的錯誤之一,以為 Commit 指令就會把所有的異動都存下來,事實上這樣的想法是不太正確的。執行一下 git status 指令,看一下目前的狀態:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   abc.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:   abc.txt

咦?你發現 abc.txt 這個檔案變成 2 個了嗎?其實你在第 2 步的確有把 abc.txt 加到暫存區沒錯,但你在第 3 步又編輯了那個檔案,對 Git 來說,編輯的內容並沒有再次被加到暫存區,所以這時候在暫存區裡的資料還是你在第 2 步時候加進來的那個檔案。

如果確定這個修改是你要的,就請再次使用 git add abc.txt 指令,再把 abc.txt 加至暫存區。

豆知識

其實這樣的動作會產生一些 Unreachable 的沒人愛邊緣物件,這部份算是比較進階的內容,有興趣可參閱「【冷知識】你知道 Git 有資源回收機制嗎?」章節內容。

【冷知識】”–all” 跟 “.” 參數有什麼不一樣?

有時候會看到別人說「git add . 指令也可以把所有的檔案全部加到暫存區喔!」,這樣的說法其實不完全正確,這得看以下幾種情況而決定:

1. Git 版本

在比較舊版本(1.x 版)的 Git, git add . 這個指令會把「新增的檔案」(也就是 Untracked 狀態的檔案)以及有「修改過的檔案」加到暫存區沒錯,但是不會處理「刪除檔案」的行為。讓我畫個表格簡單說明一下:

使用參數 新增檔案 修改檔案 刪除檔案
--all O O O
. O O X

不過,在 Git 2.x 版之後變成這樣:

使用參數 新增檔案 修改檔案 刪除檔案
--all O O O
. O O O

也就是說,在 Git 2.x 之後,這兩個參數在功能上就沒什麼差別了。

2. 執行指令時候的目錄位置

舉個例子來說:

image

在專案的根目錄的 index.html 以及在 css 目錄的 main.css 都有修改,如果是在根目錄執行 git add .,這兩個檔案都會被加進暫存區,但如果是在 css 目錄下執行,僅會加入 main.cssindex.html 的狀態不會改變。

那是因為 git add . 這個指令會把目前當下這個目錄,以及它的子目錄、子子目錄、子子子目錄…裡的異動全部加到暫存區,但在這個目錄的以外的就不歸它管了。而 git add --all 指令就沒這個問題,這個指令不管在專案的哪一層目錄執行,效果都是一樣的,在這個專案裡所有的異動都會被加至暫存區。

所以,回到原來的問題「--all. 參數有什麼不一樣?」,答案會跟所使用的 Git 版本不同以及執行指令時的目錄而有所差異。

把暫存區的內容提交到倉庫裡存檔

如果僅是透過 git add 指令把異動加到暫存區是不夠的,這樣還不算是完成整個流程。要讓暫存區的內容永久的存下來的話,使用的是 git commit 指令:

$ git commit -m "init commit"
[master (root-commit) dfccf0c] init commit
 1 file changed, 1 insertion(+)
 create mode 100644 welcome.html

在後面加上的 -m "init commit" 是指要要說明「你在這次的 Commit 做了什麼事」,只要使用簡單、清楚的文字說明就好,中、英文都可,重點是清楚,讓不久之後的你或是你的同事能很快的明白就行了。

如果是使用 SourceTree,可點擊左上角的「Commit」按鈕,就可以開始輸入訊息:

image

輸入完訊息,按下右下角的「Commit」按鈕,即可完成這次的 Commit。

當完成了這個動作後,對 Git 來說就是「把暫存區的東西存放到儲存庫(Repository)裡」,翻譯成白話文就是「我完成一個存檔(或備份)的動作了」,也是建立了一個我們在第一個章節所提到的「版本」。關於儲存庫,我們會在下個章節「工作區、暫存區與儲存庫」介紹。

注意!

要完成 Commit 指令才算是完成整個流程喔!

到底 Commit 了哪些東西?

請先記住一個很重要的觀念:「Git 每次的 Commit 都只會處理暫存區(Staging Area)裡的內容」。也就是說,如果在執行 git commit 指令的時候,那些還沒被加到暫存區裡的檔案,就不會被 Commit 到儲存庫裡。

舉例來說,如果你新增了一個檔案,但卻沒有執行 git add 指令把這個檔案加至暫存區的話,在執行 git commit 指令的時候那個檔案就會被無視喔。

那個訊息是什麼?很重要嗎?

對,很重要!很重要!很重要!(因為重要所以要說三次)

在 Commit 的時候,如果沒有輸入這個訊息,Git 預設是不會讓你完成 Commit 這件事的。它最主要的目的就是告訴你自己以及其它人「這次的修改做了什麼」。以下是幾點關於訊息的建議:

  1. 儘量不要使用太過情緒性的字眼(我知道開發者有時候工作會有低潮或遇到澳州來的客人),避免不必要的問題。
  2. 英文、中文都沒關係,重點是要簡單、清楚。
  3. 儘量不要使用像 bug fixed 這樣模糊的描述,因為沒人知道你修了什麼 bug。但如果有搭配其它的系統使用,則可使用 #34 bug fixed,因為這樣可以知道這次的 Commit 修正了第 34 號的 bug。

等等,我怎麼跳出一個奇怪的視窗了

在執行 commit 指令時,如果沒有在後面加上訊息參數,預設會跳出一個黑黑的畫面,那個就是傳說中的編輯器 - Vim。那是個對新手不太友善的編輯器,各位可參考「超精簡 Vim 操作介紹」章節的介紹,或是直接使用 SourceTree 之類的圖形介面軟體來處理輸入提交訊息的問題。

【冷知識】一定要有東西才能 Commit 嗎?

只要加上 --allow-empty 參數,沒東西也是可以 Commit 的:

$ git commit --allow-empty -m "空的"
[master 76a5b84] 空的

$ git commit --allow-empty -m "空的"
[master f4f568c] 空的

$ git commit --allow-empty -m "空的"
[master 7653117] 空的

這樣就做了 3 個空的 Commit 出來,基本上這沒什麼意義,但有時候我們在上 Git 課的時候就會滿方便的,可以不用新增檔案就快速產生 Commit 來練習合併。


← 上一章:新增、初始 Repository 下一章:工作區、暫存區與儲存庫 →

Comments