Jitrak Blog

How to deal with invalid Git commit?

วิธีจัดการกับ Git Commit ที่ผิดพลาด

28 Sep 2024 19:00

Written by: Yosapol Jitrak

Tags:

Git

Version Control

เคยไหมครับที่ Git commit เสร็จแล้ว แต่มารู้ตัวทีหลังว่ามันผิดพลาด ต้องการที่จะทำการแก้ไขมัน เนื่องจากหลายคนที่ผมทำงานด้วยไม่รู้วิธีการเหล่านี้ ผมจึงคิดว่าอีกหลายคนอาจจะยังไม่รู้ด้วย จึงคิดว่าว่าบทความนี้น่าจะเป็นประโยชน์ โดยในบทความนี้จะใช้เป็นคำสั่ง Git CLI ทั้งหมดนะครับ

วิธีการที่จะทำให้คุณสามารถแก้ไข Commit ที่ผิดพลาดใน Git มีหลายวิธี โดยขึ้นอยู่กับสถานการณ์ อย่าง รู้ตัวจังหวะไหน Push ขึ้น Origin ไปแล้วหรือยัง ทำงานคนเดียวไหม เป็นต้น

Git commit amend

หากคุณทำการ Commit ไปแล้ว แล้วรู้ตัวทันทีว่ามันมีความผิดพลาด ไม่ว่าจะเป็นต้องการแก้ไข Commit message หรือแก้ไขไฟล์เพิ่มเติม โดยที่ยังไม่ได้ทำการ Push ขึ้น Origin คุณสามารถใช้ option --amend ของ Git commit เพื่อแก้ไข Commit นั้นได้ทันที

git commit --amend ## แก้ไข commit
git commit --amend -m "New commit message" ## แก้ไข commit พร้อมกับใส่ commit message ไปพร้อมกันเลย

ลองมาดูตัวอย่างกัน

เริ่มแรกผมทำการ Commit ไฟล์ A ไปแล้ว แต่พบว่ามันมีข้อผิดพลาด และต้องการจะแก้ไขไฟล์ A นั้นอีกครั้ง โดยไม่ต้องการให้มี Commit ใหม่เพิ่มขึ้นมา ผมจะทำการแก้ไขไฟล์ A ให้เรียบร้อย จากนั้นก็ทำการ Staged ไฟล์ A อีกครั้ง (git add A) เสร็จแล้วก็ทำการ Commit ด้วย Option --amend ดังนี้

git commit --amend

หลังจากนั้นจะขึ้นให้คุณใส่ Commit message อีกครั้ง โดยจะแก้ไขหรือไม่ทำการแก้ไข Commit message ก็ได้

คำเตือน: การใช้ Commit ด้วย Option --amend เป็นคำสั่งที่ควรทำก็ต่อเมื่อยังไม่ได้ทำการ Push ขึ้น Origin ไปแล้วเท่านั้น หากคุณทำการ Push ไปแล้ว คุณจะไม่สามารถ Push commit ที่ทำการ Amend ได้โดยตรง ยิ่งหากคุณไม่ได้ทำงานคนเดียวด้วยแล้ว ควรจะใช้วิธีการอื่นมาทดแทน เพราะจะเกิด Behind ขึ้นมา ตัวอย่าง Error จะเป็นดังนี้ครับ

 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'github.com:Eji4h/git-demo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

เมื่อเจอ Error แบบนี้แล้วปกติเราก็จะทำการ Pull เพื่อไม่ให้เกิด Behind ถูกไหมครับ แต่กรณี คุณจะไม่สามารถดำเนินการต่อได้ เพราะเมื่อคุณทำการ Pull จะเกิด Fatal error มาดังนี้

fatal: refusing to merge unrelated histories

หากคุณทำงานคนเดียวเท่านั้น หรือแม้จะทำการร่วมกับคนอื่น แต่คุณมั่นใจว่ายังไม่มีคน Pull commit ล่าสุดของคุณที่จะทำการ Amend ไปแล้วแน่ ๆ และคุณต้องการจะทำการ Push ทับขึ้นไปบน Origin ใหม่จริง ๆ คุณสามารถใช้ Option --force หรือ -f ของ Git push เพื่อทำการแก้ไขได้ ดังนี้

git push --force
git push -f ## แบบย่อ

ย้ำว่าวิธีการ Push ด้วย Option --force เป็นคำสั่งที่อันตรายมาก หากมีคนอื่นทำการ Pull commit ที่ทำการ Amend ไปแล้ว จะทำให้เกิดความวุ่นวายภายในทีมมาก ยิ่งหากมีการ Push commit ใหม่มาต่อจาก Commit ที่จะทำการ Amend ไปแล้ว เมื่อโดน Push force ทับ สิ่งที่ทำการแก้ไขเหล่านั้นจะหายไปจาก Branch นั้นเลย ซึ่งดูแล้วก็เป็นการใช้ Git ที่ผิดวิธีครับ เพราะ Git เป็น Version control มันก็ควรจะเป็น Version ถึงแม้จะมีความผิดพลาดเกิดขึ้นมาบาง Version ก็ตาม ไม่ใช่บางคนที่เคลมว่าตัวเองใช้ Git เป็น แต่มี Commit เดียวตลอดใน Repository นั้น อย่าง Kotchasan Web Framework

เพิ่มเติม: หลายคนอาจจะคิดว่าการ Push ด้วย Optional --force นั้นจะทำให้ประวัติ Commit ที่เราเคย Push ขึ้นบน Origin ไปแล้วนั้นจะหายไป จริง ๆ ไม่หายนะครับ หากคุณเคย Push มันขึ้นไปแล้ว ลองดูตัวอย่างที่ Link: https://github.com/Eji4h/how-to-deal-with-invalid-git-commit/commit/1f7de88bcba0c58cfd6a13ca2f3f6bdc1be1ea1c ซึ่งเป็น Commit ที่ผมทำการ Push ไว้ก่อนจะโดน Force ทับครับ

Git reset

หลายคนอาจจะไม่เคยใช้คำสั่ง reset มาก่อน โดยปกติจะมี Option หลักอยู่ 3 ตัวนะครับ มีดังนี้

Git reset hard

เราจะสนใจเคสสำหรับการแก้ไข Commit นะครับ ตัวแรกเรามาดู Option --hard กันก่อน โดย Option นี้คือการย้อนกลับไปที่ Commit เป้าหมาย ซึ่งสิ่งที่แก้ไขไประหว่างทางจะหายไปทั้งหมด ซึ่งจะมีรูปแบบการใช้งานดังนี้

git reset --hard <commit-hash>

โดยไปการเอา Commit hash นั้น เอามาจาก Git log ได้เลยครับ

git log

ตัวอย่าง Output จากคำสั่ง git log

commit 7ac7ffc65cafd3fbc021fa7b8ce04d0d1f930afc
Author: Yosapol Jitrak <[email protected]v>
Date:   Sat Sep 28 17:28:35 2024 +0700

    feat: 🎸 add C file

commit 0df3b97974e9f5fba92e554d88cadeafcd693c90
Author: Yosapol Jitrak <[email protected]v>
Date:   Sat Sep 28 17:27:36 2024 +0700

    feat: 🎸 add B file

commit 046f4d0565db89e8b26905fd908197a49806de92
Author: Yosapol Jitrak <[email protected]v>
Date:   Sat Sep 28 13:58:12 2024 +0700

    feat: 🎸 add A file

ตัวอย่างการใช้งาน

git reset --hard 046f4d0565db89e8b26905fd908197a49806de92
git reset --hard 046f4d ## แบบย่อ

เมื่อทำแบบนี้แล้ว Commit ที่ทำการ feat: 🎸 add C file และ feat: 🎸 add B file จะหายไปทั้งหมด จะเหลือเพียงแค่ Commit feat: 🎸 add A file เท่านั้น หมายความว่าไฟล์ B และ C จะหายไปทั้งหมด ส่วนไฟล์ A จะยังคงอยู่

Git reset mixed

Option --mixed เป็น Option default ของคำสั่ง git reset ถ้าไม่ระบุ Option อื่น ๆ Git reset จะใช้ --mixed เป็นอัตโนมัติ โดย Option นี้จะย้อนกลับไปที่ Commit เป้าหมาย แต่จะเก็บการเปลี่ยนแปลงไว้ใน Working directory โดยไม่ stage การเปลี่ยนแปลงเหล่านั้น

รูปแบบการใช้งาน:

git reset --mixed <commit-hash>

ตัวอย่างการใช้งาน:

git reset --mixed 046f4d0565db89e8b26905fd908197a49806de92
git reset --mixed 046f4d ## แบบย่อ

หลังจากทำการ reset แล้ว Commit head จะไปที่ feat: add A file และพบว่ามีการเปลี่ยนแปลงที่ยังไม่ได้ Stage ของ feat: 🎸 add C file และ feat: 🎸 add B file อยู่ใน Working directory ว่าง่าย ๆ ว่าการเปลี่ยนแปลงที่เคยทำไประหว่างทางจะไม่หายไป ไฟล์ B และ C ยังอยู่ เพียงแค่ยังไม่ได้ทำการ Stage และ Commit เท่านั้น

คำเตือน: การใช้ Git reset ทั้งสองแบบที่กล่าวไป เป็นคำสั่งที่ควรทำก็ต่อเมื่อยังไม่ได้ทำการ Push ขึ้น Origin ไปแล้วเท่านั้น เฉกเช่นเดียวกับการใช้ Git commit ด้วย Option --amend ครับ

Git revert

ตามชื่อเลยครับ จะเป็นการย้อนการทำงานของ Commit นั้น ๆ โดยที่จะทำการสร้าง Commit ใหม่ขึ้นมาเก็บประวัติใหม่ครับ โดยเติมคำว่า Revert ไปข้างหน้าของ Commit message เดิมนั่นเอง หมายความว่าประวัติ Commit เก่าจะยังคงอยู่ครบถ้วนทั้งหมด ไม่หายไปไหน วิธีการนี้จะดีกว่าการ Commit ด้วย Option --amend หรือ Git reset ครับ เพราะจะไม่ทำให้ประวัติการเปลี่ยนแปลงหายไปไหน สามารถ Pull และ Push ได้ตามปกติ โดยไม่ต้องมีการ Push ด้วย Option --force ครับ แต่จะมีข้อเสีย คือประวัติอาจจะดูรก และอาจจะเกิด Conflict ได้เหมือนกันครับ

git revert <commit-hash>
git revert <commit-hash-3> <commit-hash-2> <commit-hash-1> ## สามารถใส่หลาย ๆ Commit hash ได้

ตัวอย่างการใช้งาน:

git revert 046f4d0565db89e8b26905fd908197a49806de92
git revert 046f4d ## แบบย่อ

สามารถทำเป็น Sequence ได้ โดยแทนที่จะใช้คำสั่ง git revert หลาย ๆ ครั้ง โดยใส่ Commit hash ของการเปลี่ยนแปลงที่ต้องการทำการ Revert ตามลำดับที่ต้องการได้เลย ดังนี้

git revert 7ac7ff 0df3b9 046f4d ## สามารถใส่หลาย ๆ Commit hash ได้

อธิบายคำสั่งข้างบน เราจะ Revert add C file ก่อน จากนั้นจะเป็น add B file และสุดท้ายจะเป็น add A file ตามลำดับ ซึ่งตัว Revert ก็จะทำการสร้าง Commit ใหม่ขึ้นมา แบบกลับด้าน Commit ที่เคยทำไป อธิบายดังตัวอย่างด้านล่างครับ

Revert add A file
Revert add B file
Revert add C file
add C file
add B file
add A file

จบไปแล้วนะครับสำหรับบทความนี้ ไว้เจอกันใหม่บทความหน้าครับ