M11 守住單一正本
- 說出各種保護規則各自擋住甚麼操作;
- 按團隊情況選擇規則組合,並理解「繞過名單」的風險;
- 用 CI 檢查和 CODEOWNERS 讓審查更可靠;
- 用 tag 和 release 標記正式版本;分辨機械閘和團隊紀律。
1約定不夠,要有閘門 From convention to gate
M0 說過,分散式版本管理裏,正本是約定出來的;M10 的 PR 流程是遵守約定的做法。但約定有三個弱點:有人趕時間直接推送;有人不小心 push --force 蓋掉別人的工作;AI agent 被叫去「推上去」,就真的推到 main。
託管服務的保護規則把約定寫進伺服器:違反規則的推送和合併,伺服器直接拒絕。GitHub 有兩套機制:較早的 branch protection rule(傳統分支保護),和較新的 ruleset;GitLab 稱為 protected branch。兩者能擋的操作大致相同,差別主要在「誰可以繞過」(見下文)。這就是本課第二條學生問題 FAQ-002 的答案。
2常用的保護規則 Branch protection rules
| 規則 | 擋住甚麼 |
|---|---|
| 必須經 PR | 直接 git push 到 main;所有改動都要經 PR 合併 |
| └ 必要審查人數(≥1) | 沒有其他人批准的 PR(作者不能批准自己的 PR) |
| └ 需要 CODEOWNERS 審查 | 改動了某些檔案、卻沒有該檔案負責人批准的 PR |
| └ 新推送後撤銷舊批准 | 批准之後再推送新 commit、卻沒有重新審查的 PR(預設不開:舊批准會保留) |
| 必須通過狀態檢查(CI) | 自動檢查失敗的 PR;在 ruleset 裏,也擋直接推送未經檢查的 commit |
| 禁止強制推送 | push --force 改寫受保護分支的歷史 |
| 禁止刪除 | 刪除受保護的分支,例如 release/*(預設分支 main 本身,GitHub 一律不准刪除,與規則無關) |
| 必須線性歷史 | 合併 commit(只容許 squash 或 rebase 合併) |
| 必須簽署 commit | 沒有經驗證簽名的 commit 會被擋 |
標「└」的三項是「必須經 PR」的子選項:不要求經 PR,就談不上審查人數或 CODEOWNERS。GitLab 的對應做法:Settings → Repository → Protected branches 把 Allowed to push 設為 No one,所有改動就只能經 MR;Allowed to force push 是另一個開關;審查要求則在 MR approval rules 和 Code Owners 設定。
繞過名單:規則對誰有效
兩套機制處理「規則對誰有效」的方式不同:
- Ruleset:每套規則有一份繞過名單(bypass list),預設是空的;管理員要被明確加進去才可以繞過。名單上的人違反規則時,伺服器顯示
Bypassed rule violations,然後照樣放行。 - 傳統分支保護:倉庫管理員預設可以繞過,除非勾選「Do not allow bypassing the above settings」。
所以「管理員受不受限」取決於用哪一套、怎樣設定。不要假設,到 Settings 裏看清楚。
3互動:正本守門員 Gatekeeper simulator
4CI:讓機器先檢查 Status checks and CI
CI(continuous integration)是每次推送或開 PR 時自動執行的檢查:程式跑測試,文件可以檢查格式、錯字、失效連結、是否還有 TODO。結果顯示在 PR 上(✓ 或 ✗)。GitHub 用 Actions 執行,設定檔放在倉庫裏:
# .github/workflows/check.yml
name: check
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: 不准留下 TODO
run: "! grep -rn 'TODO' --include='*.md' ."
只放這個設定檔,CI 失敗並不會擋住合併,只是顯示 ✗。要真的擋,還要在保護規則裏開「必須通過狀態檢查」,並把這個檢查的名字(這裏是 check)選為必要檢查。在 ruleset 裏,必要檢查也會擋直接推送:沒有在別的分支通過檢查的 commit,不能直接推上 main。
CI 讓審查者專心看內容:格式、測試這些機械的事,交給機器。沙盒(第 8 節)的任務 4 會遇到 CI 擋住合併。
5CODEOWNERS:誰負責哪些檔案 Code owners
# .github/CODEOWNERS
# 預設:課程組任何一位成員
* @course-org/course-team
# 課綱必須由課程主任批准
/syllabus/ @course-director
# 評分:兩人之中任何一位批准即可
/grading/ @course-director @ta-lead
開啟「需要 CODEOWNERS 審查」後,改動了 /syllabus/ 的 PR,必須有課程主任批准才能合併。這讓審查人數的要求變得有意義:不是「任何一個人點了批准」,而是「真正負責那部分的人看過」。讀這個檔案要記住三點:
- 最後一條符合的規則生效:
/syllabus/下的檔案也符合第一行的*,但後面的/syllabus/行蓋過它,所以只有課程主任算數。通用的規則放最前,特殊的放後面。 - 同一行列出幾位負責人時,任何一位批准就夠,不是全部都要批准。
- 團隊寫成
@組織/團隊(只寫@course-team會被當成個人帳號),而且該團隊要對倉庫有寫入權。
守門員(第 3 節)的「開源課程倉庫」情境用到 CODEOWNERS。
6tag 與 release:標記正式版本 Tags and releases
M3 介紹過附註標籤:一個指向 commit、帶說明的固定名字。正式版本(交給學生的教材、提交的報告、發佈的程式)都應該打標籤,並推送到遠端:
git tag -a v1.0 -m "第一版:學期初發佈"
git push origin v1.0 # 標籤不會隨普通 push 送出
gh release create v1.0 --notes "首個正式版本"
GitHub 的 release 建在標籤之上,加上發佈說明和可下載的檔案。沙盒(第 8 節)的任務 6 會實際打標籤、推送、建 release。版本號常用 語意化版本:主版本.次版本.修訂(例如 2.1.3),不相容的改動加主版本,新增內容加次版本,修正錯誤加修訂。
7簽署 commit:證明是你 Signed commits
commit 上的作者名字和電郵只是一段文字,誰都可以填。簽署 commit 用你的私密金鑰(GPG 或 SSH 金鑰)在 commit 裏加上數碼簽名,GitHub 驗證後顯示 Verified。設定方法:
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true # 附註標籤也簽署(可選)
還有一步:把公開金鑰加到 GitHub 的 Settings → SSH and GPG keys,類型選 Signing Key。同一把 SSH 金鑰用來登入(Authentication Key)和用來簽署是兩個登記,要分別加;只加了登入用的,commit 不會顯示 Verified。開了「必須簽署 commit」規則,沒有經驗證簽名的 commit 就推不上去,守門員(第 3 節)可以試這條規則。
M3 說過,commit 的編號由內容計算;簽名也是內容的一部分,所以同樣的改動,簽了和沒簽的 commit 編號不同。本課製作時,用來驗證沙盒編號的參考倉庫就特意關閉了簽署。
8沙盒:設定 protect-main Sandbox: protecting main
9機械閘與團隊紀律 Gates vs discipline
| 保護規則(機械閘) | 團隊紀律 | |
|---|---|---|
| 管得到 | 怎樣改 main:經 PR、誰批准、檢查通過、不准改寫歷史 | 改得好不好:內容、判斷、溝通 |
| 可靠程度 | 對所有人(包括 AI)一樣有效,前提是繞過名單清空 | 依賴每個人當下的自覺 |
| 代價 | 每次改動多幾步 | 出錯時才發現 |
兩者互補:規則保證「一定經過審查」,紀律保證「審查是認真的」。
- 「設了保護規則就安全了。」只要繞過名單上還有人(尤其是與 AI 共用的管理員帳號),規則就有漏洞。
- 「審查人數 0 等於沒有保護。」仍然必須經 PR、留下記錄,強制推送和刪除也照樣被擋(FAQ-002)。
- 「保護規則就是權限設定。」不是。權限決定誰能讀寫倉庫;保護規則決定可以怎樣改某條分支。
- 「CI 通過就代表內容正確。」CI 只檢查你寫成規則的事;論點、判斷仍要靠人審查。
10速查 Cheat sheet
| 做甚麼 | 在哪裏/命令 |
|---|---|
| 設定保護規則 | GitHub:Settings → Rules → Rulesets;GitLab:Settings → Repository → Protected branches |
| 查看現有規則 | gh api repos/<擁有者>/<倉庫>/rulesets |
| 合併被規則擋住的 PR(在繞過名單上時) | gh pr merge 1 --merge --admin |
| 條件滿足後自動合併 | gh pr merge 1 --squash --auto |
| 打標籤並推送 | git tag -a v1.0 -m "…" → git push origin v1.0 |
| 建立 release | gh release create v1.0 --notes "…" |
| 刪除遠端分支 | git push origin --delete 分支 |
11小測 Quiz
本單元重點 Summary
- 保護規則把「main 只能經審查進入」從約定變成伺服器上的機械閘。
- 常用組合:必須經 PR+禁止強制推送+禁止刪除;團隊再加審查人數、CI、CODEOWNERS(審查類是「必須經 PR」的子選項)。
- 繞過名單上的人(包括與 AI 共用的管理員帳號)不受規則約束;重要的倉庫應清空。
- CI 檢查機械的事;CODEOWNERS 讓真正負責的人審查。
- 正式版本打附註標籤,另外推送,再建 release。
延伸閱讀
- GitHub Docs:About rulesets
- GitHub Docs:About code owners
- GitHub Actions 文件
- Pro Git 6.2 Contributing to a Project、6.3 Maintaining a Project、6.4 Managing an organization
- Pro Git 2.6 Tagging、7.4 Signing Your Work
- 語意化版本 2.0.0
- FAQ-002:main 加不加保護規則,有甚麼分別?