แท็กใน Git: วิธีใช้ git tag และกำหนดเวอร์ชันของรีลีส
git tag ติดป้ายกำกับถาวรให้คอมมิตหนึ่งตัว มักใช้ระบุรีลีส เช่น v1.2.0 แท็กแบบ annotated สร้างด้วย git tag -a v1.2.0 -m "ข้อความ" และต้องส่งขึ้นเซิร์ฟเวอร์แยกต่างหาก ด้วย git push origin v1.2.0 หรือ git push --tags
git tag คือป้ายชื่อที่ติดไว้กับคอมมิตใดคอมมิตหนึ่ง ต่างจากบรานช์ตรงที่แท็กจะไม่เคลื่อนที่เมื่อมีคอมมิตใหม่ v1.2.0 ผ่านไปหนึ่งปียังชี้ไปยังสแนปชอตโค้ดชุดเดิม จึงนิยมใช้แท็กกับรีลีส และระบบ CI/CD มักเริ่มการบิลด์และการเผยแพร่เมื่อมีแท็กใหม่ปรากฏ
แท็กแบบเบาและแบบ annotated
Git มีแท็กสองแบบ และความแตกต่างมีความสำคัญ
| แบบเบา (lightweight) | แบบ annotated | |
|---|---|---|
| การสร้าง | git tag v1.0.0 | git tag -a v1.0.0 -m "..." |
| ลักษณะ | เป็นเพียงตัวชี้ไปยังคอมมิต | เป็นอ็อบเจกต์แยกในฐานข้อมูล Git |
| ผู้เขียน วันที่ ข้อความ | ไม่มี | มี |
| ลายเซ็น GPG/SSH | ไม่มี | ทำได้ (-s) |
git describe นับเป็นค่าเริ่มต้น | ไม่ | ใช่ |
| ควรใช้เมื่อ | ป้ายชั่วคราวส่วนตัว | รีลีสและทุกอย่างที่เผยแพร่ |
สำหรับรีลีสควรใช้แท็กแบบ annotated เพราะบันทึกว่าใครปล่อยเวอร์ชันนี้และเมื่อใด และยังลงลายเซ็นได้
วิธีสร้างแท็ก
บนคอมมิตปัจจุบัน
git tag -a v1.2.0 -m "รีลีส 1.2.0: ส่งออกรายงาน แก้ไข timeout ของ API"
หากไม่ใส่ -m Git จะเปิดตัวแก้ไขให้เขียนข้อความ ซึ่งสะดวกเมื่ออยากใส่รายการการเปลี่ยนแปลง
บนคอมมิตเก่า
ลืมติดแท็กตอนปล่อยรีลีสก็ไม่เป็นไร สามารถติดแท็กบนคอมมิตใดก็ได้
git log --oneline -5
# 7be41d0 feat: ส่งออกรายงาน
# 1c02e8f fix: timeout ของคำขอ
git tag -a v1.1.0 1c02e8f -m "รีลีส 1.1.0"
แท็กที่ลงลายเซ็น
git tag -s v1.2.0 -m "รีลีส 1.2.0"
git tag -v v1.2.0 # ตรวจสอบลายเซ็น
ต้องตั้งค่าคีย์ GPG ไว้ก่อน (หรือ SSH เมื่อ gpg.format=ssh) GitHub และ GitLab แสดงเครื่องหมายว่าตรวจสอบแล้วบนแท็กแบบนี้
วิธีดูแท็ก
git tag # แท็กทั้งหมดเรียงตามตัวอักษร
git tag -l "v1.*" # กรองด้วยรูปแบบ
git tag -n # พร้อมบรรทัดแรกของข้อความ
git tag --sort=-v:refname # เรียงตามเวอร์ชัน ใหม่สุดอยู่บน
git show v1.2.0 # ข้อมูลของแท็กและคอมมิต
git describe --tags # แท็กที่ใกล้ที่สุด: v1.2.0-3-g7be41d0
ให้สังเกตการเรียงลำดับ ค่าเริ่มต้นจะทำให้ v1.10.0 มาก่อน v1.9.0 เพราะเปรียบเทียบสตริงทีละตัวอักษร คีย์ --sort=v:refname จะเรียงเป็นเวอร์ชันจริง
ผลลัพธ์ของ git describe อ่านได้ว่า มีสามคอมมิตหลังแท็ก v1.2.0 และคอมมิตปัจจุบันคือ 7be41d0 (อักษร g หมายถึง git) นี่เป็นแหล่งเลขเวอร์ชันที่สะดวกสำหรับการบิลด์ระหว่างรีลีส
วิธีส่งแท็กขึ้นเซิร์ฟเวอร์
git push ธรรมดาไม่ส่งแท็กไปด้วย ซึ่งเป็นสาเหตุที่พบบ่อยของคำถามว่า "ทำไมไม่เห็นแท็กบน GitHub"
git push origin v1.2.0 # แท็กเดียว
git push --tags # แท็กทั้งหมดในเครื่อง
git push --follow-tags # คอมมิต + แท็กแบบ annotated ที่ชี้ไปยังคอมมิตนั้น
--follow-tags ปลอดภัยกว่า --tags เพราะจะไม่ส่งแท็กแบบเบาที่คุณติดไว้ใช้เองโดยไม่ได้ตั้งใจ จะตั้งเป็นค่าเริ่มต้นก็ได้ด้วย git config --global push.followTags true
ดึงแท็กจากเซิร์ฟเวอร์: git fetch --tags ส่วน git fetch ปกติก็ดึงแท็กที่ชี้ไปยังคอมมิตที่ถูกดึงมาด้วย
วิธีลบหรือย้ายแท็ก
การลบ
git tag -d v1.2.0 # ในเครื่อง
git push origin --delete v1.2.0 # บนเซิร์ฟเวอร์
# รูปแบบเดิมที่เทียบเท่า: git push origin :refs/tags/v1.2.0
การย้ายแท็กไปยังคอมมิตอื่น
git tag -a -f v1.2.0 <แฮชใหม่> -m "รีลีส 1.2.0"
git push origin -f v1.2.0
ทำได้ในทางเทคนิค แต่ในโปรเจกต์สาธารณะไม่ควรย้ายแท็กที่เผยแพร่แล้ว คนที่ดาวน์โหลดแท็กไปแล้วจะไม่ได้รับการอัปเดตเอง เพราะ Git ไม่เขียนทับแท็กในเครื่องที่มีอยู่แล้วตอน fetch โดยไม่ใช้ --force ผลคือแต่ละคนจะได้โค้ดต่างกันภายใต้ชื่อ "v1.2.0" ควรออกเวอร์ชัน v1.2.1 แทน
วิธีดูว่าคอมมิตใดอยู่ในรีลีส
แท็กใช้เป็นขอบเขตของช่วงได้ดีในคำสั่งอ่านประวัติทุกตัว
git log v1.1.0..v1.2.0 --oneline # คอมมิตระหว่างรีลีส
git diff v1.1.0 v1.2.0 --stat # ไฟล์ที่เปลี่ยน
git tag --contains 1c02e8f # คอมมิตนี้อยู่ในรีลีสใดบ้าง
git merge-base --is-ancestor 1c02e8f v1.2.0 && echo "อยู่ในรีลีส"
สองคำสั่งท้ายตอบคำถามที่ทีมซัพพอร์ตถามบ่อย ๆ ว่า "การแก้ไขนี้ไปถึงผู้ใช้แล้วหรือยัง" ถ้าการแก้ไขอยู่แค่ใน main และรีลีสถูกสร้างก่อนหน้า git tag --contains จะแสดงรายการว่าง ซึ่งบอกว่าต้องออกแท็กใหม่
อีกเทคนิคหนึ่งคือดึงซอร์สของเวอร์ชันหนึ่งโดยไม่มีประวัติ: git archive --format=zip -o app-1.2.0.zip v1.2.0 วิธีนี้ใช้สร้างไฟล์ส่งมอบให้ลูกค้า หรือบิลด์ในสภาพแวดล้อมที่แยกออกมา
วิธีสลับไปยังเวอร์ชันตามแท็ก
git switch --detach v1.1.0
# HEAD is now at 1c02e8f fix: timeout ของคำขอ
คุณจะอยู่ในสถานะ detached HEAD ซึ่งดูและบิลด์ได้ แต่คอมมิตใหม่จะไม่อยู่ในบรานช์ใด หากต้องการแก้ไขเวอร์ชันเก่า ให้สร้างบรานช์จากแท็ก
git switch -c hotfix/1.1.x v1.1.0
# แก้ไข แล้วคอมมิต
git tag -a v1.1.1 -m "แก้ไขด้านความปลอดภัย"
git push origin hotfix/1.1.x --follow-tags
การกำหนดเวอร์ชันรีลีสด้วย SemVer
รูปแบบชื่อแท็กที่นิยมที่สุดคือ Semantic Versioning: MAJOR.MINOR.PATCH
- MAJOR เพิ่มขึ้นเมื่อ API เปลี่ยนแบบไม่เข้ากันย้อนหลัง:
1.4.2 → 2.0.0 - MINOR คือฟีเจอร์ใหม่ที่ยังเข้ากันได้:
1.4.2 → 1.5.0 - PATCH คือการแก้บั๊ก:
1.4.2 → 1.4.3 - พรีรีลีสระบุด้วยส่วนท้าย เช่น
2.0.0-beta.1หรือ2.0.0-rc.1
คำนำหน้า v (v1.4.2) เป็นธรรมเนียมที่นิยมสำหรับชื่อแท็ก แต่ SemVer เองไม่ได้บังคับ สิ่งสำคัญคือเลือกรูปแบบเดียวและยึดตามนั้น มิฉะนั้นสคริปต์ที่หารีลีสล่าสุดจะพัง
แท็กในกระบวนการรีลีส
กระบวนการทั่วไปในทีมเป็นดังนี้
- รวมการเปลี่ยนแปลงเข้า main และเทสต์ผ่านทั้งหมด
- อัปเดต CHANGELOG และเลขเวอร์ชันในไฟล์โปรเจกต์ หากเก็บไว้ที่นั่น
- สร้างแท็กแบบ annotated:
git tag -a v1.5.0 -m "รีลีส 1.5.0" git push origin main --follow-tags- CI เห็นแท็กใหม่ สร้างอาร์ติแฟกต์และเผยแพร่รีลีส บน GitHub และ GitLab แท็กจะกลายเป็นหน้า Release พร้อมคำอธิบายและไฟล์
ด้วยวิธีนี้ เวอร์ชันใดในโปรดักชันก็สามารถผูกกับโค้ดได้อย่างชัดเจน คือดูชื่อแท็กในบิลด์แล้วใช้ git show เพื่อดูว่ามีอะไรอยู่ข้างใน และ git log v1.4.0..v1.5.0 --oneline ให้รายการการเปลี่ยนแปลงระหว่างรีลีสสำหรับหมายเหตุการออกเวอร์ชัน
สร้างหมายเหตุรีลีสจากแท็ก
หากข้อความคอมมิตมีความหมาย แท็กจะเปลี่ยนประวัติให้เป็นรายการการเปลี่ยนแปลงที่พร้อมใช้ โดยหารีลีสล่าสุดและรีลีสก่อนหน้าได้โดยไม่ต้องค้นเอง
LAST=$(git describe --tags --abbrev=0)
PREV=$(git describe --tags --abbrev=0 "$LAST^")
git log "$PREV..$LAST" --no-merges --pretty=format:"- %s (%h)"
# - feat: ส่งออกรายงาน (7be41d0)
# - fix: timeout ของคำขอ (1c02e8f)
หากทีมเขียนคอมมิตตามแนวทาง Conventional Commits (feat:, fix:, feat!:) สามารถคำนวณเลขเวอร์ชันถัดไปโดยอัตโนมัติได้ คือ fix เพิ่ม PATCH, feat เพิ่ม MINOR และเครื่องหมายอัศเจรีย์หรือ BREAKING CHANGE เพิ่ม MAJOR เครื่องมือปล่อยรีลีสอัตโนมัติยอดนิยมทำงานบนหลักการนี้ โดยติดแท็กและเขียน CHANGELOG ให้เอง
แท็กกับ CI/CD
pipeline โดยทั่วไปแยกสองเหตุการณ์ คือ push ไปยังบรานช์ และการปรากฏของแท็ก บรานช์เรียกการเทสต์และบิลด์สภาพแวดล้อมทดสอบ ส่วนแท็กเรียกการเผยแพร่ ใน GitHub Actions เขียนได้ดังนี้
on:
push:
tags:
- "v*.*.*"
ใน GitLab CI บทบาทเดียวกันทำโดยตัวแปร CI_COMMIT_TAG และกฎ rules: - if: $CI_COMMIT_TAG ชื่อแท็กใน pipeline จะกลายเป็นเลขเวอร์ชันของ Docker image หรือแพ็กเกจ รูปแบบแท็กจึงควรเคร่งครัดและคาดเดาได้
นี่เป็นอีกเหตุผลหนึ่งที่ไม่ควรย้ายแท็ก หาก pipeline เผยแพร่แพ็กเกจ 1.2.0 ไปยังรีจิสทรีแล้ว การเผยแพร่ซ้ำด้วยเลขเดิมมักถูกห้าม และ image ที่แท็ก 1.2.0 ของผู้ใช้จะยังคงเป็นของเก่า
การตั้งชื่อแท็กให้ถูกต้อง
ชื่อแท็กมีกฎเดียวกับชื่อบรานช์ คือไม่มีช่องว่าง ไม่มีอักขระ ~ ^ : ? * [ และไม่มีจุดสองจุดติดกัน เครื่องหมายสแลชใช้ได้ ดังนั้นในโมโนรีโพ จะเห็นแท็กเช่น api/v1.2.0 และ web/v3.0.1 คือมีชุดเวอร์ชันแยกต่อแพ็กเกจ ตรวจชื่อล่วงหน้าได้ด้วย git check-ref-format "refs/tags/v1.2.0" ซึ่งจะคืนค่าที่ไม่ใช่ศูนย์เมื่อชื่อไม่ถูกต้อง
การป้องกันแท็ก
ในรีโพซิทอรีของทีม ควรป้องกันแท็กรีลีส GitHub และ GitLab อนุญาตให้ห้ามการสร้าง เขียนทับ และลบแท็กตามรูปแบบ (เช่น v*) สำหรับทุกคนยกเว้นผู้ดูแล เช่นนี้ git push --tags โดยไม่ตั้งใจจากเครื่องอื่นจะไม่เผยแพร่รีลีสที่ไม่ได้วางแผน และ git push origin --delete ที่ผิดพลาดจะไม่ลบประวัติเวอร์ชัน
ข้อผิดพลาดที่พบบ่อย
- แท็กไม่ขึ้นเซิร์ฟเวอร์ ลืมรัน
git push origin <แท็ก>หรือ--follow-tags - แท็กชื่อเดียวกับบรานช์ Git จะเตือน
refname is ambiguousให้ใช้คำนำหน้าvกับแท็ก และตั้งชื่อที่ต่างกัน - ติดแท็กบนสถานะที่ยังไม่คอมมิต แท็กติดบนคอมมิต ไม่ใช่บนไฟล์ การแก้ไขที่ยังไม่คอมมิตจะไม่ถูกนับในรีลีส
- ใช้แท็กแบบเบาสำหรับรีลีส ไม่มีผู้เขียน วันที่ หรือข้อความ และ
git describeจะไม่เห็นเป็นค่าเริ่มต้น
สรุป
git tag ตรึงจุดที่เปลี่ยนแปลงไม่ได้ในประวัติ สำหรับรีลีสให้สร้างแท็กแบบ annotated ด้วย git tag -a ตั้งชื่อตาม SemVer และส่งขึ้นเซิร์ฟเวอร์อย่างชัดเจนด้วย git push origin <แท็ก> หรือ --follow-tags อย่าย้ายแท็กที่เผยแพร่แล้ว แต่ให้ออกเวอร์ชันใหม่ การใช้แท็กร่วมกับ rebase, stash และ cherry-pick เราอธิบายอย่างลงมือทำในหลักสูตร Git
คำถามที่พบบ่อย
แท็กต่างจากบรานช์อย่างไร?
บรานช์เลื่อนไปยังคอมมิตใหม่ทุกครั้งที่มีการคอมมิต ส่วนแท็กชี้ไปยังคอมมิตเดิมตลอดไป
ติดแท็กสองอันบนคอมมิตเดียวกันได้หรือไม่?
ได้ จำนวนแท็กต่อคอมมิตไม่จำกัด เช่น v2.0.0 และ latest-stable
เมื่อลบบรานช์ แท็กจะหายไปด้วยหรือไม่?
ไม่ แท็กไม่ขึ้นกับบรานช์ คอมมิตที่มีแท็กยังเข้าถึงได้แม้บรานช์จะถูกลบแล้ว
จะรู้ได้อย่างไรว่าคอมมิตหนึ่งอยู่ในรีลีสใด?
รัน git tag --contains <แฮช> คำสั่งจะแสดงแท็กทั้งหมดที่ประวัติของมีคอมมิตนั้นอยู่
จะได้แท็กล่าสุดในสคริปต์อย่างไร?
ใช้ git describe --tags --abbrev=0 ซึ่งจะแสดงชื่อแท็กที่ใกล้ที่สุดโดยไม่มีส่วนแฮช
หัวข้อนี้สอนอย่างละเอียดพร้อมฝึกปฏิบัติในคอร์ส “Git с нуля до уверенного разработчика”
ไปที่คอร์ส