快轉到主要內容
  1. 教學文章/

Git tag 與 Release 實戰:版本標記、SemVer 與安全發佈流程

·8 分鐘· loading · loading · ·
Git Tag Release SemVer Versioning GitHub Developer-Tools
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
版本控制: Git - 本文屬於一個選集。
§ 14: 本文

一. 前言:部署成功,不等於版本已經說清楚
#

功能合進 main、CI 全綠、服務也上線了。 過兩週有人問:「Production 現在到底是哪一版?」 如果答案是「大概是上上週五那顆 commit」,發版流程就還少了一塊。 Git tag 的工作,是替某個 Git 物件留下穩定名稱;Release 則把這個版本整理成使用者看得懂、拿得到、能追蹤的交付物。 今天拍拍君會把流程串起來:

  1. 分清楚 lightweight 與 annotated tag
  2. 用 SemVer 決定下一個版本號
  3. 在正確 commit 建立、檢查並推送 tag
  4. 視需求替 tag 加上密碼學簽章
  5. 產生 release notes,交接到 GitHub Release
  6. 處理打錯 tag,而不是偷偷覆寫歷史 這篇不討論怎麼設計 branch;如果你還在整理 main、feature 與 hotfix,可以先看 Git Branch 策略

二. Tag 不是 Branch:先建立正確心智模型
#

Branch 與 tag 都是 Git reference,但用途不同。

  • branch 會隨新 commit 前進
  • tag 通常固定指向某個已確認版本
  • commit ID 能精確定位,但不適合人類記憶
  • Release 以 tag 為基礎,再加上說明與附件 可以把它想成:branch 是持續移動的書籤,tag 是出版後印在版權頁上的版次。 先看看目前有哪些 tag:
git tag --list

依版本語意排序,不要只做字串排序:

git tag --list 'v*' --sort=-version:refname

如果想看 tag 指向哪顆 commit:

git show-ref --tags

只看簡潔版本:

git log --oneline --decorate --tags --simplify-by-decoration

注意:一般 git push 不會自動把所有本機 tag 都送出去。 這個分離其實是保護機制,避免你把實驗用標記一起公開。

三. Lightweight 與 Annotated Tag 怎麼選?
#

最短的建立方式是:

git tag v1.4.0

這是 lightweight tag,本質上只是 refs/tags/v1.4.0 指向某個物件。 它適合:

  • 本機暫時標記
  • 短期測試點
  • 不需要作者、時間與說明的私人 reference 正式版本更適合 annotated tag:
git tag -a v1.4.0 -m 'Release 1.4.0'

Annotated tag 會建立獨立的 tag object,保存 tagger、時間、訊息,以及可選的簽章。 檢查差異:

git cat-file -t v1.4.0
git cat-file -p v1.4.0

Annotated tag 的第一個指令會輸出 tag;lightweight tag 通常會直接解析成 commit。 正式 release 的實用預設很簡單:

對外版本用 annotated tag;一次性私人標記才用 lightweight tag。 Git 官方文件也把 annotated tag 定位為 release 用途,理由不是儀式感,而是它攜帶可稽核的版本資訊。

四. SemVer:版本號是在描述相容性
#

Semantic Versioning 的核心格式是:

MAJOR.MINOR.PATCH

判斷方式:

變更 升哪一段 範例
相容的 bug fix PATCH 1.4.21.4.3
相容的新功能 MINOR 1.4.31.5.0
不相容的公開 API 變更 MAJOR 1.5.02.0.0
重點是「公開 API」。
它可能是 library 的函式介面、CLI 參數、HTTP schema、設定檔格式,甚至使用者依賴的輸出行為。
只看 commit 數量決定升版,通常會得到很有氣勢但沒有資訊量的版本號。

4.1 v 是 tag 命名慣例,不是 SemVer 本體
#

1.4.0 是 SemVer;v1.4.0 是常見的 Git tag 名稱。 團隊可以選擇加不加 v,但必須固定:

推薦:v1.4.0, v1.4.1, v1.5.0
避免:v1.4.0, 1.4.1, release-1.5

一致命名會讓 CI pattern、排序與 changelog 工具簡單很多。

4.2 Pre-release 與 Build Metadata
#

測試版本可以寫成:

1.5.0-alpha.1
1.5.0-beta.2
1.5.0-rc.1

它們的優先序都低於正式的 1.5.0。 Build metadata 用 +

1.5.0+build.20260912

它不參與版本優先序比較。 還有一條很重要:版本一旦發布,內容就不該被修改;修正要用新版本交付。 這條規則跟「不要移動已公開 tag」剛好互相呼應。

五. 發 Tag 前:先證明 HEAD 值得被命名
#

不要把 tag 當成「觸發 CI 的按鈕」。 它首先是一份承諾:這顆 commit 就是指定版本。 先同步遠端狀態:

git fetch origin --prune --tags
git status --short --branch

確認目前 commit:

git log -1 --show-signature
git rev-parse HEAD
git rev-parse origin/main

如果 release 必須來自 main,兩個 rev-parse 結果就應符合團隊政策。 接著跑專案檢查。以下只是示意,請換成自己的命令:

make lint
make test
make build

再確認版本號尚未存在於本機與遠端:

git tag --list 'v1.4.0'
git ls-remote --exit-code --tags origin 'refs/tags/v1.4.0'

第二個指令找不到時會回傳非零狀態,寫 release script 時要明確處理,不要把「不存在」誤判成網路故障。 最後列出上一版到現在的範圍:

git log --oneline v1.3.2..HEAD
git diff --stat v1.3.2..HEAD

這一步很常抓到漏掉的文件、migration 或不該進 release 的 debug commit。

六. 建立、檢查與推送 Annotated Tag
#

確認無誤後建立版本:

git tag -a v1.4.0 -m 'Release 1.4.0'

預設會 tag 當前 HEAD。 如果要明確指定 commit,請把 commit ID 寫出來:

git tag -a v1.4.0 4f21c7a -m 'Release 1.4.0'

建立後不要急著 push,先檢查:

git show --stat v1.4.0
git rev-list -n 1 v1.4.0
git merge-base --is-ancestor v1.4.0 origin/main

最後一行可確認 tag 指向的 commit 是否已在遠端 main 歷史中。 只推這一個 tag:

git push origin tag v1.4.0

或使用等價的完整 ref:

git push origin refs/tags/v1.4.0

拍拍君偏好逐一推送 release tag,因為意圖最清楚。

git push --tags 會推所有 tag;repo 裡若混有本機實驗標記,範圍可能太大。

git push --follow-tags 則只會附帶可從被推分支到達、且遠端缺少的 annotated tags,比 --tags 收斂,但自動化前仍要了解範圍。

七. Signed Tag:驗證「誰替這版背書」
#

Annotated tag 能保存作者資訊,但名字與 email 本身不是密碼學證明。 需要供應鏈驗證時,可以簽署 tag:

git tag -s v1.4.0 -m 'Release 1.4.0'

驗證簽章:

git tag --verify v1.4.0

Git 支援的 signing backend 由 gpg.format 控制,常見選擇包含 OpenPGP 與 SSH。 以 SSH signing 為例,基本設定可能像這樣:

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub

若團隊要求所有 tag 都簽署:

git config --global tag.gpgSign true

但先別在 CI 裡盲目打開。 自動簽署牽涉 key custody、agent、權限與 rotation;保護私鑰比多一個綠色標記重要。 另外,signed tag 與 signed push 不同:

  • signed tag 驗證 tag object
  • signed push 驗證一次 ref 更新請求 大多數 release 流程首先需要的是 signed tag。

八. Release Notes:替使用者整理差異
#

Commit log 是開發紀錄,不是 release notes。 一份實用說明至少包含:

## Highlights
- 新增批次匯入模式
## Fixes
- 修正逾時後重試可能重複送出的問題
## Breaking changes
- `--config` 改為讀取 TOML,舊 JSON 格式不再支援
## Upgrade notes
- 升級前先執行 `pypy-tool migrate --check`
## Full changelog
- v1.3.2...v1.4.0

產生原始素材:

git shortlog --summary --numbered v1.3.2..v1.4.0
git log --first-parent --oneline v1.3.2..v1.4.0

--first-parent 對以 merge commit 合併 PR 的 repo 很實用,可以減少分支內部 commit 的噪音。 Release notes 要回答的是:

  • 使用者得到什麼?
  • 有沒有破壞性變更?
  • 要不要遷移資料或設定?
  • 哪些問題已知但尚未修好?
  • 如何從上一版升級或回復? 如果只是把 87 行 commit subject 原封不動貼上去,那叫 log dump,不叫交接。

九. 從 Git Tag 交接到 GitHub Release
#

Git tag 已經能標記版本;GitHub Release 再補上可閱讀頁面、自動產生的 source archive、release notes 與可下載附件。 先用 draft 準備內容:

gh release create v1.4.0 \
  --draft \
  --title 'v1.4.0' \
  --notes-file RELEASE_NOTES.md \
  --verify-tag

檢查頁面、附件與 notes 後再發布:

gh release edit v1.4.0 --draft=false

若要附上 build artifact:

gh release upload v1.4.0 dist/*

附件要先由 CI 建置、驗證 checksum,最好也保留 provenance;不要在不同電腦手工編出幾份名字相同、內容不同的檔案。 若 repo 啟用 immutable releases,先建 draft、備齊附件再發布尤其重要,因為發布後對 tag 與 assets 的修改會受限制。 GitHub Release 不是 Git tag 的替代品;它是建立在 tag 上的發佈介面。

十. 打錯 Tag 怎麼辦?先看有沒有公開
#

10.1 尚未 Push:本機修正
#

如果 tag 還沒離開你的電腦,處理很單純:

git tag -d v1.4.0
git tag -a v1.4.0 <correct-commit> -m 'Release 1.4.0'

再重新檢查內容。

10.2 已 Push、尚未被使用:協調後刪除
#

先確認團隊與自動化沒有消費它,再刪遠端:

git push origin --delete v1.4.0
git tag -d v1.4.0

建立正確 tag 後重新推送,並在團隊頻道清楚說明舊 tag 曾被替換。

10.3 已經發布:不要移動,發新版本
#

如果 package、binary、container 或使用者已經拿到這一版,最安全的方式通常是:

v1.4.0 有問題 → 修正 commit → 發 v1.4.1

不要讓同一個版本名稱今天指向 A、明天指向 B。 遠端 tag 更新預設會被拒絕;雖然可以 force,但「做得到」不代表「應該做」。 已發布版本的可重現性,比把版號修得看起來漂亮更重要。

十一. 一份可執行的 Release Checklist
#

把流程寫進 repository,不要只放在某位同事腦中。

[ ] main 已同步,工作目錄乾淨
[ ] lint、test、build 全部通過
[ ] 版本號依公開 API 變更決定
[ ] changelog、文件、migration 已更新
[ ] 新 tag 在本機與遠端都不存在
[ ] 候選 commit 已在受保護分支
[ ] 建立 annotated 或 signed tag
[ ] 本機驗證 tag 內容與簽章
[ ] 明確推送單一 tag
[ ] CI 從該 tag 建置一次
[ ] Release notes 與附件已檢查
[ ] 發布後做乾淨環境 smoke test
[ ] 保留 rollback 與事故處理說明

如果 tag push 會直接觸發 production,還可以加入 environment approval,讓「版本已命名」與「允許部署」成為兩個可稽核步驟。

十二. 常見錯誤
#

12.1 在錯的 Branch 打 Tag
#

Tag 最終指向的是 commit,不是 branch 名稱。 發版前用 git rev-parsegit showmerge-base --is-ancestor 檢查,不要只看終端機 prompt。

12.2 正式版只用 Lightweight Tag
#

它不是壞掉,但少了 tagger、時間、說明與簽章空間。 對外 release 用 annotated tag 會留下更完整的稽核資訊。

12.3 每次都 git push --tags
#

這可能把所有本機 tag 一起送出。 正式流程優先推明確名稱;要自動跟隨 annotated tags 時,再有意識地使用 --follow-tags

12.4 把 Tag 當備份
#

Tag 是 reference,不是異地備份。 Repository、CI artifact、registry 與簽章金鑰仍然需要各自的保存策略。

12.5 先發布,再想 Release Notes
#

等使用者已經升級才補 breaking changes,驚喜就會變成事故。 先建立 draft release,完成交接資訊,再按下公開。

十三. 最小安全流程
#

如果團隊今天只想先做一個能落地的版本,可以從這組開始:

git fetch origin --prune --tags
git status --short --branch
make test
git log --oneline v1.3.2..HEAD
git tag -a v1.4.0 -m 'Release 1.4.0'
git show --stat v1.4.0
git push origin tag v1.4.0
gh release create v1.4.0 \
  --title 'v1.4.0' \
  --notes-file RELEASE_NOTES.md \
  --verify-tag

等這條路徑穩定後,再逐步加入 signed tag、artifact checksum、provenance、approval 與 immutable release。 流程越成熟,發版應該越無聊。 無聊代表每一步都可預期,而不是半夜收到「這個 v1.4.0 到底是哪一顆?」

結語
#

Git tag 不是替 commit 換一個好看的名字,而是 release lifecycle 的固定錨點。 拍拍君建議記住四件事:

  1. 正式版本用 annotated tag,需要驗證來源時再簽署
  2. SemVer 要描述公開 API 的相容性,不是描述今天心情
  3. 發 tag 前檢查 commit,推送時明確指定單一 tag
  4. 已發布版本不要移動;有錯就修正並發新版本 當 tag、CI artifact、release notes 與部署紀錄都指向同一顆 commit,回溯問題與交接版本就不再靠猜。

延伸閱讀
#

版本控制: Git - 本文屬於一個選集。
§ 14: 本文

相關文章

Git rerere 實戰:記住衝突解法,讓 Rebase 與 Merge 不再重做
·8 分鐘· loading · loading
Git Rerere Merge-Conflict Rebase Merge Version-Control Developer-Tools
GitHub Pull Request 實戰:Draft、Review、Checks 與安全合併
·9 分鐘· loading · loading
GitHub Pull Request Code Review Branch Protection Ci Git
Git bisect 實戰:快速定位壞 commit 與除錯流程完全攻略
·9 分鐘· loading · loading
Git Bisect Debugging Regression Version-Control
Git reflog 救援實戰:找回 reset、rebase 後消失的 Commit
·9 分鐘· loading · loading
Git Reflog Recovery Reset Rebase Commit Version-Control
Git worktree 實戰:同時開多個分支、平行測試與安全清理完全攻略
·11 分鐘· loading · loading
Git Worktree Branch Workflow Version-Control
Git cherry-pick 實戰:精準搬運 commit、修補 hotfix 與分支同步
·11 分鐘· loading · loading
Git Cherry-Pick Hotfix Commit Version-Control