Git 全景課 / 第三部分 協作與正本控制 · M9

M9 遠端倉庫:clone、fetch、pull、push

到目前為止,一切都發生在你自己的電腦上。協作的第一步,是明白「你的倉庫」和「GitHub 上的倉庫」是兩個獨立的倉庫,它們只在你執行 fetch、pull、push 時才交換東西。看懂 origin/main 這張標籤,遠端就不再神秘。
約 75 分鐘沙盒 7 個任務 · 小測 4 題先修:M5、M7
學完本單元,你能夠:
  1. 說出本機倉庫和遠端倉庫的關係,以及 origin、origin/main 是甚麼;
  2. 分清 fetch、pull、push 各自搬動了甚麼;
  3. 讀懂 git status 的 ahead/behind/diverged,並決定下一步;
  4. 處理被拒絕的 push,並在必要時安全地用 --force-with-lease。

1兩個獨立的倉庫 Two separate repositories

M0 說過,Git 是分散式的:每個人手上都有完整的倉庫。GitHub 上的倉庫只是另一個倉庫,恰好放在一部大家都連得到的伺服器上,並由團隊約定為正本。

本機倉庫與遠端倉庫 左邊是你電腦上的倉庫,有分支 main,以及遠端追蹤分支 origin/main。右邊是 GitHub 上的倉庫,有分支 main。push 由左到右,fetch 由右到左,pull 等於 fetch 再合併。 你的電腦:~/project/.git main 你的分支,你提交時會移動 origin/main 遠端追蹤分支: 「上一次聯絡 GitHub 時,它的 main 在哪裏」。 只在 fetch/pull/push 時更新, 你不會直接在它上面提交。 GitHub:you/report.git 你的電腦稱它為 origin(一個代號) main 團隊約定的正本 只在有人 push 時改變; 它不會主動通知你, 要你去 fetch 才知道。 push fetch pull=fetch+合併
圖 1. 深色標籤是你的分支;白底黑框是遠端追蹤分支,它只是一份「記錄」,代表你最後一次看到的遠端狀態。

2clone:取得一份完整的複本 Cloning

git clone git@github.com:you/report.git
Cloning into 'report'...
done.

git clone 做了四件事:① 下載遠端的全部物件(整段歷史,不只最新版本);② 把遠端記為 origin;③ 為遠端每條分支建立遠端追蹤分支 origin/main、origin/feature……;④ 建立本機的 main,設定它「追蹤」origin/main(稱為 upstream),並把檔案攤開在工作目錄。

之後 git switch feature 時,如果本機沒有 feature 但有 origin/feature,Git 會自動建立一條追蹤它的本機分支:

Switched to a new branch 'feature'
branch 'feature' set up to track 'origin/feature'.

3origin/main:你對遠端的最後記憶 Remote-tracking branches

origin/main 不是遠端本身,而是你電腦上的一張標籤,記着「上一次聯絡時,GitHub 的 main 在哪個 commit」。所以 git status 說的 ahead/behind,是和這份記憶比較:

git status 顯示意思下一步
up to date with 'origin/main'和記憶一致(但記憶可能已過時!)不確定時先 git fetch
ahead of 'origin/main' by 1 commit你有 1 個 commit 還沒送出git push
behind 'origin/main' by 1 commit, and can be fast-forwarded遠端有你還沒收的 commitgit pull
have diverged兩邊各有對方沒有的 commitgit pull --rebase 或 --no-rebase,再 push
「up to date」不代表遠端沒有新東西 git status 不會聯絡 GitHub。如果同事剛剛 push 了,在你 git fetch 之前,你的 origin/main 仍停在舊位置,status 會繼續說 up to date。

4fetch、pull、push The three sync verbs

命令方向改變了甚麼會動你的分支和檔案嗎
git fetchGitHub → 你下載新 commit,更新 origin/*不會。完全安全
git pullGitHub → 你fetch,然後把 origin/main 合併(或 rebase)進目前分支會
git push你 → GitHub上傳 GitHub 沒有的 commit,移動 GitHub 上的分支,同時更新你的 origin/*不動你的,只動遠端的

push 只會在「遠端分支是你分支的祖先」時成功(等於讓遠端 fast-forward);否則就會被拒絕,以免蓋掉別人的 commit。第一次推送一條新分支時加 -u,設定它的 upstream,之後就能直接 git push、git pull。

5沙盒:你、GitHub、同學 Sandbox: you, GitHub and a classmate

互動 三個倉庫

右上是 GitHub 上的倉庫(origin)。按「同學推送一個 commit」,模擬同學從他的電腦 push。下面是你的電腦:白底黑框的 origin/main 只有在你 fetch、pull、push 時才會移動。開始時,你剛 clone 了這個倉庫。

應用線:

GitHub:you/report.git(origin)

同學的電腦

(同學還沒有推送)

你的電腦:~/project

6push 被拒絕的時候 When a push is rejected

被拒絕是 Git 在保護別人的工作,不是錯誤。按訊息判斷:

訊息原因做法
(fetch first)遠端有你從未見過的 commitgit pull --rebase(或 --no-rebase),再 push
(non-fast-forward)你的分支不包含遠端的 tip:可能是分岔了,或你改寫了已推送的 commit分岔 → 同上;改寫了自己的個人分支 → --force-with-lease
(stale info)用了 --force-with-lease,但遠端在你上次 fetch 之後又變了先 fetch 看看別人推了甚麼,再決定
Need to specify how to reconcile divergent branchesgit pull 時分岔了,Git 要你選合併方式--rebase、--no-rebase 或 --ff-only;也可用 git config pull.rebase true 設定預設
--force 與 --force-with-lease --force 讓遠端無條件變成你的版本,期間別人推送的 commit 會被丟掉。--force-with-lease 多一個條件:「遠端現在仍是我上次看到的那個 commit」才覆蓋,否則拒絕(stale info)。改寫自己個人分支後需要推送時,一律用後者。受保護的 main 兩者都不允許(FAQ-002、M11)。

7容易混淆的對照 Easily confused

mainorigin/main
是誰的你的本機分支你電腦上對遠端 main 的記錄
何時移動你提交、合併、reset 時只在 fetch、pull、push 時
能在上面提交嗎可以不行(switch 過去會進入 detached HEAD)
常見誤解
  • 「commit 之後,同事就看得到。」不會。commit 只在你的倉庫;要 push 之後,同事 fetch 才看得到。
  • 「git fetch 沒有改變我的檔案,所以甚麼都沒做。」它更新了 origin/* 並下載了物件;用 git log --oneline --all 或 git status 就看得到。
  • 「git pull 只是下載。」它是 fetch+合併(或 rebase),會改動你的分支和檔案。想先看看再決定,就只 fetch。
  • 「origin 是 GitHub 的意思。」origin 只是 clone 時給遠端取的預設代號,可以有多個遠端,例如 upstream(M10 的 fork 流程)。

8命令速查 Cheat sheet

命令作用
git clone <網址>取得完整複本,設定 origin 與追蹤關係
git remote -v列出遠端及其網址
git fetch下載新 commit,只更新 origin/*
git pull --rebase / --no-rebase / --ff-onlyfetch 後以 rebase/合併/只容許 fast-forward 收進來
git push推送目前分支到它的 upstream
git push -u origin feature第一次推送新分支並設定 upstream
git push --force-with-lease改寫個人分支後的安全強制推送
git branch -vv每條分支追蹤誰、ahead/behind 多少
git log --oneline --all連 origin/* 一起看

9小測 Quiz

1. 同事剛 push 了新 commit。你還沒有聯絡過 GitHub,git status 會顯示:
2. 哪一條命令只下載、完全不動你的分支和檔案?
3. push 時看到 ! [rejected] main -> main (fetch first)。最合適的下一步是:
4. 你 amend 了已推送到「只有自己在用」的個人分支上的 commit。推送時最安全的做法是:

本單元重點 Summary

延伸閱讀