← All posts

Git Merge, Rebase & Squash — ตอนที่ 6: Workflow ของทีม และตารางตัดสินใจ

Data-Sci & Digital HealthBasic InfoTech & Computing Nexus
Git Merge, Rebase & Squash — ตอนที่ 6: Workflow ของทีม และตารางตัดสินใจ
On this page

บทคัดย่อ

การกำหนดนโยบายและขั้นตอนการทำงานร่วมกันในระบบควบคุมเวอร์ชันมีความสำคัญอย่างยิ่งต่อการรักษาประวัติของโครงการให้มีความเป็นระเบียบและสามารถกู้คืนได้เมื่อเกิดข้อผิดพลาด การทำงานผ่านระบบการจัดการ Pull Request จะมีทางเลือกในการรวมโค้ดที่สอดคล้องกับคำสั่งพื้นฐาน ได้แก่ การสร้าง merge commit เพื่อเก็บรักษาประวัติการทำงานทั้งหมด การประยุกต์ใช้ squash เพื่อรวบรวมทุกการแก้ไขให้กลายเป็นหนึ่ง commit และกระบวนการ rebase เพื่อสร้างประวัติแบบเส้นตรงซึ่งเป็นการสร้าง commit ขึ้นมาใหม่และส่งผลให้ค่า hash เปลี่ยนแปลงไป นอกจากนี้ ผู้ใช้งานจำเป็นต้องแยกแยะกลไกการย้อนกลับการทำงานให้ชัดเจน โดยคำสั่ง reset จะทำหน้าที่ย้ายตำแหน่งตัวชี้ของ branch ในขณะที่ revert จะสร้าง commit ใหม่เพื่อยกเลิกการเปลี่ยนแปลงเดิมซึ่งเป็นวิธีการที่ปลอดภัยสำหรับ branch ที่ใช้งานร่วมกัน และหากต้องการจัดการกับ merge commit จะต้องระบุสายการพัฒนาหลักผ่านตัวเลือก -m 1 เสมอ สำหรับการดึงเฉพาะบางการแก้ไขสามารถใช้คำสั่ง cherry-pick เพื่อคัดลอก commit ไปยัง branch ปลายทาง และหากเกิดความผิดพลาดก็ยังสามารถกู้คืนข้อมูลที่ดูเหมือนสูญหายไปแล้วได้ผ่านเครื่องมือ reflog รวมถึงควรตั้งค่าพฤติกรรมของคำสั่ง pull ให้เป็นการทำ rebase หรือ fast-forward เพื่อป้องกันความสับสน บทความนี้รวบรวมหลักเกณฑ์และตารางการตัดสินใจในการเลือกใช้คำสั่งต่างๆ เพื่อเป็นแนวทางปฏิบัติให้ทีมพัฒนาสามารถจัดการประวัติการทำงานและผสานโค้ดได้อย่างปลอดภัยและตรงตามนโยบายของโครงการ


อินโฟกราฟิกสรุปเนื้อหา Git Merge, Rebase & Squash — ตอนที่ 6: Workflow ของทีม และตารางตัดสินใจ
Visual summary · ภาพสรุป

นี่คือตอนสุดท้ายของซีรีส์ ห้าตอนก่อนหน้าอธิบายกลไก ตั้งแต่กราฟ commit และ merge ทุกรูปแบบ, squash merge, rebase, interactive rebase ไปจนถึงการจัดการ conflict และ merge strategies ส่วนตอนที่ 6 จะเปลี่ยนทั้งหมดนั้นให้กลายเป็น การตัดสินใจ ว่าในแต่ละสถานการณ์ควรใช้คำสั่งไหน, ปุ่มบน Pull Request ของ GitHub แต่ละปุ่มตรงกับแนวคิดที่เรารู้อยู่แล้วอย่างไร และทีมควรตกลง workflow แบบไหนเพื่อให้ประวัติสะอาดและกู้คืนได้

หลังจบตอนนี้ คุณจะเลือกเครื่องมือที่เหมาะกับทุกสถานการณ์ในชีวิตประจำวันได้จากตารางตัดสินใจเพียงตารางเดียว, รัน workflow แบบ feature branch ที่ปลอดภัยได้ตั้งแต่ commit แรกจนถึง merge PR, แยก reset ออกจาก revert และ rebase ได้, revert merge commit ได้ถูกต้อง และกู้งานที่คิดว่าหายไปแล้วกลับมาด้วย reflog

สามวิธี merge Pull Request บน GitHub

เวลา merge Pull Request บน GitHub เรามักเจอสามปุ่มให้เลือก ซึ่งแต่ละปุ่มตรงกับแนวคิดจากตอนก่อน ๆ

Create a merge commit

เก็บ commits ทั้งหมดจาก branch ไว้ และเพิ่ม merge commit ทับด้านบน ประวัติการพัฒนาทั้งหมดของ PR ยังมองเห็นได้บน target branch

Squash and merge

รวมทั้ง Pull Request ให้เป็น commit เดียวบน target branch — หนึ่ง PR กลายเป็นหนึ่ง commit

Rebase and merge

สร้าง commits ของ PR ขึ้นใหม่ต่อท้าย target branch โดยไม่มี merge commit และเพราะ commits ถูกสร้างใหม่บนฐานใหม่ commit hashes ของ PR บน target branch จะเปลี่ยนเสมอ — GitHub สร้าง commits ใหม่ทุกครั้ง แม้ในกรณีที่ fast-forward ได้ก็ตาม

"Rebase and merge" ให้ทั้ง commits รายตัว และ history เป็นเส้นตรง แต่ต้องจำว่า hash เป็นชุดใหม่ เหมือนตอน rebase ในเครื่องตัวเอง

Cherry-pick เกี่ยวข้องอย่างไร

Cherry-pick ไม่ใช่ merge แต่ควรรู้ไว้ในกล่องเครื่องมือเดียวกัน

git cherry-pick <commit-hash>

ความหมาย:

คัดลอกการเปลี่ยนแปลงของ commit หนึ่งมาสร้างเป็น commit ใหม่บน branch ปัจจุบัน

ก่อน เริ่มจาก:

A---B---C  main
     \
      D---E  feature

บน main สั่ง:

git cherry-pick E

หลัง:

A B C E′ D E copy main feature
git cherry-pick E คัดลอก commit E มาไว้บน main เป็น commit ใหม่ E′ ส่วน E ตัวเดิมยังอยู่บน feature

สังเกตว่า commit ใหม่คือ E' ซึ่งเป็นสำเนาใหม่ ไม่ใช่ E ตัวเดิม ใช้ cherry-pick เมื่อ:

ไม่ควรใช้ cherry-pick เพื่อรวม feature branch ทั้งก้อนแทน merge โดยไม่มีเหตุผล เพราะจะเกิด history ซ้ำซ้อน (duplicated history)

Reset ไม่เหมือน Rebase

Reset

Reset คือการย้าย branch pointer

git reset --soft HEAD~1
git reset --mixed HEAD~1
git reset --hard HEAD~1

Rebase

Rebase คือการนำ commits ไป replay บน base ใหม่

git rebase main

ทั้งคู่แก้ history ได้ แต่จุดประสงค์ต่างกัน: reset ปรับว่า branch ชี้ ไปที่ไหน ส่วน rebase สร้างตัว commits ขึ้นใหม่บนจุดเริ่มต้นใหม่

Revert ไม่เหมือน Reset

Revert

Revert คือการสร้าง commit ใหม่ที่ย้อนผลของ commit เก่า

git revert <commit-hash>

เหมาะกับ shared branch เพราะไม่ลบ history

Reset

Reset ย้าย pointer ย้อนกลับ และอาจทำให้ commits หายไปจาก branch

git reset --hard HEAD~1

เหมาะกับ local history ที่ยังไม่ได้แชร์

Revert merge commit

Merge commit มี parent มากกว่าหนึ่งตัว จึงต้องบอก Git ว่า parent ไหนคือ mainline

git revert -m 1 <merge-commit-hash>

ลองนึกถึง three-way merge ที่ M มีสอง parent:

D E A B C F G M feature/login main 1 2 parent 1 = G (mainline) parent 2 = E (feature)
merge commit M มีสอง parent — parent 1 = G บน main (mainline) และ parent 2 = E บน feature/login; git revert -m 1 เก็บฝั่ง mainline และย้อนการเปลี่ยนแปลงจากฝั่ง feature

โดยทั่วไป -m 1 หมายถึง "ให้ parent แรกเป็น branch หลัก แล้วลบผลที่มาจาก branch ที่ merge เข้ามา" ควรตรวจ parent ก่อนเสมอ:

git show <merge-commit-hash>

git pull ทำอะไร

โดยทั่วไป:

git pull

เทียบเท่าแนวคิด:

git fetch
git merge

ซึ่งอาจสร้าง merge commit

Pull ด้วย rebase

git pull --rebase

เทียบเท่า:

git fetch
git rebase origin/main

Pull แบบห้ามสร้าง merge commit

git pull --ff-only

แนะนำให้ตั้งพฤติกรรมให้ชัดเจน ดีกว่าปล่อยค่าไว้แบบไม่แน่นอน

ตั้ง pull ให้ rebase เสมอ

git config --global pull.rebase true

ตั้ง pull เป็น fast-forward only

git config --global pull.ff only

เลือกอย่างใดอย่างหนึ่งตาม workflow ของทีม

ตัวอย่าง workflow ที่แนะนำสำหรับ feature branch

ขั้นที่ 1 — อัปเดต main

git switch main
git pull --ff-only

ขั้นที่ 2 — สร้าง feature branch

git switch -c feature/login

ขั้นที่ 3 — ทำงานและ commit

git add .
git commit -m "feat: add login form"

git add .
git commit -m "test: add login validation tests"

ขั้นที่ 4 — ก่อนเปิด PR ให้ update base

git fetch origin
git rebase origin/main

แก้ conflict หากมี:

git add .
git rebase --continue

ขั้นที่ 5 — push

ถ้ายังไม่เคย push:

git push -u origin feature/login

ถ้าเคย push ก่อน rebase:

git push --force-with-lease

ขั้นที่ 6 — Merge PR

เลือกตาม policy ของทีม: Squash and merge, Rebase and merge หรือ Merge commit

วิธีเลือกสำหรับแต่ละสถานการณ์

ใช้ normal merge เมื่อ

git merge --no-ff feature/login

ใช้ rebase เมื่อ

git switch feature/login
git rebase main

ใช้ squash merge เมื่อ

git switch main
git merge --squash feature/login
git commit -m "feat: add login system"

ใช้ fast-forward only เมื่อ

git merge --ff-only feature/login

ใช้ cherry-pick เมื่อ

git cherry-pick abc1234

ตารางตัดสินใจ (Decision table)

สรุปทั้งซีรีส์ให้เหลือตารางเดียว: หาสถานการณ์ของคุณทางซ้าย แล้วใช้วิธีทางขวา

สถานการณ์ วิธีที่เหมาะ
Branch ส่วนตัวต้องตาม main ให้ทัน git rebase main
Shared branch มีหลายคนทำ Merge
PR มี commits ยุ่งเหยิง Squash merge
ต้องการเก็บทุก commit แต่ history ตรง Rebase and merge
ต้องการเห็น feature boundary merge --no-ff
ต้องการเฉพาะ bug fix หนึ่ง commit Cherry-pick
Production branch ต้องย้อนการเปลี่ยนแปลง Revert
Local commit ล่าสุดผิด Amend หรือ interactive rebase
ไม่อนุญาต merge commit --ff-only

Policy ที่เหมาะกับทีมขนาดเล็ก

สำหรับทีมเล็ก แนะนำรูปแบบนี้:

main
  └── protected และห้าม force push

feature/*
  └── ผู้สร้างสามารถ rebase ได้

Pull Request
  └── ใช้ Squash and merge

Workflow:

git switch feature/my-feature
git fetch origin
git rebase origin/main
git push --force-with-lease

จากนั้นใช้ Squash and merge ผลลัพธ์บน main คือหนึ่ง commit สะอาดต่อหนึ่ง PR:

A B C Feature1 Feature2 BugFix main
policy แบบ squash-and-merge: main เป็นเส้นตรง หนึ่ง commit ต่อหนึ่ง feature — Feature1, Feature2, BugFix

หนึ่ง PR เท่ากับหนึ่ง commit เหมาะกับ SaaS, web application และทีมที่ต้องการ history อ่านง่าย

Policy ที่เหมาะกับโปรเจกต์ที่ต้องเก็บประวัติโดยละเอียด

เมื่อต้องเก็บบันทึกการพัฒนาแบบเต็ม ให้ใช้ branch model แบบ:

main
develop
feature/*
release/*
hotfix/*

แล้ว integrate ด้วย:

git merge --no-ff feature/x

ผลลัพธ์:

เหมาะกับระบบขนาดใหญ่, หลาย release lines, งานที่ต้อง audit และกรณีที่ต้องตรวจว่า feature ถูกพัฒนาผ่านขั้นตอนใดบ้าง

ดูกราฟ Git ให้เข้าใจง่าย

ดูกราฟให้ชัดด้วย:

git log --graph --oneline --decorate --all

ตัวอย่างผลลัพธ์:

*   a8f9210 (HEAD -> main) Merge feature/login
|\
| * 73ab110 Add validation
| * 19c4d20 Add login form
* | 10fc991 Update dependencies
|/
* 61ad820 Initial commit

แนะนำ alias:

git config --global alias.graph "log --graph --oneline --decorate --all"

จากนั้นใช้:

git graph

Recovery เมื่อทำพลาด

Git มักยังช่วยกู้ได้ด้วย reflog

git reflog

ตัวอย่าง:

a123456 HEAD@{0}: rebase finished
b234567 HEAD@{1}: rebase started
c345678 HEAD@{2}: commit: working version

กู้ branch กลับ:

git branch recovery c345678

หรือ:

git reset --hard c345678

ก่อนใช้ reset --hard ต้องแน่ใจว่าไม่ต้องการ uncommitted changes

สรุปเป็นประโยคเดียวต่อคำสั่ง

กฎจำที่สำคัญที่สุด

Merge preserves history. Rebase rewrites history. Squash compresses history.

และ:

Rebase local/private history ได้ แต่หลีกเลี่ยงการ rebase shared public history

สำหรับ workflow ทั่วไปที่ปลอดภัยและสะอาด:

git switch feature/my-feature
git fetch origin
git rebase origin/main
git push --force-with-lease

แล้วใช้ Squash and merge เมื่อ PR ผ่าน review

Key takeaways

0
ถึงนักอ่านชาวไทยและต่างชาติทำความเข้าใจบริบททางการแพทย์ของผมอ่านต่อ →ถึงนักอ่านชาวไทยและต่างชาติทำความเข้าใจเนื้อหาของผมที่นอกเหนือจากการแพทย์อ่านต่อ →

ความคิดเห็น

ยังไม่มีความคิดเห็น มาเป็นคนแรกกันเลย

เข้าสู่ระบบเพื่อแสดงความคิดเห็น