ชีตสรุป Git [PDF]
(wizardzines.com)- PDF ที่รวบรวมงานที่มักติดขัดบ่อยใน Git ให้ค้นหาได้รวดเร็วตามหมวด การเขียนคอมมิต, การย้ายบรานช์, การเปรียบเทียบการเปลี่ยนแปลง, การซิงก์กับรีโมต และคำสั่งยกเลิกการเปลี่ยนแปลง
- เวิร์กโฟลว์บนเครื่องจะเน้น
git status,git add,git commit,git diffเพื่อตรวจสอบ สถานะก่อนและหลัง staging แล้วสร้างคอมมิต - งานเกี่ยวกับบรานช์ใช้
git switch,git checkout,git branch,git merge,git rebase,git cherry-pickเพื่อจัดการ การสร้าง·การย้าย·การ merge·การ rebase - การสำรวจประวัติและการกู้คืนมี
git log,git blame,git show,git reflog,git reset,git restoreรวมถึงสามารถใช้บรานช์·แท็ก·HEAD·บรานช์รีโมตเป็นตัวอ้างอิงคอมมิตได้ - งานกับรีโมต repository ครอบคลุมขั้นตอนการรับและส่งการเปลี่ยนแปลงด้วย
git remote add,git fetch,git pull,git push,git push --force-with-lease,git push --tags
เริ่มต้น repository และตรวจสอบสถานะปัจจุบัน
- เริ่ม repository ใหม่:
git init
- clone repository ที่มีอยู่:
git clone $URL
- ตรวจสอบตำแหน่งและสถานะปัจจุบัน:
git status
เตรียมและสร้างคอมมิต
- เพิ่มไฟล์ที่ยังไม่ถูกติดตาม:
git add $FILE
- เพิ่มไฟล์ที่ยังไม่ถูกติดตามทั้งหมดและการเปลี่ยนแปลงที่ยังไม่ได้ stage:
git add .
- เลือก stage เฉพาะบางส่วนของการเปลี่ยนแปลงในไฟล์:
git add -p
- ลบหรือย้ายไฟล์:
git rm $FILEgit mv $OLD $NEW
- นำไฟล์ออกจากการติดตามของ Git โดยไม่ลบไฟล์:
git rm --cached $FILE
- ยกเลิก staging ทั้งหมด:
git reset HEAD
- สร้างคอมมิต:
git commit- เปิด text editor เพื่อเขียนข้อความ
- คอมมิตโดยระบุข้อความทันที:
git commit -m 'message'
- คอมมิตพร้อมข้อความโดยรวมถึงการเปลี่ยนแปลงที่ยังไม่ได้ stage ด้วย:
git commit -am 'message'
การย้ายและจัดการบรานช์
- สลับบรานช์:
git checkout $NAMEgit switch $NAME
- สร้างบรานช์:
git checkout -b $NAMEgit switch -c $NAME
- ดูรายการบรานช์:
git branch
- เรียงบรานช์ตามคอมมิตล่าสุด:
git branch --sort=-committerdate
- ลบบรานช์:
git branch -d $NAME
- บังคับลบบรานช์:
git branch -D $NAME
สำรวจประวัติและติดตามโค้ด
- ดูประวัติของบรานช์:
git log main
- ดูความสัมพันธ์ของสองบรานช์เป็นกราฟ:
git log --graph a b
- ดู log แบบหนึ่งบรรทัด:
git log --oneline
- ตรวจสอบว่าใครเป็นคนแก้แต่ละบรรทัดของไฟล์ล่าสุด:
git blame $FILENAME
- ดูคอมมิตทั้งหมดที่แก้ไขไฟล์:
git log $FILENAME
- ค้นหาคอมมิตที่เพิ่มหรือลบข้อความเฉพาะ:
git log -S banana
เปรียบเทียบการเปลี่ยนแปลง
- ดูความแตกต่างระหว่างคอมมิตกับคอมมิตแม่:
git show $COMMIT_ID
- ดูความแตกต่างระหว่าง merge commit กับคอมมิตแม่ที่ถูก merge เข้ามา:
git show --remerge-diff $COMMIT_ID
- ดูสรุป diff:
git diff $COMMIT_ID --statgit show $COMMIT_ID --stat
- เปรียบเทียบสองคอมมิต:
git diff $COMMIT_ID $COMMIT_ID
- เปรียบเทียบเฉพาะไฟล์กับคอมมิต:
git diff $COMMIT_ID $FILENAME
- เปรียบเทียบการเปลี่ยนแปลงที่ stage แล้วและยังไม่ได้ stage ทั้งหมด:
git diff HEAD
- เปรียบเทียบเฉพาะการเปลี่ยนแปลงที่ stage แล้ว:
git diff --staged
- เปรียบเทียบเฉพาะการเปลี่ยนแปลงที่ยังไม่ได้ stage:
git diff
ไฟล์ตั้งค่าและตัวเลือก
- ไฟล์ตั้งค่า Git แบบ local:
.git/config
- ไฟล์ตั้งค่า Git แบบ global:
~/.gitconfig
- รายการไฟล์ที่ต้อง ignore:
.gitignore
- กำหนดตัวเลือกการตั้งค่า:
git config user.name 'Julia'
- กำหนดตัวเลือกแบบ global:
git config --global ...
- เพิ่ม alias:
git config alias.st status
- ตรวจสอบตัวเลือกการตั้งค่าที่เป็นไปได้:
man git-config
แก้ไขประวัติและกู้คืนไฟล์
- ย้อนคอมมิตล่าสุด โดยคง working directory ไว้เหมือนเดิม:
git reset HEAD^
- ย้อน rebase ที่ล้มเหลว:
git reflog BRANCHNAMEgit reset --hard $COMMIT_ID
- รวม 5 คอมมิตล่าสุดให้เป็นคอมมิตเดียว:
git rebase -i HEAD^^^^^^- เปลี่ยน
pickของคอมมิตที่จะรวมเป็นfixup
- นำเวอร์ชันของไฟล์จากบรานช์หรือคอมมิตอื่นมาใช้:
git checkout $COMMIT_ID $FILEgit restore $FILE --source $COMMIT_ID
- เปลี่ยนข้อความคอมมิตหรือเพิ่มไฟล์ที่ลืมใส่:
git commit --amend
รวมบรานช์
- fast-forward merge เพื่ออัปเดตบรานช์ปัจจุบันด้วยบรานช์อื่น:
git switch maingit merge banana
- คัดลอกหนึ่งคอมมิตไปยังบรานช์อื่น:
git cherry-pick $COMMIT_ID
- รวมบรานช์ที่แยกจากกันด้วย rebase:
git switch bananagit rebase main
- รวมบรานช์ที่แยกจากกันด้วย merge:
git switch maingit merge bananagit commit
- รวมบรานช์ที่แยกจากกันด้วย squash merge:
git switch maingit merge --squash bananagit commit
รีโมต repository และการซิงก์
- เพิ่มรีโมต repository:
git remote add $NAME $URL
- push บรานช์
mainไปยังรีโมตorigin:git push origin main
- อัปโหลดบรานช์ที่ push ครั้งแรกไปยัง
originและตั้งค่าบรานช์ติดตาม:git push -u origin $NAME
- push บรานช์ปัจจุบันไปยังบรานช์ติดตามบนรีโมต:
git push
- force push:
git push --force-with-lease
- push แท็ก:
git push --tags
- ดึงการเปลี่ยนแปลงมาโดยไม่เปลี่ยนบรานช์ local:
git fetch origin main
- fetch ทุกบรานช์:
git fetch --all
- rebase เข้ากับบรานช์ปัจจุบันหลัง fetch:
git pull --rebase
- merge เข้ากับบรานช์ปัจจุบันหลัง fetch:
git pull origin maingit pull
วิธีอ้างอิงคอมมิต
- ในตำแหน่ง
$COMMIT_IDสามารถใช้ตัวอ้างอิงได้หลายแบบ:- บรานช์:
main - แท็ก:
v0.1 - คอมมิต ID:
3e887ab - บรานช์รีโมต:
origin/main - คอมมิตปัจจุบัน:
HEAD - คอมมิตก่อนหน้า 3 คอมมิต:
HEAD^^^ - คอมมิตก่อนหน้า 3 คอมมิต:
HEAD~3
- บรานช์:
ทิ้งการเปลี่ยนแปลง
- ลบทั้งการเปลี่ยนแปลงที่ stage แล้วและยังไม่ได้ stage ของไฟล์หนึ่งไฟล์:
git checkout HEAD $FILE
- ลบการเปลี่ยนแปลงที่ยังไม่ได้ stage ของไฟล์หนึ่งไฟล์:
git checkout $FILE
- ลบการเปลี่ยนแปลงทั้งหมดทั้งที่ stage แล้วและยังไม่ได้ stage:
git reset --hard
- stash การเปลี่ยนแปลงที่ stage แล้วและยังไม่ได้ stage:
git stash
- ลบไฟล์ที่ยังไม่ถูกติดตาม:
git clean
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
มีคำสั่งที่เหมือนอัญมณีซ่อนอยู่หลายคำสั่ง:
git diff --stagedจะแสดงความแตกต่างระหว่างคอมมิตล่าสุดกับสิ่งที่สเตจไว้ตอนนี้ จึงเหมาะมากสำหรับตรวจว่ามีเฉพาะสิ่งที่ตั้งใจไว้หรือไม่เมื่อต้องสเตจไฟล์หรือ hunk แบบละเอียดgit log <file>จะแสดงเฉพาะคอมมิตที่ไฟล์นั้นถูกเปลี่ยนแปลง ทำให้ไม่ต้องไล่ดูgit logทั้งหมดมากนัก และมีประโยชน์เป็นพิเศษกับไฟล์ที่ไม่ค่อยเปลี่ยนบ่อยเวลาใช้
git add --patchถ้าอยากเลือกให้ละเอียดกว่า hunk ที่ Git แบ่งไว้ ลองใช้ ตัวเลือกeได้ มีคำแนะนำวิธีรวม/ตัดแต่ละบรรทัดให้ดู จึงไม่จำเป็นต้องท่องจำgit diff --stagedเป็น alias ของgit diff --cachedใช้cachedมาหลายปีแล้ว แต่stagedเข้าใจง่ายกว่ามากจริง ๆ แค่ว่าไม่รู้จะลบ muscle memory นี้ได้ไหมถ้าสนใจก็ดูหรือเสนอ alias เพิ่มได้: https://gitlab.com/Falimonda/gitrc
git config --global commit.verbose trueGit จะใส่ diff ที่สเตจไว้ ลงในส่วนคอมเมนต์ของ editor สำหรับข้อความคอมมิตโดยอัตโนมัติคำสั่งหนึ่งที่ชอบแต่ไม่มีอยู่ในรายการคือ
git log -LstartLine,endLine:fileNameมันเหมือน
git blameแบบขยาย โดยแสดงว่าบรรทัดที่ระบุเปลี่ยนไปอย่างไรตามเวลา และใครเป็นคนเปลี่ยน แม้หมายเลขบรรทัดจะเปลี่ยนหรือไฟล์ถูกย้ายก็ยังติดตามได้ช่วยตอบคำถามอย่าง “ใครทำแบบนี้ เมื่อไร และทำไม?” หรือ “มันเป็นแบบนี้มาตั้งแต่เมื่อไร?”
นึกถึงคำพูดของ Phil Karlton ที่ว่า “ในวิทยาการคอมพิวเตอร์มีเรื่องยากอยู่แค่สองอย่าง: การทำให้แคชหมดอายุ และการตั้งชื่อ”
มองจากมุมนั้น ชื่อคำสั่ง Git ว่า
blameก็รู้สึกค่อนข้างแปลก และไม่ได้มีแค่ฉันที่คิดแบบนั้นWhat does 'git blame' do? https://stackoverflow.com/questions/31203001/what-does-git-blame-do
Blame someone else for your bad code https://news.ycombinator.com/item?id=27963868
Git blame should be called git credit https://dev.to/damcosset/git-blame-should-be-called-git-credit-27h5
Does Git Blame sound too negative? https://www.reddit.com/r/ProgrammerHumor/comments/r5lzyo/does_git_blame_sound_too_negative_just_look_up/
gitแล้วblameก็ดูเป็น ชื่อที่เข้ากับแบรนด์ อยู่blameเป็นwhoใน การตั้งค่า แล้ว จริง ๆ เป็นผู้ใช้ Vim สายแข็งเลยใช้แค่wชอบวิธีที่ตัวเองเรียน Git ตอนแรกเริ่มจาก Sourcetree ซึ่งเป็น Git GUI ของบุคคลที่สาม ใช้งานและทำความเข้าใจง่ายมาก
จากนั้นย้ายไป
git guiแต่ขั้นนี้ข้ามได้ ต่อมารู้สึกว่าการเปิด UI น่ารำคาญ เลยเรียนคำสั่งบนเทอร์มินัลแนะนำสำหรับมือใหม่ Git แต่ก็เป็นแค่ประสบการณ์ n=1 ของฉันเท่านั้น
clone,pull,commit,pushและจนถึงตอนนี้ก็พอแล้ว Git อาจเรียบง่ายได้ขนาดนั้น แน่นอนว่าถ้าจำเป็นก็เรียนวิธีใช้ที่ซับซ้อนกว่านี้ได้พอเริ่มใช้ Magit ก็เริ่มเข้าใจ Git ขึ้นมา
Sublime Merge มีแต่เรื่องให้ชม
สงสัยว่า Git ซับซ้อนเกินจำเป็นด้วย เหตุผลทางประวัติศาสตร์ หรือเปล่า
พอดูชีตสรุปนี้แล้วก็ยิ่งรู้สึกถึงความซับซ้อนนั้นมากขึ้น
การที่มันถูกใช้แพร่หลายขนาดนี้อาจเป็น “อุบัติเหตุที่น่ายินดี” หรืออาจเป็นโชคร้ายก็ได้ GitHub มีความดีความชอบมากในการทำให้ Git เป็นที่นิยมด้วย “โฮสติ้ง Git ฟรี” แต่ผมไม่คิดว่า Git เป็นระบบควบคุมเวอร์ชันที่เหมาะสมที่สุดสำหรับหลาย ๆ โปรเจกต์
เพราะมันถูกสร้างมาเพื่อ Linus และ Linux คำศัพท์และเวิร์กโฟลว์ของมันจึงบางครั้งให้ความรู้สึกกลับทางกับระบบควบคุมเวอร์ชันทั่วไปอื่น ๆ แม้แต่ชื่อ
Pull Requestก็เป็นเพราะในการพัฒนา Linux นั้น Linus หรือเมนเทนเนอร์จะดึงการเปลี่ยนแปลงเข้ามาในทรีของเคอร์เนล ในระบบอื่นมักเรียกว่าMerge Requestซึ่งดูมีเหตุผลกว่ามาก เพราะไม่ว่าการเปลี่ยนแปลงจะมาจากที่ไหน ก็คือการ merge เข้าสู่ทรีGit แทบจะเป็น ระบบควบคุมเวอร์ชันแบบกระจายศูนย์ โอเพนซอร์สเพียงตัวเดียวที่จัดการโค้ดเบสขนาดใหญ่มากได้อย่างรวดเร็ว จึงเกิดแรงเฉื่อยในการใช้งานในองค์กรขนาดใหญ่ด้วย
สำหรับโปรเจกต์ส่วนตัว ผมชอบและใช้ Mercurial มากกว่าเยอะ แต่ถ้าลองใช้ Hg กับโค้ดเบสใหญ่ ๆ อย่าง Android AOSP จะเห็นว่ามันหน่วงอย่างหนัก Mercurial มีส่วนติดต่อผู้ใช้และระบบคำสั่งที่ดีกว่า และยังมีฟีเจอร์เปิดเว็บเซิร์ฟเวอร์ขึ้นมาทันทีเพื่อดูทรีของการเปลี่ยนแปลงได้ด้วย แต่เพราะเขียนด้วย Python ประสิทธิภาพและความสามารถในการขยายสเกลจึงไม่ใช่อุดมคติ
git checkoutเรื่อย ๆ ทำให้ ประสบการณ์ผู้ใช้ สับสนยิ่งขึ้นใน Git รุ่นใหม่ ๆ ต้นทุนในการเพิ่มคำสั่งย่อยลดลงแล้ว และเริ่มเพิ่มคำสั่งที่เฉพาะเจาะจงมากขึ้นอย่าง
git switchlog,add,commit,pull,pushก็พอแล้ว และยังมีเครื่องมือ GUI ด้วยพูดตรง ๆ ในงานทั่วไปผมไม่คิดว่า Git ซับซ้อนขนาดนั้น เพียงแต่มันมี ฟีเจอร์ทรงพลัง จำนวนมากให้ใช้เมื่อต้องการเท่านั้น
นักพัฒนาเคอร์เนลต้องสื่อสารกันแบบกระจายศูนย์ และต้องทำงานต่อยอดจากงานของกันและกัน ในโปรเจกต์ที่มีคนเขียนโค้ด 15,000 คน จะทำงานกันอย่างไร? Git ถูกสร้างขึ้นมาเพื่อจุดประสงค์นั้น
มันซับซ้อนเท่าที่จำเป็นเพื่อรองรับเวิร์กโฟลว์ที่ออกแบบมาให้รองรับ การสร้างเครื่องมือที่ง่ายกว่านั้นทำได้ง่าย แต่จะไม่สามารถรองรับเวิร์กโฟลว์ซับซ้อนอย่างการพัฒนาเคอร์เนล Linux ได้ ดังนั้นมันไม่ได้ซับซ้อนเกินจำเป็น แต่ก็จริงที่ว่าสำหรับการใช้งานง่าย ๆ มันซับซ้อนกว่าที่จำเป็น
ถ้าต้องการฟรอนต์เอนด์ที่ซับซ้อนน้อยกว่า ก็มีเครื่องมือมากมายที่ช่วยให้ใช้งาน Git ได้ง่ายขึ้น ถ้าไม่ใช่นักพัฒนาซอฟต์แวร์เต็มเวลา ผมไม่แนะนำให้ใช้ Git โดยตรง แต่ถ้าเป็นนักพัฒนาเต็มเวลา ก็คุ้มที่จะใช้เวลาทำความเข้าใจแนวคิดภายใน และเมื่อเข้าใจแล้ว Git ก็ค่อนข้างสง่างามเมื่อเทียบกับสิ่งที่มันทำ
ผมคงไม่ใช้การรู้ Git มาแบ่งว่าใครมีคุณสมบัติเป็นนักพัฒนาซอฟต์แวร์หรือไม่ แต่ถ้าการพยายามทำความเข้าใจด้วยตัวเองว่าระบบพื้นฐานแบบนี้ทำงานอย่างไรไม่ได้ให้โดพามีนกับคุณเลย สาขานี้อาจไม่ใช่รสนิยมของคุณก็ได้ ซึ่งก็ไม่เป็นไร ทุกคนมีสิ่งที่เหมาะและไม่เหมาะกับตัวเอง
สำหรับการเรียนรู้การเคลื่อนที่ใน Git ขอแนะนำ https://learngitbranching.js.org แน่นอนว่า https://xkcd.com/1597/ ก็ใช้ได้เช่นกัน
ไม่เคยรู้จัก
git add -pมาก่อน แต่มันมีประโยชน์จริง ๆ-p,--patchช่วยให้เลือก patch hunk ระหว่าง index กับ working tree แบบโต้ตอบ แล้วเพิ่มเข้า index ได้ เปิดโอกาสให้ตรวจสอบ diff ก่อนเพิ่มการแก้ไขเข้า indexสิ่งที่อยากเพิ่มตรงนี้คือคอมโบ
git commit --fixup $COMMIT_IDกับgit rebase -i upstream/master --autosquashมันกลายเป็นองค์ประกอบจำเป็นในเวิร์กโฟลว์ช่วงหลัง ๆ ของผมแล้ว ตัวอย่างของ Julia ใช้
HEAD^^^^^เพื่อ rebase commit 5 รายการก่อนหน้า แต่จนถึงไม่นานมานี้ผมใช้HEAD~5อยู่ แล้วตอนนี้เพิ่งรู้ว่าสามารถ rebase commit ทั้งหมดจนถึง upstreamHEADได้เลยgit commit --fixup $COMMIT_IDนี่ต้องเอามาใช้ทันทีเลย ปกติผมมักสร้าง commit สำหรับแก้เล็ก ๆ อย่าง “oops GET users/me” แล้วค่อยย้ายไป fixup เองในการ rebase แบบโต้ตอบครั้งถัดไป แต่วิธีนี้ดีกว่ามาก“รวม 5 commit ล่าสุดให้เป็นหนึ่งเดียว:
git rebase -i HEAD^^^^^^” ผมว่าน่าจะเป็นgit rebase -i HEAD~5มากกว่าโดยมีเงื่อนไขว่าต้องการ rebase เฉพาะ history แบบเส้นตรงเท่านั้น
https://stackoverflow.com/questions/2221658/whats-the-difference-between-head-and-head-in-git
การตระหนักครั้งใหญ่ที่สุดคือช่วงที่เข้าใจว่า commit ทั้งหมด ยังคงอยู่ข้างใน แท็กและ branch เป็นเพียงตัวชี้ไปยัง commit เท่านั้น และ commit ที่ branch เคยชี้ไปก่อนหน้านี้ก็ไม่ได้หายไปไหน
คำสั่งจำนวนมาก หรืออาจพูดได้ว่าเกือบทั้งหมด เป็นแบบไม่ทำลายข้อมูล จึงสามารถอ้างอิงสถานะเดิมของ branch ได้ง่าย
เคยเห็น
git reflogมาก่อน แต่ส่วนใหญ่ใช้แค่เพื่อจำว่า “branch ที่เคยทำงานอยู่ก่อนหน้านี้ชื่ออะไรนะ?”เลยเกิดความกลัวแบบพอสมควรต่อการทำงานบางอย่างที่อาจพาเข้าไปอยู่กลางการแก้ conflict ที่ไม่ต้องการ เพราะปกติ conflict มักเกิดจากการอ้างอิง commit หรือ branch ผิด
ความกลัวนั้นยังไม่ได้หายไปทั้งหมด แต่ อย่างน้อยก็รู้สึกสบายใจขึ้นมากในการกู้คืนงานเก่าระหว่างงานอย่าง rebase
ถ้าไม่แน่ใจว่าหลังทำงานบางอย่างแล้วจะเกิด conflict ไหม ก็แค่ลองทำแล้วดูผล ถ้าเกิด conflict ก็แก้ไข ย้อนกลับงานที่ทำให้เกิด conflict หรือสร้าง commit ใหม่ไว้ที่อื่น แล้วปล่อย conflict ไว้จัดการทีหลัง
ถ้ารู้อยู่แล้วว่าจะเกิด conflict ก็ clone branch ที่เกี่ยวข้องแล้วลองปรับให้เข้ากันล่วงหน้า หรือรันงานที่ทำให้เกิด conflict แล้วลองทำขั้นตอนการแก้ไขดู ถ้าการแก้เริ่มยุ่งเหยิง ก็ย้อนกลับเฉพาะบางส่วนที่แก้ไปแล้วลองต่อไป หรือพักไว้ชั่วคราวแล้วไปทำอย่างอื่น หรือทิ้งทั้ง tree ไปเลยก็ได้
พูดตรง ๆ คือควรวาง Git ลงแล้วไปดู JJ
https://martinvonz.github.io/jj/v0.17.1/
แม้แต่ Linus ก็น่าจะคิดว่า UI ของ Git แย่มาก JJ มี workflow ที่ยอดเยี่ยม
อย่างไรก็ตาม นอกจากลองจับเล่นนิดหน่อยแล้ว ยังไม่ได้ใช้จริงจังในงานจริง จึงยังฟันธงไม่ได้