เรามาลองใช้ Git Command ระดับกลาง (หรือสูง) กันมั้ยครับ: stash, squash, rebase
Published: 2022-03-02 13:44:54


หากว่าเราพูดถึง git สำหรับคนเริ่มต้น คงจะหนีไม่พ้นคำสั่งต่างๆ เหล่านี้
- git add . _// เพิ่ม files ที่ถูกแก้เข้า staged change_
- git commit -m "message" _// เพิ่ม staged change files เข้า commit history_
- git push & git pull _// syncing ระหว่าง local (เครื่องที่บ้าน) กับ remote (เครื่องบน server ของผู้ให้บริการ เช่น github)
_
วันนี้เราจะลองดูอีก 3 คำสั่งที่ใช้กันค่อนข้างบ่อย เมื่อเริ่มคุ้นชินกับการทำงานของ git มากขึ้น

- git stash
- git squash
- git rebase

## git stash
ลองนึกถึงสถานการณ์ที่พวกเรากำลังแก้ไฟล์อยู่ และทำไปได้ระดับหนึ่งแล้วแต่บังเอิญว่ามีงานด่วนเข้ามา และเราต้องหันไปทำงานนั้นทันที ปัญหาจะเกิดดังนี้ครับ
1) ยังมี change files ที่ยังไม่เสร็จ และยัง commit ไม่ได้ เพราะอาจจะเกิดผลเสียมากกว่าข้อดี
2) เราสามารถสร้าง branch ใหม่ให้เก็บ change ของเราได้ แต่ก็เปลืองอยู่ดี


![git stash example 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lj3i950gghr9h75fg7w4.png)

ในกรณีแบบนี้ git stash จะมีประโยชน์มาก เพราะสิ่งที่ git stash ทำจะเป็นการ save ตัว change files ของเราเอาไว้ใน local แต่จะไม่ถือว่าเป็น commit ทำให้สามารถสลับไปทำงานอื่นและ syncing ข้อมูลกับ remote ได้อย่างอิสระ (เพราะอย่างไร change ก็ไม่ปนขึ้นไปด้วยอยู่ดี)

โดยเมื่อต้องการทำ `git stash` ให้ทำการเพิ่มทุก files ที่ต้องการเข้าสู่ staged change (`git add`) ก่อน จากนั้นให้พิมพ์คำสั่ง `git stash` เพื่อ save เก็บไว้ในเครื่อง (ตามภาพด้านล่าง)


![git stash example 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1npmu85qxwbzsgsnfjw.png)

จากนั้นเมื่อทำการ git status (หรือ gst) อีกครั้งจะเห็นได้ว่า change file ได้หายไป (ถูกเก็บเข้า stash) เรียบร้อยแล้ว ซึ่งเราสามารถสลับไปทำ features อื่นได้โดยไม่ต้องกังวลถึง change files อีก โดยสามารถเช็คได้ว่าเรามี change files ที่ stash ไว้กี่ครั้ง สามารถทำได้โดยคำสั่ง

`git stash list`


![git stash example 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rexvncu7s526uda3eek4.png)

และเมื่อต้องการนำสิ่งที่ stash ไปกลับมาเพื่อแก้ไข พัฒนาต่อเราสามารถใช้คำสั่ง

`git stash apply stash@{i}` // โดย i คือตัวเลขของ stash order ซึ่ง stash ล่าสุดจะไล่จากน้อยไปมาก เช่น stash@{0} คือ stash ที่ถูกเพิ่มล่าสุด


![git stash example 4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cualqtfakfxwf6gaxwtv.png)

เพียงเท่านี้ก็จะสามารถจัดการกับ change ที่เราต้องการ แต่ยังไม่อยาก commit ได้แล้ว

## git squash
ในสถานการณ์ที่ เรามีการทำงานบน features ใดๆ ไม่ใช่เรื่องแปลกที่จะมีการ commit อยู่บ่อยครั้ง เพื่อความสะดวกในการติดตามการเปลี่ยนแปลง ซึ่งในบางครั้ง เมื่อรู้สึกตัวอีกที commit history ของเราก็หน้าตาเช่นด้านล่างไปแล้ว


![git squash history example 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mccs2e0mbwzfrv3oo0qs.png)

เราจะสังเกตุได้ว่า มี commits 3 ครั้งคือ
- init squash
- set up init config in squash.txt
- modify config in squash.txt

ซึ่ง messages ทั้ง 3 ครั้งนี้ รวมถึงตัว source code เองมีความใกล้เคียงกันมาก หากเราทำงานในทีมขนาดใหญ่ การทำให้ commit history มีความกระชับและง่ายต่อการติดตาม เป็นเรื่องที่สำคัญอย่างหลีกเลี่ยงไม่ได้

และ `git squash` จะเข้ามาแก้ปัญหาข้างต้นครับ โดยปกติการทำกิจ squash ทักจะใช้คู่กับ git rebase หรือ git merge โดยเราจะใช้ git rebase ในตัวอย่างนี้ เพื่อ rebase squash นั้นวิธีที่ง่ายที่สุดคือใช้ interactive mode ของ git rebase โดยใช้งานคำสั่ง

`git rebase [commit hash] -i` // โดย hash คือตัว commit ที่เราต้องการ rebase กลับไป


![git squash history example 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uubi1ysi5djrpwofyhtu.png)

จากรูปด้านบน เราจะ squash 3 commits เข้าด้วยกัน 


![git squash history example 6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a4cxkgl1y4pes8pnm5ne.png)

หลังพิมพ์คำสั่ง terminal จะเปิดหน้าจอให้เราเลือกว่าจะ squash ตัวใด และตัวใดจะเป็น hash ที่รอรับการ squash ซึ่งเราเลือก hash `bdfaf72` สำหรับรองรับ commit ที่กำลังจะถูก squash


![git squash history example 4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7942ddzmp66b4ce9fq4r.png)

- s, squash คือการเลือก commit นั้นๆ ให้ถูกมัดรวม
- p, pick คือการเลือก commit นั้นๆ ให้เป็นที่รองรับ squash commit

จากนั้นตัว terminal (git) จะเปิดหน้าต่างให้เราเลือก commit message ของการ squash 


![git squash history example 5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jxllyvxx0w5b3bartyql.png)

หลังเสร็จสิ้นการ squash เราจะสังเกตุได้ว่า commits ทั้ง 3 ตัวนั้นหายไปและมี commit ใหม่ที่เกิดจากการ squash ขึ้นมาแทนเรียบร้อยแล้ว


![git squash history example 5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wg4wjiip9orn8dvycjm2.png)

## git rebase
เมื่อคำสั่งที่ผ่านมา เราได้ใช้ git rebase ไปบ้างแล้ว แต่จริงๆ คำสั่ง git rebase เขาเอาไว้ทำอะไรกันแน่ละ

ลองนึกถึงกรณีที่ระหว่างการพัฒนา ทีมของเรา commit code บางอย่างเข้ามาใน branch main และไม่มีใครสังเกตุ จนกระทั่งเวลาผ่านไป พึ่งรู้ว่ามีปัญหาอยู่ใน source code และต้องการแก้อย่างเร่งด่วน ประเด็นคือ เราไม่สามารถอยู่ๆ ไปไล่ลบ commit ออกได้เลย เพราะ commit ดังกล่าวก็มีคำสั่งอื่นๆ ที่จำเป็นอยู่ด้วย


![git rebase example 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7sjk9ptp0sop88d4udj.png)

โดยเราสามารถเลือกแก้ได้หลายแบบ โดยวิธีที่กำลังจะทำก็คือ 
- checkout กลับไปยัง commit ที่ต้องการแก้
- แตก branch ใหม่ และแก้ปัญหา


![git rebase example 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uo9xmkxxb7uq2rtmrpsp.png)

เมื่อทราบ commit ที่ต้องการแก้แล้ว ให้ทำการ checkout กลับไปที่ hash ดังกล่าว

`git checkout d0ec37e`


![git rebase example 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mwg3ex1pz9t2yrx3szfz.png)

จากนั้นให้ทำการแก้สิ่งที่ต้องการและแตก branch แยกออกมา


![git rebase example 4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpd02d3j1vb8rce9a7iz.png)

ซึ่งเมื่อสังเกตุจะเห็นได้ว่า history ของ git นั้นมีบางส่วนหายไป (อิงจากรูปด้านบน) คือ `bd3b066` และ `eebae51` ซึ่งกรณีนี้นั้น 


![git rebase example 5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4aphy8x9l0giw7uk1p4x.png)

หากเรา **merge** main branch ทันที สิ่งที่เกิดขึ้นคือ ตัว history ของเราใน branch นี้จะหายไป (ซึ่งนำไปสู่การตาม track ที่ยากขึ้น)


![git rebase example 6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/03clrwrc4909wjmxy2m3.png)

แต่หากเราใช้ git rebase สิ่งที่เกิดขึ้นคือ git จะนำ history ของ change มาต่อส่วนหัวของ branch ที่เราต้องการ rebase ทำให้ history ไม่หายไป (และตาม track ง่ายขึ้น)


![git rebase example 7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s5xona5n0zrmo1z71y9p.png)

โดยเราสามารถพิมพ์คำสั่ง

`git rebase [target branch name] [current branch]`

`git rebase main fixed-critical-001` 


![git rebase example 8](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kub5esffe51yurw12ug6.png)

และหากเรา `git log --oneline` อีกครั้ง เราจะพบว่า history ที่หายไปถูกนำมารวมกลับใน branch ใหม่แล้ว


![git rebase example 9](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jz7aqtvuxg5u3pf5s0ca.png)