git fetch และ git pull: ต่างกันอย่างไร และควรเลือกอะไร
git fetch ดาวน์โหลดคอมมิตใหม่จากเซิร์ฟเวอร์และอัปเดตบรานช์รีโมตอย่าง origin/main โดยไม่แตะไฟล์หรือบรานช์ของคุณ ส่วน git pull ทำเหมือนกันและรวมสิ่งที่ดาวน์โหลดเข้ากับบรานช์ปัจจุบันทันทีด้วย merge หรือ rebase fetch ปลอดภัยเสมอ ส่วน pull สะดวกเมื่อคุณพร้อมรับการเปลี่ยนแปลงของผู้อื่นทันที
คำตอบสั้น
git fetch ดาวน์โหลดคอมมิตใหม่จากเซิร์ฟเวอร์และอัปเดตการอ้างอิงของบรานช์รีโมต (origin/main, origin/feature-x) แต่ไม่เปลี่ยนบรานช์ปัจจุบันหรือไฟล์ในไดเรกทอรีทำงาน ส่วน git pull คือ git fetch บวกกับการรวมสิ่งที่ดาวน์โหลดเข้ากับบรานช์ปัจจุบันทันที ด้วย merge (ค่าเริ่มต้น) หรือ rebase
จากนี้เป็นหลักปฏิบัติ: fetch รันได้ตลอดเวลาและไม่ทำให้อะไรพัง pull เปลี่ยนบรานช์ของคุณและอาจจบด้วยความขัดแย้ง ดังนั้นควรรันเมื่อไดเรกทอรีทำงานสะอาดและเข้าใจว่าจะมีอะไรเข้ามา
บรานช์รีโมตอยู่ที่ไหน
เพื่อให้เห็นความต่างชัดเจน ต้องเข้าใจว่ารีโพซิทอรีในเครื่องมีบรานช์สองประเภท บรานช์ท้องถิ่นอย่าง main และ feature-x ขยับไปตามคอมมิตของคุณ ส่วนบรานช์รีโมตที่ติดตามอยู่ เช่น origin/main เป็นบุ๊กมาร์กที่บอกว่า "เซิร์ฟเวอร์หน้าตาแบบนี้ ณ การซิงก์ครั้งล่าสุด" ไม่สามารถขยับด้วยมือได้ มีเพียงการสื่อสารกับเซิร์ฟเวอร์ ได้แก่ fetch, pull และ push ที่อัปเดตมันได้
git branch -a
* main
remotes/origin/HEAD -> origin/main
remotes/origin/main
remotes/origin/feature-x
git status เปรียบเทียบบรานช์ของคุณกับบุ๊กมาร์ก origin/main ไม่ใช่กับเซิร์ฟเวอร์จริง ดังนั้นข้อความ "Your branch is up to date with 'origin/main'" อาจไม่ถูกต้อง หากนานแล้วไม่ได้ fetch Git จะไม่รู้เรื่องคอมมิตใหม่ของเพื่อนร่วมทีม
git fetch ทำอะไร
$ git fetch
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
Unpacking objects: 100% (4/4), done.
From github.com:team/project
3f2a1c9..8b7d4e2 main -> origin/main
บรรทัดสุดท้ายบอกประเด็นหลัก: การอ้างอิง origin/main เลื่อนจาก 3f2a1c9 ไปยัง 8b7d4e2 ส่วน main ของคุณยังอยู่ที่เดิม ตอนนี้ดูได้อย่างสบายใจว่ามีอะไรเข้ามา:
git status # Your branch is behind 'origin/main' by 2 commits
git log main..origin/main --oneline # คอมมิตที่คุณยังไม่มี
git diff main origin/main # ไฟล์จะเปลี่ยนไปอย่างไร
ตัวเลือกที่มีประโยชน์ของ fetch:
git fetch --allดึงการเปลี่ยนแปลงจาก remote ทั้งหมด (มีประโยชน์เมื่อ fork มีทั้งoriginและupstream)git fetch --prune(-p) ลบบุ๊กมาร์กบรานช์ท้องถิ่นที่ไม่มีอยู่บนเซิร์ฟเวอร์แล้วgit fetch origin mainดึงเฉพาะหนึ่งบรานช์git fetch --tagsดึงแท็กทั้งหมดเพิ่มเติม
เพื่อให้การล้างทำงานเสมอ เปิดใช้ครั้งเดียว: git config --global fetch.prune true
git pull ทำอะไร
git pull ทำสองขั้นตอนติดกัน ขั้นแรกคือ fetch บรานช์ upstream ของบรานช์ปัจจุบัน จากนั้นรวม origin/<บรานช์> เข้ากับบรานช์ของคุณ ในความหมายตรง ๆ เท่ากับ:
git fetch origin
git merge origin/main # หรือ git rebase origin/main เมื่อใช้ pull --rebase
ผลของการรวมขึ้นอยู่กับว่าประวัติแยกออกจากกันอย่างไร:
- หากคุณไม่มีคอมมิตใหม่ จะเป็น fast-forward บรานช์เลื่อนไปข้างหน้าเฉย ๆ
- หากทั้งคุณและเซิร์ฟเวอร์มีคอมมิตใหม่ merge จะสร้างคอมมิต merge ส่วน rebase จะจัดเรียงคอมมิตของคุณใหม่ให้อยู่บนสุดของคอมมิตจากเซิร์ฟเวอร์
- หากบรรทัดเดียวกันถูกแก้ไขทั้งสองฝั่ง จะเกิดความขัดแย้งที่ต้องแก้ด้วยมือ
เปรียบเทียบ git fetch และ git pull
| git fetch | git pull | |
|---|---|---|
| ดาวน์โหลดคอมมิต | ใช่ | ใช่ |
| อัปเดต origin/* | ใช่ | ใช่ |
| เปลี่ยนบรานช์ปัจจุบัน | ไม่ | ใช่ |
| เปลี่ยนไฟล์ในไดเรกทอรีทำงาน | ไม่ | ใช่ |
| อาจเกิดความขัดแย้ง | ไม่ | ใช่ |
| ต้องการไดเรกทอรีทำงานที่สะอาด | ไม่ | ควรมี |
| กระทบบรานช์อื่น | อัปเดตบุ๊กมาร์กทุกบรานช์ที่ติดตาม | รวมเฉพาะบรานช์ปัจจุบัน |
| ใช้เมื่อไร | ดูว่ามีอะไรเปลี่ยน ก่อน rebase และในสคริปต์ | อัปเดตบรานช์ของตัวเองให้ทันเซิร์ฟเวอร์อย่างรวดเร็ว |
git pull: merge หรือ rebase
ตั้งแต่ Git 2.27 เมื่อประวัติแยกออกโดยไม่มีการตั้งค่าที่ชัดเจน pull จะแสดงคำเตือน และในเวอร์ชันใหม่จะหยุดพร้อมข้อผิดพลาด "Need to specify how to reconcile divergent branches" Git ขอให้เลือกกลยุทธ์หนึ่งครั้ง:
git config --global pull.rebase false # merge (พฤติกรรมเริ่มต้นในอดีต)
git config --global pull.rebase true # rebase
git config --global pull.ff only # เฉพาะ fast-forward ไม่เช่นนั้นปฏิเสธ
Merge คงประวัติไว้ตามที่เป็นและเพิ่มคอมมิต "Merge branch 'main' of github.com:..." หากทุกวันทำ pull เข้าบรานช์ที่มีคอมมิตในเครื่อง ประวัติจะเต็มไปด้วยการ merge ทางเทคนิคเหล่านี้
Rebase จัดเรียงคอมมิตที่ยังไม่ได้ส่งของคุณใหม่ให้อยู่บนสุดของคอมมิตจากเซิร์ฟเวอร์ ประวัติยังคงเป็นเส้นตรง และความขัดแย้งแก้ทีละคอมมิต สำหรับคอมมิตท้องถิ่นที่ยังไม่เผยแพร่ นี่มักเป็นตัวเลือกที่ดี อันตรายของ rebase มีเฉพาะกับคอมมิตที่ส่งไปแล้วและคนอื่นกำลังต่อยอดอยู่
ff only เป็นโหมดที่เข้มงวดที่สุด pull จะทำงานก็ต่อเมื่อคุณไม่มีคอมมิตของตัวเอง หากประวัติแยกออก คุณต้องตัดสินใจเอง นักพัฒนาที่มีประสบการณ์หลายคนตั้งค่านี้สำหรับบรานช์หลัก
หากมีการแก้ไขที่ยังไม่คอมมิตในไดเรกทอรีทำงาน และต้องการ pull ทันที git pull --rebase --autostash จะช่วยได้: Git จะพักการเปลี่ยนแปลง ทำ rebase และนำกลับมา
สถานการณ์ทั่วไป: fetch ดู แล้วค่อย merge
แนวทาง "fetch ก่อน" ให้การควบคุมว่าอะไรเข้าสู่บรานช์ของคุณ มีประโยชน์เป็นพิเศษก่อนส่ง pull request และหลังหยุดพักนาน
git fetch
git log --oneline --graph HEAD origin/main # บรานช์แยกกันอย่างไร
git rebase origin/main # หรือ git merge origin/main
git push
อีกกรณีที่พบบ่อยคืออัปเดตบรานช์ฟีเจอร์ด้วย main ล่าสุดโดยไม่ต้องสลับไปที่ main:
git switch feature-x
git fetch
git rebase origin/main
ที่นี่ pull ไม่เหมาะ เพราะ git pull ในบรานช์ฟีเจอร์จะดึงการเปลี่ยนแปลงจาก upstream ของมันเอง (origin/feature-x) ไม่ใช่บรานช์หลัก จะเขียน git pull --rebase origin main ก็ได้ แต่วิธี fetch ชัดเจนกว่า
การซิงก์กับเซิร์ฟเวอร์: ทำให้บรานช์ทันสมัย
เมื่อพูดถึง "git sync" มักหมายถึงวงจร "ดึงของคนอื่น แล้วส่งของเรา" ใน Git ล้วนไม่มีคำสั่ง sync เฉพาะ (ปุ่ม Sync มีใน IDE และไคลเอนต์ GUI ซึ่งภายในทำ pull แล้วตามด้วย push) วงจรที่เชื่อถือได้เมื่อทำด้วยมือคือ:
git statusตรวจว่าทุกอย่างคอมมิตแล้วหรือเก็บด้วยgit stashgit pull --rebaseดึงการเปลี่ยนแปลงและจัดเรียงคอมมิตของคุณไว้ด้านบน- แก้ความขัดแย้งหากมี: แก้ไฟล์
git addแล้วgit rebase --continue - รันเทสต์
git push
การซิงก์ fork กับรีโพซิทอรีต้นฉบับทำได้ดังนี้:
git remote add upstream https://github.com/original/project.git
git fetch upstream
git switch main
git merge --ff-only upstream/main
git push origin main
ปัญหาที่พบบ่อยกับ git pull
Your local changes would be overwritten by merge
pull ต้องการเปลี่ยนไฟล์ที่คุณแก้ไขและยังไม่ได้คอมมิต ทางเลือกคือคอมมิต พักไว้ (git stash จากนั้น pull และ git stash pop) หรือใช้ --autostash ไม่ต้องลบการเปลี่ยนแปลงเพื่อ pull
ความขัดแย้งหลัง pull
Git จะหยุดและแสดงไฟล์ที่ขัดแย้งใน git status เปิดไฟล์ เลือกเวอร์ชันที่ต้องการระหว่างเครื่องหมาย <<<<<<< และ >>>>>>> จากนั้น git add และ git commit (เมื่อ merge) หรือ git rebase --continue (เมื่อ rebase) หากเปลี่ยนใจ git merge --abort หรือ git rebase --abort จะคืนทุกอย่างเป็นแบบก่อน pull
There is no tracking information for the current branch
บรานช์ไม่มี upstream และ Git ไม่รู้ว่าจะดึงจากที่ไหน ระบุให้ชัด: git pull origin main หรือตั้งการเชื่อมครั้งเดียว: git branch --set-upstream-to=origin/main
refusing to merge unrelated histories
รีโพซิทอรีในเครื่องและรีโมตถูกสร้างแยกกัน เช่น เพิ่ม README บนเซิร์ฟเวอร์ตอนสร้าง หากแน่ใจว่าต้องการรวมกัน: git pull origin main --allow-unrelated-histories
pull ที่ผิดพลาด: วิธียกเลิก
หาก merge หลัง pull เสร็จแล้วแต่ผลลัพธ์ไม่เป็นที่พอใจ ให้คืนบรานช์ไปยังสถานะก่อนหน้า: git reset --hard ORIG_HEAD คำสั่งนี้จะลบการเปลี่ยนแปลงที่ยังไม่คอมมิต ดังนั้นตรวจ git status ก่อน ในกรณีสุดท้ายตำแหน่งเดิมของบรานช์ยังหาได้ใน git reflog
ควรเลือกอะไร
- อยากรู้ว่ามีอะไรใหม่โดยไม่เปลี่ยนแปลงอะไร ใช้
git fetch - ทำงานในบรานช์ของตัวเองและแค่ตามเซิร์ฟเวอร์ให้ทัน ใช้
git pull --rebase(หรือ pull ที่ตั้งpull.rebase trueไว้แล้ว) - อัปเดตบรานช์หลักที่ไม่มีคอมมิตของคุณ ใช้
git pull --ff-only - ต้องการรวม
mainเข้าบรานช์ฟีเจอร์ ใช้git fetchแล้วgit rebase origin/mainหรือgit merge origin/main - เขียนสคริปต์หรือ CI ใช้ fetch และคำสั่งที่ระบุชัดเจน ไม่ใช้ pull
ลักษณะบนกราฟคอมมิต
สมมุติว่าคุณและเพื่อนร่วมงานเริ่มจากคอมมิต C คุณทำคอมมิต D ในเครื่อง ส่วนเพื่อนร่วมงาน push E ขึ้นเซิร์ฟเวอร์ หลัง git fetch รีโพซิทอรีของคุณรู้จักทั้งสองสาย: main ชี้ไปที่ D และ origin/main ชี้ไปที่ E และมีบรรพบุรุษร่วมคือ C
D <- main (HEAD)
/
A---B---C
\
E <- origin/main
จากนั้นวิธีรวมจะเป็นตัวกำหนดผล git merge origin/main จะสร้างคอมมิต M ที่มีพาเรนต์สองตัวคือ D และ E ส่วน git rebase origin/main จะคัดลอก D ไปไว้บน E เป็นคอมมิตใหม่ D' ที่มีแฮชต่างออกไป และประวัติจะเป็นเส้นตรง git pull ทำขั้นตอนเหล่านี้โดยอัตโนมัติ ไม่มีจังหวะให้หยุด ช่วงหยุดนี้เองคือคุณค่าหลักของ fetch แยกต่างหาก คือโอกาสดูกราฟก่อนการรวม
Fetch และ pull ใน IDE
ใน VS Code, IntelliJ IDEA และเอดิเตอร์อื่น ปุ่มมีชื่อต่างกัน แต่ใช้คำสั่งเดียวกันเบื้องหลัง "Fetch" คือ git fetch "Pull" หรือ "Update Project" คือ pull ด้วยกลยุทธ์ที่เลือกในการตั้งค่า (merge หรือ rebase) "Sync Changes" คือ pull แล้วตามด้วย push ก่อนกด Sync บนบรานช์ที่ใช้ร่วมกัน ควรรู้ว่าเลือกกลยุทธ์ใดไว้ มิฉะนั้นคอมมิต merge จะปรากฏในประวัติโดยไม่คาดคิด IDE หลายตัวยัง fetch เบื้องหลังเป็นระยะ ดังนั้นตัวนับคอมมิตที่กำลังเข้ามาในแถบสถานะมักเป็นปัจจุบัน
สรุป
git fetch อัปเดตความรู้ของคุณเกี่ยวกับเซิร์ฟเวอร์ ส่วน git pull นำความรู้นั้นไปใช้กับบรานช์ปัจจุบันด้วย Fetch ปลอดภัยทุกเวลา pull สะดวกแต่เปลี่ยนบรานช์และอาจนำความขัดแย้งมาได้ ดังนั้นให้ตั้งค่ากลยุทธ์ของมันครั้งเดียวและรักษาไดเรกทอรีทำงานให้สะอาด ความต่างระหว่าง push pull และ fetch การทำงานกับ remote และ GitHub ถูกอธิบายและฝึกจริงในหลักสูตร Git
คำถามที่พบบ่อย
ต้องรัน git fetch ก่อน git pull หรือไม่?
ไม่จำเป็น pull จะ fetch เป็นขั้นตอนแรกให้เอง การรัน fetch แยกต่างหากมีประโยชน์เมื่อคุณอยากดูการเปลี่ยนแปลงก่อนแล้วจึงตัดสินใจว่าจะรวมอย่างไร
git fetch จะลบการเปลี่ยนแปลงของฉันได้หรือไม่?
ไม่ได้ fetch เปลี่ยนเฉพาะการอ้างอิงรีโมตอย่าง origin/main และไม่แตะบรานช์ท้องถิ่น ดัชนี หรือไดเรกทอรีทำงาน
git pull อัปเดตทุกบรานช์หรือไม่?
ไม่ pull รวมการเปลี่ยนแปลงเข้าบรานช์ปัจจุบันเท่านั้น บุ๊กมาร์ก origin/* ของบรานช์อื่นจะอัปเดตผ่าน fetch แต่บรานช์ท้องถิ่นยังคงอยู่ที่เดิม
ควร fetch บ่อยแค่ไหน?
ก่อนเริ่มงาน ก่อนสร้าง pull request และก่อน push IDE หลายตัว fetch เบื้องหลังโดยอัตโนมัติ ซึ่งปลอดภัย
FETCH_HEAD คืออะไร?
การอ้างอิงระบบที่ fetch บันทึกสิ่งที่ดาวน์โหลดล่าสุดไว้ เช่น หลัง git fetch origin main คุณรัน git merge FETCH_HEAD ได้
หัวข้อนี้สอนอย่างละเอียดพร้อมฝึกปฏิบัติในคอร์ส “Git с нуля до уверенного разработчика”
ไปที่คอร์ส