คู่มือ

แท็กใน Git: วิธีใช้ git tag และกำหนดเวอร์ชันของรีลีส

อัปเดต: · Skillaria.Top

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.0git 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 เองไม่ได้บังคับ สิ่งสำคัญคือเลือกรูปแบบเดียวและยึดตามนั้น มิฉะนั้นสคริปต์ที่หารีลีสล่าสุดจะพัง

แท็กในกระบวนการรีลีส

กระบวนการทั่วไปในทีมเป็นดังนี้

  1. รวมการเปลี่ยนแปลงเข้า main และเทสต์ผ่านทั้งหมด
  2. อัปเดต CHANGELOG และเลขเวอร์ชันในไฟล์โปรเจกต์ หากเก็บไว้ที่นั่น
  3. สร้างแท็กแบบ annotated: git tag -a v1.5.0 -m "รีลีส 1.5.0"
  4. git push origin main --follow-tags
  5. 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 &lt;แฮช&gt; คำสั่งจะแสดงแท็กทั้งหมดที่ประวัติของมีคอมมิตนั้นอยู่

จะได้แท็กล่าสุดในสคริปต์อย่างไร?

ใช้ git describe --tags --abbrev=0 ซึ่งจะแสดงชื่อแท็กที่ใกล้ที่สุดโดยไม่มีส่วนแฮช

เรียนหัวข้อนี้ผ่านวิดีโอ

หัวข้อนี้สอนอย่างละเอียดพร้อมฝึกปฏิบัติในคอร์ส “Git с нуля до уверенного разработчика”

ไปที่คอร์ส