Skip to content

บทที่ 2 — Git + GitHub Workflow

← บทที่ 1 | สารบัญ | ต่อไป: Docker Book →

📖 ตามลำดับแนะนำใน README ต่อจากบทนี้ให้ไปอ่าน Docker Book แล้ว Kubernetes Book ของจริง (บท 03/04 ในโฟลเดอร์นี้เป็นแค่ quick reference สั้น ๆ)

TL;DR: บทนี้ตอบ "Git ทำงานยังไงข้างใน + GitHub workflow แบบมืออาชีพ" — แผนภาพคิด (โครงสร้างกราฟของ commit + 3 พื้นที่ทำงาน), การรวม branch สองวิธี (merge / rebase), การย้อนกลับ (คำสั่ง reset 3 โหมด + reflog ที่เป็นตัวช่วยกู้คืน), การเปิด Pull Request + ตรวจโค้ด, hook ก่อน commit, และ workflow แบบ GitHub Flow. ข้ามได้ถ้า: ใช้ rebase + reflog + ตรวจ PR บน GitHub เป็นนิสัยอยู่แล้ว

หลังจบบท คุณจะ:

  • ใช้ git ใน workflow ปกติคล่อง (add, commit, push, pull)
  • เข้าใจ branch, merge, rebase + แก้ conflict โดยไม่ panic
  • ทำ PR + code review
  • กู้ commit ที่ลบ + undo อะไรก็ได้
  • ใช้ git อย่างมืออาชีพ (interactive rebase, cherry-pick, bisect)

0. บทนำ — อ่านก่อนเริ่ม

💡 มือใหม่ที่ใช้แค่ add/commit/push:

ส่วนนี้ (Mental Model + DAG + 3 พื้นที่ + reset modes) เป็น "ทฤษฎี" ที่จะอินเมื่อเจอปัญหาจริง — ถ้าคุณเพิ่งหัด Git ลอง ข้ามไปที่ Basic Workflow ก่อน. กลับมาอ่านส่วน Mental Model เมื่อ:

  • เจอ merge conflict ครั้งแรกแล้วงง
  • อยาก rebase แต่กลัวพัง
  • reset แล้ว commit หาย — อยากกู้

ตอนนั้น 3 อย่างข้างล่างจะกลายเป็น "อ้อ! เพราะอย่างนี้เอง"

Git ≠ Dropbox (หรือ Google Drive). Dropbox/Google Drive คือบริการ sync ไฟล์บนคลาวด์ — sync แค่ "ไฟล์ล่าสุด" Git sync ประวัติของ commit ทั้งหมด (ทุก snapshot ที่เคยบันทึกไว้ ย้อนกลับไปดูของเก่าได้)

💡 commit คืออะไร? คิดว่า commit คือ snapshot ของโค้ด ณ เวลาหนึ่ง — เหมือนถ่ายรูปโค้ดไว้ แต่ละรูปมีเลขประจำตัว (hash) และรู้ว่ารูปก่อนหน้าคืออะไร ทำให้ย้อนเวลากลับไปดูสถานะโค้ดตอนไหนก็ได้

ลึกกว่านั้น — Git เก็บข้อมูลเป็น DAG (Directed Acyclic Graph = กราฟที่ลูกศรไหลไปทิศทางเดียวและวนกลับมาที่เดิมไม่ได้) ของ commit — แต่ละ commit ชี้ไปหา commit ก่อนหน้า (parent) เป็นทอด ๆ ไม่มีวันวนเป็นวง:

กฎทอง 3 ข้อ:

  1. Commit = snapshot ไม่ใช่ diff — Git เก็บไฟล์เต็ม ๆ ในแต่ละ commit ไม่ใช่ "สิ่งที่เปลี่ยน" (แต่บีบอัดข้อมูลให้เล็กลง)
  2. Branch = ตัวชี้ (pointer) ขนาดเล็กมาก — ไม่ใช่ "สำเนาของโค้ดทั้งโปรเจกต์" สร้าง branch = สร้าง pointer ใหม่ที่ชี้ไปยัง commit ตัวหนึ่ง → ใช้พื้นที่แทบไม่กี่ไบต์ ฟรีและเร็ว
  3. HEAD = ตัวชี้ไปยัง branch ปัจจุบัน — ตัวอย่าง: HEAD → main → commit D

เมื่อเข้าใจ 3 ข้อนี้ → ทุก command ของ git จะกลายเป็น "ขยับ pointer" ที่เข้าใจง่าย:

  • git commit = สร้าง commit ใหม่ + ขยับ branch pointer
  • git checkout branch = ขยับ HEAD ไปอีก branch
  • git reset HEAD~1 = ขยับ branch pointer ถอยหลัง 1 commit (commit ไม่ได้หาย — แค่ไม่ชี้ไปแล้ว)
  • git merge = สร้าง commit ใหม่ที่มี 2 parents
  • git rebase = สร้าง commit ใหม่ที่ลอก content แต่เปลี่ยน parent

💡 ถ้างงตอนเรียน → กลับมาอ่าน mental model นี้ทุกครั้ง


1. ทำไม Git สำคัญ

ก่อนจะเรียนคำสั่ง ต้องเข้าใจก่อนว่า Git แก้ปัญหาอะไร — มันคือ "version control" ที่จดทุกการเปลี่ยนแปลงเป็น snapshot ทำให้ย้อนเวลาได้ ทดลอง feature โดยไม่พังของเดิม และให้หลายคนทำงานพร้อมกันได้ ลองเทียบชีวิตที่ไม่มีกับมี Git:

text
ไม่มี Git:
- Backup ทุกครั้งที่เขียน → folder version 1, 2, 3, ...
- ทำคนเดียวเท่านั้น
- เปลี่ยน 100 ที่ → revert ลำบาก

มี Git:
- ทุก commit = snapshot
- หลายคนทำงานพร้อมกัน
- ย้อนกลับได้
- แตก branch ไปทดลอง feature ได้โดยไม่ทำให้ main เสียหาย

2. Setup

ก่อนใช้งานครั้งแรกต้องตั้งค่า identity (ชื่อ/อีเมล ที่จะติดไปกับทุก commit) และเชื่อมต่อ GitHub ด้วย SSH key เพื่อ push/pull โดยไม่ต้องพิมพ์ password ทุกครั้ง ขั้นตอนนี้ทำครั้งเดียวต่อเครื่อง:

ติดตั้ง Git

bash
# Ubuntu / Debian / WSL2
sudo apt install git

# macOS (ผ่าน Homebrew)
brew install git

Windows — มี 2 ทางเลือก:

powershell
# (แนะนำ) ใช้ WSL2 — Linux อยู่ใน Windows สะดวกที่สุดสำหรับงาน DevOps
wsl --install
# จากนั้นเข้า WSL แล้ว: sudo apt install git

# หรือ Git for Windows แบบ native (ใช้ Git Bash)
winget install Git.Git

💡 ทำไมแนะนำ WSL2? เครื่องมือ DevOps ส่วนใหญ่ (Docker, Kubernetes CLI, shell script) สร้างมาให้รันบน Linux — WSL2 ทำให้ Linux อยู่ใน Windows โดยไม่ต้อง dual boot รายละเอียดอยู่ใน บทที่ 1

ตั้งค่า identity + ค่า default (ทำครั้งเดียวต่อเครื่อง)

bash
# ชื่อ/อีเมล ติดทุก commit
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

# ใช้ "main" เป็นชื่อ branch แรกเสมอ (แทน master)
git config --global init.defaultBranch main

# git pull ให้ใช้ rebase (เส้นตรง สวย — รายละเอียดในข้อ 8)
git config --global pull.rebase true

# Git 2.37+: push branch ใหม่ครั้งแรกไม่ต้องพิมพ์ --set-upstream
git config --global push.autoSetupRemote true

# จำการแก้ conflict ครั้งก่อน → ครั้งหน้า rebase ซ้ำ Git แก้ให้อัตโนมัติ
git config --global rerere.enabled true
bash
# ไม่บังคับ แต่สะดวก
git config --global core.editor "code --wait"          # ใช้ VS Code เป็น editor
git config --global alias.st status
git config --global alias.sw switch                    # ใช้ git sw แทน git switch
git config --global alias.br branch

# ตรวจสอบค่าที่ตั้ง
git config --list

💡 ทำไมเลือก pull.rebase true? ทำให้ประวัติเป็นเส้นตรงอ่านง่าย ไม่มี "merge commit งอก" จากการ pull ทุกครั้ง — รายละเอียดของ rebase อยู่ในข้อ 7–8

SSH Key สำหรับ GitHub

📖 ส่วน SSH key พื้นฐาน (ssh-keygen คืออะไร, key อยู่ที่ไหน) อยู่ใน บทที่ 1 — Linux & Shell หัวข้อ SSH ถ้ายังไม่คุ้นกลับไปอ่านก่อน

💡 public key คืออะไร? public key = กุญแจที่แชร์ได้ (เหมือนล็อกที่วางไว้ที่ GitHub) — private key = กุญแจที่ต้องเก็บในเครื่องตัวเอง (ห้ามแชร์ให้ใคร) เมื่อ push code GitHub ใช้ทั้งคู่ยืนยันตัวตนโดยไม่ต้องพิมพ์ password

bash
# สร้าง SSH key แบบ ed25519 (ใหม่กว่า RSA, สั้นกว่า, ปลอดภัยกว่า)
ssh-keygen -t ed25519 -C "you@example.com"

# ดู public key เพื่อ copy ไป paste บน GitHub
cat ~/.ssh/id_ed25519.pub
# เปิดเบราว์เซอร์ไปที่ GitHub → Settings → SSH and GPG keys → New SSH key → paste

# ทดสอบเชื่อมต่อ
ssh -T git@github.com
# ถ้าได้ข้อความ "Hi <username>! You've successfully authenticated" = สำเร็จ

⚠️ ตั้ง passphrase ให้ private key ด้วย — ตอน ssh-keygen มันจะถามว่าจะใส่ passphrase ไหม อย่ากด Enter ผ่านเฉย ๆ เพราะถ้าไม่ตั้ง ใครก็ตามที่เข้าถึงเครื่อง (เช่น โน้ตบุ๊กหาย/โดนขโมย) จะ push โค้ดในนามคุณได้ทันทีโดยไม่ต้องรู้รหัสอะไรเลย — ตั้ง passphrase ไว้ แล้วใช้ ssh-agent ช่วยจำ (พิมพ์ครั้งเดียวต่อ session ไม่ต้องพิมพ์ทุกครั้งที่ push)

(แนะนำสำหรับปี 2026) เซ็น commit ด้วย SSH key

ตั้งแต่ Git 2.34+ เซ็น commit ด้วย SSH key ที่มีอยู่แล้วได้เลย ไม่ต้องตั้ง GPG ใหม่ — GitHub จะแสดง badge Verified บน commit ของคุณ:

bash
# ใช้ SSH เซ็น (แทน GPG ที่ตั้งยาก)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub

# เซ็นทุก commit/tag อัตโนมัติ
git config --global commit.gpgsign true
git config --global tag.gpgsign true

จากนั้นไปเพิ่ม public key เดิมที่ GitHub อีกที — คราวนี้เลือกประเภท "Signing Key"

💡 Sigstore / gitsign (เริ่มฮิตในวงการ OSS) — เซ็นด้วย OIDC identity (เช่น GitHub account) แบบ keyless ไม่ต้องดูแล key เอง น่าจับตาแต่ยังไม่ใช่ default


3. Mental Model

กุญแจที่ทำให้เข้าใจ Git ทั้งหมดคือภาพ "3 พื้นที่" — ไฟล์เดินทางผ่าน 4 จุดตามลำดับนี้:

  • working directory — โฟลเดอร์ที่คุณแก้ไฟล์อยู่ตามปกติ
  • staging area — คิวพักก่อน commit สั่งด้วย git add (เลือกว่าจะเอาอะไรเข้า commit บ้าง)
  • repository — ที่บันทึก snapshot ถาวร สั่งด้วย git commit
  • remote — เซิร์ฟเวอร์ปลายทางอย่าง GitHub สั่งด้วย git push

ถ้าจำภาพนี้ได้ คำสั่งทุกตัวจะเข้าใจง่ายขึ้นทันที:


4. Basic Workflow

นี่คือวงจรที่คุณจะทำซ้ำทุกวัน — แก้ไฟล์ → git add (stage) → git commit (บันทึก) → git push (อัปขึ้น remote) บวกกับคำสั่งดูสถานะ (status), ดูประวัติ (log) และดูความต่าง (diff) จำชุดนี้ให้คล่องก่อน แล้วค่อยต่อยอดไปคำสั่งขั้นสูง:

bash
# clone โปรเจกต์ที่มีอยู่แล้ว
git clone https://github.com/user/repo.git
cd repo

# หรือสร้างใหม่
mkdir myproject && cd myproject
git init

# ดูการเปลี่ยนแปลง
git status

# stage (เลือกไฟล์เข้าคิวรอ commit)
git add file.txt           # ทีละไฟล์
git add .                  # ทั้งหมด
git add -p                 # เลือกทีละส่วน (interactive)

# commit (บันทึก snapshot)
git commit -m "Add user authentication"

# add + commit รวบเดียว (เฉพาะไฟล์ที่ track อยู่แล้ว)
git commit -am "Fix typo"

# push (อัปขึ้น remote)
git push origin main

# pull (= fetch ดึงมา + merge รวม)
git pull
git pull origin main

# ดูประวัติ
git log
git log --oneline
git log --graph --oneline --all
git log -p file.txt        # เห็นการเปลี่ยนแปลงต่อ commit
git log --since "1 week ago"

# ดูความต่าง
git diff                   # ที่ยังไม่ stage
git diff --staged          # ที่ stage แล้ว
git diff main feature      # เทียบ branch
git diff HEAD~1            # เทียบกับ commit ก่อนหน้า

5. Commit Message Convention

commit message ที่ดีคือจดหมายถึงตัวเองในอนาคต (และเพื่อนร่วมทีม) — "Conventional Commits" เป็น convention ยอดนิยมที่กำหนดรูปแบบ type(scope): subject ทำให้อ่าน history เข้าใจง่ายและ generate changelog อัตโนมัติได้ ลองดูโครงสร้างและตัวอย่าง:

Conventional Commits

text
<type>(<scope>): <subject>

<body>

<footer>

โดย <type> คือชนิดของการเปลี่ยน, <scope> คือส่วนของระบบที่กระทบ (เช่น auth, api), <subject> คือสรุปสั้น ๆ (ไม่เกิน ~50 ตัวอักษร), <body> อธิบายเพิ่ม (ทำไม), <footer> ใส่ issue ที่เกี่ยวข้องหรือ breaking change

ชนิด (Types):

Typeความหมาย
featfeature ใหม่
fixแก้บั๊ก
docsแก้เฉพาะเอกสาร
styleจัด format (ไม่กระทบ logic เช่น ช่องว่าง/เครื่องหมาย)
refactorปรับโครงสร้างโค้ด ไม่เพิ่ม feature ไม่แก้บั๊ก
testเพิ่ม/แก้ test
choreงานบ้าน (อัปเดต dependency, ตั้งค่า)
perfเพิ่ม performance
ciปรับ config ของ CI
buildปรับระบบ build / package

ตัวอย่าง:

text
feat(auth): เพิ่ม endpoint refresh JWT
fix(api): แก้ pagination off-by-one (เลขเพี้ยน 1)
docs(readme): อัปเดตขั้นตอนติดตั้ง
refactor(user): แยก validation ออกเป็น UserValidator
test(order): เพิ่ม integration test ของ checkout
chore(deps): อัปเกรด Spring Boot เป็น 3.3.1

💡 ในชีวิตจริงคนเขียน subject เป็นภาษาอังกฤษกันมากกว่า (เพราะ tool, changelog, search หาง่าย) แต่เขียนเป็นไทยก็ไม่ผิด — ขอแค่ทีมตกลงกัน

Good vs Bad

❌ Bad (ไม่บอกอะไรเลย):

text
update
fix bug
wip
asdf

✅ Good (บอกว่าแก้อะไร, ทำไม):

text
fix(auth): handle expired token in refresh endpoint
(แก้: รับมือ token หมดอายุใน endpoint refresh)

Previously refresh endpoint would return 500 if the refresh
token was expired. Now returns 401 with clear error message.
(ก่อนหน้านี้ endpoint refresh จะคืนค่า 500 ถ้า refresh token
หมดอายุ — ตอนนี้คืน 401 พร้อมข้อความ error ชัดเจน)

Fixes #234

ทำไมต้องใส่ใจ?

  • สร้าง CHANGELOG อัตโนมัติ — tool อย่าง standard-version, release-please อ่าน type แล้วเขียน release note ให้
  • ใช้กับ Semantic Versioningfeat → bump minor (1.2.0 → 1.3.0), fix → bump patch (1.2.0 → 1.2.1), มี BREAKING CHANGE → bump major (1.x → 2.0)
  • Code review ง่ายขึ้น — เห็น type ปุ๊บเข้าใจเจตนาทันที
  • ค้นประวัติได้git log --grep "^fix" ดูบั๊กทั้งหมดที่แก้ไป

6. Branching

💡 switch / restore vs checkout (Git 2.23+)checkout ทำ 2 หน้าที่ที่ไม่เกี่ยวกัน (เปลี่ยน branch + restore ไฟล์) สับสนมาก. ปี 2019 Git แยกเป็น 2 command:

  • git switch <branch> — เปลี่ยน branch อย่างเดียว
  • git restore <file> — เอาไฟล์กลับมาจาก commit

checkout ยังใช้ได้ (backward compat) แต่ 2026 แนะนำใช้ switch/restore

bash
# ดู branch
git branch                 # เฉพาะ local
git branch -a              # ทั้งหมด (รวม remote)

# สร้าง + สลับไป branch ใหม่ (แบบสมัยใหม่ แนะนำ)
git switch -c feature/login    # -c = create (สร้างใหม่)
git switch main                # สลับไป branch ที่มีอยู่แล้ว

# แบบเก่า (ยังใช้ได้)
git checkout -b feature/login
git checkout main

# เปลี่ยนชื่อ branch ปัจจุบัน
git branch -m new-name

# ลบ branch
git branch -d feature/login              # ลบแบบปลอดภัย (ต้อง merge เข้า main แล้วเท่านั้น)
git branch -D feature/login              # บังคับลบ (ใช้เมื่อมั่นใจ — งานในนั้นจะหาย)
git push origin --delete feature/login   # ลบ branch บน remote ด้วย

Branch Naming Convention

text
feature/user-login
fix/login-redirect-bug
chore/upgrade-spring
hotfix/critical-security
refactor/extract-validator
release/v1.5.0

7. Merge vs Rebase

🚀 ขั้นสูง — ข้ามได้ในรอบแรก — เนื้อหาในส่วนนี้ครอบคลุม SHA-1 hash, การเขียน history ใหม่ และผลกระทบจาก force push ซึ่งต้องใช้ความเข้าใจ DAG ก่อน ถ้าเพิ่งหัด Git ให้คล่องคำสั่ง add/commit/push/pull + branch/merge ในหัวข้อ 4–6 ก่อน แล้วค่อยกลับมาอ่านส่วนนี้เมื่อจำเป็น

นี่คือจุดที่มือใหม่งงที่สุดในเรื่อง git — ใช้เวลาเข้าใจให้ละเอียด

Merge — เก็บ history ของจริง

bash
git switch main
git merge feature/login

สร้าง commit ใหม่ 1 ตัว (E) ที่มี 2 parents (A คือ commit เดิมบน main, D คือ commit สุดท้ายของ feature)

→ history แสดง "ใคร merge อะไรเข้ามาเมื่อไหร่" จริง ๆ

Rebase — เขียน history ใหม่

bash
git switch feature
git rebase main

Before rebase:

After rebase:

ลอก commit B, C, D ทีละตัว มาวาง "ทับ" ต่อจาก E → ได้ B', C', D' (commit ใหม่ที่ content เหมือนเดิม)

🚨 ทำไม commit hash ถึงเปลี่ยน

SHA-1 คือ อัลกอริทึมสร้างรหัสประจำตัว commit ที่เป็นเลขฐาน 16 (hex — เลขฐาน 16 ใช้ตัวเลข 0-9 กับตัวอักษร a-f แทนค่า ต่างจากเลขฐาน 10 ที่เราคุ้นเคย) ยาว 40 ตัวอักษร (เช่น a3c5e1b...) — Git ใช้รหัสนี้แทน "ชื่อ" ของแต่ละ commit และคำนวณจากข้อมูล 5 อย่างรวมกัน:

text
content (ไฟล์ที่เปลี่ยน) +
parent (commit แม่) +
author (ผู้เขียน) +
timestamp (เวลา) +
message
→ hash 40 ตัว เช่น a3c5e1b...
text
Commit B  : parent = A → hash xyz123
Commit B' : parent = E → hash abc789   ← parent เปลี่ยน → hash เปลี่ยนหมด

→ B' ≠ B (แม้เนื้อโค้ดเหมือนกัน) — Git มอง 2 commit นี้เป็นคนละตัว

ผลกระทบ:

  • ถ้าเพื่อนร่วมทีม pull commit B ไปแล้ว แล้วคุณ rebase → B หายจากประวัติ → คุณต้อง push แบบบังคับ (force) → เพื่อนเจอ conflict ตอน pull รอบหน้า
  • เพราะอย่างนี้ — อย่า rebase commit ที่ push แล้วและคนอื่นกำลังใช้อยู่

💡 เกร็ดสำหรับสายเทคนิค (ปี 2026): Git รองรับ SHA-256 แล้วตั้งแต่ 2.42+ (git init --object-format=sha256) แต่ default ยังเป็น SHA-1 และยังเข้ากับ SHA-256 repo ไม่ได้ — ในชีวิตจริงยังเจอ SHA-1 เป็นหลัก

เปรียบเทียบ

ประเด็นMergeRebase
รูปร่างประวัติไม่เป็นเส้นตรง (มี merge commit)เป็นเส้นตรง
เก็บประวัติจริง✅ เก็บครบ❌ เขียนทับ
ใช้กับ branch ที่มีคนอื่นแชร์✅ ปลอดภัย⚠️ อันตราย
แก้ conflictครั้งเดียวจบอาจต้องแก้ทีละ commit
git log --oneline อ่านง่ายกลาง ๆ (มี merge commit เกะกะ)สวย เป็นเส้นตรง

หลักการใช้งานจริงในวงการ

text
หลักประมาณการ (rule of thumb):
- branch ที่ "เรา" คนเดียวใช้ (feature ของตัวเอง)  → REBASE ได้ ปลอดภัย
- branch ที่ "หลายคน" ใช้ (main, develop, shared)  → MERGE เป็นหลัก
- หลักจริง: "อย่า rebase commit ที่คนอื่นเอาไปต่อยอดแล้ว"
- รูปแบบที่นิยม: "rebase ก่อน merge"
  → rebase ตอน sync กับ main ระหว่างทำงาน
  → merge (หรือ squash) ตอนเข้า main จริง

⚠️ อย่ายึดติดกฎ "main ต้อง merge เท่านั้น" แบบตายตัว — เหตุผลจริงคือ "ไม่แก้ประวัติที่คนอื่นพึ่งพา" ถ้าทำคนเดียว rebase main ของตัวเองก็ได้

Workflow มาตรฐาน (ใช้กับ feature branch ของคุณ):

bash
# งานประจำวัน — sync feature ให้ทันกับ main
git switch feature
git fetch origin
git rebase origin/main      # ได้ประวัติเส้นตรง (เพราะ feature ยังเป็นของเราคนเดียว)

# พร้อม merge เข้า main แล้ว — เลือก 1 ใน 2 ตามนโยบายทีม
# (1) Merge commit แบบเก็บประวัติ branch
git switch main
git merge --no-ff feature   # --no-ff = บังคับสร้าง merge commit ไว้ดูภายหลังว่าใคร merge อะไร

# (2) Squash merge — รวมทุก commit ของ feature เป็น 1 ตัว
git switch main
git merge --squash feature
git commit -m "feat(auth): add login (#123)"

💡 Squash merge เป็น default ของหลายทีมบน GitHub (Squash and merge ในปุ่ม UI) — main จะมี commit ละ 1 ฟีเจอร์ ประวัติสะอาดมาก ข้อเสียคือเสียประวัติย่อยใน feature ไป


8. Pull = Fetch + Merge (or Rebase)

git pull ที่ดูเหมือนคำสั่งเดียว จริง ๆ ทำ 2 อย่าง: fetch (ดึง commit ใหม่จาก remote) แล้ว merge หรือ rebase รวมเข้ากับ branch ของเรา การเข้าใจว่ามันแยกเป็น 2 ขั้นช่วยให้เลือกได้ว่าจะ merge (เก็บประวัติ branch จริง) หรือ rebase (ได้ประวัติเป็นเส้นตรงสวยงาม):

bash
# แบบ default ของคนทั่วไป (merge)
git pull origin main

# แบบ rebase pull — ได้ประวัติเส้นตรง
git pull --rebase origin main

# ตั้งให้ทุกครั้ง git pull ใช้ rebase อัตโนมัติ (เราตั้งไปแล้วในข้อ 2)
git config --global pull.rebase true

# หรือทำเองเป็น 2 ขั้น (fetch ก่อน)
git fetch origin
git merge origin/main
# หรือ
git rebase origin/main

💡 สอดคล้องกับข้อ 2: เราตั้ง pull.rebase true เป็น default ตั้งแต่ setup แล้ว เพราะอยากได้ประวัติเส้นตรงตอน sync


9. Conflict Resolution

Conflict เกิดเมื่อ:

  • 2 คนแก้บรรทัดเดียวกัน
  • Merge / rebase / pull
bash
git merge feature
# CONFLICT (content): Merge conflict in file.txt
# Automatic merge failed; fix conflicts then commit.
# (Git บอกว่า merge อัตโนมัติไม่ได้ — ต้องแก้ conflict เองแล้ว commit)

จากนั้นเปิดไฟล์ที่ Git บอกว่า conflict — Git จะแทรก เครื่องหมาย conflict เข้าไปในไฟล์ แสดงโค้ดที่ชนกัน 2 ฝั่ง:

text
<<<<<<< HEAD
existing code
(โค้ดที่อยู่บน branch ปัจจุบันของเรา)
=======
incoming code
(โค้ดที่กำลังจะ merge เข้ามา จาก branch feature)
>>>>>>> feature

ขั้นตอนแก้:

  1. เปิดไฟล์ในเอดิเตอร์ → หา <<<<<<<
  2. ตัดสินใจว่าจะเอาฝั่งไหน หรือผสมทั้งสอง
  3. ลบเครื่องหมาย <<<<<<<, =======, >>>>>>> ออกให้หมด
  4. เก็บโค้ดที่ต้องการไว้
bash
git add file.txt
git commit                # ทำให้ merge สมบูรณ์

# ถ้าอยากยกเลิก
git merge --abort         # ยกเลิก merge กลับสู่สภาพก่อนหน้า
git rebase --abort        # ถ้ากำลัง rebase อยู่

Tool ช่วยแก้ conflict

bash
git mergetool             # เปิด tool แบบ GUI
# VS Code และ IntelliJ มี merge editor ในตัวอยู่แล้ว — สะดวกที่สุด

Strategy แบบเลือกฝั่งทั้งไฟล์

bash
# เลือกของฝั่ง "เขา" (incoming) ทั้งไฟล์
git checkout --theirs file        # ใช้โค้ดของฝั่งที่กำลังจะ merge เข้ามา

# เลือกของฝั่ง "เรา" (ours) ทั้งไฟล์
git checkout --ours file          # ใช้โค้ดของ branch ปัจจุบัน

# จากนั้น stage + commit
git add file
git commit

💡 เกร็ด: ถ้าเปิด rerere.enabled true ไว้ (ตั้งไปแล้วในข้อ 2) — Git จะ "จำ" ว่าครั้งก่อนแก้ conflict นี้ยังไง แล้วครั้งหน้าที่เจอ conflict เดิม (เช่นตอน rebase ซ้ำ ๆ) จะแก้ให้อัตโนมัติ ช่วยมากกับ feature branch ที่อยู่นาน


10. Undo + Recovery

ย้อน change ใน working directory

bash
git restore file.txt               # ทิ้ง change ที่ยังไม่ stage (ดึงไฟล์กลับมาจาก stage/repo)
git restore --staged file.txt      # เอาออกจาก stage แต่ change ยังอยู่ใน working dir

# ⚠️ คำสั่งเก่า — ใช้ได้แต่ "ทำลายข้อมูลแบบเงียบ ไม่เตือน"
git checkout -- file.txt           # ทิ้ง change (เลิกใช้ — ให้ใช้ git restore แทน)

⚠️ ทั้ง git restore file.txt และ git checkout -- file.txt ทิ้งงานที่ยังไม่ commit แบบไม่ถามและกู้ไม่ได้ (เพราะยังไม่เคยอยู่ใน Git) — เช็กก่อนรันให้แน่ใจ

Undo last commit (keep changes)

git reset มี 3 mode ที่ต่างกัน — เข้าใจสามเขตของ git ก่อน:

git reset --MODE HEAD~1 ขยับ branch pointer ถอยหลัง 1 commit + ทำงานต่างกันตาม mode:

ModeRepository (commit)StagingWorking Dirใช้เมื่อ
--softถอย 1 ก้าวคงเดิมคงเดิมอยาก commit ใหม่ (เปลี่ยน message / รวมหลาย commit)
--mixed (default)ถอย 1 ก้าวล้างทิ้งคงเดิมuncommit + unstage แต่ยังเก็บการแก้ไว้ใน working dir
--hardถอย 1 ก้าวล้างทิ้งลบทิ้ง!อยากทิ้งทุกอย่าง ⚠️ กู้คืน change ที่ยังไม่ commit ไม่ได้
bash
git reset --soft HEAD~1     # ถอย commit แต่ของยังค้างใน stage รอ commit ใหม่
git reset HEAD~1            # = --mixed (default) — ถอย commit + unstage แต่การแก้ยังอยู่
git reset --hard HEAD~1     # ⚠️ ถอย commit + ลบการแก้ทั้งหมด (ทิ้งงานหมด)

🚨 --hard คือคำสั่งอันตรายที่สุดของ Git — ใช้เมื่อมั่นใจ 100% ว่าไม่ต้องการของที่ทิ้ง

ข้อสำคัญเรื่องการกู้: reflog กู้ได้เฉพาะ commit ที่ "เคย commit แล้ว" — ถ้าทำ --hard ทับ change ที่ยังไม่ commit (อยู่แค่ใน working dir/stage) กู้กลับไม่ได้เลย

ย้อน commit ที่ push ไปแล้ว

ห้ามใช้ reset กับ commit ที่ push แล้ว — ใช้ revert แทน (สร้าง commit ใหม่ที่หักล้างของเก่า ประวัติยังครบ):

bash
git revert HEAD                   # สร้าง commit ใหม่ที่ย้อน commit ล่าสุด
git revert <sha>                  # ย้อน commit ที่ระบุ (ไม่ต้องเป็นตัวล่าสุด)

แก้ commit ล่าสุด (amend)

bash
git commit --amend                # แก้ message หรือเพิ่มไฟล์ใน commit เดิม
git commit --amend --no-edit      # แค่เพิ่มไฟล์ที่ stage เข้าใน commit เดิม (ไม่แก้ message)

⚠️ อย่า amend commit ที่ push ไปแล้ว — hash จะเปลี่ยน → ทำให้คนอื่นที่ใช้ branch นี้พัง

กู้ branch / commit ที่ลบไปแล้ว

bash
git reflog                        # บันทึกการเปลี่ยนที่ HEAD ชี้ ทุกครั้งที่ขยับ
# ตัวอย่างผลลัพธ์:
# 0c1d2e3 HEAD@{2}: commit: feature work

git checkout 0c1d2e3 -- file      # กู้ไฟล์เดียวจาก commit เก่า
git branch recovered 0c1d2e3      # สร้าง branch ใหม่จาก SHA ที่กู้มา

→ Reflog เก็บประมาณ 90 วัน — commit ที่ "เคย commit แล้ว" ส่วนใหญ่กู้ได้


11. Stash — temporary save

เคยกำลังแก้ของค้างอยู่ แล้วต้องรีบสลับไป branch อื่น? git stash คือที่พักชั่วคราว — มันเก็บงานที่ยังไม่ commit ไว้ก่อน ให้ working directory สะอาด แล้วค่อยดึงกลับมาทำต่อทีหลังด้วย pop เหมาะมากกับสถานการณ์ "ขอ pull ก่อน เดี๋ยวค่อยทำต่อ":

bash
# เก็บ change ปัจจุบันเข้า stash
git stash                         # เก็บ change ที่ยังไม่ commit เข้า stash
git stash push -m "WIP login"     # เก็บพร้อมใส่ข้อความกำกับ

# ดูรายการ stash
git stash list

# ดึง stash กลับมา
git stash pop                     # ดึงตัวล่าสุดกลับ + ลบออกจาก stash
git stash apply stash@{1}         # ดึงตัวที่ระบุ (ไม่ลบจาก stash)

# ลบ stash
git stash drop stash@{0}          # ลบเฉพาะตัวที่ระบุ
git stash clear                   # ลบทั้งหมด

# สถานการณ์ที่ใช้บ่อย — "ขอ pull ก่อน เดี๋ยวค่อยทำต่อ"
git stash
git pull
git stash pop

🚀 โซนขั้นสูง — ข้ามได้ หัวข้อ 12–14 (interactive rebase, cherry-pick, bisect) เป็นเทคนิคขั้นสูงที่ยังไม่จำเป็นตอนเริ่ม ถ้าเพิ่งหัด Git ขอแค่คล่อง add/commit/push/pull + branch/merge ก่อน (หัวข้อ 1–11) แล้วค่อยกลับมาอ่านส่วนนี้ตอนเจอสถานการณ์จริง

12. Interactive Rebase — Clean History

interactive rebase คือเครื่องมือ "จัดระเบียบ" ประวัติ commit ก่อนเปิด PR — รวม commit เล็ก ๆ ("fix typo" หลาย ๆ อัน) เป็นอันเดียว, แก้ข้อความ, สลับลำดับ หรือลบทิ้งได้ ผลคือ history สะอาดอ่านง่าย ⚠️ ใช้กับ commit ที่ยังไม่ push เท่านั้น (อย่า rebase ประวัติที่คนอื่นใช้ร่วมแล้ว):

bash
git rebase -i HEAD~5             # จัด 5 commit ล่าสุดแบบ interactive

Git จะเปิด editor พร้อมรายการ commit:

text
pick c1a2b3 Add login
pick d4e5f6 Fix typo
pick g7h8i9 Add password reset
pick j1k2l3 Fix typo 2
pick m4n5o6 Style

# คำสั่งที่ใช้ได้:
# pick   = ใช้ commit นี้ตามเดิม
# reword = ใช้ commit แต่แก้ข้อความ
# edit   = ใช้ commit แต่หยุดให้แก้เนื้อหา
# squash = รวมเข้ากับ commit ก่อนหน้า + รวมข้อความ
# fixup  = เหมือน squash แต่ทิ้งข้อความของตัวนี้
# drop   = ลบทิ้ง

แก้เป็น:

text
pick c1a2b3 Add login
fixup d4e5f6 Fix typo            ← รวมเข้า commit ก่อนหน้า
pick g7h8i9 Add password reset
fixup j1k2l3 Fix typo 2          ← รวมเข้า commit ก่อนหน้า
drop m4n5o6 Style                ← ลบทิ้ง

→ ได้ประวัติสะอาดก่อนเปิด PR

bash
# หรือยุบทุก commit เป็น 1 ตัว
git rebase -i HEAD~5
# เปลี่ยน "pick" ทุกตัว (ยกเว้นตัวแรก) ให้เป็น "fixup" หรือ "squash"

13. Cherry-pick

cherry-pick คือการ "หยิบ" commit ตัวใดตัวหนึ่งจาก branch หนึ่งมาแปะอีก branch โดยไม่ต้อง merge ทั้ง branch — เหมาะกับการ apply hotfix ไปหลาย branch หรือดึงเฉพาะ feature ที่พร้อมแล้วมาก่อน:

bash
# หยิบ commit เดียวจาก branch อื่นมา apply
git checkout main
git cherry-pick <sha-from-feature>

# หยิบหลาย commit
git cherry-pick sha1 sha2 sha3
git cherry-pick sha1..sha3       # ช่วง (range)

ใช้ตอน:

  • เอา hotfix ที่แก้ใน branch หนึ่งไป apply กับหลาย branch (เช่น main + release/v1.5)
  • ดึงเฉพาะ feature ที่พร้อมแล้วจาก branch รวม โดยยังไม่ merge ทั้ง branch

14. Bisect — Find Bad Commit

เมื่อบั๊กโผล่มาแต่ไม่รู้ว่า commit ไหนทำพัง git bisect ช่วยหาให้ด้วย binary search — บอกมันว่า commit เก่าตัวไหน "ดี" และตัวไหน "เสีย" แล้วมันจะ checkout commit ตรงกลางให้ทดสอบไปเรื่อย ๆ จนเจอตัวการ ใน history ยาว ๆ วิธีนี้เร็วกว่าไล่ทีละ commit มาก:

bash
git bisect start
git bisect bad                   # commit ปัจจุบันมีบั๊ก
git bisect good v1.0             # tag v1.0 ตอนนั้นยังดี

# Git จะ checkout commit ตรงกลางให้
# ทดสอบ:
git bisect good                  # ถ้ารันแล้วยังดี
git bisect bad                   # ถ้าเจอบั๊ก

# วนต่อจนได้คำตอบ:
# bdf3a4 is the first bad commit
# (= commit แรกที่ทำให้พัง)

git bisect reset                 # จบ — กลับสู่ branch เดิม

→ binary search หา commit แรกที่ทำให้บั๊กโผล่ — เร็วกว่าไล่ทีละ commit มาก

bash
# แบบอัตโนมัติ — ให้ Git รัน test เองในแต่ละ commit ที่ checkout
git bisect run npm test           # ตัวอย่างกับ Node project
git bisect run ./mvnw test        # ตัวอย่างกับ Maven (Java)
git bisect run ./test.sh          # หรือ script ของคุณเอง — exit 0 = good, ไม่ใช่ 0 = bad

15. Remote

"remote" คือ repository เวอร์ชันที่อยู่บนเซิร์ฟเวอร์ (เช่น GitHub) ที่ local ของคุณเชื่อมต่อด้วย — โดยทั่วไป origin คือ repo ของคุณเอง ส่วน upstream มักใช้ชี้ไปยัง repo ต้นฉบับเวลาทำงานกับ fork การจัดการ remote คือพื้นฐานของการทำงานร่วมกันบน GitHub:

bash
# ดูรายการ remote ที่ตั้งไว้
git remote -v

# เพิ่ม remote
git remote add origin https://github.com/user/repo.git
git remote add upstream https://github.com/original/repo.git    # ใช้กับ fork — ชี้ไปยัง repo ต้นฉบับ

# เปลี่ยนชื่อ remote
git remote rename old new

# ลบ remote
git remote remove origin

# ดึงข้อมูลใหม่จากทุก remote
git fetch --all

Multi-Remote Workflow (Fork)

bash
# clone fork ของคุณ
git clone https://github.com/yourusername/forked-repo.git
cd forked-repo

# เพิ่ม upstream ชี้ไปยัง repo ต้นฉบับ
git remote add upstream https://github.com/original/repo.git

# sync main ของ fork ให้ทันกับต้นฉบับ
git fetch upstream
git switch main
git rebase upstream/main          # ใช้ rebase เพราะ main ของ fork เป็นของคุณคนเดียว
git push origin main              # ปกติ push ตรง ๆ ได้เพราะ history เป็น superset

⚠️ อย่าใช้ git merge upstream/main กับ fork main — มันจะสร้าง merge commit แปลก ๆ เข้ามาใน main ของคุณ ทำให้ประวัติเพี้ยนจากต้นฉบับ จะกลายเป็นปัญหาเวลาเปิด PR กลับ

ถ้าอยาก clean สุด (ไม่สนประวัติ commit ตัวเองที่อาจมีใน main ของ fork) ใช้:

bash
git fetch upstream
git switch main
git reset --hard upstream/main    # ทับ main ของ fork ด้วย main ของต้นฉบับเป๊ะ ๆ
git push --force-with-lease origin main

16. .gitignore

ไม่ใช่ทุกไฟล์ที่ควรขึ้น Git — ไฟล์ build, dependency (node_modules/), secret (.env) และไฟล์เฉพาะเครื่อง ไม่ควร commit .gitignore คือรายการบอก Git ว่า "อย่าสนใจไฟล์พวกนี้" ช่วยให้ repo สะอาดและกัน secret หลุดโดยไม่ตั้งใจ:

gitignore
# ไฟล์/โฟลเดอร์ที่ "ไม่ควร" commit เด็ดขาด
node_modules/         # dependency ของ Node.js — โหลดกลับมาได้
target/               # ผลลัพธ์ build ของ Java/Maven
*.log                 # log ไฟล์
.env                  # ความลับ! token, password, API key
.DS_Store             # ไฟล์ขยะของ macOS
.idea/                # การตั้งค่าส่วนตัวของ IntelliJ
.vscode/settings.json # การตั้งค่าส่วนตัวของ VS Code

# ผลลัพธ์ compile
*.class               # Java compiled
*.pyc                 # Python compiled
dist/                 # ผล build ของ frontend
build/                # ผล build ทั่วไป

# ขยะของระบบปฏิบัติการ
Thumbs.db             # Windows
.DS_Store             # macOS

💡 หมายเหตุเรื่อง .vscode/ — หลายทีม commit .vscode/extensions.json (แนะนำ extension ให้ทีม) และ .vscode/launch.json (config debug) ไว้ตั้งใจ — ignore เฉพาะ .vscode/settings.json ที่เป็นค่าส่วนตัวของแต่ละคนพอ

.gitignore ที่ root มีผลกับทุกโฟลเดอร์ย่อย
.gitignore ใน subfolder มีผลเฉพาะ folder นั้น

กรณีไฟล์เคย commit ไปแล้ว

bash
# ถ้าเผลอ commit ไฟล์ไปแล้วค่อยมาเพิ่มใน .gitignore
git rm --cached file.txt
git commit -m "Remove file from tracking"

.gitignore ไม่ทำงานกับไฟล์ที่ Git track แล้ว — ต้อง rm --cached ให้เลิก track ก่อน


17. .gitattributes

.gitattributes ใช้กำหนดว่า Git ควรปฏิบัติกับไฟล์แต่ละชนิดยังไง — ที่ใช้บ่อยสุดคือจัดการ line ending ข้าม OS (Windows ใช้ CRLF, Linux/Mac ใช้ LF) เพื่อกัน diff รก ๆ จากเรื่อง newline และระบุว่าไฟล์ไหนเป็น binary หรือเป็น docs (สำหรับสถิติภาษาของ GitHub):

gitattributes
# จัดการ line ending ข้าม OS (Windows ใช้ CRLF, Linux/Mac ใช้ LF)
* text=auto                # ปล่อยให้ Git ตัดสินใจ
*.sh text eol=lf           # shell script บังคับ LF (รันบน Linux)
*.bat text eol=crlf        # batch script บังคับ CRLF (รันบน Windows)

# บอกว่าเป็นไฟล์ binary — Git จะไม่พยายาม diff/merge
*.png binary
*.jpg binary

# Linguist = ตัวที่ GitHub ใช้ตรวจว่า repo นี้ใช้ภาษาอะไรบ้าง
# กำกับว่าไฟล์นี้เป็นเอกสาร ไม่ใช่โค้ด (ไม่ให้รวมในสถิติภาษา)
*.md linguist-documentation
docs/* linguist-documentation

Part 2: GitHub Workflow

18. Pull Request Flow

Pull Request (PR) คือหัวใจของการทำงานเป็นทีมบน GitHub — แทนที่จะ push ตรงเข้า main คุณแยก branch ทำงาน แล้วเปิด PR เพื่อให้ทีม review ก่อน merge ขั้นตอนมาตรฐานไล่จากสร้าง branch → commit → push → เปิด PR → review → CI ผ่าน → merge → ลบ branch:

text
1. สร้าง branch ใหม่
   git checkout -b feature/login

2. แก้โค้ด + commit
   git commit -m "feat(auth): add login form"

3. push ขึ้น remote
   git push origin feature/login

4. เปิด PR บน GitHub
   - Title: feat(auth): add login form
   - Description: ทำอะไร, ทำไม, ทดสอบยังไง
   - ผูกกับ issue (Closes #123)
   - เพิ่ม reviewer (คนตรวจ)
   - ใส่ label (เช่น bug, feature, priority)

5. รอ code review
   - reviewer คอมเมนต์ในโค้ด
   - แก้ตาม feedback → commit เพิ่ม → push (PR อัปเดตอัตโนมัติ)
   - ตอบทุก comment (resolve หรือ discuss)

6. รอ CI ผ่าน (test, lint, build อัตโนมัติ)

7. merge — เลือก 1 ใน 3 วิธี
   - Merge commit         (เก็บประวัติทุก commit + เพิ่ม merge commit)
   - Squash and merge     (รวมทุก commit ของ PR เป็น 1 ตัว — นิยมที่สุดบน GitHub)
   - Rebase and merge     (วาง commit ต่อจาก main เป็นเส้นตรง ไม่มี merge commit)

8. ลบ branch (ที่ remote + ที่ local)

19. PR Description Template

คำอธิบาย PR ที่ดีช่วยให้ reviewer เข้าใจเร็วและ review ได้ตรงจุด — ควรบอกว่า PR นี้ทำอะไร (summary), เปลี่ยนอะไรบ้าง (changes), ทดสอบยังไง (test plan) และมี breaking change ไหม เทมเพลตด้านล่างวางใน .github/PULL_REQUEST_TEMPLATE.md แล้ว GitHub จะเติมให้ทุก PR อัตโนมัติ:

markdown
## Summary (สรุป)
Brief description of what this PR does.
(บอกสั้น ๆ ว่า PR นี้ทำอะไร)

## Changes (สิ่งที่เปลี่ยน)
- Added login form (เพิ่มฟอร์ม login)
- Created `/api/auth/login` endpoint (สร้าง API endpoint)
- Added JWT generation (เพิ่มการสร้าง JWT)

## Test Plan (วิธีทดสอบ)
- [ ] Manual: try login with valid creds (ลอง login ด้วย credential ที่ถูก)
- [ ] Manual: try invalid creds (ลอง login ด้วย credential ที่ผิด)
- [x] Unit test added (เพิ่ม unit test แล้ว)
- [x] Integration test added (เพิ่ม integration test แล้ว)

## Screenshots (รูปประกอบ ถ้าเปลี่ยน UI)

## Breaking Changes (มีอะไรพังของเดิมไหม)
None (ไม่มี)

Closes #123

Template ใน Repo

.github/PULL_REQUEST_TEMPLATE.md:

markdown
## Summary
<!-- สรุป 1-2 ประโยค -->

## Changes
<!-- สิ่งที่เปลี่ยน -->

## Test Plan
- [ ] 

## Risk / Rollback (ความเสี่ยง + แผนถอย)
<!-- ถ้า deploy แล้วพัง จะ rollback ยังไง? -->

## Migration (สำหรับ DB/config ที่ต้องทำเพิ่ม)
<!-- ต้องรัน migration ไหม? ลำดับยังไง? -->

## Related Issues
Closes #

→ ตั้งไว้แล้ว GitHub จะกรอกให้ทุก PR อัตโนมัติ

💡 ทำไมต้องมี Risk/Rollback + Migration? PR ที่ deploy ขึ้น production ต้องตอบ "ถ้าพังจะ rollback ยังไง" ได้เสมอ — SRE/ops จะขอบคุณคุณ


20. Code Review

code review ไม่ใช่แค่หาบั๊ก แต่เป็นการแชร์ความรู้และรักษาคุณภาพโค้ดทั้งทีม — มีมารยาทและเทคนิคทั้งสองฝั่ง: ฝั่ง reviewer ควรดูอะไร พูดยังไงให้สร้างสรรค์ และฝั่ง author ควรทำ PR เล็ก ๆ และรับ feedback อย่างมืออาชีพ:

ในฐานะ reviewer (คนตรวจ)

✅ ดูอะไรบ้าง:

  • Correctness — โค้ดทำงานถูกต้องไหม? logic ครบ?
  • Tests — มี test ครอบคลุมไหม?
  • Naming + readability — ตั้งชื่อชัดไหม? อ่านเข้าใจไหม?
  • Security — มี input validation? มี secret หลุดในโค้ดไหม?
  • Performance — มี N+1 query หรือ loop ที่ช้าชัด ๆ ไหม?
  • Architecture — โค้ดอยู่ใน layer/module ที่ถูกต้องไหม?

✅ ตรวจยังไง:

  • คอมเมนต์ที่บรรทัดเฉพาะเจาะจง (อย่าคอมเมนต์รวมที่ PR description)
  • ใช้ฟีเจอร์ "Suggest change" ของ GitHub เสนอโค้ดตรง ๆ
  • ใช้ "Approve" หรือ "Request changes" ตามผล
  • ใช้น้ำเสียงสร้างสรรค์ — เช่น "ลองพิจารณา X เพราะ Y" แทน "ผิด"

❌ อย่า:

  • ถกเรื่องจุกจิกไม่เกี่ยวเนื้อหา (เช่น เถียงเรื่องช่องว่าง — ปล่อยให้ formatter จัดการ)
  • คอมเมนต์เชิงตำหนิตัวคน — เช่น "โค้ดคุณห่วย" → ให้พูดถึงโค้ด ไม่ใช่คน
  • ขวาง merge เพราะเรื่องเล็กน้อยที่แก้ทีหลังก็ได้

ในฐานะ author (คนเขียน)

✅ ควรทำ:

  • เปิด PR เล็ก ๆ (diff น้อยกว่า ~400 บรรทัด) — review เร็ว เจอบั๊กได้ง่ายขึ้น
  • Self-review ก่อน — เปิด PR แล้วอ่าน diff ของตัวเองก่อนกด request review
  • ตอบทุก comment — แก้แล้วก็ตอบ "Done" หรือถ้าไม่เห็นด้วยก็แลกเปลี่ยน
  • Sync กับ main ถ้า branch ห่างไกล — กัน conflict ตอน merge

❌ อย่า:

  • รับ feedback เป็นเรื่องส่วนตัว — review โค้ด ไม่ใช่ review คน
  • เปิด PR ใหญ่มหึมา ("ช่วย review 5000 บรรทัดที") — แตกย่อยก่อน
  • ใช้ git push --force ระหว่าง review (ทำให้ comment ของ reviewer หลุดจาก line) → ให้ commit เพิ่มแทน

21. GitHub Features

GitHub เป็นมากกว่าที่เก็บโค้ด — มันมีเครื่องมือจัดการโปรเจกต์ครบ: Issues (ติดตามบั๊ก/งาน), Milestones (จัดกลุ่มเป็น release), Projects (บอร์ด Kanban), Actions (CI/CD), Releases (ปล่อยเวอร์ชัน) มารู้จักทีละตัวว่าใช้ตอนไหน:

Issues (ติดตามบั๊ก/งาน)

text
Title (หัวข้อ):
[Bug] Login fails for users with special chars in email
(Login ล้มเหลวสำหรับผู้ใช้ที่อีเมลมีอักขระพิเศษ)

Description (รายละเอียด):
Steps to reproduce (ขั้นตอนทำให้เกิดบั๊ก):
1. ...

Expected (สิ่งที่ควรเกิด):
Actual (สิ่งที่เกิดจริง):
Screenshots (รูปประกอบ):
Environment (สภาพแวดล้อม): Chrome 120, macOS

Labels (ป้ายกำกับ): bug, priority-high

Milestones (จัดกลุ่ม issue เป็น release)

จัด issues ที่จะแก้ในรุ่นเดียวกันเข้ากลุ่มเดียว เช่น "v1.5.0"

Projects (บอร์ดงานแบบ Kanban)

จัดงานเป็น column (To Do / In Progress / Done) ใช้แทน Trello/Jira ได้

Actions (CI/CD ในตัว)

ตั้ง workflow ให้รัน test/build/deploy อัตโนมัติเวลามี push หรือ PR → รายละเอียดอยู่ในบทที่ 5

Releases (ปล่อยเวอร์ชัน)

ใช้ tag คู่กับ release notes:

bash
git tag -a v1.5.0 -m "Release 1.5.0"   # tag แบบ annotated (แนะนำ — มี metadata ครบ)
git push origin v1.5.0

จากนั้นไปที่หน้า Releases บน GitHub → Create release → GitHub สร้าง release notes ให้อัตโนมัติจาก commit + PR ที่อยู่ระหว่างสอง tag

💡 อย่าใช้ git tag v1.5.0 แบบไม่มี -a (tag แบบ lightweight) สำหรับ release จริง — มันเป็นแค่ pointer ไม่เก็บ author/date/message

Wiki / Discussions

เอกสารโปรเจกต์ (Wiki) และพื้นที่ถาม-ตอบของ community (Discussions)


22. Git Workflows

A. Trunk-based (สมัยใหม่)

text
main ────●──●──●──●──●──●──●──
          \       \       /
           feat-1  feat-2
           (อายุสั้น น้อยกว่า 1 วัน)
  • ทุกคน push เข้า main (ผ่าน PR สั้น ๆ)
  • feature branch อายุไม่เกิน 1 วัน
  • ฟีเจอร์ที่ยังไม่เสร็จซ่อนด้วย feature flag
  • deploy บ่อย (วันละหลายครั้ง — Continuous Deployment)
  • ใช้ที่: Google, Facebook, Netflix, startup สมัยใหม่

B. Git Flow (รูปแบบคลาสสิก)

text
main ──────────●─────────●──── (สาย release จริง)
                \       /
                 release-1.5

develop ────●────●────●────●─── (สายรวมงาน integration)
              \       /
               feature-x
  • main = โค้ดที่อยู่ production จริง
  • develop = สายรวมงานก่อน release
  • feature/* → merge เข้า develop
  • release/* → merge เข้าทั้ง main และ develop
  • hotfix/* → merge เข้าทั้ง main และ develop

ซับซ้อน เหมาะกับโปรเจกต์ที่ release ไม่บ่อย (mobile app, embedded firmware)

💡 เกร็ดประวัติศาสตร์: Vincent Driessen ผู้คิด Git Flow ออกมาประกาศปี 2020 ว่ารูปแบบนี้ไม่เหมาะกับทีมส่วนใหญ่แล้ว — แนะนำให้ใช้ trunk-based แทน ถ้าทำเว็บ/SaaS ที่ deploy บ่อย

C. GitHub Flow (เรียบง่าย)

text
main ──●──●──●──●──●──
        \    \    /
         f1   f2-fix
  • main พร้อม deploy ได้ตลอดเวลา
  • เริ่ม feature → แตก branch → เปิด PR → merge → deploy
  • เรียบง่าย เหมาะกับทีมเล็ก-กลาง

ถ้าเริ่มต้นใหม่ให้ใช้ GitHub Flow ก่อน แล้วค่อยขยับเป็น trunk-based ถ้าทีมพร้อม


23. Hooks

Git hook คือ script ที่รันอัตโนมัติเมื่อเกิด event บางอย่าง เช่นก่อน commit (pre-commit) — ใช้บังคับให้ test/lint ผ่านก่อนถึงจะ commit ได้ ป้องกันโค้ดเสียเข้า repo

⚠️ ข้อสำคัญ: โฟลเดอร์ .git/hooks/ ไม่ได้อยู่ใน version control — ก็คือคนที่ clone repo มาจะไม่ได้ hook ไปด้วย ในงานทีมต้องใช้ tool ที่เก็บ hook ไว้ในโฟลเดอร์ของโปรเจกต์แทน (เช่น Husky, pre-commit framework, หรือ core.hooksPath)

ตัวอย่างพื้นฐาน (สำหรับเรียนรู้เท่านั้น — ทีมใช้ Husky)

bash
# ไฟล์: .git/hooks/pre-commit
#!/bin/bash
# script นี้จะรันก่อน git commit — ถ้า exit ไม่ใช่ 0 จะ block commit

./mvnw test                       # รัน test
if [ $? -ne 0 ]; then
    echo "Tests failed! (เทสต์ไม่ผ่าน)"
    exit 1
fi
bash
chmod +x .git/hooks/pre-commit    # ทำให้ executable (ขั้นจำเป็น ไม่งั้นไม่รัน)

Husky (สำหรับ Node.js — ยอดนิยม)

💡 Husky ใช้ได้เฉพาะ project ที่มี package.json (Node.js) — ถ้าเป็น Java/Python/Go ใช้ pre-commit framework (เขียน Python) หรือเขียน hook ตรง ๆ ใน core.hooksPath

bash
# Husky v9+ (2024+) — ติดตั้งง่ายกว่าเดิม
npm install -D husky
npx husky init                      # สร้างโฟลเดอร์ .husky/ + ตั้ง prepare script

แล้วแก้ .husky/pre-commit ให้รันคำสั่งที่ต้องการ:

bash
# ไฟล์: .husky/pre-commit
npm test

ℹ️ Husky v8 ใช้ npx husky install + npx husky add — ทั้งสองคำสั่งถูก deprecated ใน v9 แล้ว ตัวอย่างเก่าใน internet ส่วนใหญ่จะยังใช้แบบเก่าอยู่

lint-staged (lint เฉพาะไฟล์ที่แก้ — เร็ว)

bash
npm install -D lint-staged

ใส่ใน package.json:

json
{
  "lint-staged": {
    "*.ts": "eslint --fix",
    "*.json": "prettier --write"
  }
}

→ pre-commit จะ lint เฉพาะไฟล์ที่ stage (ไม่ใช่ทั้งโปรเจกต์) — เร็วและไม่สแปม diff


24. Monorepo

"monorepo" คือการเก็บหลายโปรเจกต์ (web, mobile, api, shared library) ไว้ใน repo เดียว แทนที่จะแยกหลาย repo — ข้อดีคือแชร์โค้ดและ refactor ข้ามโปรเจกต์ง่าย แต่ต้องใช้เครื่องมือช่วย (pnpm workspace, Turborepo, Nx) เพื่อจัดการ build และ cache ให้เร็ว เป็นรูปแบบที่บริษัทใหญ่นิยม:

Single repo, multiple project:

text
my-monorepo/
├── apps/
│   ├── web/
│   ├── mobile/
│   └── api/
├── packages/
│   ├── shared/
│   ├── ui/
│   └── utils/
├── pnpm-workspace.yaml
└── turbo.json

Tools:

  • pnpm workspace
  • Turborepo — task runner + cache
  • Nx — full-featured

เครื่องมือ Git สำหรับ repo ใหญ่ (ปี 2026)

ถ้า monorepo โตจน clone นาน Git มี feature ใหม่ที่ช่วยมาก:

bash
# Partial clone — ไม่โหลด blob (เนื้อไฟล์) ทั้งหมด โหลดเฉพาะที่ต้องใช้
git clone --filter=blob:none https://github.com/big/repo.git

# Sparse checkout — checkout เฉพาะบางโฟลเดอร์
cd repo
git sparse-checkout init --cone
git sparse-checkout set apps/web packages/shared

# Git maintenance — บำรุงรักษา repo อัตโนมัติ (gc, prefetch, etc.)
git maintenance start

# fsmonitor — ใช้กับ repo ใหญ่ ทำให้ git status เร็วขึ้น 10x+
git config --global core.fsmonitor true

24a. สิ่งใหม่ใน Git สมัย 2026

สรุปฟีเจอร์ใหม่ที่ Git เพิ่มมาในช่วงไม่กี่ปีหลัง (≥ 2.30) ที่ควรเปิดใช้:

ฟีเจอร์คำสั่ง / configใช้ทำอะไร
switch / restoregit switch <branch>แทน checkout ที่ทำหลายอย่างปนกัน (2.23+)
push.autoSetupRemotegit config --global push.autoSetupRemote truepush branch ใหม่ไม่ต้องพิมพ์ --set-upstream (2.37+)
--force-if-includesgit push --force-with-lease --force-if-includesกัน race condition ตอน force push (2.30+)
rereregit config --global rerere.enabled trueจำการแก้ conflict ครั้งก่อน แก้ครั้งต่อไปอัตโนมัติ
Partial clonegit clone --filter=blob:noneclone repo ใหญ่โดยไม่โหลดไฟล์ทั้งหมด
Sparse checkoutgit sparse-checkout init --conecheckout เฉพาะบาง path (monorepo)
Git maintenancegit maintenance startบำรุง repo อัตโนมัติ (gc, prefetch)
SSH signinggit config --global gpg.format sshเซ็น commit ด้วย SSH key (2.34+ — ดูข้อ 2)
SHA-256 repogit init --object-format=sha256repo ใหม่ใช้ SHA-256 (2.42+ — ยังไม่ default และไม่เข้ากับ SHA-1)

💡 config ใน ข้อ 2 ของเราเปิด pull.rebase, push.autoSetupRemote, rerere.enabled ไว้ครบแล้ว


25. ⚠️ Common Pitfalls

รวมกับดักที่มือใหม่ (และมือเก๋าบางที) มักพลาดกับ Git — ส่วนใหญ่กู้คืนยากหรือก่อปัญหากับทั้งทีม เช่น force push ทับ branch ที่ใช้ร่วมกัน หรือ commit secret/ไฟล์ใหญ่ ตารางนี้จับคู่ "สิ่งที่ไม่ควรทำ" กับ "สิ่งที่ควรทำแทน":

❌ ไม่ควรทำ✅ ทำแบบนี้แทน
git push --force เข้า branch ที่หลายคนใช้--force-with-lease --force-if-includes (ปลอดภัยขึ้น)
commit .env หรือไฟล์ลับใส่ใน .gitignore ก่อน + เก็บใน secret vault
commit ไฟล์ binary ขนาดใหญ่ใช้ git-lfs (Large File Storage)
branch อายุยาว (เป็นสัปดาห์)sync (rebase) กับ main ทุกวัน
commit แบบ "wip" / "asdf"ใช้ Conventional Commits
push โดยไม่รัน testตั้ง pre-commit hook ให้รัน test ก่อน
ยุบทุก commit เป็น 1 ตัวเสมอคง commit ที่มีความหมายไว้
git pull แล้วเจอ conflict ทุกทีgit pull --rebase (หรือตั้ง pull.rebase true)
ไฟล์ .env ที่เผลอ track แล้วgit rm --cached .env แล้วใส่ใน .gitignore
มีหลาย commit ที่เป็น "fix typo"git rebase -i แล้วใช้ fixup รวมเข้าตัวก่อนหน้า

26. Productivity

พิมพ์ git status/checkout วันละหลายสิบครั้งทำให้เสียเวลา — ตั้ง alias สั้น ๆ และใช้เครื่องมือเสริม (GitHub CLI gh, lazygit, GitLens) ช่วยให้ทำงานกับ Git เร็วขึ้นมาก โดยเฉพาะ gh ที่จัดการ PR/issue ได้จาก terminal ไม่ต้องสลับไปเปิดเบราว์เซอร์:

Aliases

bash
git config --global alias.st status
git config --global alias.sw switch
git config --global alias.br branch
git config --global alias.cm commit
git config --global alias.lg "log --oneline --graph --all"
git config --global alias.last "log -1 HEAD"
git config --global alias.unstage "reset HEAD --"

⚠️ ระวัง alias ชน: ในข้อ 2 เราตั้ง alias.sw = switch ไปแล้ว — อย่าตั้ง alias.co = checkout คู่กันถ้าจะใช้ทั้งสองตัว เลือกใช้แนวเดียวไป (switch/restore แบบใหม่)

Tools

  • GitHub CLI (gh) — จัดการ PR/issue จาก terminal ไม่ต้องเปิดเบราว์เซอร์
  • lazygit — TUI (terminal UI) สำหรับ Git — สลับ branch / stage / commit ด้วยปุ่มเดียว
  • fork / GitKraken — GUI client (ดูกราฟ commit สวย)
  • VS Code + extension GitLens — เห็น blame/history ในตัว editor

gh CLI

bash
gh pr create                      # สร้าง PR (interactive)
gh pr list                        # ดูรายการ PR
gh pr checkout 123                # checkout PR เลขที่ 123 มาทดสอบ local
gh pr view 123                    # ดูรายละเอียด PR
gh pr merge 123 --squash          # merge แบบ squash จาก terminal

gh issue create                   # สร้าง issue ใหม่
gh issue list                     # ดูรายการ issue

gh repo clone user/repo           # clone repo (สั้นกว่า https://...)

27. Real-world Workflow

มาดูว่าคำสั่งทั้งหมดในบทนี้ร้อยเรียงเป็นวันทำงานจริงของ developer ยังไง — เริ่มเช้าด้วย pull main → แตก branch → commit งาน → จัด history ให้สะอาด → sync กับ main → push → เปิด PR → แก้ตาม review → merge → ลบ branch วนแบบนี้ทุกฟีเจอร์:

bash
# เช้า — เริ่มวัน
git switch main
git pull --rebase                  # ดึงงานใหม่จากทีม

# เริ่มทำ feature ใหม่
git switch -c feature/user-search
# ... เขียนโค้ด ...
git add .
git commit -m "feat(user): add search by email"
# ... เขียนเพิ่ม ...
git commit -m "feat(user): add fuzzy match"

# ก่อนเปิด PR — จัดประวัติให้สะอาด
git rebase -i HEAD~2               # ยุบ commit "fix typo" รวมเข้าตัวก่อนหน้า

# sync กับ main (กัน conflict ตอน merge)
git fetch origin
git rebase origin/main
# ถ้าเจอ conflict — แก้แล้ว git add → git rebase --continue

# push ขึ้น remote
git push origin feature/user-search
# (push ครั้งแรก ปกติต้อง --set-upstream แต่ถ้าตั้ง push.autoSetupRemote ในข้อ 2 แล้วไม่ต้อง)

# เปิด PR
gh pr create --fill --reviewer alice,bob

# หลังได้ review feedback
# ... แก้โค้ดตามคอมเมนต์ ...
git commit -m "address review: simplify regex"
git push                           # commit เพิ่ม ไม่ใช้ force push (จะทำให้ comment หาย)

# หลัง merge แล้ว
git switch main
git pull
git branch -d feature/user-search  # ลบ branch ทิ้ง (local)

28. Checkpoint

🛠️ Checkpoint 2.1 — Basic Flow

  • Clone repo
  • Create branch
  • Make 3 commits
  • Push + PR
  • Self-review + merge

🛠️ Checkpoint 2.2 — Conflict Resolution

  • Create branch A, modify line 10
  • Create branch B (from main), modify same line 10
  • Merge A → main
  • Try merge B → conflict → resolve

🛠️ Checkpoint 2.3 — Interactive Rebase

  • 5 commits ที่มี "fix typo" สลับกัน
  • Interactive rebase → squash typos
  • Force push (your own branch only!)

🛠️ Checkpoint 2.4 — Recover Deleted Commit

  • Commit, then git reset --hard HEAD~1
  • ใช้ git reflog กู้กลับ

🛠️ Checkpoint 2.5 — Setup Husky

  • npm install -D husky lint-staged
  • Pre-commit hook ที่ run lint + format
  • Test ว่า block bad commit

29. สรุปบท

ทบทวนทุกอย่างที่เรียนในบทนี้ — ตั้งแต่ mental model ของ Git, คำสั่งพื้นฐานในชีวิตประจำวัน, ไปจนถึงเทคนิคขั้นสูงและ workflow การทำงานเป็นทีมบน GitHub เช็กลิสต์นี้คือสิ่งที่ควรทำได้คล่องก่อนไปบทถัดไป:

✅ แผนภาพคิดของ Git: working → staging → repo → remote
✅ คำสั่งพื้นฐาน: add, commit, push, pull, status, diff, log
✅ commit message: ใช้ Conventional Commits (feat:, fix:, ...)
✅ branch: switch -c, ตั้งชื่อแบบ feature/*
✅ merge กับ rebase — branch ที่หลายคนใช้ใช้ merge, branch ของตัวเองใช้ rebase
✅ conflict — อ่านเครื่องหมาย → แก้ → add → commit
✅ การย้อน: restore, reset, revert, reflog (ตัวช่วยกู้คืน)
✅ interactive rebase — จัดประวัติให้สะอาดก่อนเปิด PR
✅ cherry-pick, bisect, stash — เทคนิคขั้นสูงไว้ใช้ตอนเจอสถานการณ์จริง
✅ GitHub Flow — workflow ง่าย ๆ ที่เหมาะกับทีมส่วนใหญ่
✅ PR workflow — PR เล็ก, self-review ก่อน, ใช้ Conventional Commits, รัน test ผ่าน
✅ Hooks (Husky) + lint-staged — บังคับ test/lint ก่อน commit
gh CLI — เพิ่มความเร็วในการทำงานกับ PR/issue


← บทที่ 1 | ต่อไป → Docker Book (หรือดู quick reference ที่ บท 03)