📸 完整的專案快照
不是只記錄「哪些行改了」,而是記錄整個專案在該時刻的完整狀態。Git 透過 tree 物件指向所有檔案的 blob。
Commit 是 Git 的核心操作——每一次 commit 就是為專案拍一張「快照」。本章帶你理解 commit 的概念、內部結構、歷史查閱與版本回溯。
Commit(提交)是 Git 中最基本的版本紀錄單位。每一次 commit 會記錄:
不是只記錄「哪些行改了」,而是記錄整個專案在該時刻的完整狀態。Git 透過 tree 物件指向所有檔案的 blob。
包含作者(Author)、提交者(Committer)、時間戳記、提交訊息(Commit Message),以及父 commit 的指標。
要建立一筆 commit,需要兩個步驟:
# 建立第一個檔案
$ echo "# My Project" > README.md
# 查看狀態
$ git status
# 將檔案加入暫存區
$ git add README.md
# 建立 commit
$ git commit -m "初始提交:新增 README"
# 查看歷史紀錄
$ git log
# git add 之前
$ git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
# git add 之後
$ git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
# git commit 之後
$ git status
On branch main
nothing to commit, working tree clean
git add 把檔案加入暫存區後,Git 知道「下次 commit 要包含它」。git commit 把暫存區的內容打包成一個快照,寫入物件資料庫。每一筆 commit 都有一個唯一的 SHA-1 雜湊值作為 ID。這個 ID 是根據 commit 的完整內容(包含 tree、parent、author、message)計算出來的。
$ git log --oneline
c3a2f1b (HEAD -> main) 新增 style.css
a7d8e9f 新增 index.html
b1c2d3e 初始提交:新增 README
完整 IDc3a2f1b4e5d6f7...
40 字元
短 IDc3a2f1b
通常 7 字元就夠用
唯一性
內容不同 → ID 不同
內容相同 → ID 相同
一個 commit 物件包含以下資訊:
$ git cat-file -p c3a2f1b
tree 4b825dc642cb6eb9a060e54bf899d31b8fc28e3b
parent a7d8e9f1234567890abcdef1234567890abcdef12
author Alice <alice@example.com> 1710849600 +0800
committer Alice <alice@example.com> 1710849600 +0800
新增 style.css
| 欄位 | 說明 |
|---|---|
tree | 指向一個 tree 物件,代表這次 commit 時整個專案的目錄結構快照。 |
parent | 指向上一個 commit 的 SHA-1。第一筆 commit 沒有 parent;merge commit 有兩個以上的 parent。 |
author | 原始程式碼的作者名稱、email 與時間戳記。 |
committer | 實際執行 commit 的人(通常和 author 相同,但在 cherry-pick 或 rebase 時可能不同)。 |
| 空行後的文字 | 提交訊息(Commit Message)。 |
Git 的提交歷史形成一條鏈狀結構——每個 commit 指向它的 parent,組成一條時間線。
| 指令 | 效果 |
|---|---|
git log | 顯示完整的提交歷史(按時間倒序) |
git log --oneline | 每筆 commit 只顯示一行(短 ID + 訊息) |
git log --graph | 用 ASCII 圖形顯示分支結構 |
git log --oneline --graph --all | 圖形化顯示所有分支的歷史 |
git log -n 5 | 只顯示最近 5 筆 commit |
git log --stat | 顯示每筆 commit 修改了哪些檔案 |
git log -p | 顯示每筆 commit 的詳細 diff |
| 指令 | 比較對象 |
|---|---|
git diff | 工作目錄 vs 暫存區(尚未 add 的變更) |
git diff --staged | 暫存區 vs 最新 commit(已 add 但尚未 commit 的變更) |
git diff HEAD | 工作目錄 vs 最新 commit |
git diff a7d8e9f c3a2f1b | 兩筆 commit 之間的差異 |
Git 最大的優勢之一就是可以隨時回到過去的版本。以下是幾種常見的回溯方式:
暫時跳到某個歷史 commit 查看內容,不影響現有分支。
$ git checkout a7d8e9f
# 進入 Detached HEAD 狀態
# 查看完畢後回到分支
$ git checkout main
將工作目錄或暫存區的檔案還原到特定版本。
# 還原工作目錄中的檔案
$ git restore index.html
# 取消暫存(unstage)
$ git restore --staged index.html
# 還原到特定 commit 的版本
$ git restore --source=a7d8e9f index.html
git restore 會直接覆蓋工作目錄的檔案,這個操作無法復原。如果有尚未提交的重要修改,請先 commit 或 stash。
當你執行 git commit 時,Git 在內部依序完成以下步驟:
git add 新的變更時,暫存區才會和工作目錄有差異。
好的提交訊息讓團隊成員(包括未來的你)能快速理解每筆變更的目的。
fix bug
update
修改
asdflkj
WIP
修正登入頁面密碼驗證的錯誤邊界條件
新增使用者註冊的 Email 格式驗證
重構購物車計算邏輯,提取折扣計算為獨立函式
更新 README 安裝說明,補充 Node.js 18+ 需求
# 格式
<類型>: <簡短描述>(50 字以內)
<詳細說明>(可選,72 字換行)
# 常見類型
feat: 新功能
fix: 修正 bug
docs: 文件修改
style: 格式調整(不影響邏輯)
refactor: 重構(不改功能也不修 bug)
test: 測試相關
chore: 雜事(建置、設定等)
$ echo "# My Project" > README.md
$ git add README.md
$ git commit -m "feat: 新增 README"
$ echo "<h1>Hello</h1>" > index.html
$ git add index.html
$ git commit -m "feat: 新增首頁"
$ echo "body { margin: 0; }" > style.css
$ git add style.css
$ git commit -m "feat: 新增樣式表"
$ git log --oneline
# 查看最近一筆 commit 的 SHA-1
$ git log --oneline -1
# 用 cat-file 查看 commit 物件
$ git cat-file -p HEAD
# 用 cat-file 查看 tree 物件
$ git cat-file -p HEAD^{tree}
# 比較兩筆 commit 的差異
$ git diff HEAD~2 HEAD
git log 查閱。git checkout 或 git restore。git push、git pull、git clone。