2026-06-26 · remote · origin · fetch · pull · push · tracking branch · PR
fetch、pull、push 這三個指令做的事是在兩個版本庫之間搬運 Git 物件——搬的仍然是第 2 章學過的 blob、tree、commit。搞懂傳輸的方向與時機,就不會再搞混 fetch 和 pull。
remote 是 Git 給「另一個版本庫的 URL」取的短名。你可以有多個 remote,
每個各有一個名字。origin 只是 git clone 時 Git 自動取的預設名稱,
它沒有任何魔法,只是慣例。
| 指令 | 作用 |
|---|---|
git remote -v | 列出所有 remote 及對應 URL(fetch / push 各一行) |
git remote add <name> <url> | 新增一個 remote |
git remote rename origin upstream | 重新命名 |
git remote remove <name> | 移除 |
Remote 的資料儲存在 .git/config 裡,格式如下:
[remote "origin"]
url = https://github.com/you/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
那行 fetch = ... 叫做 refspec:它說「把 origin 的 refs/heads/*
全部對映到本地的 refs/remotes/origin/*」。這就是 remote-tracking branch
(遠端追蹤分支)的由來——它們住在 refs/remotes/ 命名空間,是「上次 fetch 時遠端長什麼樣」的快照,
你無法直接 commit 到上面,只能由 fetch 自動更新。
refs/remotes/origin/main 追蹤分支;本地的 main 不動三個指令都在搬運物件,差別在方向與是否自動合併:
| 指令 | 方向 | 合併本地分支? | 適合時機 |
|---|---|---|---|
git fetch |
遠端 → 本地物件庫 (只更新 origin/*) |
❌ 不動你的分支 | 想先看看遠端多了什麼,再決定如何整合 |
git pull |
遠端 → 本地物件庫 → 自動 merge(或 rebase) |
✅ 會動你的 HEAD 分支 | 確定要整合、快速同步 |
git push |
本地 → 遠端 | — (更新遠端分支) | 把本地 commit 分享出去 |
git pull 其實就是 git fetch + git merge 的組合拳。
想改成 fetch + rebase 模式可加 --rebase 旗標,或設定:
git config pull.rebase true # 這台機器所有 repo 都用 rebase 模式
Your local changes to the following files would be overwritten by merge)。
改的若是無關的檔案,pull 照常完成,你的改動原封不動留著。
注意這是檔案層級的判斷、不是內容層級:即使你改第 1 行、遠端改第 50 行,
兩者不可能衝突,Git 一樣擋——它只問「這個檔要不要被覆寫」。
--rebase 模式嚴格得多:工作目錄只要有任何未 commit 的改動就拒絕
(cannot pull with rebase: You have unstaged changes),哪怕跟遠端變更八竿子打不著。
所以把 pull.rebase 設成 true 之後,你會更常被要求先 commit 或 stash。
每條本地分支都可以設定一條「upstream」(上游),指向某個 remote 的某條分支。
設定之後,git status、git push、git pull 就知道「預設對象是誰」。
| 動作 | 指令 |
|---|---|
| 查看各分支的 upstream | git branch -vv |
| 設定 upstream(第一次 push 時常用) | git push -u origin feature |
| 手動設定 upstream | git branch --set-upstream-to=origin/main main |
| 查看追蹤分支的差距 | git log main..origin/main 或 git fetch; git status |
git clone 時,Git 自動把 main(或 master)的 upstream 設好——
這就是為什麼 clone 完直接 git pull 就能用。自己建的分支沒有 upstream,
第一次 push 要加 -u:
git switch -c feature # 建新分支
# ... 寫程式、commit ...
git push -u origin feature # -u = --set-upstream,同時推上去並設定追蹤
git branch -vv 輸出範例:方括號裡就是「本地相對 upstream 的差距」,五種狀態認一次——
experiment 8464d13 先做個實驗
* feature 9a0bc12 [origin/feature: ahead 2] 加入登入功能
hotfix 1457d78 [origin/hotfix: ahead 1, behind 3] 修正 header 樣式
main 5e6fa8d [origin/main] 更新 README
release 3c2b1a0 [origin/release: behind 2] 準備 v1.2
[origin/main] 只有名字、沒有數字 = 完全同步,沒事做。push 就同步。pull 就同步(能 fast-forward,不會有 merge commit)。push 會被拒,
得先決定用 merge 還是 rebase 把兩邊接起來(第 7 章的主題)。
experiment 那行整個方括號都不見 = 還沒設 upstream,正是上面 -u 要解決的事。
絕大多數團隊採用 feature branch 工作流:每個功能或修復各開一條短命分支,
完成後透過 Pull Request(PR)/Merge Request(MR) 合回主線。
這讓 main 永遠保持可部署狀態,且每次合併都有 code review 記錄。
git switch main → git pullgit switch -c feature/loginfeature/、fix/、chore/ 前綴讓一眼看出用途git add ... → git commit -m "..."git push -u origin feature/logingit fetch + git rebase origin/main 把最新 main 納入git push origin --delete feature/logingit branch -d feature/logingit fetch + git rebase origin/main(或 git merge origin/main)
把遠端新 commit 納入,再 push。絕對不要用 --force 解決 main 上的非快進問題
——那會讓別人的 commit 消失。
--force-with-lease
改寫已推送的歷史(rebase、amend)之後,普通 push 會被拒,你只能強推。
但「強推」有兩個層級,差別不只是打字長度——它們問的問題根本不同:
--force | --force-with-lease | |
|---|---|---|
| 它問的問題 | 沒問題,照我說的做 | 遠端還停在我上次看到的位置嗎? |
| 比對對象 | — | 本地的 origin/<branch> vs 遠端實際的值 |
| 同事剛推了新 commit | 直接蓋掉,他的 commit 消失 | 擋下(! [rejected] (stale info)) |
「lease」(租約)這個字是關鍵:你上次 fetch 時,遠端在某個位置,Git 把這個位置當成你「租」下的憑證。
push 時它先驗這張租約——遠端還在原位,才讓你蓋。有人動過,租約就過期(stale info),擋下。
--force 則完全不驗,誰的 commit 都照輾。
--force-with-lease 有個致命陷阱:git fetch 會把租約重新簽一次。origin/<branch>,而不是「你真正看過的東西」。
fetch(或 pull)正好會更新 origin/<branch>——於是同事的新 commit
悄悄變成你的新租約,驗證照樣通過,他的工作照樣被輾掉。
最陰的是編輯器的背景自動 fetch:你什麼都沒做,保護就沒了。
Git 2.30 起補上了 --force-if-includes:它改問「我的分支真的建立在遠端現況之上嗎?」
——也就是你是否真的把那些 commit 納入過,而不只是「本地有那個 ref」。搭配使用才是完整的安全網:
git push --force-with-lease --force-if-includes # 背景 fetch 也擋得住
# 我沒 fetch,還不知道同事推了東西
$ git push --force
+ d5fb4eb...956d470 main -> main (forced update)
↑ 同事的 commit 沒了,Git 一聲都沒吭
$ git push --force-with-lease
! [rejected] main -> main (stale info)
↑ 擋下了,同事的工作保住
# 但只要 fetch 過一次(或編輯器背景自動 fetch)……
$ git fetch
$ git push --force-with-lease
+ 0300603...0802030 main -> main (forced update)
↑ 租約被刷新,照樣輾過去——同一個指令,結果相反
$ git push --force-with-lease --force-if-includes
! [rejected] main -> main (remote ref updated since checkout)
↑ 這才擋得住
--force 從肌肉記憶裡刪掉,改用 --force-with-lease
(可設個 alias)。但別把它當成免死金牌——它防的是「你不知道遠端動過」,
不是「你不該改寫這條分支」。第 7 章的黃金法則仍然成立:
共用分支就別改寫歷史,再好的安全網也只是最後一道防線。
| 概念 | 一句話記住 |
|---|---|
| remote / origin | URL 的別名;存在 .git/config;origin 是 clone 時的預設名稱 |
| remote-tracking branch | origin/main 是「上次 fetch 時遠端長什麼樣」的唯讀快照 |
| fetch | 下載物件 + 更新追蹤分支,不動你的本地分支 |
| pull | fetch + merge(或 rebase),會動你的 HEAD 分支 |
| push | 把本地 commit 傳上遠端;需確保 fast-forward 或有強制旗標 |
| upstream / -u | 設定本地分支的追蹤對象,之後 push/pull 就知道預設目標 |
| feature branch + PR | main 永遠可部署;新功能在獨立分支開發、review、再合入 |
--force-with-lease | 強推前先驗「遠端還在我上次看到的位置嗎」;但 fetch 會刷新這張租約,要配 --force-if-includes 才完整 |
reset 三模式、revert、restore,以及用 reflog
找回以為永遠消失的 commit——這是讓你真正無後顧之憂使用 Git 的最後一塊拼圖。