瞭解使用 Git 時所必備的指令,可以從單人開發及多人合作開發的情境著手。
單人開發的情境,可以了解如何利用 Git 從無到有建立具版本控制的開發流程;多人合作開發的情境能夠了解如何透過 Git 在不影響開發環境之下與其他團隊成員共同協作。
在開始之前,有一點觀念必須釐清。版本的演進,是線性的演進,因此我們通常會有一個稱為 master
(或稱 main
) 的主線。而開發過程所發現的 issue或是需要新增的功能,我們會希望在不影響 master
的情況下,複製master
衍生出一個分支(branch)進行開發,等到分支開發完成、測試穩定之後,才會與 master
主線合併(merge),演化出全新的版本,如下圖所示。
單人開發模式 - 新增篇
本章節將會介紹在單人開發模式之下所需要的幾個重要指令。
首先,建立一個專案資料夾 gittest
,我們將對這個專案資料夾裡面的所有檔案進行版本控制。
$ mkdir gittest
接著,我們必須指定 Git 對這個專案資料夾進行版本控制,指令形式為 git init [資料夾名稱]
。
$ git init gittest
上述指令成功之後,系統會顯示類似以下的訊息,可以看到 Git 會在專案資料夾內建立一個 .git
的隱藏資料夾。事實上,Git 就是將所有的版本控制相關的資訊儲存於此一隱藏資料夾內,因此這個資料夾不應被刪除。
Initialized empty Git repository in /home/you/gittest/.git/
再來,我們試圖模擬一般的程式開發過程,在這個資料夾內新增兩個檔案 README
與 HelloWorld.c
。
$ touch README HelloWorld.c
新增檔案之後,我們仍需要知道這些檔案的在版本控制系統內的狀態 (untracked, unmodified, modified, staged) ,以確定哪些檔案被新增、變更、刪除等,便於進行後續的管理,例如告知 Git 需要將新增的檔案納入版本控制、接受變更等。
察看檔案狀態的指令為 git status
:
$ git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
HelloWorld.c
README
nothing added to commit but untracked files present (use "git add" to track)
上述的指令顯示了幾項資訊,分述如下:
- On branch master, 位於名為
master
的分支 - No commits yet, 目前尚未有第一次的
commit
(提交變更) - Untracked files, 顯示
HelloWorld.c
,README
尚未被 Git 列入追蹤項目,可以使用 git add <file> ...
或 git add .
將這些檔案列入追蹤,以列入後續提交變更 (commit) 的項目。
由於這些檔案尚未被列入追蹤項目,因此可以使用 git add <file> ...
將這些項目列入追蹤。
$ git add HelloWorld.c README
若使用 git add .
則是直接將當前路徑下,所有未加入追蹤的檔案一併列入追蹤。
將檔案列入追蹤之後,可以再使用 git status
察看目前的檔案狀態,可以觀察到檔案狀態已經變更了。
$ git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: HelloWorld.c
new file: README
上方的 git status
結果,可以看到 Changes to be committed 指明了
HelloWorld.c
與 README
已經列入追蹤並且在等待提交變更之列。
如果你後悔不想將這個項目列入追蹤,可以使用 git rm --cached <file> ...
將指定的檔案移除追蹤項目。
最後,當你覺得這些檔案已經可以定版,或是程式 bug 已經被修正之後,就可以使用 git commit
提交變更。
例如:
$ git commit -m "The first commit of gittest project"
[main (root-commit) 40e99c9] The first commit of gittest project
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 HelloWorld.c
create mode 100644 README
同樣地,上述的指令結果在以下分點說明:
git commit -m <此次提交的簡短說明>
, 若不使用-m
參數,則會以系統偏好使用的編輯器請使用者輸入此次提交的簡短說明[main (root-commit) 40e99c9]
代表於main
分支上提交變更,提交變更碼為40e99c9
(簡短碼)
每一次的提交變更,都會有一筆日誌紀錄,如果要察看這些日誌紀錄,可以使用 git log
察看。
$ git log
commit 40e99c9e44ac54d4eeff4f5604add7684ec9016d (HEAD -> main)
Author: Amo Chen <the email>
Date: Tue Oct 18 22:21:58 2022 +0800
The first commit of gittest project
此外,如果沒有任何提交變更紀錄,就會出現類似以下的紀錄:
fatal: bad default revision 'HEAD'
HEAD 代表當前所在的分支(current branch),Git以 HEAD 做為指標以指明當前所在的分支。
提交變更之後,可以試試再看一次 git status
,就可以發現目前已經沒有任何檔案需要再提交變更。
$ git status
On branch main
nothing to commit, working tree clean
至此,我們可以用下圖闡述 Git 的檔案狀態的轉換。
圖上解釋了大部份的檔案狀態轉換,其中較為令人疑惑的狀態應為 staged 。
事實上,在使用 git add <file> ...
將檔案加入追蹤項目之後,在提交變更之前,就是處於 staged 的狀態,而以 git rm --cache <file> ...
將檔案移出追蹤項目就會使得檔案轉換為 unstaged 狀態 (not staged) 。
單人開發模式 - 修改篇
進行首次的提交變更之後,可能會發現某些檔案又需要進行再次的修正,我們以修改 HelloWorld.c
做為練習,並以 git status
查看檔案狀態。 :
$ echo "#include <stdio.h>" > HelloWorld.c
$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: HelloWorld.c
no changes added to commit (use "git add" and/or "git commit -a")
從上述的修改結果,可以發現 HelloWorld.c
進入了 modified
的狀態,但仍處於 not staged
的狀態。
修改檔案後,我們可以有兩種選擇:
git add <file> ...
將檔案加入staged
git checkout -- <file> ...
取消檔案變更,回到未更改前的狀態
此處,我們將檔案加入 staged
,以觀察檔案狀態變化:
$ git add HelloWorld.c
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: HelloWorld.c
上述結果可以發現, Changes not staged for commit 已變成 Changes to be committed ,代表檔案已經進入 staged
狀態。
此處可得出幾個小結論:
- 檔案修改後還沒被
git add
時的狀態為unstaged
- 檔案修改後被
git add
之後的狀態為staged
staged
的檔案被提交變更之後就會回到unmodified
同樣地,在提交變更之前,仍可使用 git reset HEAD <file> ...
將檔案從 staged
狀態回復至 unstaged
。
這些提示在使用 git status
時就能夠看到。
接下來試著將檔案 unstaged
:
$ git reset HEAD HelloWorld.c
Unstaged changes after reset:
M HelloWorld.c
同樣使用 git status
察看檔案狀態,可以發現檔案回到 unstaged
狀態,如下所示:
$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: HelloWorld.c
no changes added to commit (use "git add" and/or "git commit -a")
在 unstaged
的狀態下,還可以使用 git diff
指令,比較變動之前的版本與變動之後的差異:
$ git diff
diff --git a/HelloWorld.c b/HelloWorld.c
index e69de29..53c5fdf 100644
--- a/HelloWorld.c
+++ b/HelloWorld.c
@@ -0,0 +1 @@
+#include <stdio.h>
上述 git diff
的結果,a
代表變動之前, b
代表變動之後。
除了比較 unstaged
之檔案變動差異,也能夠指定Git比較與 staged
之差異:
$ git diff --staged
git diff --staged
等於 git diff --cached
。原因在於使用
git add <file> ...
之後,Git 會將變動的檔案快取起來,因此 staged
又可以稱為 cached
,也由於快取機制,因此在檔案進入 staged
狀態之後,就存在一份快取,若此時再對檔案進行變動,其變動將不會出現,因為是以快取起來的檔案為主,所以進行 git diff
就無法看到差異。解決此種問題,就需要將檔案回復為 unstaged
狀態之後再變為 staged
,或者直接再 git add <file>
一次,將更動的部分變為 staged
。
瞭解 staged
與 unstaged
之後,就可以接續學 git commit -a
偷懶指令:
$ git commit -a
上述指令代表將全部 unstaged
加到 staged
後直接提交變更。
本篇最後一個修改的指令為 git mv <file> <new_file>
,可使用此一指令對檔案進行重新命名。
需要注意的是,如果你的檔案系統(file system) 不區分大小寫 (例如 macOS 預設的檔案系統 APFS ),檔名就算相同但大小寫不同也會視為同一檔案而無法進行重新命名。
$ git mv README README_V2
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: README -> README_V2
git mv <file> <new_file>
指令結束,該檔案只會進入 staged
狀態,後續仍需要提交變更才能產生新版本。
單人開發模式 - 刪除篇
開發過程也會遇到需要將檔案刪除的情況,若直接將檔案刪除,Git 也能夠偵測到檔案已被刪除。但最好使用 git rm <file> ...
較為安全,且能夠有回復的機會。
下列一系列,指令過程為新增一檔案 bad.c
並且提交之後以 git rm <file> ...
刪除該檔案。
新增檔案 bad.c
,並提交變更:
$ touch bad.c
$ git add .
$ git commit -a
輸入 git rm bad.c
指令,刪除檔案 bad.c
,從結果可以看到檔案 bad.c
已被刪除 :
$ git rm bad.c
rm 'bad.c'
$ ls
HelloWorld.c README
使用 git status
可以發現檔案仍在 unstaged
仍可以回復 :
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
deleted: bad.c
如要救回該檔案,可使用 git restore --staged bad.c
將 bad.c
放回 unstaged
之後,再輸入一次 git restore bad.c
即可救回。
最後,試著真的刪除 bad.c
:
$ git rm bad.c; git commit -m 'delete bad.c'
[main edd5b1a] test git rm
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 bad.c
察看日誌紀錄
如果想查看提交(commit)的紀錄,可以輸入 git log
,越舊的紀錄會在越底下:
$ git log
commit edd5b1aa7ad82401623dbd26fa1a74e4532be856 (HEAD -> main)
Author: Amo Chen <the email>
Date: Tue Oct 18 23:10:57 2022 +0800
test git rm
commit 33c68b2eef2352fd0ecc58440be7a778c9ba316f
Author: Amo Chen <the email>
Date: Tue Oct 18 23:01:35 2022 +0800
commit bad.c
commit 0c9772aff5778a79f5cd8ff43ae2ee2ed84f03de
Author: Amo Chen <the email>
Date: Tue Oct 18 23:01:16 2022 +0800
rename readme
commit 76b284a4d87db7c1f4e60da247f3741e67bd01a9
Author: Amo Chen <the email>
Date: Tue Oct 18 22:51:30 2022 +0800
test commit -a
commit 40e99c9e44ac54d4eeff4f5604add7684ec9016d
Author: Amo Chen <the email>
Date: Tue Oct 18 22:21:58 2022 +0800
The first commit of gittest project
除了使用 git log
指令外,也可以用 Sourcetree 等 GUI 工具查看 log, 會更加方便直覺。
單人開發模式 - 更正提交說明篇
假設提交變更的說明寫錯,想要試圖更正的話,可以使用以下指令進行更正。
$ git commit --amend