結業專題
1兩條路線 Two tracks
| 軟件線 | 知識線 | |
|---|---|---|
| 做甚麼 | 三人小組協作開發一個小工具(例如命令列記帳、課表產生器) | 為一門課或一個主題建立版本化知識庫 |
| 協作方式 | 每人至少兩個 PR,並審查別人至少兩個 PR | 自己的改動全部走 PR;另接受至少一次外部貢獻(別人 fork 後提交的 PR) |
| 審查要求 | 每個 PR 至少一位組員批准才能合併 | 每個 PR 至少一位審查者批准才能合併。審查者可以是同學、導師或同事,要加為倉庫的協作者(只有具寫入權的人,批准才算數) |
| 正本控制 | main 受保護:必須 PR、至少一人批准、CI 通過、禁止強制推送和刪除 | main 受保護:必須 PR、至少一人批准、CI 檢查連結、禁止強制推送和刪除 |
| 最低規模 | 合併的 PR ≥ 6(每人 ≥ 2);每人 ≥ 8 個 commit(用 squash 合併時,以各人 PR 裏的 commit 計);issue ≥ 6 | 筆記 ≥ 15 篇;合併的 PR ≥ 6,其中 ≥ 1 個來自外部貢獻 |
| 重點單元 | M4–M11、M14 | M6、M9–M12、M14 |
知識線找不到審查者、只能一個人做?見第 5 節「單人變通版」。軟件線必須是三人小組。
2建議節奏 Suggested pacing
以下是三週的建議分配,小組和自學者都適用。不設硬性日期,按自己的進度調整;重點是先把閘門裝好,再開始做內容。
| 階段 | 完成甚麼 | 完成的標誌 |
|---|---|---|
| 第 1 週 搭好正本 | 建倉庫、README、.gitignore;設定 main 的保護規則和 CI;拆 issue、分工;加 PR 範本 | 有人試著直推 main,被拒絕;第一個 PR 經審查合併 |
| 第 2 週 協作 | 大部分 PR;製造並解決一次衝突;rebase -i;worktree;(知識線)兩個 clone 的分歧 | 每人都有已合併的 PR 和寫過的審查;過程記錄開始累積 |
| 第 3 週 收尾與發佈 | bisect/log -S、hook、救回錯誤;寫完過程記錄和反思;打 v1.0 並建立 release | 檢核清單全部完成;第 9 節的提交內容齊全 |
3軟件線檢核清單 Software track
每一項下面的灰字是「證據放在哪裏」。評核者看不到你的勾選,只看倉庫裏的證據;做完一項,就把證據寫進 README 的「過程記錄」(範本見第 7 節)。
4知識線檢核清單 Knowledge track
同樣,灰字是證據的位置。
.git:一條分支同一時間只能在一個 worktree 簽出(第二個會被拒絕:'main' is already used by worktree),而且一邊 commit,另一邊立即看得到,不需要也無從 pull。要練習兩地分歧,得用兩個獨立的 clone(兩部電腦,或同一部電腦上的兩個資料夾),經遠端交換(M9)。
5單人變通版 Solo variant
知識線的自學者如果真的找不到任何審查者,可以用這個變通版:自己分飾「作者」和「協作者」兩個角色。先搞清楚 GitHub 容許甚麼:
- 作者不能批准自己的 PR。在自己的 PR 上,Approve 和 Request changes 都不能用,只能提交 Comment 類的審查。
- 不要為此開第二個 GitHub 帳號。GitHub 服務條款規定每人最多一個免費個人帳號;另外可以有一個機器帳號(machine account),但只限用來跑自動化,不能拿來手動批准自己的 PR。
- 不能 fork 自己的倉庫到同一個帳號。所以「外部貢獻」要改用第二個 clone 開分支模擬。
因此單人版的分工是:git 身份(commit 上的作者名)可以有兩個,GitHub 帳號只有一個。做法:
# 1. 主 clone:你自己(作者身份)
git clone https://github.com/你的帳號/notes.git notes
# 2. 第二個 clone:扮演協作者,只在這個倉庫設定另一個 git 身份
git clone https://github.com/你的帳號/notes.git notes-collab
cd notes-collab
git config user.name "協作者(單人模擬)"
git config user.email "collab@example.com" # 任何不屬於你帳號的電郵都可以
# 3. 協作者在自己的分支改動,推上去,開 PR
git switch -c collab/補充第三章
# …改動、commit…
git push -u origin collab/補充第三章
gh pr create --fill
# 4. 回到作者身份審查這個 PR:只能留 Comment 類審查
gh pr review 7 --comment --body "第 12 行的出處請補上頁碼"
單人版的檢核清單照第 4 節做,以下幾項改變:
| 原要求 | 單人版改為 |
|---|---|
| 保護規則:至少一人批准 | 必要審查人數設為 0(否則你永遠不能合併),其餘規則不變:必須 PR、CI 必過、禁止強制推送和刪除、繞過名單清空 |
| 每個 PR 經審查者批准 | 每個 PR 合併前都有一則 Comment 類審查,寫出具體意見;至少一次因審查而再修改後才合併 |
| 外部貢獻(fork 後提交 PR) | 由第二個 clone 的協作者身份開 PR,至少 2 個;其中一個要被你要求修改 |
| 兩部電腦的分歧 | 不變,就用主 clone 和協作者 clone |
評核時,單人版的「PR 與審查」一項看 Comment 審查的質素;README 要寫明採用單人變通版。單人版沒有真正的他人審查,總等級最高為 B。
6證據:評核者看甚麼 Evidence
評核者只看倉庫,不看你的電腦。有些事倉庫本身就記錄了(commit、PR、審查、標籤);有些只發生在你的終端機裏(bisect、worktree、log -S),必須把原始輸出貼進 README 的「過程記錄」一節,或放進 evidence/ 資料夾再從 README 連過去。
| 項目 | 倉庫已有的記錄 | 要你補上的證據 |
|---|---|---|
| 保護規則 | 只有部分(見下) | 規則設定的輸出或截圖 |
| CI | PR 頁的檢查結果;.github/workflows/ | 最好附一個被 CI 擋下、修正後才通過的 PR 編號 |
| PR 與審查 | PR 列表、審查意見 | — |
| 合併衝突 | PR 時間線 | PR 編號,一句話說明衝突在哪裏、怎樣解決 |
| rebase -i | PR 時間線上的「force-pushed」記錄 | PR 編號,整理前後的 git log --oneline |
| worktree | 無 | git worktree list 的輸出 |
| bisect | 無 | git bisect log 的輸出,以及找到的 commit |
| log -S | 無 | 完整命令和輸出 |
| hook | 如果把腳本放進倉庫(hooks/)就有 | 被 hook 擋下時的訊息 |
| 救回錯誤 | revert 會留下 commit;restore 不會 | 反思一節寫清楚經過和命令 |
保護規則的設定,有讀取權的人看不全,所以要你提供:
- 用 ruleset(建議):任何能讀倉庫的人都可以用
gh api repos/帳號/倉庫/rules/branches/main看到 main 上生效的規則,但看不到繞過名單;繞過名單只回傳給有寫入權的人。所以請你自己執行下面兩句,把第二句的輸出存成evidence/protection.json:gh api repos/帳號/倉庫/rulesets --jq '.[] | "\(.id) \(.name) \(.enforcement)"' gh api repos/帳號/倉庫/rulesets/編號 > evidence/protection.json # 含 bypass_actors - 用舊式的分支保護(classic branch protection):只有管理員讀得到。執行
gh api repos/帳號/倉庫/branches/main/protection > evidence/protection.json,或者在 Settings 頁截圖。
軟件線另外看個人貢獻。評核者會執行這幾句,你也可以先自己跑一次,確認每人都有份:
git shortlog -sn main # 每人在 main 上的 commit 數(squash 合併時改看下一句)
gh pr list --state merged --limit 100 --json number,author,reviews \
--jq '.[] | "#\(.number)\t\(.author.login)\t審查:\([.reviews[].author.login] | unique | join(", "))"'
第二句逐個 PR 列出作者和審查者。每位組員都要有自己開的 PR,也要在別人的 PR 上出現為審查者。README 列出組員與 GitHub 帳號的對照(只寫帳號名,不寫學號、電話等個人資料)。
7範本 Templates
以下三份可以直接複製進倉庫再修改。
PR 範本:.github/pull_request_template.md
放在這個路徑,開 PR 時 GitHub 會自動填入說明欄。
## 改了甚麼
(一兩句。關聯的 issue:Closes #)
## 為甚麼
## 怎樣驗證
- [ ] 本機跑過測試/預覽過
- [ ] CI 通過
## AI 使用
(沒有使用,或寫明哪些部分由哪個工具產生;commit 已加 Co-authored-by trailer)
README 的「過程記錄」與「反思」
## 組員(軟件線)/ 協作方式(知識線)
- 組員 A:@帳號 組員 B:@帳號 組員 C:@帳號
-(單人變通版:寫明採用單人變通版,協作者身份的 git 名稱是 ____)
## 過程記錄
### 保護規則
設定方式:ruleset/classic;輸出見 evidence/protection.json
### CI
被 CI 擋下的 PR:#__,原因:____
### 合併衝突
PR #__:衝突在 ____,在分支上用 merge/rebase 解決,選擇的版本是 ____
### rebase -i(軟件線)
PR #__:整理前
(貼 git log --oneline)
整理後
(貼 git log --oneline)
### worktree(軟件線)/兩個 clone 的分歧(知識線)
(貼 git worktree list 輸出)/(貼 push 被拒的訊息,和解決分歧的 commit)
### bisect(軟件線)/log -S(知識線)
(貼 git bisect log 輸出,或 log -S 的命令與輸出)
找到的 commit:____(來自 PR #__)
### hook(軟件線)
腳本:hooks/pre-commit;啟用:git config core.hooksPath hooks
被擋下時的訊息:(貼上)
## 反思
- 一次出錯:發生了甚麼,怎樣發現
- 怎樣救回來:用了哪個命令(reflog/revert/restore/bisect…),為甚麼選它
- 下次怎樣避免
- AI 使用:哪些改動由 AI 產生,你怎樣檢查它(或寫「沒有使用 AI」)
知識線的連結檢查 CI:.github/workflows/links.yml
檢查兩種內部連結:Obsidian 的 [[筆記名]],以及 Markdown 的 [文字](相對路徑.md)。找到斷鏈就列出並失敗。設定保護規則時,把 links 這個檢查設為「必須通過」。
name: links
on:
pull_request:
push:
branches: [main]
jobs:
links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 檢查內部連結
shell: python
run: |
import pathlib, re, sys
from urllib.parse import unquote
notes = {p.stem for p in pathlib.Path('.').rglob('*.md')}
bad = []
for p in pathlib.Path('.').rglob('*.md'):
text = p.read_text(encoding='utf-8')
for name in re.findall(r'\[\[([^\]|#]+)', text):
if name.strip().split('/')[-1] not in notes:
bad.append(f'{p}: [[{name}]]')
for target in re.findall(r'\]\(([^)#\s]+\.md)\)', text):
if not (p.parent / unquote(target)).exists():
bad.append(f'{p}: ({target})')
print('\n'.join(bad) or '沒有斷鏈')
sys.exit(1 if bad else 0)
它只認筆記檔名(不分資料夾),也不檢查外部網址和圖片;夠用就好,需要時再擴充。軟件線的 CI 則跑你自己的測試,格式參考 M11 CI 一節。
8使用 AI 的規定 Using AI
可以讓 AI agent 參與,但照 M13 的做法(一個 agent 一張書桌、追溯):
- 每個 agent 一個 worktree、一條分支,經 PR 合併。
- 含 AI 產生內容的 commit,在說明最後一段加 trailer,一行一個工具:
Co-authored-by: 工具名 <電郵>。例如:
評核者會用補充第三章的例子 Co-authored-by: Claude <noreply@anthropic.com>git log -i --grep "^Co-authored-by:"或git log --format='%h %(trailers:key=Co-authored-by,valueonly)'列出這些 commit。PR 範本的「AI 使用」一欄也要填。 - 審查者必須是人。AI 留下的審查意見可以參考,但不算作審查。
沒有照做的後果:漏標,「過程記錄與反思」一項最高只能得 D;評核者可以要求你解釋任何一個 commit 的內容,解釋不了自己提交的改動,整份專題不合格。
9提交 Submission
專題經課程平台提交,不另交報告。提交時準備好以下內容:
- 倉庫網址。公開倉庫最簡單;私有倉庫要把評核者的 GitHub 帳號加為協作者(帳號在提交入口公布)。倉庫裏不可以有密鑰。
- release 頁網址,例如
…/releases/tag/v1.0。 - README:組員對照、「過程記錄」和「反思」兩節(範本見第 7 節)。
evidence/protection.json或保護規則的截圖(見第 6 節)。- 路線:軟件線、知識線,或知識線單人變通版。
10評分 Grading rubric
採用等級制。六個準則各評 A、B、C、D 或不合格,按權重合成總等級。
- A、B、C、D 都是合格;D 是合格線,表示剛好達到最低要求。
- 任何一個準則低於 D,或出現下面任何一種不合格情況,總評就是「不合格(需重交)」,不論其他準則多好。
- 總等級:把 A=4、B=3、C=2、D=1 按權重平均;≥ 3.5 為 A,≥ 2.5 為 B,≥ 1.5 為 C,其餘為 D。單人變通版的總等級最高為 B(見第 5 節)。
| 準則(權重) | A | B | C | D |
|---|---|---|---|---|
| 正本控制 15% |
規則齊全、繞過名單是空的;CI 真的擋下過一次問題;release 說明清楚 | 規則和 CI 齊全,證據完整;release 有說明 | 規則齊全,但 CI 形同虛設(例如永遠通過)或證據要追問才清楚 | 規則缺一項(例如沒有禁止刪除),但 main 沒有被直推 |
| PR 與審查 25% |
每個 PR 說明目的和驗證方法;審查意見具體(指出位置、提出做法),作者回應並修改;至少一次「要求修改」後才批准 | 大部分 PR 有說明和有內容的審查意見 | 有審查,但多數只是「LGTM」式批准;說明簡短 | 剛好達到最低數量,審查幾乎沒有內容 |
| 歷史品質 15% |
每個 commit 一件事,說明具體;分支命名一致;rebase -i 前後對比清楚 | 大致一件事一個 commit,偶有混雜 | 不少說明籠統(「update」「fix」) | 歷史難讀,但 main 沒有被改寫 |
| 工具運用 20% |
清單上的工具項目全部完成,證據是原始輸出,並說明每一步為何這樣做 | 全部完成,證據齊全 | 缺一項,或有一項證據不完整 | 缺兩項 |
| 倉庫衛生 10% |
.gitignore 合適;沒有大型二進位檔;陌生人看 README 就能上手 |
同上,README 略簡 | 有少量不應入庫的檔案 | 曾放入大型檔案,已移除並在反思裏說明 |
| 過程記錄與反思 15% |
過程記錄逐項可核實;反思具體到命令和原因,並提出改進 | 記錄齊全,反思具體 | 記錄齊全,反思泛泛 | 記錄有缺漏,評核者要自行翻查;或 AI 使用漏標 |
「工具運用」的項目:軟件線是合併衝突、rebase -i、worktree、bisect、hook;知識線是大型檔案處理、兩個 clone 的分歧、log -S、救回刪錯的筆記。缺三項或以上,該準則不合格。
不合格情況(需重交)
- main 沒有受保護,或 main 上有未經 PR 的改動(第一個 commit 除外)。
- 沒有任何經審查的 PR(單人變通版:沒有 Comment 審查)。
- 倉庫歷史裏有密鑰或密碼。即使之後刪除,它仍在歷史裏;要先撤銷該密鑰,再清理歷史後重交。
- 沒有
v1.0(或知識線的階段)標籤和 release。 - README 沒有「過程記錄」,評核者無法核實清單項目。
- 未達最低規模(第 1 節)。
- 無法解釋自己提交的改動(見第 8 節)。
個人貢獻(軟件線)
組員的個人等級原則上等於小組等級,但:
- 任何一位組員沒有自己的 commit、沒有自己開的 PR,或沒有審查過別人的 PR,該組員個人不合格,需要補做;不影響其他組員。
- 貢獻明顯少於組員(例如 commit 和 PR 數只有其他人的一小部分),評核者可以把該組員下調一級,並寫明理由。
證據就是第 6 節的 git shortlog -sn 和 PR 列表。分工時就讓每人負責自己的 issue,不要由一個人代為提交。
完成之後 What next
- 回到術語表,看看還有哪個詞說不出它的意思。
- 到 Learn Git Branching 做進階關卡,鞏固 rebase 和遠端操作。
- 把這門課沒講的留待需要時再學:submodule、sparse checkout、Git LFS 的伺服器設定、Git 內部的 packfile。