commit 訊息就是更新日誌。 tool/release_notes.sh 直接讀這些訊息產生 GitHub
release 的內容,所以一則寫壞的 commit 會在使用者讀得到的地方留下一個洞——而
commit 訊息推出去之後改不了,唯一的修法是 rebase。
tool/check_commits.sh 是 CI gate,不合格直接失敗。
<type>(<scope>): <英文摘要>
<Category>(<locale>): <更新日誌條目>
<Category>(<locale>): <更新日誌條目>
摘要行給 git log 讀,條目行給使用者讀。單一平台的變更多一行 trailer(見
「平台」)。
沒有散文說明。 為什麼這樣做、試過什麼、踩到什麼坑,全部寫在程式碼註解裡 ——那是下一個改這段程式的人會看到的地方,而更新日誌的讀者一項都用不到。
feat(mesh): show the radio's own packet counters
└┬─┘ └─┬┘ └──────────────┬──────────────────┘
type scope 摘要
| type | 用在 | 進更新日誌? |
|---|---|---|
feat |
使用者拿到新東西 | 🌟 新功能 |
fix |
修正使用者遇得到的錯誤行為 | 🐞 錯誤修正 |
perf |
一樣的行為,更少的資源 | 🔌 最佳化 |
refactor |
一樣的行為,更好的結構 | 🔌 最佳化 |
docs |
只有文件 | ✗ |
test |
只有測試 | ✗ |
build |
建置、發布、相依套件 | ✗ |
ci |
workflow、gate | ✗ |
style |
只有排版,沒有語意 | ✗ |
chore |
其他雜務 | ✗ |
revert |
回退某個 commit | ✗ |
選 type 的判準是「使用者看不看得到」,不是「改了哪個資料夾」。
- 把一個使用者可見的行為改動藏在
build:底下,它就不會出現在任何更新日誌裡 ——這正是拆掉舊build: cut releases from tags…那則的原因,Play track 改成 production 是使用者可見的 - 加一個 142 行的腳本不是
docs:,就算它旁邊有文件
選填,小寫,對應功能區:mesh map weather eew notify location
home changelog settings。
跨多個區的改動就不要寫 scope——硬填一個會讓更新日誌把它歸錯地方。
- 英文,純 ASCII。 中文在下面的區塊,不在這裡
- 祈使句:
add、fix、stop、show。不是added、fixes、adding - 不是名詞片語:
commit message rules✗ →define a bilingual commit format✓ - 最多 72 字元,結尾不加句號
- 說明做了什麼給使用者,不是碰了哪個檔案
這是這份格式存在的理由。 說明區塊裡的每一行 Category(locale): 文字 都是
更新日誌的一個條目,tool/release_notes.sh 用一條正則表達式把它們抓出來。
feat(map): overlay radar echo on the map
New(zh-Hant): 地圖可以疊加雷達回波
New(en-US): the map can overlay radar echo
New(zh-Hant): 地圖可以疊加雷達回波
└┬┘ └──┬──┘ └────────┬────────┘
分類 語系 條目文字
| 分類 | 進更新日誌的哪一區 |
|---|---|
New |
🌟 新功能 |
Optimization |
🔌 最佳化 |
Fix |
🐞 錯誤修正 |
分類是宣告的,不是從 <type> 推的。 所以一個 chore: 的 commit 如果真的
修好了使用者看得到的東西,寫一行 Fix(...) 它就會出現——舊做法會讓它無聲消失。
| 必填 | zh-Hant、en-US |
| 選填 | zh-Hans zh-Hant-HK ja-JP ko-KR th-TH vi-VN id-ID fil-PH |
必填的兩個是 app 自己的語言,和所有其他人的退路。選填的有人寫就有,沒有就退回 英文——app 內會依讀者的語系挑,挑不到才退。
gate 會檢查各語言的條目數量對得起來。 中文寫了兩則、英文只寫一則,那些讀者 拿到的就是一份少一條的清單,而且不會有任何徵兆。
一行講完一件使用者感覺得到的事。
✗ New(zh-Hant): 重構 changelog repository 並加入分頁
✓ New(zh-Hant): 更新日誌改成捲到底再載入下一頁,開啟快很多
- 寫結果,不是實作。
ETag、domain 契約、分層 gate都是 diff 的事 - 不用寫「為什麼」——那是給審查者的,寫在程式碼註解裡
- 一則 commit 可以有多行,也可以跨分類;但超過三行就回去看 一個 commit 一件事
docs test build ci style chore refactor 通常不寫任何一行,就不會
出現在更新日誌裡。feat / fix / perf 至少要有一行,gate 會擋。
只影響單一平台的變更加一行 trailer,放在說明之前:
fix(notify): stop the crash when Android 14 starts the service
Platform: android
Android 14 requires a foregroundServiceType on every start…
- 只接受
android或ios(gate 會擋拼錯的——拼錯只會讓圖示無聲消失) - 兩個平台都影響就不要加
- 更新日誌會在該項前面放對應的平台圖示
這條 gate 驗不了——沒有辦法判斷兩個改動算不算「同一件事」。把它做成猜測式 的檢查會擋掉正當的跨模組修改,然後大家開始想辦法繞過它,那比沒有檢查更糟。 它靠 review,所以寫在這裡。
- 更新日誌一則 commit 一個項目。兩件事塞在一起,使用者會看到一個「A 和 B」 的條目,而它在三個分類裡只能選一個
- revert 一件事不會連帶 revert 另一件
git bisect才指得出是哪一個改動
看到這些就停下來想一下:
| 徵兆 | 例子 |
|---|---|
| 摘要裡有 and | show one language and draw the tags locally |
| 摘要在列舉 | cut releases from tags, notes from commits, symbols from both |
| 說明分成互不相關的兩段 | 第一段講語言、第二段講圖示 |
| 一個檔案是文件、另一個是程式 | AGENTS.md + tool/check_commits.sh |
| 需要兩個 type 才講得清楚 | 一半是 fix 一半是 feat |
這五個都不是硬性錯誤——fix: stop A and B from racing 是合法的,兩者是同一個
競態。但它們值得停一下。
git reset --mixed HEAD~1 # 保留變更,取消 commit
git add <第一組檔案>
git commit
git add <第二組檔案>
git commit同一個檔案裡有兩件事的話,先寫出中間狀態、commit、再寫回最終狀態、再 commit。
Co-Authored-By:任何形式- 任何工具署名:
Generated with、🤖、agent 名稱、模型名稱
commit 的作者是人。工具把自己寫進紀錄,等於讓歷史對「誰為這個改動負責」說謊 ——而那份紀錄正是幾年後有人要問「當初為什麼這樣做」時會去讀的東西。
fix(notify): stop the crash when Android 14 starts the service
Platform: android
Fix(zh-Hant): 修正 Android 14 上升級後第一則地震速報會讓 app 閃退
Fix(en-US): fix the app crashing on the first earthquake alert after an
upgrade on Android 14
perf(changelog): fetch one page at a time
Optimization(zh-Hant): 更新日誌改成捲到底再載入下一頁,開啟快很多
Optimization(en-US): the changelog loads a page at a time and opens faster
Fix(zh-Hant): 修正某一頁載入失敗會清空整個清單
Fix(en-US): a failed page no longer clears the list already on screen
有人寫了就有,沒寫就退回英文。
fix(changelog): show one language instead of both
Fix(zh-Hant): 更新日誌不再中英文一起顯示
Fix(en-US): the changelog no longer shows both languages at once
Fix(ja-JP): 更新履歴が日本語と英語を同時に表示しなくなりました
chore(deps): bump the pubspec lockfile
ci: cache the Swift package resolution
✗ New(zh-Hant): 重構 changelog repository,改用 cursor 分頁,並把 pageSize
移到 domain 契約(分層 gate 說 presentation 不能伸進 data)
實作細節、檔名、分層規則——全部在 diff 裡,而更新日誌的讀者一項都用不到。
三個區塊由條目的 Category 決定,不是由 commit 的 <type> 推導——所以訊息
裡寫的分類和日誌上的分類不可能對不起來。
繁體中文直接展開;其他每個語言各自摺在 <details> 裡,並用
<!-- dpip-lang:xx --> 標記包起來。app 內只會顯示讀者語系的那一份:找不到完全
相符就找同語言、再找不到就退回英文(<details> 是 HTML,app 的 Markdown 渲染器
不支援,不處理的話十種語言會全部攤在同一頁)。
每一項後面標上提交者,CI 解析成 GitHub @帳號——是 GitHub 帳號,不是 git 的
顯示名稱,顯示名稱 @ 不到任何人。
| 涵蓋範圍 | 為什麼 | |
|---|---|---|
快照 26w33a |
上一個 tag 到現在 | 讀的人已經有前一個了,只需要增量 |
正式版 26.1 |
上一個正式版到現在 | 從 26.1 升到 26.2 的人一個快照都沒看過,只給增量等於只告訴他一小部分 |
每一則條目都會標上平台圖示——只影響一個平台就標一個,兩個都影響就兩個都標。 只標單平台的話,其餘每一行都變成「兩個平台都有」和「沒有人說」分不出來,而那是 兩件不同的事。
正式版會累積期間所有快照的內容,每一則行尾標上它第一次出現在哪個測試版:
- ⬢ ⬡ 更新日誌改成捲到底再載入下一頁 — @whes1015 · `26w33b`
- ⬡ 修正 iOS 上拖曳雷達時間軸會跟不上手指 — @whes1015
跑測試版的人用它對照自己已經有什麼;沒有標記的就是這一版才第一次出現,誰都 還沒看過。快照本身的條目全部來自它自己,所以不標。
署名是 GitHub 帳號,CI 透過 API 解析。不是 git 的顯示名稱——顯示名稱 @ 不到 任何人,而且用名字去搜會搜出不只一個帳號,猜錯比不標更糟。
實際長相:pre-release-example.md 與
release-example.md,兩份都是 tool/release_notes.sh 真的
產出來的(範例的署名用 DPIP_NOTE_AUTHOR 指定,因為臨時 repo 的 commit 不存在
於 GitHub,API 查不到)。
squash 會壓縮條目數。 正則是逐行抓的,所以 squash 不會像舊格式那樣把內容 弄壞——但四則 commit 的條目會全部掛在同一個作者和同一個快照下。要保留就用 rebase-merge。
CI 會印出哪一則、哪裡不對。因為訊息無法事後修改:
git rebase -i <base> # 把有問題的標成 reword
git push --force-with-lease用 --force-with-lease 而不是 --force:期間有人推過東西它會拒絕,而那正是
--force 會無聲摧毀別人工作的情況。
推之前先在本機驗:
tool/check_commits.sh origin/main..HEAD