Git 版本控制教學 - 單兵必懂指令

Posted on  Oct 18, 2022  in  Git 版本控制  by  Amo Chen  ‐ 6 min read

瞭解使用 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/

再來,我們試圖模擬一般的程式開發過程,在這個資料夾內新增兩個檔案 READMEHelloWorld.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)

上述的指令顯示了幾項資訊,分述如下:

  1. On branch master, 位於名為 master 的分支
  2. No commits yet, 目前尚未有第一次的 commit (提交變更)
  3. 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.cREADME 已經列入追蹤並且在等待提交變更之列。

如果你後悔不想將這個項目列入追蹤,可以使用 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

同樣地,上述的指令結果在以下分點說明:

  1. git commit -m <此次提交的簡短說明> , 若不使用 -m 參數,則會以系統偏好使用的編輯器請使用者輸入此次提交的簡短說明
  2. [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 的狀態。

修改檔案後,我們可以有兩種選擇:

  1. git add <file> ... 將檔案加入 staged
  2. 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 狀態。

此處可得出幾個小結論:

  1. 檔案修改後還沒git add 時的狀態為 unstaged
  2. 檔案修改後被 git add 之後的狀態為 staged
  3. 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

瞭解 stagedunstaged 之後,就可以接續學 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.cbad.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

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!