Git Handbook
เข้าใจกลไกภายในของ Git ตั้งแต่ commit แรกจนถึงระดับเทพ — branch, merge, rebase, กู้คืนงาน และซ่อมประวัติได้อย่างมั่นใจ
สารบัญ
- LEVEL 0 · ปูพื้นจากศูนย์
- 01Git คืออะไร & การติดตั้ง
- 02แบบจำลองความคิดหลัก
- LEVEL 1 · พื้นฐานที่ใช้ทุกวัน
- 03Workflow ประจำวัน
- 04Branch & Merge
- 05ทำงานกับ Remote
- LEVEL 2 · ระดับกลาง
- 06ทิ้ง/ย้อนงานที่ยังไม่แชร์
- 07ย้อนงานที่แชร์แล้ว & reflog
- 08Merge vs Rebase
- 09Stash · Cherry-pick · Tag
- LEVEL 3 · ระดับสูง
- 10ทำงานเป็นทีม
- 11เครื่องมือสืบสวน
- LEVEL 4 · ระดับเทพ
- 12กลไกภายในของ Git
- 13เครื่องมือทรงพลัง
- 14กู้ของหาย & ซ่อมประวัติ
- 15Repo ใหญ่ & บทสรุป
- ·เล่มต่อไป: git push แล้วเกิดอะไรต่อ?
- ·อ่านต่อ: System Design
คู่มือที่พาคุณจากคนไม่เคยแตะ Git ไปจนเข้าใจว่ามันเก็บข้อมูลอย่างไรในระดับลึกที่สุด — เน้นแบบจำลองความคิดและเหตุผลเบื้องหลัง ไม่ใช่การท่องคำสั่ง
- ระดับ: เริ่มจากศูนย์ → ระดับเทพ
- เวลาอ่าน: ~90 นาที
- ภาษา: ไทย พร้อมไดอะแกรม
LEVEL 0 · ปูพื้นจากศูนย์
Git คืออะไร & การติดตั้ง#
ลองนึกถึงตอนเขียนรายงานแล้วเซฟไฟล์เป็น report_final.doc, report_final2.doc, report_final_จริงๆ.doc — นั่นคือการทำ version control ด้วยมือ ที่พังง่ายและไม่มีทางรู้ว่าแต่ละไฟล์ต่างกันตรงไหน Git คือเครื่องมือที่ทำสิ่งนี้ให้อย่างเป็นระบบ: บันทึกทุกช่วงเวลาของโปรเจกต์ไว้ พร้อมข้อมูลว่าใครเปลี่ยนอะไร เมื่อไหร่ และเพราะอะไร แล้วย้อนกลับไปจุดใดก็ได้
สิ่งที่ทำให้ Git ทรงพลังกว่าการเซฟไฟล์ซ้ำ ๆ คือมันถูกออกแบบมาเพื่อ การทำงานหลายคนพร้อมกัน และ การทดลองโดยไม่กลัวพัง — แตกเส้นทางการพัฒนาออกไปลองของใหม่ แล้วรวมกลับเข้ามาเมื่อพร้อม โดยของเดิมไม่เคยเสียหาย
1.1 Distributed — ทุกเครื่องมีประวัติครบ
จุดต่างสำคัญจากระบบรุ่นเก่า: Git เป็นแบบ กระจายศูนย์ เมื่อคุณ clone โปรเจกต์มา คุณไม่ได้ได้แค่ไฟล์ล่าสุด แต่ได้ ประวัติทั้งหมดติดมาในเครื่องด้วย นั่นแปลว่าการ commit, ดูประวัติ, สลับเวอร์ชัน ทำได้ทันทีโดยไม่ต้องต่อเน็ต และไม่มี "เซิร์ฟเวอร์กลาง" จุดเดียวที่ถ้าพังแล้วงานหายหมด
1.2 ติดตั้งและตั้งค่าครั้งแรก
หลังติดตั้ง Git (จาก git-scm.com หรือ package manager) สิ่งแรกที่ต้องทำคือบอก Git ว่าคุณเป็นใคร เพราะทุก commit จะถูกประทับชื่อและอีเมลนี้ไว้ถาวร:
$ git config --global user.name "Khaofang"
$ git config --global user.email "you@example.com"
$ git config --global init.defaultBranch main # ตั้งชื่อ branch แรกเป็น main--global หมายถึงตั้งค่านี้ให้ทุกโปรเจกต์ในเครื่อง ตั้งครั้งเดียวพอ
1.3 สร้าง repository แรก
คำสั่ง git init เปลี่ยนโฟลเดอร์ธรรมดาให้เป็นโปรเจกต์ที่ Git เฝ้าดู — มันสร้างโฟลเดอร์ซ่อนชื่อ .git/ ขึ้นมา ซึ่งเก็บประวัติทั้งหมด (บทที่ 12 จะเปิดดูข้างใน):
$ git init
Initialized empty Git repository in /myproject/.git/
$ git status
On branch main · No commits yet✅ สรุปบทที่ 1 — Git = เครื่องบันทึกทุกช่วงเวลาของโปรเจกต์ พร้อมเหตุผลและผู้รับผิดชอบ · เป็นระบบกระจายศูนย์ ทุก clone มีประวัติครบในตัว · ตั้งชื่อ/อีเมลด้วย
git configครั้งเดียว · เปิดโปรเจกต์ด้วยgit init(ใหม่) หรือgit clone(ที่มีอยู่)
แบบจำลองความคิดหลัก#
บทนี้สำคัญที่สุดในเล่ม ถ้าเข้าใจแนวคิดสามอย่างต่อไปนี้ คำสั่งที่เหลือทั้งหมดจะกลายเป็นเรื่องที่ "เดาได้" แทนที่จะต้องท่องจำ
2.1 Git เก็บภาพถ่าย ไม่ใช่ส่วนต่าง
ความเข้าใจผิดที่พบบ่อยคือคิดว่า Git เก็บ "รายการการเปลี่ยนแปลง" ของแต่ละไฟล์ ความจริงคือทุกครั้งที่ commit Git ถ่ายภาพสถานะของไฟล์ทั้งโปรเจกต์ ณ วินาทีนั้น แล้วเก็บไว้ทั้งภาพ ไฟล์ไหนไม่เปลี่ยน Git ก็แค่ชี้กลับไปใช้ภาพเดิม (ไม่เก็บซ้ำ จึงไม่เปลืองที่) มอง commit เป็น "เซฟพอยต์" ที่หยิบสถานะทั้งหมดกลับมาได้ทันที ไม่ใช่ผลรวมของ diff ที่ต้องไล่คำนวณ
2.2 สามพื้นที่ที่ไฟล์เดินทางผ่าน
หัวใจของการใช้งาน Git คือเข้าใจว่าไฟล์หนึ่งไฟล์มีได้สามสถานะ อยู่ในสามพื้นที่:
add ย้ายการเปลี่ยนแปลงเข้าพื้นที่จัดเตรียม, commit ผนึกพื้นที่จัดเตรียมเป็นภาพถ่ายถาวร — แยกสองขั้นเพื่อให้ “เลือก” ได้ว่าจะบันทึกอะไรทำไมต้องมีพื้นที่จัดเตรียม (staging) คั่นกลาง? เพราะมันให้คุณ ประกอบ commit อย่างตั้งใจ — แก้ไปสิบไฟล์ แต่เลือก add แค่สามไฟล์ที่เป็นเรื่องเดียวกันมา commit ก่อนได้ ทำให้ประวัติแต่ละ commit เล่าเรื่องชัดเจนทีละเรื่อง ไม่ปนกันมั่ว
2.3 SHA — ลายนิ้วมือของทุกอย่าง
ทุก commit มีรหัสประจำตัวยาว 40 ตัวอักษร เช่น 9f2c1a8... รหัสนี้ไม่ได้สุ่มมา แต่คำนวณจากเนื้อหาของ commit เองทั้งหมด (ไฟล์ + ข้อความ + ผู้เขียน + commit ก่อนหน้า) ผลที่ตามมาคือสิ่งที่ลึกซึ้งมาก: ถ้าเนื้อหาเปลี่ยนแม้ตัวอักษรเดียว รหัสจะเปลี่ยนทันที — แปลว่า commit แก้ไขไม่ได้ การ "แก้ประวัติ" จริง ๆ คือการสร้าง commit ใหม่ที่มีรหัสใหม่มาแทน (เข้าใจจุดนี้แล้ว บทที่ 6, 8, 12 จะกระจ่างทันที)
✅ สรุปบทที่ 2 — commit = ภาพถ่ายทั้งโปรเจกต์ ไม่ใช่ส่วนต่าง · ไฟล์เดินทางผ่าน 3 พื้นที่: working → staging (
add) → repository (commit) · staging มีไว้ให้ประกอบ commit อย่างตั้งใจ · SHA คำนวณจากเนื้อหา ทำให้ commit แก้ไม่ได้และปลอมไม่ได้ — รากฐานของความน่าเชื่อถือทั้งหมดของ Git
LEVEL 1 · พื้นฐานที่ใช้ทุกวัน
Workflow ประจำวัน#
วงจรการทำงานจริงของ Git แทบทั้งหมดวนอยู่ในสี่จังหวะ: แก้ → ดูว่าแก้อะไรไป → เลือกเข้าพื้นที่จัดเตรียม → บันทึก เข้าใจสี่คำสั่งนี้แล้วคุณทำงานได้ 90% ของชีวิตประจำวัน
3.1 status — เข็มทิศที่ต้องดูบ่อยที่สุด
git status บอกว่าตอนนี้ไฟล์ไหนอยู่สถานะใด — แก้แล้วแต่ยังไม่ add (untracked / modified), add แล้วรอ commit (staged) มือใหม่ควรพิมพ์คำสั่งนี้บ่อย ๆ จนเป็นนิสัย มันคือเข็มทิศที่กันหลงทาง
3.2 วงจรพื้นฐาน add → commit
$ git status # 1. ดูว่ามีอะไรเปลี่ยน
$ git diff # 2. ดูว่าเปลี่ยน "อะไร" ทีละบรรทัด
$ git add login.js # 3. เลือกไฟล์เข้าพื้นที่จัดเตรียม
$ git commit -m "เพิ่มหน้า login" # 4. ผนึกเป็นภาพถ่ายถาวรข้อความ commit (-m) ไม่ใช่พิธีกรรม — มันคือบันทึกที่ "ตัวคุณในอีกหกเดือน" จะอ่านเพื่อเข้าใจว่าทำไมโค้ดถึงเป็นแบบนี้ เขียนให้บอก เหตุผล ไม่ใช่แค่สิ่งที่ทำ
3.3 log — อ่านประวัติ
--oneline ย่อแต่ละ commit เหลือบรรทัดเดียว --graph วาดเส้นการแตกสายให้เห็นข้าง ๆ — คู่นี้คือท่าอ่านประวัติที่ใช้บ่อยที่สุด:
$ git log --oneline --graph
* 9f2c1a8 เพิ่มหน้า login
* 3b7d8c1 แก้บั๊กการคำนวณยอด
* a1e9f02 ตั้งค่าโปรเจกต์เริ่มต้น3.4 .gitignore — บอก Git ว่าอะไรไม่ต้องสน
บางไฟล์ไม่ควรอยู่ในประวัติ: รหัสลับ (.env), ไฟล์ที่สร้างอัตโนมัติ (node_modules/, build/), ไฟล์ระบบ (.DS_Store) ใส่ชื่อหรือรูปแบบลงไฟล์ .gitignore แล้ว Git จะมองข้ามถาวร
# .gitignore
node_modules/
.env
*.log
dist/✅ สรุปบทที่ 3 — วงจรประจำวัน:
status→diff→add→commit· ดูstatusให้ติดเป็นนิสัย · ข้อความ commit เขียนเพื่ออธิบายเหตุผลให้คนในอนาคต ·.gitignoreกันไฟล์ลับและไฟล์ขยะ แต่กันได้เฉพาะไฟล์ที่ยังไม่เคยติดตาม
Branch & Merge#
Branch คือสิ่งที่ทำให้ Git เปลี่ยนวิธีทำงานของคนทั้งวงการ แต่คนส่วนใหญ่เข้าใจมันซับซ้อนเกินจริง ความจริงนั้นเรียบง่ายมาก
4.1 Branch คือป้ายชี้ ไม่ใช่สำเนา
หลายคนคิดว่าสร้าง branch คือก๊อปโปรเจกต์ทั้งชุด ผิดอย่างสิ้นเชิง branch เป็นเพียงป้ายเล็ก ๆ ที่ชี้ไปยัง commit หนึ่งดวง — เบาแค่ไฟล์ข้อความที่เก็บรหัส SHA หนึ่งบรรทัด นี่คือเหตุผลที่สร้าง branch ใหม่เร็วทันใจและแทบไม่กินที่เลย ส่วน HEAD คือป้ายพิเศษที่บอกว่า "ตอนนี้คุณกำลังยืนอยู่ที่ branch ไหน"
HEAD อยู่ที่ feature ไม่ใช่ main$ git switch -c feature # สร้าง branch ใหม่ + ย้ายไปยืนที่นั่น
$ git switch main # ย้ายกลับมา main (HEAD ขยับ)เมื่อ HEAD ขยับไป branch ไหน Git จะปรับไฟล์ในโฟลเดอร์ให้ตรงกับภาพถ่ายของ branch นั้นทันที — คุณจึงทดลองบน feature ได้อย่างอิสระโดย main ไม่กระทบเลย
4.2 Merge — รวมเส้นทางกลับเข้าด้วยกัน
เมื่องานบน feature เสร็จ ก็รวมกลับเข้า main ด้วย merge ซึ่งมีสองหน้าตาขึ้นกับสถานการณ์:
✅ สรุปบทที่ 4 — branch = ป้ายชี้ commit ที่เบาและสร้างเร็ว ไม่ใช่สำเนาโปรเจกต์ ·
HEAD= ตำแหน่งที่คุณยืนอยู่ ·switchขยับ HEAD และปรับไฟล์ให้ตรง branch · merge มีสองแบบ: fast-forward (เลื่อนป้าย) และ merge commit (รวมสองสายที่ต่างมีงานใหม่)
ทำงานกับ Remote#
จนถึงบทนี้ทุกอย่างเกิดในเครื่องคุณคนเดียว Remote คือสำเนาของ repository ที่อยู่บนเซิร์ฟเวอร์ (เช่น GitHub) ทำหน้าที่เป็นจุดนัดพบให้ทีมแลกเปลี่ยนงานกัน
5.1 clone และ origin
git clone <url> ดึงทั้ง repository (ประวัติครบ) ลงเครื่อง และตั้งชื่อย่อให้เซิร์ฟเวอร์ต้นทางว่า origin โดยอัตโนมัติ — origin เป็นแค่ชื่อเล่นของ URL ยาว ๆ จะได้ไม่ต้องพิมพ์ซ้ำ
5.2 ยืนยันตัวตนกับ remote — HTTPS vs SSH
ก่อน push (หรือ clone repo ส่วนตัว) ได้ Git ต้องพิสูจน์ก่อนว่าคุณคือคุณจริง ๆ — ด่านนี้เป็นจุดที่มือใหม่แทบทุกคนสะดุดตอน push ครั้งแรก เพราะรูปแบบ URL ของ remote มีสองสายที่ยืนยันตัวตนต่างกัน:
| HTTPS | SSH | |
|---|---|---|
| รูปแบบ URL | https://github.com/user/repo.git | git@github.com:user/repo.git |
| ยืนยันตัวตนด้วย | Personal Access Token (PAT) | คู่กุญแจ SSH (public/private) |
| ตั้งค่า | เร็ว แต่ต้องกรอก token ทุกเครื่องที่ใช้ | ตั้งครั้งเดียวต่อเครื่อง ใช้ได้ทุก repo |
ฝั่ง SSH ตั้งค่าครั้งเดียวแล้วไม่ต้องยุ่งอีก:
$ ssh-keygen -t ed25519 -C "you@example.com" # สร้างคู่กุญแจ — Enter รับค่าเริ่มต้นได้ทุกช่อง
$ ssh-add ~/.ssh/id_ed25519 # โหลดกุญแจเข้า ssh-agent
$ cat ~/.ssh/id_ed25519.pub # คัดลอกไปวางที่ GitHub → Settings → SSH keys
$ ssh -T git@github.com # ทดสอบ — ควรเห็นข้อความทักทายชื่อคุณสลับ remote ระหว่างสองแบบได้ทุกเมื่อโดยไม่ต้อง clone ใหม่ ด้วย git remote set-url origin <url ใหม่>
5.3 fetch ต่างจาก pull อย่างไร — จุดที่คนสับสนมากที่สุด
fetch = ดาวน์โหลดงานใหม่มาดูก่อน ปลอดภัยเสมอ · pull = fetch แล้ว merge เข้า branch ให้เลยในก้าวเดียว · push = ส่งงานของคุณขึ้นเซิร์ฟเวอร์กฎง่าย ๆ: fetch ไม่เคยแก้ไฟล์ในโฟลเดอร์คุณ มันแค่อัปเดต "ความรู้" ว่าเซิร์ฟเวอร์มีอะไรใหม่ ส่วน pull เปลี่ยนไฟล์จริงเพราะมัน merge ให้เลย ถ้าอยากเห็นก่อนว่าทีมเปลี่ยนอะไรมาแล้วค่อยรวม ให้ fetch แล้วดู git log origin/main ก่อน
5.4 ไปทำงานต่อบน branch ของเพื่อนที่ยังไม่มีในเครื่อง
เพื่อนสร้าง feature-x แล้ว push ขึ้นเซิร์ฟเวอร์ แต่เครื่องคุณยังไม่มี branch นี้ (มีแค่ origin/feature-x ที่เห็นหลัง fetch) ไม่ต้องสร้าง branch ใหม่เอง แค่:
$ git fetch
$ git switch feature-x
branch 'feature-x' set up to track 'origin/feature-x'.
Switched to a new branch 'feature-x'Git ใช้หลัก "เดาแบบตรงไปตรงมา" (DWIM): ไม่มี branch ชื่อนี้ในเครื่อง แต่มี origin/feature-x ที่ชื่อตรงกันอยู่แหล่งเดียว มันจึงสร้าง branch ท้องถิ่นให้อัตโนมัติพร้อมผูก tracking กับ origin/feature-x เลยในคำสั่งเดียว — ทำงานต่อได้ทันทีเหมือนสร้างเอง หากมีมากกว่าหนึ่ง remote ที่มีชื่อนี้ Git จะเดาไม่ได้ ต้องระบุเองด้วย git switch -c feature-x --track origin/feature-x
5.5 push และ tracking branch
$ git push -u origin main # ส่งขึ้น + ผูก local กับ origin/main
$ git push # ครั้งต่อไปพิมพ์สั้น ๆ ได้เลย-u สร้างความสัมพันธ์ "tracking" ระหว่าง branch ในเครื่องกับบนเซิร์ฟเวอร์ ครั้งเดียวพอ หลังจากนั้น Git รู้เองว่า push/pull ควรคุยกับ branch ไหน
✅ สรุปบทที่ 5 —
cloneดึง repo + ตั้งชื่อต้นทางว่าorigin·fetchดาวน์โหลดมาดูเฉย ๆ ปลอดภัย,pull= fetch + merge เปลี่ยนไฟล์จริง ·pushส่งงานขึ้น,-uผูก tracking ครั้งเดียว · push ถูกปฏิเสธให้ pull ก่อน ไม่ใช่ force
LEVEL 2 · ระดับกลาง
ทิ้ง/ย้อนงานที่ยังไม่แชร์#
ความกลัวที่สุดของมือใหม่คือ "ทำพัง แล้วงานหาย" ความจริงคือ Git ออกแบบมาให้กู้คืนได้แทบทุกอย่าง ขอแค่เคย commit ไว้ บทนี้ว่าด้วยการทิ้ง/ย้อนงานที่ ยังไม่แชร์กับใคร — ยังอยู่ในเครื่องคุณคนเดียว จึงย้อนได้อย่างอิสระเต็มที่โดยไม่กระทบใคร กุญแจคือเข้าใจว่าแต่ละคำสั่ง "ย้อน" คนละชั้นกัน (งานที่ push แชร์ไปแล้วมีกติกาต่างออกไป — บทที่ 7)
6.1 restore — ทิ้งการแก้ที่ยังไม่ commit
แก้ไฟล์มั่วจนอยากเริ่มใหม่? git restore <file> ดึงไฟล์กลับไปเป็นเหมือน commit ล่าสุด ส่วน git restore --staged <file> เอาไฟล์ออกจากพื้นที่จัดเตรียม (ถอน add) โดยไม่ทิ้งการแก้
6.2 reset — เลื่อนป้าย branch ถอยหลัง
เข้าใจ reset ได้ทันทีถ้าจำว่ามันแค่ เลื่อนป้าย branch ไปชี้ commit อื่น ความต่างอยู่ที่ว่ามันจะแตะพื้นที่จัดเตรียมและไฟล์จริงด้วยไหม:
--soft แตะแค่ป้าย, --mixed แตะป้าย+staging, --hard แตะถึงไฟล์จริง — จุดเดียวที่ทำให้งานหายถาวร| โหมด | เลื่อนป้าย | พื้นที่จัดเตรียม | ไฟล์จริง | ใช้เมื่อ |
|---|---|---|---|---|
--soft | ✓ | คงไว้ | คงไว้ | รวมหลาย commit เป็นอันเดียว, แก้ข้อความ |
--mixed (ค่าเริ่มต้น) | ✓ | ล้าง | คงไว้ | ถอน commit แต่เก็บงานไว้ add ใหม่ |
--hard | ✓ | ล้าง | ลบทิ้ง | ทิ้งทุกอย่าง กลับไปจุดเดิมจริง ๆ |
✅ สรุปบทที่ 6 —
restoreทิ้งการแก้ที่ยังไม่ commit ·resetเลื่อนป้าย branch (soft เก็บงาน, hard ลบงาน) ·commit --amendแก้ commit ล่าสุด · ทั้งหมดนี้ทำได้อย่างอิสระเพราะยังไม่แชร์กับใคร — พอ push แล้วกติกาเปลี่ยนทันที (บทที่ 7)
ย้อนงานที่แชร์แล้ว & reflog#
งานที่ push ขึ้นไปแล้วมีคนอื่นถือสำเนาอยู่ด้วย จะ reset หรือ amend ทิ้งตรง ๆ แบบบทที่ 6 ไม่ได้อีกต่อไป เพราะนั่นคือการเขียนประวัติทับสิ่งที่คนอื่นมีอยู่แล้ว บทนี้คือวิธีย้อนงานที่แชร์แล้วอย่างปลอดภัย และตาข่ายนิรภัยตัวจริงที่รองรับความผิดพลาดได้เกือบทุกกรณีตลอดทั้งเล่ม
7.1 revert — ย้อนแบบปลอดภัยสำหรับงานที่แชร์แล้ว
reset เขียนประวัติใหม่ จึงห้ามใช้กับ commit ที่ push ขึ้นไปแล้ว (คนอื่นมีของชุดนั้นอยู่) ทางที่ถูกคือ git revert <commit> ซึ่ง สร้าง commit ใหม่ที่ทำตรงข้ามกับ commit เดิม — ผลลัพธ์เหมือนยกเลิก แต่ประวัติเดินหน้าต่อ ไม่มีใครเดือดร้อน
7.2 reflog — ตาข่ายนิรภัยที่ช่วยชีวิต
นี่คือคำสั่งที่จะทำให้คุณเลิกกลัว Git ตลอดไป ทุกครั้งที่ HEAD ขยับ — commit, switch, reset, rebase — Git จดไว้ใน reflog สมุดบันทึกส่วนตัวในเครื่อง ถึงจะ reset --hard ทิ้ง commit ไปแล้ว commit นั้นก็ยังอยู่ และ reflog ยังจำรหัสมันได้:
$ git reflog
a1e9f02 HEAD@{0}: reset: moving to a1e9f02 ← จุดที่เพิ่งพลาด (ย้อนไป init)
9f2c1a8 HEAD@{1}: commit: เพิ่มหน้า login ← commit ที่นึกว่าหาย
$ git reset --hard 9f2c1a8 # ดึงกลับมา — งานกลับมาครบข้อควรรู้: reflog ไม่ได้จำไว้ตลอดกาล มันมีอายุจำกัด (รายละเอียดและตัวเลขจริงอยู่บทที่ 14.1) — เจอของหายให้รีบกู้ อย่าปล่อยผ่านนานเป็นเดือน
✅ สรุปบทที่ 7 —
revertสร้าง commit ตรงข้ามกับของเดิม ใช้กับงานที่ push แชร์แล้วโดยไม่กระทบคนอื่น ·reflogจำทุกที่ที่ HEAD เคยอยู่ — กู้ commit ที่นึกว่าหายได้เกือบเสมอ แต่มีอายุจำกัด ไม่ใช่ถาวร (บทที่ 14.1)
Merge vs Rebase#
สองคำสั่งนี้ตอบโจทย์เดียวกัน — "เอางานสองสายมารวมกัน" — แต่ให้ประวัติคนละหน้าตา การเลือกใช้สะท้อนปรัชญาว่าคุณอยากให้ประวัติ บอกความจริงตามที่เกิด หรือ เล่าเรื่องให้อ่านง่าย
8.1 เลือกอย่างไร
| Merge | Rebase | |
|---|---|---|
| ประวัติ | ตามจริง เห็นการแตกสาย | เส้นตรง อ่านเป็นเรื่องเดียว |
| commit เดิม | คงไว้ทั้งหมด | สร้างใหม่ (รหัสเปลี่ยน) |
| เหมาะกับ | รวมงานเข้า branch หลักที่แชร์กัน | เก็บกวาด branch ส่วนตัวให้สะอาดก่อนเสนอ |
ทางเลือกนี้มักผูกกับกลยุทธ์ branch ของทั้งทีมด้วย — trunk-based (บทที่ 10.3) มักไปกับ rebase เพราะอยากได้ประวัติ main เป็นเส้นตรง ส่วน Git Flow ที่มี branch ยืนยาวมักไปกับ merge เพื่อเก็บร่องรอยการแตกสายไว้
8.2 interactive rebase — เครื่องมือเก็บกวาดประวัติ
git rebase -i ให้คุณแก้ชุด commit ก่อนเสนอเข้าทีม — รวมหลาย commit ย่อย ("แก้ typo", "แก้ typo อีกที") ให้เหลือก้อนเดียวที่มีความหมาย, เปลี่ยนข้อความ, สลับลำดับ คำสั่งที่ใช้บ่อย: squash (ยุบรวม), reword (แก้ข้อความ), fixup (ยุบโดยทิ้งข้อความ), drop (ลบ commit)
8.3 conflict ตอน rebase ต่างจาก merge conflict อย่างไร
merge conflict (บทที่ 10.2) เกิดครั้งเดียว แก้จบในคราวเดียว แต่ rebase เล่นซ้ำ (replay) commit ทีละดวงไปวางบนฐานใหม่ ถ้าหลาย commit ต่างชนกับฐานคนละจุด conflict จะโผล่ทีละรอบตามลำดับ ไม่ใช่ครั้งเดียวจบ:
$ git rebase main
CONFLICT (content): Merge conflict in price.js
# แก้ไฟล์ ลบเครื่องหมาย <<< === >>> เหมือน merge conflict ทุกประการ
$ git add price.js
$ git rebase --continue # ไปต่อ commit ถัดไป — อาจเจอ conflict ซ้ำอีกดวงทางเลือกเมื่อติด conflict ระหว่าง rebase: --continue แก้เสร็จแล้วไปต่อ, --skip ข้าม commit นี้ทั้งดวง (ทิ้งการเปลี่ยนแปลงของมันไปเลย), --abort ยกเลิกทั้งหมดกลับไปจุดก่อน rebase เหมือนไม่เคยเริ่ม — ตัวเลือกที่ปลอดภัยที่สุดเวลาพันกันจนไม่แน่ใจ หาก conflict เดิมโผล่ซ้ำหลายรอบระหว่าง rebase ยาว ๆ นั่นคือสถานการณ์ที่ rerere (บทที่ 13) ถูกออกแบบมาแก้โดยเฉพาะ
✅ สรุปบทที่ 8 — merge เก็บประวัติตามจริง (มี merge commit), rebase ทำประวัติให้เป็นเส้นตรงโดยสร้าง commit ใหม่ · ใช้ rebase เก็บกวาด branch ส่วนตัว, ใช้ merge รวมเข้า branch หลัก ·
rebase -iยุบ/แก้/จัดเรียง commit ก่อนเสนอ · conflict ตอน rebase แก้ไฟล์เหมือน merge แต่เกิดซ้ำได้ทีละ commit ใช้--continue/--skip/--abort· กฎเหล็ก: ห้าม rebase สิ่งที่แชร์ไปแล้ว
Stash · Cherry-pick · Tag#
สามเครื่องมือที่ไม่ได้ใช้ทุกวัน แต่วันที่ต้องใช้มันช่วยได้มาก
9.1 stash — พักงานค้างไว้ชั่วคราว
กำลังแก้อยู่ครึ่งทาง แล้วต้องรีบสลับไปแก้บั๊กด่วนอีก branch? git stash เก็บงานที่ยังไม่ commit เข้าที่พักชั่วคราว คืนโฟลเดอร์ให้สะอาด พอกลับมาก็ git stash pop เอางานออกมาทำต่อ — เหมือนกวาดของบนโต๊ะใส่ลิ้นชักไว้ก่อน แล้วหยิบกลับมาทีหลัง
$ git stash # เก็บงานค้าง โฟลเดอร์กลับมาสะอาด
$ git switch hotfix # ไปทำงานด่วน
$ git stash pop # กลับมาเอางานค้างออกมาทำต่อ9.2 cherry-pick — หยิบ commit เดี่ยวข้าม branch
บางที branch อื่นมี commit ที่เราอยากได้แค่ดวงเดียว ไม่ใช่ทั้ง branch git cherry-pick <commit> ก๊อปการเปลี่ยนแปลงของ commit นั้นมาวางบน branch ปัจจุบัน เช่นดึงเฉพาะ commit แก้บั๊กเร่งด่วนมาใส่ branch release โดยไม่เอาฟีเจอร์ที่ยังไม่เสร็จมาด้วย
9.3 tag — ปักหมุดเวอร์ชันสำคัญ
ขณะที่ branch ขยับไปเรื่อย ๆ tag คือป้ายถาวรที่ปักลง commit หนึ่งแล้วไม่ขยับ ใช้ทำเครื่องหมายเวอร์ชันที่ปล่อยจริง เช่น v1.0.0 มีสองชนิด: lightweight (แค่ชื่อ) และ annotated (มีผู้สร้าง วันที่ ข้อความ — แนะนำสำหรับ release จริง)
$ git tag -a v1.0.0 -m "ปล่อยเวอร์ชันแรก"
$ git push origin v1.0.0 # tag ไม่ถูก push ไปกับ push ปกติ ต้องส่งเอง✅ สรุปบทที่ 9 —
stashพักงานค้างชั่วคราวเพื่อสลับไปทำอย่างอื่น แล้วpopกลับมา ·cherry-pickหยิบ commit เดี่ยวข้าม branch ·tagปักหมุดถาวรลงเวอร์ชันสำคัญ (annotated สำหรับ release) และต้อง push แยกต่างหาก
LEVEL 3 · ระดับสูง
ทำงานเป็นทีม#
คำสั่ง Git เป็นแค่เครื่องมือ สิ่งที่ทำให้ทีมไม่พังคือ ข้อตกลงร่วมกันว่าจะใช้มันอย่างไร บทนี้ว่าด้วยแนวทางที่ทีมส่วนใหญ่ยึด
10.1 Pull Request — ประตูตรวจงานก่อนรวม
แทนที่จะ push เข้า main ตรง ๆ ทุกคนทำงานบน branch ของตัวเองแล้วเปิด Pull Request (PR) — คำขอให้ทีมรีวิวก่อนรวม PR ทำให้เกิดสามอย่างที่ดีต่อทีม: มีคนตรวจโค้ดก่อนเข้า main, มีที่ถกเถียงเป็นลายลักษณ์อักษร, และรัน CI (ทดสอบอัตโนมัติ) ก่อน merge ได้ — main จึงสะอาดและพร้อมใช้เสมอ
10.2 แก้ merge conflict อย่างเข้าใจ
Conflict เกิดเมื่อสองสายแก้ไฟล์เดียวกัน ตำแหน่งเดียวกัน Git เปิดเครื่องหมายไว้ให้คุณเลือก:
<<<<<<< HEAD
ราคา = total * 1.07 # ฝั่งเรา (branch ปัจจุบัน)
=======
ราคา = total * 1.10 # ฝั่งที่กำลังรวมเข้ามา
>>>>>>> feature-vatหน้าที่คุณคือตัดสินใจว่าเก็บอันไหน (หรือผสมเป็นอันใหม่) ลบสามบรรทัดเครื่องหมายออกให้หมด แล้ว git add ไฟล์นั้นเพื่อบอกว่าแก้เสร็จ จากนั้น commit — conflict ไม่ใช่ความผิดพลาด แต่เป็น Git ฉลาดพอที่จะไม่เดาแทนคุณ วิธีแก้ไฟล์เหมือนกันทุกประการไม่ว่าจะชนตอน merge หรือตอน rebase ต่างแค่ตอน rebase อาจเจอซ้ำได้หลายรอบทีละ commit (รายละเอียดบทที่ 8.3)
10.3 สองปรัชญาการจัดการ branch
| Git Flow | Trunk-based | |
|---|---|---|
| แนวคิด | มีหลาย branch ยืนยาว (develop, release, hotfix) | ทุกคนรวมเข้า main บ่อย ๆ branch สั้น ๆ |
| เหมาะกับ | ซอฟต์แวร์ปล่อยเป็นรอบ มีหลายเวอร์ชันต้องดูแล | เว็บ/SaaS ที่ deploy บ่อย ทีมเล็ก-กลาง |
| ความเสี่ยง | branch อยู่นานยิ่ง merge ยาก | ต้องมีชุดทดสอบดี + feature flag |
กระแสปัจจุบันเอนไปทาง trunk-based เพราะ branch ที่อยู่สั้นแปลว่า conflict น้อยและรวมงานง่าย — หลักการลึก ๆ คือ ยิ่งรวมงานบ่อย ยิ่งเจ็บน้อย แนวโน้มนี้เชื่อมกับการเลือก merge/rebase ด้วย (บทที่ 8) — trunk-based มักคู่กับ rebase, Git Flow มักคู่กับ merge
10.4 ข้อความ commit ที่ทีมอ่านรู้เรื่อง
หลายทีมใช้ Conventional Commits ขึ้นต้นด้วยชนิดงาน เช่น feat: (ฟีเจอร์ใหม่), fix: (แก้บั๊ก), docs:, refactor: — รูปแบบสม่ำเสมอนี้ทำให้สแกนประวัติได้เร็วและสร้าง changelog อัตโนมัติได้
✅ สรุปบทที่ 10 — PR = ประตูรีวิว+ทดสอบก่อนเข้า main ทำให้ branch หลักสะอาดเสมอ · conflict คือ Git ขอให้คุณตัดสินใจ — เลือก ลบเครื่องหมาย add แล้ว commit · Git Flow เหมาะงานปล่อยเป็นรอบ, trunk-based เหมาะ deploy บ่อย · รวมงานบ่อยเจ็บน้อย · Conventional Commits ทำให้ประวัติอ่านง่าย
เครื่องมือสืบสวน#
ประวัติที่ Git เก็บไว้ไม่ได้มีไว้แค่ย้อนเวลา แต่เป็นฐานข้อมูลให้สืบหา "บั๊กนี้เกิดตอนไหน ใครเขียน เพราะอะไร" บทนี้คือเครื่องมือของนักสืบ
11.1 bisect — ค้นหา commit ต้นเหตุด้วยการแบ่งครึ่ง
บั๊กโผล่มาแต่ไม่รู้ commit ไหนทำพัง และมีหลายร้อย commit ให้ไล่ git bisect ใช้หลักการค้นแบบแบ่งครึ่ง: คุณบอกจุดที่ยังดี (good) และจุดที่พังแล้ว (bad) Git จะกระโดดไปกึ่งกลางให้ทดสอบ แล้วตัดครึ่งที่ไม่เกี่ยวทิ้งทุกครั้ง — 1,000 commit หาเจอใน ~10 ครั้ง
$ git bisect start
$ git bisect bad # ตอนนี้พัง
$ git bisect good v1.0.0 # เวอร์ชันนี้เคยดี
Bisecting: 250 revisions left to test... # Git พาไปกึ่งกลาง ทดสอบแล้วบอก good/bad11.2 blame — ใครแก้บรรทัดนี้ และทำไม
git blame <file> แสดงทีละบรรทัดว่า commit ไหนแก้ครั้งล่าสุด ใครเป็นคนแก้ ชื่อฟังดูเหมือนหาคนผิด แต่จริง ๆ ใช้เพื่อ เข้าใจบริบท — เจอโค้ดแปลก ๆ blame ไปดู commit นั้นว่าแก้เพราะอะไร มักมีเหตุผลซ่อนอยู่
11.3 log ขั้นสูง — ขุดประวัติแบบเจาะจง
| คำสั่ง | หาอะไร |
|---|---|
git log -S "ฟังก์ชัน" | commit ที่เพิ่ม/ลบข้อความนี้ในโค้ด (pickaxe) — ตามรอยว่าโค้ดบรรทัดหนึ่งโผล่มาหรือหายไปเมื่อไหร่ |
git log --grep "ยอด" | commit ที่ข้อความ commit มีคำนี้ |
git log main..feature | commit ที่อยู่ใน feature แต่ยังไม่อยู่ใน main |
git log --author="Khaofang" | เฉพาะ commit ของคนนี้ |
✅ สรุปบทที่ 11 —
bisectหา commit ต้นเหตุด้วยการแบ่งครึ่ง — เร็วแบบ log₂ ·blameดูว่าใครแก้บรรทัดไหนเพื่อเข้าใจบริบท ไม่ใช่หาคนผิด ·logขั้นสูง:-Sตามรอยข้อความในโค้ด,--grepค้นข้อความ commit,A..Bดูส่วนต่างระหว่าง branch
LEVEL 4 · ระดับเทพ
กลไกภายในของ Git#
บทนี้คือเส้นแบ่งระหว่าง "ใช้ Git เป็น" กับ "เข้าใจ Git" เมื่อเห็นว่าข้างในมันเก็บข้อมูลอย่างไร คำสั่งทุกบทก่อนหน้าจะกระจ่างขึ้นพร้อมกัน และคำถามกวนใจอย่าง "ทำไม commit ถึงแก้ไม่ได้" หรือ "ทำไม branch ถึงสร้างเร็วจัง" จะมีคำตอบที่ชัดเจน
12.1 เปิดดูข้างใน .git/
โฟลเดอร์ .git/ ที่ init สร้างขึ้น ไม่ใช่กล่องดำ — มันคือฐานข้อมูลที่เปิดดูได้ ข้างในมี objects/ (ที่เก็บข้อมูลจริงทั้งหมด), refs/ (ป้าย branch และ tag), และไฟล์ HEAD หัวใจของทั้งหมดคือประโยคเดียว:
Git คือฐานข้อมูลที่ค้นด้วยเนื้อหา (content-addressable)
หมายความว่า Git เก็บทุกชิ้นข้อมูลโดยใช้ SHA ที่คำนวณจากเนื้อหานั้นเอง เป็นกุญแจ ให้เนื้อหา → ได้กุญแจ → เก็บ/หยิบด้วยกุญแจนั้น เนื้อหาเดียวกันได้กุญแจเดียวกันเสมอ จึงไม่มีวันเก็บซ้ำ
12.2 วัตถุสี่ชนิดที่ประกอบเป็น Git ทั้งหมด
ลองพิสูจน์ด้วยตาเอง — แกะ commit ออกมาดู:
$ git cat-file -p HEAD
tree a1b2c3... ← ภาพถ่ายโปรเจกต์ ณ commit นี้
parent 3b7d8c1... ← commit ก่อนหน้า
author Khaofang <...> 1718...
เพิ่มหน้า login12.3 ไขปริศนาที่ค้างคามาทั้งเล่ม
- ทำไม commit แก้ไม่ได้? เพราะ SHA ของมันคำนวณจากเนื้อหา (รวมถึง tree และ parent) แก้อะไรนิดเดียว SHA เปลี่ยน กลายเป็น commit คนละดวงทันที ของเดิมยังอยู่ — "แก้ประวัติ" จึงคือสร้างของใหม่มาแทน เสมอ
- ทำไม branch ถึงเบาและเร็ว? เพราะมันเป็นแค่ไฟล์ข้อความใน
refs/heads/ที่เก็บ SHA หนึ่งบรรทัด สร้าง branch = เขียนไฟล์ 40 ตัวอักษร ไม่ได้ก๊อปอะไรเลย - ทำไมไฟล์ที่ไม่เปลี่ยนถึงไม่กินที่เพิ่ม? เพราะ blob เดิมมี SHA เดิม commit ใหม่แค่ชี้ blob เดิมซ้ำ ไม่เก็บใหม่
- reflog/reset กู้ของได้เพราะอะไร? commit ที่ "หาย" แค่ไม่มีป้ายชี้ แต่ object ยังอยู่ใน
objects/จนกว่าจะถูกเก็บกวาด — รู้ SHA ก็ดึงกลับได้
12.4 packfiles — ทำไม repo ใหญ่ ๆ ถึงไม่บวม
ถ้าเก็บทุก blob แยกไฟล์จริง ๆ repo ที่มีประวัติยาวคงโตมหาศาล Git จึงบีบอัด object จำนวนมากเข้า packfile เดียวเป็นระยะ ๆ (ตอน gc หรือ push) โดยเก็บเฉพาะส่วนต่างระหว่าง object ที่คล้ายกัน — ได้ประโยชน์ของ snapshot ในการใช้งาน แต่ประหยัดที่แบบ diff ในการจัดเก็บ
✅ สรุปบทที่ 12 — Git = ฐานข้อมูลค้นด้วยเนื้อหา เก็บทุกอย่างด้วย SHA ของเนื้อหานั้น · วัตถุสี่ชนิด: blob (เนื้อไฟล์), tree (สารบบ), commit (tree + ผู้เขียน + parent), tag · commit แก้ไม่ได้เพราะ SHA ผูกกับเนื้อหา · branch เบาเพราะเป็นแค่ไฟล์เก็บ SHA · ของที่ "หาย" ยังอยู่จนถูกเก็บกวาด · packfile บีบอัดเพื่อประหยัดที่
เครื่องมือทรงพลัง#
เครื่องมือเฉพาะทางที่แก้ปัญหาซึ่งคำสั่งพื้นฐานทำได้ลำบาก รู้ไว้ว่ามีอยู่ วันที่เจอโจทย์ตรงจะนึกออก
13.1 worktree — หลายโฟลเดอร์ ประวัติเดียว
ปกติหนึ่ง repo เปิดได้ทีละ branch git worktree ให้คุณกาง branch หลายอันลงหลายโฟลเดอร์พร้อมกันโดยใช้ประวัติชุดเดียว — แก้ฟีเจอร์ที่โฟลเดอร์หนึ่ง ขณะตรวจ PR อีกโฟลเดอร์ ไม่ต้อง stash สลับไปมา:
$ git worktree add ../myapp-hotfix hotfix # กาง branch "hotfix" ที่มีอยู่แล้วลงโฟลเดอร์ใหม่
$ git worktree add -b fix/urgent ../myapp-fix main # หรือสร้าง branch ใหม่จาก main พร้อมกางในตัว
$ git worktree list
/path/myapp 9f2c1a8 [main]
/path/myapp-hotfix 3b7d8c1 [hotfix]
$ git worktree remove ../myapp-hotfix # เลิกใช้แล้วเก็บโฟลเดอร์ทิ้งทั้งสองโฟลเดอร์ชี้ไปที่ .git ชุดเดียวกัน — commit จาก worktree ไหนก็เห็นได้จากอีกอันทันที ต่างจากการ clone ซ้ำที่ได้ประวัติแยกกันคนละชุด
13.2 hooks — สั่งให้ Git ทำงานอัตโนมัติ
ในทุก repo มีโฟลเดอร์ .git/hooks/ สำหรับสคริปต์ที่ Git เรียกอัตโนมัติเมื่อเกิดเหตุการณ์ ที่นิยมคือ pre-commit — รันก่อน commit ทุกครั้ง ใช้ตรวจ lint หรือฟอร์แมตโค้ดให้ผ่านก่อนถึงจะ commit ได้ ป้องกันงานเสียเข้าประวัติตั้งแต่ต้นทาง:
# .git/hooks/pre-commit — ตั้งชื่อไฟล์ให้ตรงเป๊ะ ไม่มีนามสกุล แล้ว chmod +x ให้รันได้
#!/bin/sh
npm run lint || { echo "lint ไม่ผ่าน — commit ถูกยกเลิก"; exit 1; }ถ้าสคริปต์จบด้วย exit code ที่ไม่ใช่ 0 Git จะยกเลิก commit ทันที — ยังไม่ทันขอข้อความ commit ด้วยซ้ำ hook ยอดนิยมอื่น ๆ: commit-msg (ตรวจรูปแบบข้อความ commit), pre-push (รันเทสต์ก่อนส่งขึ้น remote) หมายเหตุ: hook อยู่เฉพาะเครื่อง ไม่ถูก push ไปกับ repo — ถ้าอยากให้ทั้งทีมใช้ชุดเดียวกัน ตั้งค่า core.hooksPath ให้ชี้ไปโฟลเดอร์ที่ commit เก็บไว้ในโปรเจกต์แทน
13.3 submodule กับ subtree — ฝัง repo ในอีก repo
| Submodule | Subtree | |
|---|---|---|
| เก็บอะไร | แค่ตัวชี้ไป commit ของ repo อื่น | ก๊อปโค้ดเข้ามารวมในประวัติเลย |
| ข้อดี | repo หลักเล็ก แยกเวอร์ชันชัด | clone ทีเดียวได้ครบ ไม่ต้องสั่งเพิ่ม |
| ข้อเสีย | คนในทีมต้องรู้จักคำสั่ง submodule | ประวัติปนกัน อัปเดตย้อนกลับยุ่ง |
13.4 rerere & sparse-checkout
- rerere (reuse recorded resolution) — เปิดไว้แล้ว Git จะจำวิธีที่คุณแก้ conflict หนึ่ง ๆ พอเจอ conflict เดิมซ้ำ (เช่นตอน rebase ยาว ๆ) มันแก้ให้เองอัตโนมัติ
- sparse-checkout — กางเฉพาะบางโฟลเดอร์ของ repo ลงเครื่อง มีประโยชน์มากกับ monorepo ขนาดมหึมาที่คุณสนใจแค่ส่วนเดียว
✅ สรุปบทที่ 13 —
worktreeกาง branch หลายอันหลายโฟลเดอร์พร้อมกัน ·hooks(เช่น pre-commit) รันสคริปต์อัตโนมัติตามเหตุการณ์ · submodule = ตัวชี้ repo อื่น, subtree = ฝังโค้ดเข้ามาเลย · rerere จำวิธีแก้ conflict ซ้ำ, sparse-checkout กางเฉพาะบางส่วนของ repo ใหญ่
กู้ของหาย & ซ่อมประวัติ#
เมื่อเข้าใจแล้วว่า object ไม่หายไปง่าย ๆ (บทที่ 12) คุณจะกู้สถานการณ์ที่คนอื่นคิดว่าหมดหวังได้ บทนี้คือชุดทักษะกู้ภัย — และเส้นแบ่งว่าเมื่อไหร่การเขียนประวัติใหม่ถึงปลอดภัย
14.1 กู้ commit ที่นึกว่าหายแล้ว
เผลอ reset --hard หรือลบ branch ที่ยังไม่ merge? commit เหล่านั้นกลายเป็น dangling object — ไม่มีป้ายชี้ แต่ยังอยู่ใน objects/ วิธีกู้: หา SHA จาก reflog (ถ้า HEAD เคยแตะ) แล้วชี้ branch ใหม่กลับไป หรือใช้ git fsck --lost-found กวาดหา commit ที่ลอยอยู่ทั้งหมด
$ git fsck --lost-found
dangling commit 3b7d8c1...
$ git branch recovered 3b7d8c1 # ปักป้ายกลับ — งานกลับมา14.2 ลบไฟล์ลับออกจากทั้งประวัติ
เผลอ commit รหัสผ่านหรือไฟล์ใหญ่ไป การลบใน commit ใหม่ไม่พอ — มันยังฝังอยู่ในประวัติเก่าทุก commit ที่ผ่านมา ต้องเขียนประวัติใหม่ทั้งสายด้วย git filter-repo (เครื่องมือมาตรฐานปัจจุบัน แทน filter-branch ที่ช้าและพลาดง่าย) ที่จะลบไฟล์นั้นออกจากทุก commit
14.3 เมื่อไหร่เขียนประวัติใหม่ได้ เมื่อไหร่ห้าม
กฎเดียวที่ต้องจำ ครอบคลุมทั้ง rebase, reset, filter-repo, commit --amend:
| สถานการณ์ | เขียนประวัติใหม่ |
|---|---|
| commit ยังอยู่ในเครื่องคุณคนเดียว (ยังไม่ push) | ได้เลย — อิสระเต็มที่ |
| push ขึ้น branch ส่วนตัวที่ไม่มีใครใช้ร่วม | ได้ แต่ต้อง push --force-with-lease |
| commit อยู่บน main/branch ที่ทีมใช้ร่วม | ห้าม — ใช้ revert แทน |
--force-with-lease ปลอดภัยกว่า --force ตรงที่มันจะปฏิเสธถ้ามีคนอื่น push เพิ่มเข้ามาหลังคุณดึงล่าสุด — กันการทับงานเพื่อนโดยไม่รู้ตัว
✅ สรุปบทที่ 14 — commit ที่ "หาย" กู้ได้จาก
reflogหรือfsck --lost-foundแล้วปักป้ายกลับ · ลบไฟล์ลับทั้งประวัติด้วยfilter-repoแต่ถ้าเคยรั่วต้องเปลี่ยนรหัสเสมอ · เขียนประวัติใหม่ได้อิสระถ้ายังไม่แชร์, ใช้--force-with-leaseบน branch ส่วนตัว, ห้ามแตะประวัติที่ทีมใช้ร่วม — ใช้revertแทน
Repo ใหญ่ & บทสรุป#
15.1 เมื่อ repo เริ่มอืด
repo ที่โตมาก (ประวัติยาว ไฟล์เยอะ) จะเริ่มช้า เครื่องมือที่ช่วย:
- shallow clone —
git clone --depth 1ดึงแค่ commit ล่าสุด ไม่เอาประวัติทั้งหมด เหมาะกับ CI ที่ต้องการแค่โค้ดล่าสุด - Git LFS — ไฟล์ใหญ่ที่ไม่ใช่โค้ด (วิดีโอ, โมเดล, รูปความละเอียดสูง) ไม่เหมาะเก็บใน Git ตรง ๆ เพราะทุกเวอร์ชันถูกเก็บครบ LFS เก็บไฟล์จริงแยกที่อื่น เหลือไว้ใน repo แค่ตัวชี้
- git gc — เก็บกวาด object ที่ลอยและบีบอัดเป็น packfile (ปกติ Git ทำเองเป็นระยะ)
15.2 บทสรุปทั้งเล่ม — แก่นที่ถือกลับไป
ถ้าจำได้แค่ไม่กี่อย่างจากทั้งเล่ม ขอให้เป็นสิ่งเหล่านี้ เพราะทุกคำสั่งงอกออกมาจากมัน:
- commit คือ ภาพถ่ายทั้งโปรเจกต์ ที่มี SHA ผูกกับเนื้อหา — จึงแก้ไม่ได้ ปลอมไม่ได้ และเป็นรากของความน่าเชื่อถือทั้งหมด
- ไฟล์เดินทางผ่าน สามพื้นที่ (working → staging → repository) — เข้าใจสิ่งนี้แล้ว add/commit/restore/reset เป็นเรื่องเดียวกันหมด
- branch เป็นแค่ ป้ายชี้ commit — merge, rebase, reset ล้วนคือการขยับป้ายและผูกสายประวัติ
- เกือบทุกอย่าง กู้คืนได้ ถ้าเคย commit —
reflogคือตาข่ายนิรภัย กล้าทดลองได้เต็มที่ - เส้นแบ่งเดียวที่ต้องเคารพ: อย่าเขียนประวัติที่แชร์กับคนอื่นไปแล้ว
✅ สรุปบทที่ 15 — repo ใหญ่ใช้ shallow clone, Git LFS สำหรับไฟล์ใหญ่,
gcเก็บกวาด · แก่นทั้งเล่ม: commit = ภาพถ่ายที่แก้ไม่ได้ · สามพื้นที่ · branch = ป้ายชี้ · กู้คืนได้เสมอด้วย reflog · ห้ามแตะประวัติที่แชร์แล้ว — เท่านี้คุณก็จาก 0 ถึง Hero แล้ว
— จบหลักสูตร —
เล่มต่อไป: git push แล้วเกิดอะไรต่อ?#
ตลอดเล่มนี้ทุกอย่างจบลงที่ git push — งานของคุณขึ้นไปอยู่บนเซิร์ฟเวอร์แล้ว แต่มีคำถามที่ค้างอยู่: ใครเป็นคนตรวจว่าโค้ดที่เพิ่งรวมเข้ามายัง build ได้ เทสต์ผ่าน และไม่ทำของเดิมพัง? ถ้าให้คนนั่งตรวจเองทุกครั้ง ทุกอย่างจะช้าและพลาดง่าย คำตอบคือ CI/CD — การให้เครื่องตรวจและส่งมอบโค้ดอัตโนมัติทุกครั้งที่คุณ push หรือเปิด Pull Request
มันคือก้าวต่อจากตรงนี้พอดี: เหตุการณ์ใน Git ที่คุณเรียนมาทั้งเล่ม (push, PR, tag) คือ "ทริกเกอร์" ที่ปลุกสายการตรวจสอบและ deploy ให้ทำงานเอง
อ่านต่อ: System Design#
เข้าใจกลไกของ Git แล้ว รู้วิธีจัดการโค้ดของทีมให้ปลอดภัยและย้อนกลับได้ — แต่โค้ดที่ push ไปแล้วต้องรันบนระบบที่รับมือกับผู้ใช้จริงได้ System Design พาไปจากคำถาม "จัดการโค้ดอย่างไร" สู่ "ระบบที่ deploy ไปแล้วต้องออกแบบอย่างไรให้ scale ได้จริง" ตั้งแต่เซิร์ฟเวอร์เครื่องเดียวไปจนถึง production พร้อม auth และความปลอดภัย · เปิดอ่าน System Design Handbook →
Git 0 → Hero — คู่มือฉบับเข้าใจถึงแก่น · เขียนเป็นภาษาไทย พร้อมไดอะแกรมทุกหัวข้อ
วิธีใช้ให้คุ้ม: อ่านไล่ระดับ อย่าข้าม Level 0–1 แม้จะใช้ Git เป็นแล้ว เพราะแบบจำลองความคิดในบทต้นคือกุญแจของบทท้าย · ลองทุกคำสั่งใน repo ทดลองที่พังได้