git add และ git commit: วิธีบันทึกการเปลี่ยนแปลงให้ถูกต้อง
การบันทึกการเปลี่ยนแปลงใน Git มีสองขั้นตอน: git add นำการแก้ไขที่ต้องการเข้าสู่ดัชนี (staging area) แล้ว git commit -m "ข้อความ" บันทึกเป็นสแนปชอตหนึ่งรายการในประวัติ git status แสดงว่ามีอะไรเปลี่ยนและอะไรถูกเตรียมไว้แล้ว และสามารถแก้คอมมิตล่าสุดด้วย git commit --amend
ใน Git ไม่มีปุ่ม "บันทึก" การเปลี่ยนแปลงถูกบันทึกเป็นสองขั้นตอน ขั้นแรก git add คัดเลือกว่าการแก้ไขใดจะเข้าสู่สแนปชอตถัดไป จากนั้น git commit บันทึกสแนปชอตนั้นลงในประวัติ พื้นที่กลางระหว่างสองขั้นตอนนี้เรียกว่าดัชนี หรือ staging area ซึ่งหลายคนไม่เข้าใจเป็นเวลาหลายปี และเป็นสิ่งที่ทำให้คอมมิตมีความหมาย
สามสถานะของไฟล์: ไดเรกทอรีทำงาน ดัชนี และประวัติ
การเปลี่ยนแปลงทุกอย่างผ่านสามพื้นที่:
- ไดเรกทอรีทำงาน คุณแก้ไขไฟล์ในเอดิเตอร์
- ดัชนี (stage)
git addนำเวอร์ชันปัจจุบันของไฟล์ไปไว้ในร่างของคอมมิตถัดไป - ประวัติ
git commitเปลี่ยนเนื้อหาของดัชนีเป็นคอมมิตที่มีผู้เขียน วันที่ และข้อความ
การเปรียบเทียบที่เข้าใจง่ายคือพัสดุ ไดเรกทอรีทำงานเปรียบเหมือนห้องที่มีของวางอยู่ ดัชนีคือกล่องที่เปิดอยู่ซึ่งคุณใส่สิ่งที่อยากส่ง และคอมมิตคือพัสดุที่ปิดผนึกและลงชื่อแล้ว คุณเป็นคนตัดสินใจว่าจะใส่อะไรในกล่อง และไม่จำเป็นต้องส่งทุกอย่างพร้อมกัน
เหตุผลที่ต้องมีดัชนี
ลองนึกภาพ ภายในหนึ่งชั่วโมงคุณแก้บั๊กการอนุญาตเข้าใช้งาน แก้คำพิมพ์ผิดใน README และเริ่มฟีเจอร์ใหม่ หากไม่มีดัชนี ทุกอย่างจะเข้าไปในคอมมิตเดียวชื่อ "เบ็ดเตล็ด" แต่เมื่อมีดัชนี คุณทำคอมมิตแยกกันสามรายการ ซึ่งแต่ละรายการเข้าใจง่าย ตรวจรีวิวได้ง่าย และย้อนกลับได้โดยไม่กระทบส่วนอื่น
git status: อะไรถูกแก้ไขและอะไรถูกเตรียมไว้แล้ว
ก่อนทุกครั้งที่ add และ commit ควรดู git status นี่คือคำสั่งที่ปลอดภัยที่สุดของ Git เพราะไม่เปลี่ยนแปลงอะไรเลย
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/auth.js
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/profile.js
- Changes to be committed อยู่ในดัชนีแล้ว และจะเข้าสู่คอมมิต
- Changes not staged ถูกแก้ไขแต่ยังไม่ได้เพิ่มเข้าดัชนี
- Untracked files เป็นไฟล์ใหม่ที่ Git ยังไม่รู้จัก
รูปแบบสั้นจะสะดวกเมื่อมีไฟล์จำนวนมาก:
$ git status -s
M src/auth.js
M README.md
?? src/profile.js
คอลัมน์แรกคือสถานะในดัชนี คอลัมน์ที่สองคือสถานะในไดเรกทอรีทำงาน M ทางซ้ายหมายถึง "แก้ไขและเพิ่มแล้ว" ทางขวาหมายถึง "แก้ไขแต่ยังไม่เพิ่ม" และ ?? หมายถึงไฟล์ที่ไม่ถูกติดตาม
git add: เพิ่มการเปลี่ยนแปลงเข้าดัชนี
| คำสั่ง | สิ่งที่เพิ่ม |
|---|---|
git add file.js | ไฟล์ที่ระบุ |
git add src/ | ทุกอย่างที่แก้ไขในโฟลเดอร์ |
git add . | การเปลี่ยนแปลงทั้งหมดในโฟลเดอร์ปัจจุบันและที่อยู่ข้างใน รวมไฟล์ใหม่และไฟล์ที่ถูกลบ |
git add -A | การเปลี่ยนแปลงทั้งหมดในรีโพซิทอรี |
git add -u | เฉพาะไฟล์ที่ถูกติดตามอยู่แล้ว ไม่แตะไฟล์ใหม่ |
git add -p | แบบโต้ตอบ ทีละส่วน (hunk) |
git add '*.css' | ไฟล์ที่ตรงกับรูปแบบ |
คำพ้องของ git add คือ git stage เป็นคำสั่งเดียวกัน จึงเป็นที่มาของคำว่า "stage" หรือ "สเตจ"
git add -p: เพิ่มเฉพาะบางส่วนของไฟล์
เทคนิคที่ถูกมองข้ามบ่อยที่สุด หากในไฟล์เดียวมีการแก้ไขสองอย่างปนกัน git add -p จะแสดงทีละส่วนและถามว่าจะเพิ่มหรือไม่:
$ git add -p src/auth.js
@@ -12,7 +12,7 @@ function login(user) {
- if (user.password = input) {
+ if (user.password === input) {
(1/2) Stage this hunk [y,n,q,a,d,s,e,?]?
y คือเพิ่ม n คือข้าม s คือแบ่งส่วนให้เล็กลง q คือออก ด้วยวิธีนี้ไฟล์เดียวสามารถกลายเป็นคอมมิตที่เป็นระเบียบสองรายการ
วิธีตรวจว่าอะไรจะเข้าสู่คอมมิต
git diff # การเปลี่ยนแปลงที่ยัง ไม่ ถูกเพิ่มเข้าดัชนี
git diff --staged # สิ่งที่อยู่ในดัชนีแล้วและจะเข้าสู่คอมมิต
ความสับสนที่พบบ่อย: หลัง git add คำสั่ง git diff ธรรมดาจะไม่แสดงอะไร ไม่ได้หมายความว่าการเปลี่ยนแปลงหายไป มันอยู่ในดัชนี ให้ดู git diff --staged (คำพ้องคือ --cached)
วิธีนำไฟล์ออกจากดัชนี
git restore --staged file.js
การแก้ไขในไฟล์ยังคงอยู่ เพียงแค่ไฟล์นั้นไม่ได้เตรียมไว้สำหรับคอมมิตอีกต่อไป ในเวอร์ชัน Git รุ่นเก่า ใช้ git reset HEAD file.js ซึ่งยังใช้งานได้อยู่
git commit: บันทึกการเปลี่ยนแปลง
git commit -m "แก้การตรวจสอบรหัสผ่านตอนเข้าสู่ระบบ"
[main 3f2a9c1] แก้การตรวจสอบรหัสผ่านตอนเข้าสู่ระบบ
1 file changed, 1 insertion(+), 1 deletion(-)
ผลลัพธ์แสดงบรานช์ แฮชย่อของคอมมิตใหม่ และสถิติ คอมมิตจะรวมเฉพาะเนื้อหาของดัชนี หากคุณแก้ไฟล์อีกครั้งหลัง git add การแก้ไขใหม่จะไม่เข้าไปในคอมมิต จนกว่าคุณจะเพิ่มอีกครั้ง
แฟล็กที่มีประโยชน์ของ git commit
| คำสั่ง | ทำอะไร |
|---|---|
git commit | เปิดเอดิเตอร์สำหรับข้อความหลายบรรทัด |
git commit -m "..." | ใส่ข้อความในคำสั่งเลย |
git commit -am "..." | เพิ่มไฟล์ที่ถูกติดตามและถูกแก้ไขทั้งหมดแล้วคอมมิต ไฟล์ใหม่จะไม่รวมอยู่ด้วย |
git commit -v | แสดง diff ในเอดิเตอร์ระหว่างที่คุณเขียนข้อความ |
git commit --amend | เขียนทับคอมมิตล่าสุด |
git commit --allow-empty -m "..." | คอมมิตว่าง เช่น เพื่อรัน CI ใหม่ |
หากเอดิเตอร์เริ่มต้นคือ Vim และคุณติดอยู่ในนั้น: พิมพ์ข้อความ กด Esc แล้วพิมพ์ :wq และ Enter เปลี่ยนเอดิเตอร์ได้ด้วย git config --global core.editor "code --wait" หรือ nano
วิธีเขียนข้อความคอมมิตที่ดี
ข้อความจะถูกอ่านอีกหกเดือนต่อมา เมื่อมีคนค้นหาว่าบั๊กมาจากไหน ข้อความควรตอบคำถาม "คืออะไรและทำไม" ไม่ใช่ "วันนี้ฉันทำอะไรไปบ้าง"
- บรรทัดแรกไม่เกิน 50–72 ตัวอักษร บอกแก่นของการเปลี่ยนแปลง
- ใช้รูปคำสั่งหรือกลาง ๆ เช่น "Add email validation" หรือ "Fix login redirect"
- หากจำเป็น ให้เว้นบรรทัดว่างแล้วเขียนรายละเอียด ว่าทำไมจึงทำแบบนี้ และมีข้อจำกัดอะไร
- หนึ่งคอมมิตคือหนึ่งการเปลี่ยนแปลงเชิงตรรกะ
ไม่ดี: fix, edits, wip, asdf ดี: Fix crash in cart when product list is empty หลายทีมใช้รูปแบบ Conventional Commits เช่น feat: ..., fix: ..., docs: ...
git commit --amend: แก้คอมมิตล่าสุด
พิมพ์ข้อความผิดหรือลืมเพิ่มไฟล์ ไม่จำเป็นต้องทำคอมมิตใหม่ "ลืมไฟล์" ใช้ amend
แก้ข้อความ
git commit --amend -m "แก้การตรวจสอบรหัสผ่านตอนเข้าสู่ระบบ"
เพิ่มไฟล์ที่ลืม
git add forgotten.js
git commit --amend --no-edit
แฟล็ก --no-edit คงข้อความเดิมไว้
สิ่งสำคัญเกี่ยวกับ amend
--amend ไม่ได้ "แก้ไข" คอมมิต แต่สร้างคอมมิตใหม่ที่มีแฮชต่างออกไป และแทนที่ของเดิม ตราบใดที่คอมมิตนั้นอยู่กับคุณคนเดียว การทำเช่นนี้ปลอดภัย หากคุณ git push ไปแล้ว หลัง amend การ push ปกติจะถูกปฏิเสธ และคุณต้องใช้ git push --force-with-lease ในบรานช์ที่มีคนอื่นทำงานอยู่ อย่าทำเช่นนั้น เพราะคุณจะเขียนประวัติทับสิ่งที่เพื่อนร่วมทีมกำลังต่อยอดอยู่
หาก amend ผิดพลาด คอมมิตเดิมยังหาได้ใน git reflog และกลับไปได้ด้วย git reset --soft HEAD@{1}
ข้อผิดพลาดที่พบบ่อย
"nothing to commit, working tree clean"
ไม่มีการเปลี่ยนแปลง อาจคอมมิตไปหมดแล้ว หรือคุณกำลังแก้ไฟล์ในโฟลเดอร์อื่น ตรวจด้วย git status และเส้นทางของโปรเจกต์
"no changes added to commit"
ไฟล์ถูกแก้ไขแต่ยังไม่ได้เพิ่มเข้าดัชนี ให้รัน git add หรือใช้ git commit -am สำหรับไฟล์ที่ถูกติดตามอยู่แล้ว
"Please tell me who you are"
ยังไม่ได้ตั้งชื่อและอีเมล รันครั้งเดียว: git config --global user.name "ชื่อ" และ git config --global user.email "อีเมล"
คอมมิตสิ่งที่ไม่ควรคอมมิต
หากคอมมิตยังไม่ได้ส่งขึ้นไป ให้ยกเลิกโดยคงการเปลี่ยนแปลงไว้ในดัชนี: git reset --soft HEAD~1 จากนั้นนำส่วนที่ไม่ต้องการออกด้วย git restore --staged แล้วคอมมิตใหม่ หากรหัสลับหลุดเข้าไปในคอมมิต ให้ถือว่ารหัสนั้นถูกเปิดเผยแล้วและออกรหัสใหม่
git add . เพิ่มขยะเข้ามา
ตั้งค่า .gitignore ไว้ล่วงหน้า และตรวจ git status ก่อนคอมมิตเสมอ ไฟล์ที่ถูกติดตามอยู่แล้วจะหยุดถูกติดตามก็ต่อเมื่อรัน git rm --cached ไฟล์
คอมมิตจริง ๆ คืออะไร
คอมมิตไม่ได้เก็บความต่างจากเวอร์ชันก่อนหน้า แต่เก็บสแนปชอตของทั้งโปรเจกต์: การอ้างถึงต้นไม้ไฟล์ การอ้างถึงคอมมิตพาเรนต์ ผู้เขียน วันที่ และข้อความ ไฟล์ที่ไม่เปลี่ยนแปลงจะไม่ถูกคัดลอก Git อ้างถึงอ็อบเจกต์ที่บันทึกไว้แล้ว ประวัติจึงกินพื้นที่น้อย
แฮชของคอมมิต (เช่น 3f2a9c1) คำนวณจากเนื้อหาทั้งหมดนี้ เปลี่ยนตัวอักษรเดียวในข้อความ แฮชก็จะต่างออกไป นี่คือเหตุผลที่ --amend และ rebase สร้างคอมมิตใหม่แทนการแก้ไขของเดิม และเหตุผลที่ประวัติที่ถูกเขียนใหม่ขัดแย้งกับสำเนาของเพื่อนร่วมทีม
ดูประวัติแบบย่อได้ด้วย:
git log --oneline --graph -10
แต่ละบรรทัดคือหนึ่งคอมมิต หากข้อความในประวัติของคุณเข้าใจได้โดยไม่ต้องเปิด diff แสดงว่าคุณทำถูกต้อง
วงจรการทำงานประจำวัน
git status # มีอะไรเปลี่ยนบ้าง
git diff # ดูการแก้ไข
git add -p # เลือกสิ่งที่ต้องการ
git diff --staged # ตรวจว่าอะไรจะเข้าคอมมิต
git commit -m "คำอธิบายที่เข้าใจได้"
git log --oneline -5 # ตรวจว่าคอมมิตอยู่ในประวัติ
git push # ส่งขึ้นเซิร์ฟเวอร์
คอมมิตบันทึกการเปลี่ยนแปลงไว้ในเครื่องเท่านั้น หากต้องการให้ไปถึง GitHub หรือเซิร์ฟเวอร์อื่น ต้องใช้ git push
สรุป
git add รวบรวมสแนปชอตถัดไปไว้ในดัชนี git status และ git diff --staged แสดงว่าอะไรจะเข้าไปในนั้น ส่วน git commit บันทึกสแนปชอตลงในประวัติ ทำคอมมิตเล็ก ๆ ที่มีเหตุผล พร้อมข้อความที่ชัดเจน และใช้ git commit --amend สำหรับแก้ไขเล็กน้อยของคอมมิตล่าสุดก่อนการ push ดัชนีและการสองขั้นตอน add → commit ถูกฝึกจริงในหลักสูตร Git
คำถามที่พบบ่อย
ควรคอมมิตบ่อยแค่ไหน?
ทุกครั้งที่งานการเปลี่ยนแปลงเชิงตรรกะหนึ่งเสร็จและโปรเจกต์อยู่ในสถานะใช้งานได้ ดีกว่าคอมมิตจำนวนมากเล็ก ๆ มากกว่าคอมมิตเดียวขนาดใหญ่ตอนท้ายวัน
มีคำสั่ง git save ใน Git หรือไม่?
ไม่มี การบันทึกคือ git add และ git commit ส่วนการพักงานที่ยังไม่เสร็จชั่วคราวใช้ git stash
จะดูว่ามีอะไรอยู่ในคอมมิตได้อย่างไร?
คำสั่ง git show แฮช จะแสดงผู้เขียน ข้อความ และการเปลี่ยนแปลงทั้งหมดของคอมมิต หากไม่ใส่แฮช จะแสดงคอมมิตล่าสุด
git add . ต่างจาก git add -A อย่างไร?
ใน Git เวอร์ชันใหม่ ทั้งสองคำสั่งเพิ่มไฟล์ใหม่ ไฟล์ที่แก้ไข และไฟล์ที่ถูกลบ ความต่างอยู่ที่ขอบเขต git add . ทำงานกับโฟลเดอร์ปัจจุบันและโฟลเดอร์ย่อย ส่วน git add -A ทำงานกับทั้งรีโพซิทอรี
แก้คอมมิตที่ไม่ใช่คอมมิตล่าสุดได้หรือไม่?
ได้ ด้วย interactive rebase: git rebase -i แฮชของพาเรนต์ จากนั้นทำเครื่องหมายคอมมิตที่ต้องการเป็น edit หรือ reword ทำเช่นนี้เฉพาะกับคอมมิตที่ยังไม่ได้ส่งเข้าบรานช์ที่ใช้ร่วมกัน
หัวข้อนี้สอนอย่างละเอียดพร้อมฝึกปฏิบัติในคอร์ส “Git с нуля до уверенного разработчика”
ไปที่คอร์ส