TL;DR: git revert เพิ่มคอมมิตใหม่ที่ยกเลิกคอมมิตเก่า — ประวัติถูกเก็บไว้ ปลอดภัยบนบรานช์ที่คนอื่น pull ไปแล้ว git reset ลากพอยเตอร์บรานช์ไปข้างหลัง — ประวัติถูกเขียนใหม่ ปลอดภัยเฉพาะคอมมิตที่ยังไม่พุช ใช้ revert บนบรานช์ร่วม ใช้ reset กับงานทำความสะอาดในเครื่อง และ reset --hard ลบการเปลี่ยนแปลงที่ยังไม่ commit ใน working tree ด้วย — นั่นคือตัวที่กินงานจริง
git revert กับ git reset ต่างกันอย่างไร?
ทั้งคู่ทำให้โปรเจกต์ดูเหมือนคอมมิตในอดีตไม่เคยเกิดขึ้น ต่างกันที่วิธี:
git revert <sha>คำนวณแพตช์ตรงข้ามของคอมมิตแล้ว commit ลงไป ประวัติของคุณโตขึ้นหนึ่งคอมมิตที่บอกว่า “ยกเลิกอันนั้น” คอมมิตร้ายยังอยู่ใน log ตามด้วยการยกเลิกของมัน SHA ของอย่างอื่นทั้งหมดไม่ถูกแตะgit reset <sha>เลื่อนพอยเตอร์บรานช์ปัจจุบันไปที่<sha>คอมมิตหลังจากนั้นหลุดจากสาย — ยังอยู่ในฐานอ็อบเจกต์จนกว่าจะเก็บขยะ แต่เข้าถึงไม่ได้อีกจากบรานช์ไหนหรือgit log
ตัวหนึ่งไม่เขียนทับอะไรเลย อีกตัวแสร้งว่าไม่มีอะไรเกิดขึ้น คุณสมบัติบรรทัดเดียวนี้ตัดสินว่าสถานการณ์ไหนเรียกใช้ตัวไหน:
# ตัวอย่าง: repo ทิ้งได้ที่รันได้จริง
git init /tmp/revert-vs-reset && cd /tmp/revert-vs-reset
echo one > file.txt && git add . && git commit -m "one"
echo two > file.txt && git add . && git commit -m "two"
# revert: ประวัติเก็บทั้งสองคอมมิต บวกอีกตัวที่ยกเลิก "two"
git revert --no-edit HEAD
git log --oneline # 3 commits: revert, two, one
# reset: พอยเตอร์บรานช์ถอยหลัง "two" หายจาก log
git reset --hard HEAD~1
git log --oneline # 1 commit: one รันทั้งคู่แล้วส่อง git log — ความไม่สมมาตรคือคำตอบทั้งหมด
reset —soft, —mixed กับ —hard ทำอะไรกันแน่?
สามโหมดควบคุมว่าการเปลี่ยนแปลงของคุณรอดที่ไหนหลังพอยเตอร์ขยับ:
| โหมด | พอยเตอร์บรานช์ | Staging area | Working tree | การแก้ไขของคุณ |
|---|---|---|---|---|
--soft | ถอยหลัง | ยัง staged | ไม่ถูกแตะ | อยู่ครบ |
--mixed (ดีฟอลต์) | ถอยหลัง | ยกเลิก staged | ไม่ถูกแตะ | อยู่ แต่ยังไม่ staged |
--hard | ถอยหลัง | รีเซ็ต | รีเซ็ต | ถูกลบ |
อ่านตารางนี้แบบลงมือทำได้:
git reset --soft HEAD~1— “คอมมิตเร็วไป” ทุกอย่างกลับมาสถานะ staged พร้อมคอมมิตใหม่ อาจพร้อมงานเพิ่มด้วยgit reset HEAD~1(mixed) — “stage ไฟล์ผิด” การแก้ไขอยู่ใน working tree ไม่มีอะไรถูก stage คู่กับการลบไฟล์ untracked เมื่ออยากให้ไฟล์เร่ร่อนหายไปด้วยgit reset --hard HEAD~1— “คอมมิตนี้กับสิ่งที่ยังไม่ได้ commit ของฉันคือขยะ” git จะไม่ถามสองรอบ ไม่มี undo สำหรับส่วน working tree ถ้าไม่ได้ stash หรือ commit ไว้ก่อน
revert ไม่มีโหมดเพราะมันไม่เคยทำลายอะไรเลย — มันแค่เพิ่มคอมมิตชดเชย
เมื่อไรควรใช้ git revert แทน git reset?
ถามหนึ่งคำถาม: มีคนอื่น pull คอมมิตนี้ไปแล้วหรือยัง? ถ้าแล้ว reset ต้องออกจากโต๊ะ
การ reset บรานช์ที่คนอื่นวางรากงานไว้คือการเขียนทับประวัติร่วม pull รอบถัดไปของพวกเขาจะเจอบรานช์ที่แยกทาง และ “วิธีแก้” มักเป็น force-push ที่เปลี่ยนความผิดพลาดของคุณให้เป็นปัญหาของทุกคน revert เติมคอมมิตปกติเข้าไป ดังนั้น git pull ธรรมดาจะ merge สะอาดสำหรับทุกคน — นั่นคือเหตุผลที่ revert เป็นคำตอบดีฟอลต์บน main และบน GitHub pull request ที่ปุ่ม “Revert” มีอยู่เพราะการเขียนทับประวัติ PR ที่ merge แล้วไม่ใช่ทางเลือก
reset ใช้กับหน้าต่างก่อนการแชร์: คอมมิตที่คุณทำเมื่อสามสิบวินาทีก่อน ความผิดพลาดในการ stage หรือบรานช์ทดลองในเครื่อง ถ้าสำเนาเดียวของงานอยู่บนเครื่องคุณ คุณมีอิสระจัดประวัติใหม่ — นั่นคือความหมายทั้งหมดของหน้าต่างก่อนพุช
มีกรณีตรงกลาง: คุณพุชไปที่ feature branch ของตัวเองและไม่มีใครสร้างงานบนมัน force-push หลัง reset เป็นที่ยอมรับได้ทางสังคมที่นั่น แต่ revert ยังใช้การประสานงานน้อยกว่า เก็บ force-push ไว้กับบรานช์ที่คุณเป็นเจ้าของเพียงลำพัง
git revert ปลอดภัยกว่า git reset สำหรับบรานช์ร่วมจริงไหม?
จริงในเชิงโครงสร้าง — ไม่ใช่แค่ตามธรรมเนียม:
- revert ผลิตคอมมิตธรรมดา: CI วิ่งบนมัน ดิฟรีวิวได้ และ
git logอธิบายให้ตัวคุณในอนาคตฟังว่าทำไมการเปลี่ยนแปลงถึงหายไป - reset เงียบ ๆ ทิ้งสถานะระหว่างกลางไป ไม่มีใครที่รีวิวทีหลังเห็นว่าอะไรถูกยกเลิกหรือเมื่อไร เพราะ log แสดงเส้นตรงที่ไม่เคยมีมัน
สองคมที่ควรระวังในทางปฏิบัติ:
- ย้อน merge commit ต้องใช้
git revert -m 1 <sha>(ยึดเส้นของพ่อตัวแรก) ถ้าไม่มี-mgit จะปฏิเสธแล้วปล่อยให้คุณอ่าน error - revert ไม่ใช่เครื่องย้อนเวลา: มันยกเลิกดิฟของคอมมิตเดียว ถ้าคอมมิตทีหลังแตะบรรทัดเดิม คุณอาจต้องแก้คอนฟลิกต์ — นั่นคือ git บอกว่าการยกเลิกพันกันอยู่ ซึ่งเป็นข้อมูลที่มีประโยชน์ ไม่ใช่อาการผิดปกติ
สำหรับการย้อนคอมมิตล่าสุดของคุณโดยเฉพาะ — เก็บหรือทิ้งการเปลี่ยนแปลง — ตัวเลือกทั้งหมดวางไว้แล้วในgit undo last commit: เก็บการเปลี่ยนแปลงไว้
แล้ว git restore ล่ะ — วางอยู่ตรงไหน?
git restore (กับ git switch คู่หูของมัน) มาถึงใน git 2.23 เพื่อรับงานที่ reset ทำแย่เพราะชื่อถูกใช้มากเกิน:
| งาน | วิธีเก่า | วิธีชัดเจน |
|---|---|---|
| ทิ้งการแก้ working tree ของไฟล์ | git checkout -- file / git reset --hard | git restore file |
| ดึงไฟล์ออกจาก staging | git reset HEAD file | git restore --staged file |
| ย้ายบรานช์ไปคอมมิตอื่น | git reset <sha> | git reset <sha> (ไม่มีตัวแทน — อันนี้อยู่ต่อ) |
การแบ่งยุคใหม่คือ: restore ซ่อมไฟล์ reset ย้ายบรานช์ revert ยกเลิกคอมมิตที่เผยแพร่แล้ว คนค้นหา “git revert vs reset vs restore” พร้อมกันด้วยเหตุผลที่ดี — มันคือโมเดลความคิดเดียวที่กระจายอยู่ในสามคำสั่ง รูปแบบเก่าของ git checkout ยังทำงานทุกที่ รูปแบบใหม่แค่กันไม่ให้คุณส่งบรานช์ทั้งกิ่งเข้าเครื่องบด เมื่อที่คุณตั้งใจคือดึงไฟล์เดียวออกจาก staging
ถ้าเป้าหมายคือการคัดลอกคอมมิตดีไปข้างหน้า แทนการลบคอมมิตแย่ นั่นคือหน้าที่ของ cherry-pick — ดูgit cherry-pick: หลายคอมมิต หลายบรานช์ และคอนฟลิกต์
สรุปการตัดสินใจอย่างเร็ว
- คอมมิตสาธารณะ (พุชแล้ว คนอื่น pull แล้ว) →
git revert <sha> - คอมมิตอยู่ในเครื่องเท่านั้น อยากทำใหม่ →
git reset --softหรือ--mixedแล้วคอมมิตใหม่ - อยู่ในเครื่องเท่านั้น อยากให้หายไปพร้อมกองขยะที่ยังไม่ commit →
git reset --hardหลังมองหนึ่งครั้งอย่างซื่อสัตย์ว่า working tree ยังถืออะไรอีก - ไฟล์เดียวพัง →
git restore <file>แล้วปล่อยบรานช์ไว้ตามเดิม
รายการเดียวในลิสต์นั้นที่อันตรายจริงคือ --hard — ที่เหลือทุกตัวคุยกลับได้ผ่าน git reflog ความสูญเสียถาวรใน git แคบมากและส่วนใหญ่ต้องให้คุณพิมพ์มันออกมาชัด ๆ นิสัยที่สมควรสร้างคือหยุดครึ่งวินาทีก่อน --hard ไม่ใช่การหลีกเลี่ยงเครื่องมือคมของ git ทั้งชุด
— mrsaynothing
— mrsaynothing
บันทึกหน้างานเรื่อง AI, Linux และ self-hosted
คุยต่อโพสต์นี้บน dev.to dev.to ↗
รับวิธีแก้ฉบับถัดไปทางอีเมล
อีเมลหนึ่งฉบับต่อหนึ่งโพสต์ แก้เสร็จแล้วไปต่อ
นี่คืออะไร?Systemd service ไม่ยอมสตาร์ต? วิธีแก้แบบตรงจุด
ถ้าอ่านแล้วชอบ — ผมสร้างงานแบบนี้เป็นอาชีพ จ้างผม