วิธียุบหลาย ๆ git commit เป็นหนึ่ง
เวลาเราเขียนโค้ดเสร็จสักฟีเจอร์หนึ่งแล้วต้องการ push กลับเข้าไปใน remote หรือ upstream โดยปกติแล้ว เรามักจะ push commit ที่เกิดขึ้นทั้งหมดของฟีเจอร์นั้น ซึ่งบางทีอาจจะมีการแก้ไขเล็ก ๆ น้อย ๆ การแก้คำผิด หรือ commit แบบอื่น ๆ ทีนี้พอมันเข้าไปรวมใน upstream แล้วเวลาเราดู log มันก็จะมี commit เต็มไปหมด
มันมีเทคนิคหนึ่งในการยุบรวมเอาหลาย ๆ commit เข้ามาเป็น commit เดียวก่อนที่จะ push ขึ้น upstream เราเรียกว่า git squash
ซึ่งจะเปิดโอกาสให้เราเลือก commit ที่จะยุบรวมเข้าด้วยกัน และแก้ไขข้อความใน commit ให้สวย ๆ ก่อนที่จะ push กลับเข้าสู่ upstream
เช่น จากฟีเจอร์เพิ่มฟีเจอร์การโอนเงินเข้า wallet เราอาจจะมีข้อความ commit หลาย ๆ อันแบบนี้
(ใหม่สุด) 9fe2bc0 Add icon to available wallets
b8c2eab fix: test
77e1cef fix: correct typo in configuration file
038ca10 Add money transfer gateway to config file
dea4cd4 Make wallet configurable
(เก่าสุด) 817eac3 Add target wallet selection
พอมันถูก push กลับเข้า upstream มันจะกลายเป็นข้อความ commit แบบนี้จากหลาย ๆ ฟีเจอร์ปน ๆ กันโดยไม่ได้บ่งบอกว่ามันคือฟีเจอร์อะไร
สิ่งที่เราจะทำต่อไปนี้คือ เราจะยุบรวมข้อความ commit ทั้ง 6 ข้อความเข้าด้วยกันเป็น 1 ข้อความ และทำให้มันอ่านง่ายขึ้น เริ่มจาก
1. ดูก่อนว่าเราอยากจะยุบรวมกี่ commit เข้าด้วยกัน
เราสามารถใช้คำสั่ง ด้านล่างนี้เพื่อดูย้อนหลังไปว่าใน commit ที่ผ่าน ๆ มาเราทำอะไรไปบ้าง และมี commit id อะไรบ้าง
git log --graph --decorate --pretty=oneline --abbrev-commit
จริง ๆ ใช้ git log
เฉย ๆ ก็ได้ แต่มันจะเป็นแบบหลายบรรทัด หลายหน้าจอ ไล่ยากพอสมควร
พอเราสั่ง git log --graph --decorate --pretty=oneline --abbrev-commit
แล้วเราจะเห็นหน้าจอประมาณนี้
* (HEAD -> master) 9fe2bc0 Add icon to available wallets
* b8c2eab fix: test
* 77e1cef fix: correct typo in configuration file
* 038ca10 Add money transfer gateway to config file
* dea4cd4 Make wallet configurable
* 817eac3 Add target wallet selection
* c31dd0a feat: Add favorite feature
* ...
:
สิ่งที่เราสังเกตได้คือตอนนี้ HEAD อยู่ที่ 9fe2bc0
และเราต้องการยุบ commit ที่เป็นฟีเจอร์การโอนเงินเข้า wallet เข้าด้วยกัน ถ้านับดูก็จะได้ทั้งหมด 6 commit
โดยมี 817eac3
เป็น commit ที่เก่าที่สุดของฟีเจอร์
สังเกตว่า ถ้าเรามีจำนวน commit ที่มากเกิน 1 หน้า มันจะมีเครื่องหมาย :
มารอรับคำสั่งต่อไป ถ้าเราต้องการไปหน้าถัดไป เราสามารถกด spacebar
เพื่อไปหน้าถัดไปได้ หรือกด q
เพื่อออกจากหน้านี้
2. ยุบ commit
หลังจากที่เราได้จำนวน commit แล้ว เราก็สั่งคำสั่งนี้
git rebase -i HEAD~<จำนวน commit>
หรือ
git rebase -i <commit-id>^
git จะทำการเปิด editor มีหน้าตาประมาณนี้
pick 9fe2bc0 Add icon to available wallets
pick b8c2eab fix: test
pick 77e1cef fix: correct typo in configuration file
pick 038ca10 Add money transfer gateway to config file
pick dea4cd4 Make wallet configurable
pick 817eac3 Add target wallet selection# Rebase 9fe2bc0..817eac3 onto 817eac3 (6 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
สิ่งที่เราจะทำคือ แก้คำว่า pick
ด้านหน้า commit ที่ต้องการยุบ เป็นคำว่า squash
หรือใช้แค่ตัว s
ก็พอ โดยที่เราจะต้องปล่อยให้ commit แรกเอาไว้เป็น pick เหมือนเดิม เพื่อให้เป็น commit เริ่มต้นด้วย
หลังจากที่เราแก้แล้ว เราจะเห็นว่า เราแก้จาก pick
เป็น s
ไปทั้งหมด 5 commit
และทั้ง 5 commit นี้จะถูกเอาไปรวมกับอีก 1 commit ที่เราเก็บไว้
s 9fe2bc0 Add icon to available wallets
s b8c2eab fix: test
s 77e1cef fix: correct typo in configuration file
s 038ca10 Add money transfer gateway to config file
s dea4cd4 Make wallet configurable
pick 817eac3 Add target wallet selection# Rebase 9fe2bc0..817eac3 onto 817eac3 (6 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
เสร็จแล้วก็ save และปิดไฟล์ได้เลย ใครที่ใช้ editor ตระกูล VI ก็กด Escape แล้วก็ wq!
ได้เลย
3. แก้ข้อความ commit ให้สวย ๆ
# This is a combination of 6 commits.
# This is the 1st commit message:Add target wallet selection# This is the commit message #2:Make wallet configurable# This is the commit message #3:Add money transfer gateway to config file# This is the commit message #4:fix: correct typo in configuration file# This is the commit message #5:fix: test# This is the commit message #6:Add icon to available wallets# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Fri Aug 7 23:48:49 2020 +0700
#
# interactive rebase in progress; onto 817eac3
เราก็แก้เป็นข้อความ commit ที่เราต้องการแล้วเซฟได้เลย
feat: Add wallet transfer feature
หลังจากที่เราเซฟ git ก็จะทำการยุบ commit ที่เราเลือกไปเป็น commit ใหม่ให้เหลือ commit เดียว
[detached HEAD d490ef0] feat: Add wallet transfer feature
Date: Fri Aug 7 23:20:58 2020 +0700
12 files changed, 2334 insertions(+)
create mode 100644 src/services/wallet.service
create mode 100644 src/assets/icons/wallet.png
...
Successfully rebased and updated refs/heads/master.
หลังจากนั้นเราก็แค่ push กลับขึ้นไปที่ upstream เป็นอันจบ
มันมีประโยชน์ยังไง
สำหรับคนที่ใช้เครื่องมือที่ช่วยในการทำ Release note หรือ CHANGELOG แบบอัตโนมัติจากข้อความ commit เครื่องมือพวกนั้นจะใช้ข้อความ commit มาใช้นี่แหละ ดังนั้นถ้าข้อความ commit ที่เราเขียนมันเป็นการแก้แบบหยุมหยิม เราก็อาจจะทำให้ Release note ของเราเต็มไปด้วยสิ่งที่ user อ่านไม่เข้าใจเลยว่าแอพหรือไลบรารี่ของเรามีออกอะไรใหม่ หรือแก้อะไรใหม่ไป
ทีมพัฒนาบางทีมเอาเทคนิค git squash
นี้เข้าไปเป็นส่วนหนึ่งของการพัฒนาเลย เพื่อที่จะทำให้การทำ CI/CD มันเป็นอัตโนมัติได้มากขึ้น และ flow ในการทำงานแบบที่ใช้ git squash
เพื่อยุบ commit ก่อน push เราเรียกว่า squash rebase workflow