git ที่ไม่ใช่ของสงฆ์…
บทความนี้จะมาพูดถึง Tools ที่มีความสำคัญมากที่สุดสำหรับเหล่า Programmer กัน อย่างสิ่งเล็ก ๆ (หรอ) ที่เรียกว่า git กันครับ (เกร็ดเล็ก ๆ น้อย ๆ คำนี้อ่านว่า กิด ไม่ใช่ จิด นะครับ) เพราะจากประสบการณ์ที่ผ่านมาพบว่าเมื่อพูดถึง git ก็จะพบว่าใครหลาย ๆ คนก็เคยใช้ แต่ใช้ได้แบบทั่วไปมาก ๆ คือ Commit, Push, Pull, Merge ฯลฯ พอเจอโจทย์นอกจากนี้นิดหน่อย ก็จะเริ่มไปต่อไม่ถูก เนื่องจากไม่รู้จัก Command และใช้เป็นแต่ GUI ซึ่งไม่สามารถตอนสนองงานจริงได้ทำให้ git Commit ต่าง ๆ ที่ออกมาไม่เรียบร้อยและลดความสามารถหลายอย่างของ git ลงไป
***ไม่เหมาะสำหรับผู้เริ่มต้นที่ไม่เคยใช้ git มาก่อน(อ่านได้ แต่ไม่ได้สอน Step by step) เพราะจะเป็นการอธิบายเพิ่มเติมจากสิ่งที่คุ้นเคยมาก่อนแล้วระดับหนึ่ง
git คืออะไร
ก่อนจะพูดถึงสิ่งนี้ เรามาดูกันว่า git ในโลกสมัยที่เรายังไม่มี git เราใช้ชีวิตอยู่กันยังไง 555 ว่าไปนั่น ไม่ต้องถึงขนาดนั้นหรอกครับ เอาแค่ช่วงแรกของสมัยเรียน Programming กันว่าในตอนนั้นเราเขียน Program หรือทำงานกันยังไง
ในตอนนั้นส่วนใหญ่เราก็ทำงานกันที่ไฟล์เดียว เขียนอะไรไปทดลองอะไรไป แล้วก็กดเซฟ พอมันมีทางแยกเช่นอยากลองวิธี A วิธี B ก็แยก 2 ไฟล์ชื่อต่างกันแล้วลองว่าวิธีไหนใช้ได้ แล้วค่อยลบ
ต่อมาพอเป็นงานที่เราทำมากกว่า 1 ไฟล์และมากกว่า 1 คน เราก็ต่างคนต่าง Copy Project แล้วไปนั่งทำของเราจนมันเสร็จก็เอามารวมกับเพื่อนทีละไฟล์หรืออย่างน้อยก็ให้เพื่อนและเราทำคนละไฟล์ เพราะมันจะง่ายเวลาเอามารวมใช่ไหมครับ
จากจุดนั้นให้เราจินตนาการเข้าไปเพิ่มเติมอีกว่า ในอนาคตงานของเราจะมีใครก็ไม่รู้อีกมากหน้าหลายตา หลายความคิดเข้ามาทำงานของเรา เราไม่สามารถห้ามได้ว่า ใครห้ามเขียนอะไรที่ไฟล์ไหนเพราะมีอีกคนเขียนอยู่ อะไรแบบนี้อยู่สิ ให้เวลานึกภาพ… มันคงเป็นภัยพิบัติระดับหมอลำกันเลยทีเดียว(เผื่อใครไม่รู้จักมุกนี้ “ภัยพิบัติระดับหมอลำ” เรื่องบันเทิงบนความบรรลัยระดับวันพระใหญ่ (the-e-world.com) และ อภิมหาพากย์นรก ตอน EVOLUTION ภัยพิบัติระดับหมอลำลิง — YouTube)
หรือแม้แต่ในช่วงที่เราทำงานจริง ๆ แล้วเราอยาก POC(Proof of concept) อะไรบางอย่างเรากลัวว่าสิ่งที่เราทำ จะทำให้ Code ที่มันใช้ได้อยู่แล้วพังไปหรือป่าว เราก็ต้อง Copy ไฟล์ทั้งหมดแยก แล้วก็ไปลองทำอีกที่หนึ่ง
จากเรื่องที่ผ่านมา เราจะเห็นว่าในโลกของเราสมัยนั้นมีปัญหาอยู่หนึ่งเรื่องนั่นคือเรื่องของการจัดการ Code นั่นเอง และก็มี Tools ตัวหนึ่งเกิดมาเพื่อช่วยเราในการจัดการปัญหาเรื่องนี้ ดังนั้น git คือ ตัวช่วยในการจัดการ Code โดย Code เก่า Code ใหม่ มันคือเรื่องของ Version
หรืออีกนัย git คือ ตัวช่วยในการทำ Version Control นั่นเอง!!!
อยากที่ได้เกริ่นไปตอนต้นบทความนี้จะไม่ได้สอนใช้ git ในแบบที่ทั่วไปเราใช้กันได้อยู่แล้ว ดังนั้นบทความนี้จะเน้นไปทางการใช้ในระดับที่ advance ขึ้นมานึดนึงจากโจทย์เล็ก ๆ ที่ทีมผมมักพบเจอในงานจริง
- ไม่อยากให้มี commit merge ใน branch ที่กำลัง develop ทำยังไงได้บ้าง
- แก้ commit message ที่ผ่านหลาย commit แล้วยังไง
แต่ก่อนจะไปพูดถึงวิธีการที่เหล่านี้ มีอยู่หนึ่ง command ที่สำคัญมาก ๆ ที่อยากให้รู้จักนั่นคือ rebase
git rebase
Rebase คือการ re-commit โดยที่เอา commit ที่เราทำเพิ่มเติมหลังจาก head ไปต่อท้ายสุด เช่น
1. branch develop (b.dev) มี commit เป็น [a] [b] [c]
b.dev = [a] [b] [c]
2. เมื่อเราแตก branch feature A (b.featureA) เริ่มมาจะมี commit เป็น [a] [b] [c] เหมือนของ b.dev
b.dev = [a] [b] [c]
b.featureA = [a] [b] [c]
3. b.featureA เราเมื่อทำงานแล้ว commit ไป จะมีเป็น [a] [b] [c] [d]
b.dev = [a] [b] [c]
b.featureA = [a] [b] [c] [d]
4. ในขณะเดียวกันก็มีคนแตก branch จาก develop ไปทำงานด้วยเช่นกันเป็น branch featureX พอเราทำงานไปสักพักหนึ่ง
b.dev = [a] [b] [c]
b.featureA = [a] [b] [c] [d]
b.featureX = [a] [b] [c] [x]
5. b.featureX ก่อนก็ merge เข้ามาที่ b.dev และจะมี commit เป็น [a] [b] [c] [x] [merge-featureX] ประมาณนี้
b.dev = [a] [b] [c] [x] [merge featureX into develop]
b.featureA = [a] [b] [c] [d]
̶b̶.̶f̶e̶a̶t̶u̶r̶e̶X̶ ̶=̶ ̶[̶a̶]̶ ̶[̶b̶]̶ ̶[̶c̶]̶ ̶[̶x̶]̶
6. b.featureA ของเราทำการ rebase จะมี commit เป็น [a] [b] [c] [x] [merge-featureX] [d] แบบนี้
b.dev = [a] [b] [c] [x] [merge featureX into develop]
b.featureA = [a] [b] [c] [x] [merge featureX into develop] [d]
จะเห็นว่าเมื่อ rebase จะทำการยก commit ที่ต่อจาก base หรือ HEAD ไปต่อท้ายเป็นลำดับ แล้วต่างจาก merge ยังไง
ลองดูว่าถ้าเราใช้ merge ในขั้นตอนที่ 6 แทนการ rebase จะเกิดอะไรขึ้น
1. branch develop (b.dev) มี commit เป็น [a] [b] [c]
b.dev = [a] [b] [c]
2. เมื่อเราแตก branch feature A (b.featureA) เริ่มมาจะมี commit เป็น [a] [b] [c] เหมือนของ b.dev
b.dev = [a] [b] [c]
b.featureA = [a] [b] [c]
3. b.featureA เราเมื่อทำงานแล้ว commit ไป จะมีเป็น [a] [b] [c] [d]
b.dev = [a] [b] [c]
b.featureA = [a] [b] [c] [d]
4. ในขณะเดียวกันก็มีคนแตก branch จาก develop ไปทำงานด้วยเช่นกันเป็น branch featureX พอเราทำงานไปสักพักหนึ่ง
b.dev = [a] [b] [c]
b.featureA = [a] [b] [c] [d]
b.featureX = [a] [b] [c] [x]
5. b.featureX ก่อนก็ merge เข้ามาที่ b.dev และจะมี commit เป็น [a] [b] [c] [x] [merge-featureX] ประมาณนี้
b.dev = [a] [b] [c] [x] [merge featureX into develop]
b.featureA = [a] [b] [c] [d]
̶b̶.̶f̶e̶a̶t̶u̶r̶e̶X̶ ̶=̶ ̶[̶a̶]̶ ̶[̶b̶]̶ ̶[̶c̶]̶ ̶[̶x̶]̶
6. b.featureA ของเราทำการ merge (แทนการ rebase )จะมี commit เป็น [a] [b] [c] [x] [merge-featureX] [d] แบบนี้
b.dev = [a] [b] [c] [x] [merge featureX into develop]
b.featureA = [a] [b] [c] [x] [merge featureX into develop] [d] [merge develop into featureA]
จะเห็นว่าทุก ๆ การ merge นั้นจะมี commit merge ขึ้นมาเสมอ อันที่จริงก็ไม่ได้แย่อะไร แต่ใน project ขนาดใหญ่และมีการแตก branch มากมายและการปล่อย feature ที่ไม่คาดว่าจะปล่อยแต่สุดท้ายก็ไม่ได้ปล่อยไป ทำให้เหล่า developer ทั้งหลายจะต้องมาไล่ดู commit เพื่อ cherry-pick หรือ revert ของที่อยู่ใน branch ต่าง ๆ ออกไป แล้วถ้ามี commit merge ที่มากมายนั้น ไม่ทำให้การไล่ง่ายขึ้นเลย แม้นี่จะเป็นสิ่งเล็ก ๆ น้อย ๆ แต่มันก็เป็นอะไรที่ทำงานของเราดีขึ้น และยิ่งเวลาผ่านไปมากเท่าไหร่ มันก็ยิ่งดีขึ้นเทียบกับ Project ที่ไม่ได้ใส่ใจเรื่องแบบนี้ ทำให้ commit merge มีมากมายโดยไม่จำเป็น (บอกก่อนนะครับ ห้ามใช้ merge แต่ไม่ควรจะใช้มันตลอดเวลา ใช้ก็ต่อเมื่อควรใช้)
อันนี้เป็นอีกภาพที่ยกให้เห็นความต่างระหว่าง merge กับ rebase
ไม่อยากให้มี commit merge ใน branch ที่กำลัง develop ทำยังไงได้บ้าง
เพียงแค่เรารู้จัก rebase ก็สามารถแก้ปัญหานี้ได้แล้วครับ จะเห็นว่าการที่มี commit merge ไม่ใช่เรื่องที่แย่ แต่การที่มี commit merge มากเกินความจำเป็นจะทำให้เราอ่าน commit log ได้อยาก การแก้โจทย์ข้อนี้ก็ตามตัวอย่างที่ยกมาตอนแนะนำ git rebase เลยครับ
git rebase develop
เมื่อเราอยู่ใน branch ของเราและสั่งแบบนี้แปลว่า rebase branch ของเราให้ตาม develop เพียงเท่านี้ก็สามารถแก้ปัญหาเรื่อง commit merge ที่เกินความจำเป็นได้
แก้ commit message ที่ผ่านหลาย commit แล้วยังไง
อันนี้เป็นอีกหนึ่งปัญหา Classic ที่เจอบ่อย ๆ พี่ครับ 3 commit ที่แล้วผมพิมพ์ผิดจะแก้ message ยังไง
ก่อนจะไปที่เรื่องนี้ ถ้าแก้ที่ commit ล่าสุดจะง่ายหน่อย เราสามารถใช้ท่า
git commit --amend
ก็จะสามารถแก้ไข commit ล่าสุดได้แล้ว แล้วถ้าจะแก้ 3 commit message ที่ผ่านมายังไงนั่น
คำตอบคือเราต้องใช้ rebase ช่วยครับ จริง ๆ rebase เป็นคำสั่งหนึ่งที่ใช้ได้หลายประโยชน์มาก ๆ ที่เราควรรู้จัก
git rebase -i HEAD~3
โดยตรง HEAD~3 คือย้อนไป 3 Commit ของ Branch นั้น ๆ เราจะเห็นหน้าต่างประมาณนี้
แล้วเราก็ไปแก้ไขตรง pick เป็นคำว่า r หรือ reword ก็ได้ จากนั้นก็ :wq (ตรงนี้ขึ้นอยู่กับ Editor ของแต่ละคน แต่ปกติถ้าเป็น VI ก็ :wq)
จากนั้นก็จะได้หน้าต่างที่เอาไว้แก้ไข message หลังจากแก้ไขเสร็จก็ Save เพียงเท่านี้เราก็สามารถแก้ไข Commit message ได้แล้ว
นอกจากการใช้ reword แล้วเราจะเห็นว่ามี option อื่น ๆ นอกเหนือจากนี้อีกมากมาย ถ้ามีโอกาสไว้จะมาแนะนำว่าทีมผมใช้ในสถานการณ์ไหนบ้าง เผื่อเป็นแนวคิดได้นำไปใช้กับปัญหาที่ตัวเองเจอได้บ้าง
จากบทความนี้จะหลัก ๆ แล้ว git เป็นของเหล่าผู้คนที่ใช้ในการช่วยจัดการ Code และโดยทั่วไปเราจะใช้เพียงคำสั่งทั่ว ๆ ไป ดังนั้นบทความนี้จึงอยากนำเสนอว่าในการทำงานจริง อาจจะมีความต้องการหรือโจทย์ต่าง ๆ ที่คำสั่งทั่วไปยังไม่ค่อยตอบโจทย์ และการใช้ rebase ให้เป็นนั้นก็จะช่วยให้เราใช้ความสามารถต่าง ๆ ของ git ได้มากขึ้นจากที่เป็นอยู่
บทความนี้อาจจะไม่ได้เป็นการจับมือทำซะเดียว เนื่องจากผมเองมองว่าจุดประสงค์ของบทความนี้ ไม่ใช่การสอน แต่เป็นนำเสนอบางอย่างที่อาจจะไม่เคยใช้ ดังนั้นจึงอยากให้ผู้อ่าน อ่านแล้วลองลงมือเล่นและทำเองมากกว่าที่จะเป็น How to และด้วยกลุ่มเป้าหมายไม่ใช่กลุ่มที่ไม่เคยใช้ git เลย ดังนั้นผมเชื่อว่าผู้ที่อ่านมาถึงจุดนี้ได้เป็นคนที่มีประสบการณ์ระดับหนึ่งแล้ว ท้ายสุดก็ขอบคุณผู้อ่านทุกท่านที่อ่านมาถึงจุดนี้ไว้มีโอกาสเจอกันใหม่ในบทความต่อไปนะครับ