Git:程式碼的時光機
💡 學習指南:這一章專門寫給完全沒用過 Git 的人。我們不會一上來就讓你背指令,而是先搞清楚「Git 到底在幫你解決什麼問題」,再一步步把指令和概念串起來。讀完後,你應該能獨立完成:本地提交、建立分支、推送到 GitHub。
0. 先問一個問題:你有沒有經歷過這些噩夢?
場景一:版本地獄
你寫論文或寫程式,改到一半發現改錯了,想回到三天前的版本——但你找不到了。
專案_v1.zip
專案_v2_修改版.zip
專案_v3_最終版.zip
專案_v3_最終版_真的最終版.zip
專案_v3_最終版_打死不改了.zip每次存一個新副本,硬碟越來越亂,而且你根本記不住哪個版本改了什麼。
場景二:協作噩夢
你和隊友同時改同一個檔案:
- 你改了第 10 行,新增了登入功能
- 隊友改了第 10 行,修復了一個 Bug
- 你們用電子郵件互傳程式碼,結果合併時一個人的改動被另一個人覆蓋了
- 沒人知道最後哪段程式碼是對的
場景三:沒有「後悔藥」
你在正式環境部署了新程式碼,結果出 Bug 了,想緊急回退到上一個穩定版本——但你不知道怎麼回退,只能手忙腳亂地找備份。
Git 就是為了解決這三個問題而生的。
Git 是一個版本控制系統(Version Control System)。它的本質是:把你每一次「存檔」操作都記錄下來,形成一條完整的歷史時間線,讓你可以隨時回到任意一個歷史節點。
不誇張地說,Git 是現代軟體開發最重要的工具之一。幾乎所有的公司、所有的開源專案都在用它。
1. Git 和 GitHub 是一回事嗎?
很多初學者會混淆這兩個概念,先澄清一下:
| Git | GitHub | |
|---|---|---|
| 是什麼 | 一個執行在你電腦上的版本控制工具 | 一個存放 Git 儲存庫的網站(雲端) |
| 在哪裡 | 你的本地電腦 | 網路上 |
| 能獨立使用嗎 | ✅ 可以,只管理本地歷史 | ❌ 需要配合 Git 使用 |
| 類比 | 你本地的日記本 | 存日記的雲端硬碟 |
簡單說:Git 是工具,GitHub 是託管服務。 就像 Word 是工具,OneDrive 是雲端硬碟一樣,兩者配合使用,但並不是同一個東西。
除了 GitHub,類似的服務還有 GitLab、Gitee(國內)等。
2. 核心概念:三個區域
這是整個 Git 最重要的設計,理解了這三個區域,你就理解了 Git 的靈魂。
Git 把你的檔案狀態分成三層:
工作區(Working Directory) 就是你的普通資料夾,你現在看到的、正在編輯的所有檔案都在這裡。你隨便改,Git 會感知到你改了什麼,但不會做任何記錄。
暫存區(Staging Area / Index) 這是一個「預備提交」的中轉站。你可以把工作區裡想要儲存的檔案「放進」暫存區,就像把快遞放進快遞盒——還沒寄出去,但已經選好了要寄什麼。
儲存庫(Repository) 這是永久存檔的歷史記錄庫,藏在 .git 資料夾裡。每次你執行 git commit,暫存區裡的內容就會被封存進儲存庫,形成一條不可竄改的歷史記錄。
👇 動手試試看:依次點擊指令按鈕,觀察檔案在三個區域之間的流轉。
files you are editing
login.jsunstagedstyle.cssunstageddebug.logunstagedgit addfiles prepared for this commit
git commitversions saved permanently
9f3e1b2init: initialize projectHEAD為什麼要「兩步走」(add + commit)?
很多初學者會問:為什麼不能直接一鍵儲存,非要先 add 再 commit?
因為現實開發中,你經常不想把所有改動都一起提交。
舉個例子:你今天改了 5 個檔案:
login.js:完成了登入功能(想提交)style.css:調整了登入頁樣式(想提交)debug.log:臨時除錯輸出(不想提交)experiment.js:正在測試的新功能,還沒完成(不想提交)todo.txt:你的個人備忘(不想提交)
如果沒有暫存區,你要麼把這 5 個檔案全部提交(提交記錄很混亂),要麼一個都不提交。
有了暫存區,你可以精確控制:git add login.js style.css,只把這兩個檔案放進快遞盒,然後 commit,這次提交就清清楚楚地記錄「登入功能完成」。
3. 第一次使用 Git:初始化和基礎工作流程
3.1 安裝和初始化
安裝好 Git 後(macOS 自帶,Windows 去 git-scm.com 下載),開啟終端機,進入你的專案資料夾:
# 在目前資料夾初始化一個 Git 儲存庫
git init
# Git 會建立一個隱藏的 .git 資料夾,所有歷史記錄存在裡面
# 輸出:Initialized empty Git repository in .../your-project/.git/第一次使用還需要告訴 Git 你是誰(這個資訊會附在每次提交記錄上):
git config --global user.name "你的名字"
git config --global user.email "你的電子郵件"3.2 日常工作流程:三步存檔
初始化之後,日常開發 90% 的操作就是反覆執行這三步:
第一步:檢視狀態
git status這是你用得最多的指令,沒有之一。它告訴你:
- 你在哪個分支上
- 哪些檔案被修改了(紅色 = 未暫存)
- 哪些檔案在暫存區裡(綠色 = 已暫存,等待提交)
第二步:把檔案放進暫存區
# 新增單一檔案
git add login.js
# 新增多個檔案
git add login.js style.css
# 新增目前資料夾裡所有修改過的檔案(用 . 表示「全部」)
git add .⚠️ 初學者常見誤區:
git add .非常方便,但會把所有修改都加進去,包括你不想提交的臨時檔案。養成精確 add 的習慣,或者用.gitignore排除不想追蹤的檔案(後面會講)。
第三步:提交,寫上說明
git commit -m "feat: 新增使用者登入功能"-m 後面引號裡的內容叫做 commit message(提交說明)。這是寫給未來的自己和隊友看的,要寫得有意義。
3.3 Commit Message 怎麼寫才專業?
# ❌ 沒用的寫法——看了不知道做了什麼
git commit -m "update"
git commit -m "fix"
git commit -m "改了一些東西"
# ✅ 好的寫法:類型 + 冒號 + 一句話描述
git commit -m "feat: 新增使用者登入功能"
git commit -m "fix: 修復首頁在 iOS Safari 上的白畫面問題"
git commit -m "docs: 更新 README 中的部署說明"
git commit -m "refactor: 將 UserService 拆分為獨立模組"
git commit -m "style: 統一程式碼縮排為 2 空格"常用前綴含意:
| 前綴 | 含意 |
|---|---|
feat: | 新功能(feature) |
fix: | 修復 Bug |
docs: | 文件改動 |
style: | 程式碼格式調整(不影響功能) |
refactor: | 程式碼重構(功能不變,結構最佳化) |
chore: | 建構、工具、依賴相關 |
test: | 測試相關 |
養成這個習慣,幾個月後翻歷史記錄,一眼就知道每次提交做了什麼。這在團隊協作中尤其重要。
3.4 檢視歷史記錄
# 詳細格式(每次提交的完整資訊)
git log
# 簡潔格式(每行一條,推薦日常使用)
git log --oneline
# 範例輸出:
# a1b2c3d (HEAD -> main) feat: 新增使用者登入功能
# 9f3e1b2 init: 專案初始化4. 平行宇宙:分支(Branch)
分支是 Git 最強大、也是最讓初學者困惑的功能。但理解了它之後,你會發現這個設計非常優雅。
4.1 分支是什麼?用「平行宇宙」來理解
想像你在玩一個角色扮演遊戲,遊戲裡有一個關鍵選擇:
- 選擇 A:去挑戰大 Boss(開發新功能)
- 選擇 B:繼續穩定當前局面(主線不動)
如果你直接在主存檔上做選擇 A,萬一失敗了,整個遊戲進度就毀了。
但如果你複製一個存檔,在副本裡去挑戰 Boss:
- 打贏了?把副本的成果合併回主存檔
- 打輸了?主存檔完全沒有影響,刪掉副本重來
Git 分支就是這個「副本存檔」機制。
在 Git 裡,main(或 master)分支是你的「主存檔」,永遠保持穩定可用。當你要開發新功能時,你從 main 建立一個新分支,在那裡開發、測試,完成後再合併回 main。
4.2 分支的視覺化演示
👇 動手試試看:依次點擊指令按鈕,觀察下方分支圖如何分叉、延伸、最終合併。重點關注 HEAD 標籤的位置變化——它始終指向「你目前在哪裡」。
4.3 分支操作詳解
建立並切換到新分支:
# 方式一:先建立,再切換(兩步)
git branch feature-login # 建立分支
git checkout feature-login # 切換過去
# 方式二:一步到位(推薦)
git checkout -b feature-login
# 輸出:Switched to a new branch 'feature-login'建立分支後,你的命令列提示符會顯示目前分支名,比如:
user@mac ~/project (feature-login) $檢視所有分支:
git branch
# 輸出(* 表示目前所在分支):
# * feature-login
# main在分支上正常開發:
# 在 feature-login 分支上,改程式碼、add、commit,和平時完全一樣
git add login.js
git commit -m "feat: 新增登入表單 HTML 結構"
git add login.js api.js
git commit -m "feat: 完成登入介面對接"這些提交只在 feature-login 分支上,main 分支完全不知道你做了什麼。
切回主分支,合併:
# 切回 main
git checkout main
# 把 feature-login 的所有改動合併進來
git merge feature-login
# 合併完成後,可以刪掉這個分支(可選)
git branch -d feature-login4.4 什麼時候該開分支?
| 場景 | 建議 | 理由 |
|---|---|---|
| 開發一個新功能 | ✅ 開分支 | 功能完成前不影響主線,隨時可以放棄 |
| 修復線上緊急 Bug | ✅ 從 main 開 hotfix-xxx 分支 | 修完直接合併上線,不帶入未完成的功能 |
| 和隊友平行開發 | ✅ 各自開分支 | 互不干擾,完成後統一透過 Pull Request 合併 |
| 只改一個錯字 | ❌ 直接在 main 改 | 風險極低,沒必要額外開分支 |
4.5 團隊常用的分支策略
在實際專案中,團隊通常會約定好分支的命名和用途:
| 分支名 | 用途 | 特點 |
|---|---|---|
main / master | 正式環境的穩定程式碼 | 只有測試通過的程式碼才能進來,不能直接推送 |
dev / develop | 日常整合分支 | 所有功能分支先合併到這裡,測試通過再上 main |
feature/xxx | 具體功能開發 | 如 feature/user-login,完成後合併到 dev |
hotfix/xxx | 緊急修復 | 從 main 建立,修完直接合併回 main 和 dev |
5. 與隊友協作:遠端儲存庫
到目前為止,你學的都是本地的 Git 操作——所有歷史記錄都存在你自己的電腦上。要和隊共享程式碼,你需要一個遠端儲存庫,也就是 GitHub、GitLab 這樣的雲端儲存。
5.1 遠端儲存庫的運作原理
可以把遠端儲存庫理解為團隊共用的「公共存檔」:
- 每個人在本地寫程式碼、commit
- 寫完後
push(上傳)到遠端儲存庫 - 隊友
pull(下載)遠端儲存庫的最新內容到自己本地 - 這樣大家的程式碼就保持同步了
👇 動手試試看:依次點擊指令,體驗從關聯遠端儲存庫、推送到拉取隊友更新的完整流程。
9f3e1b2init: initialize projectc4d8a31feat: homepage layout5.2 第一次推送專案到 GitHub
第一步:在 GitHub 上建立一個新儲存庫(點擊右上角 + → New repository),不要勾選初始化選項。
第二步:回到本地終端機,關聯遠端儲存庫:
# 把本地儲存庫和 GitHub 上的儲存庫關聯起來
# "origin" 是遠端儲存庫的別名,是約定俗成的名字(也可以改,但沒必要)
git remote add origin https://github.com/你的使用者名稱/儲存庫名.git
# 確認關聯成功
git remote -v
# 輸出:
# origin https://github.com/你的使用者名稱/儲存庫名.git (fetch)
# origin https://github.com/你的使用者名稱/儲存庫名.git (push)第三步:推送本地內容到遠端:
# 第一次推送,-u 的意思是「以後 git push 時,預設推到 origin 的 main 分支」
git push -u origin main
# 之後每次推送只需要:
git push5.3 日常協作的指令
推送(你改了東西,要讓隊友看到):
git push拉取(隊友改了東西,你要同步):
git pullgit pull 實際上是兩個指令的組合:
git fetch:先去遠端儲存庫下載最新的提交記錄git merge:把下載回來的內容合併到你目前的分支
第一次從 GitHub 獲取別人的專案:
# 把整個遠端儲存庫複製到本地(只需要做一次)
git clone https://github.com/某人/某專案.git
# clone 會自動建立與遠端的關聯,之後直接 push/pull 就行5.4 push 和 pull 的方向
你的電腦(本地儲存庫) ←→ GitHub(遠端儲存庫)
git push: 本地 → 遠端 (你改了東西,上傳給隊友)
git pull: 遠端 → 本地 (隊友改了東西,下載到你這裡)
git clone: 遠端 → 本地 (第一次完整複製整個儲存庫)最佳實踐:每天開始工作前先
git pull,拿到最新程式碼;下班或完成一個功能後git push,及時備份並讓隊友看到你的進展。
6. 進階:處理衝突
衝突是協作中不可避免的,但也沒那麼可怕。
6.1 衝突是怎麼發生的?
當你和隊友同時修改了同一個檔案的同一行,在合併時 Git 不知道該用誰的版本,就會產生衝突。
舉個例子:
- 你在
login.js第 5 行寫了:const timeout = 3000 - 隊友同時在同一行寫了:
const timeout = 5000 - 當你
git pull或git merge時,Git 發現了這個矛盾,就會「暫停」並告訴你:我不知道該用哪個,你來決定。
6.2 衝突檔案長什麼樣?
Git 會在衝突的地方插入特殊標記:
function login() {
const url = '/api/login'
<<<<<<< HEAD
const timeout = 3000 // 你的版本
=======
const timeout = 5000 // 隊友的版本
>>>>>>> feature/update-timeout
return fetch(url, { timeout })
}<<<<<<< HEAD到=======之間:是你目前分支的內容=======到>>>>>>> xxx之間:是合併過來的內容
6.3 如何解決衝突?
第一步:開啟衝突檔案,找到所有 <<<<<<< 標記(通常 VS Code 等編輯器會自動高亮)
第二步:決定保留哪段程式碼,然後手動編輯檔案,刪掉所有標記符號(<<<<<<<、=======、>>>>>>>)。
比如決定用 5000(隊友的版本):
function login() {
const url = '/api/login'
const timeout = 5000 // 採用隊友的修改
return fetch(url, { timeout })
}第三步:重新提交
# 標記衝突已解決
git add login.js
# 完成合併提交(Git 會自動產生合併提交資訊)
git commit6.4 減少衝突的好習慣
- 勤 pull:開始工作前同步最新程式碼,減少「你落後太多」的情況
- 小步提交:不要寫了一週程式碼才一次性提交,頻繁小提交更容易發現和解決衝突
- 分支隔離:不同功能用不同分支,減少對同一行程式碼的競爭
- 溝通:要改公共檔案(比如
config.js)前,跟隊友打個招呼
7. 常用指令速查
Save this table and check it whenever a Git command slips your mind:
8. 實戰:加入一個團隊專案的完整流程
這是你加入新團隊或新專案時的標準操作流程,可以直接照抄:
# ① 第一天:把專案 clone 到本地(只做一次)
git clone https://github.com/team/project.git
cd project
# ② 每天開始工作:先拉取最新程式碼,確保你的程式碼是最新的
git pull origin main
# ③ 建立自己的功能分支(不要直接在 main 上改)
git checkout -b feature/user-profile
# ④ 正常開發...寫程式碼...
# ⑤ 完成一個小功能點後,立即提交(不要攢著)
git add src/UserProfile.vue
git commit -m "feat: 完成使用者頭像上傳功能"
git add src/UserProfile.vue src/api/user.js
git commit -m "feat: 完成使用者資料編輯介面"
# ⑥ 把自己的分支推送到遠端,讓隊友能看到
git push origin feature/user-profile
# ⑦ 在 GitHub 上建立 Pull Request(PR),請求合併到 main
# (這步在 GitHub 網頁上操作)
# ⑧ 等隊友 Code Review,按回饋修改,繼續 commit + push
# ⑨ PR 合併後,回到 main,更新本地,刪掉功能分支
git checkout main
git pull
git branch -d feature/user-profile9. .gitignore:哪些檔案不應該被追蹤?
有些檔案你不想提交到 Git 儲存庫裡,比如:
node_modules/:依賴套件,體積巨大,可以用npm install重新產生.env:環境變數檔案,裡面可能有資料庫密碼、API Key,絕對不能上傳到公開儲存庫*.log:日誌檔案.DS_Store:macOS 自動產生的隱藏檔案dist/、build/:編譯產物,可以重新建構
在專案根目錄建立一個 .gitignore 檔案,寫上不想追蹤的檔案規則:
# 依賴套件
node_modules/
# 環境變數(重要!密碼不能提交)
.env
.env.local
# 建構產物
dist/
build/
# 系統檔案
.DS_Store
Thumbs.db
# 日誌
*.logGitHub 上有各種語言和框架的 .gitignore 範本:github.com/github/gitignore
名詞速查表
| 名詞 | 英文 | 解釋 |
|---|---|---|
| 儲存庫 | Repository (Repo) | 存放專案所有版本歷史的資料庫,在 .git 資料夾裡 |
| 提交 | Commit | 一次完整的版本記錄,像遊戲存檔點,附有說明和時間戳 |
| 分支 | Branch | 獨立的開發線,像平行時間線,互不影響 |
| 合併 | Merge | 把一個分支的改動整合到另一個分支 |
| 衝突 | Conflict | 同一行程式碼被多人修改,Git 不知道該用哪個,需要手動解決 |
| 暫存 | Stage / Index | 把修改放入「準備提交」清單的操作 |
| 遠端 | Remote | 雲端的儲存庫副本(GitHub / GitLab / Gitee) |
| 複製 | Clone | 把整個遠端儲存庫完整複製到本地 |
| 推送 | Push | 把本地提交上傳到遠端儲存庫 |
| 拉取 | Pull | 把遠端最新內容下載並合併到本地 |
| HEAD | HEAD | 目前所在分支/提交的指標,表示「你現在在哪裡」 |
| origin | origin | 遠端儲存庫的預設別名(約定俗成的名字) |
| stash | Stash | 臨時儲存還沒 commit 的改動,切換任務時用 |
| PR / MR | Pull Request / Merge Request | 請求把你的分支合併進主分支,通常需要隊友 review |