Version Control and Git From Zero to Hero : Part 7.1 (Branch Management)

บทความโดย ผศ.ดร.ณัฐโชติ พรหมฤทธิ์
ภาควิชาคอมพิวเตอร์
คณะวิทยาศาสตร์
มหาวิทยาลัยศิลปากร

ถึงตอนนี้ผู้อ่านก็เข้าใจแนวคิดของ Branch กันแล้ว ซึ่งจะเห็นว่าเมื่อ 15 ปีที่แล้ว Linus ได้ออกแบบ Git Branch ไว้อย่างชาญฉลาดมาก เพราะ Branch ไม่ได้เป็นอะไรมากไปกว่า Pointer ของ Commit บน Timeline การทำงานกับ Branch จึงมีค่าใช้จ่ายที่ถูกอย่างไม่น่าเชื่อ ใน Part นี้เราจะฝึกปฏิบัติเพื่อจัดการกับ Branch ให้มีความชำนาญตามหัวข้อดังต่อไปนี้

  • การสร้าง Branch
  • การ Checkout  Branch
  • การ Push Branch ไปยัง Remote Project
  • การ Fetch/Pull Branch
  • การลบ Branch
  • การ Merge Branch
  • การแก้ปัญหาเมื่อเกิด Conflict
  • การเคลื่อนย้าย Commit ระหว่าง Branch
  • การกู้คืน Commit และไฟล์ที่ถูกสลายไปแล้ว
  • การ Undo Commit อย่างปลอดภัย
  • การสร้าง Branch ใหม่จากจุดต่างๆ บน Timeline
  • การพัก Source Code ที่ยังไม่ได้ Check-In ไว้ชั่วคราว
  • การตกแต่ง Branch
  • การติด Tag

Initializing Git Branch

ให้สร้าง Project ใหม่ชื่อว่า gitbranch บน https://gitlab.cpsudevops.com หรือ https://gitlab.com

กลับมาที่ Local Host สร้าง Folder gitbranch โดยใช้คำสั่ง git init

git init

เชื่อมโยง Local Project กับ Remote Project โดยใช้คำสั่ง git remote

git remote add origin http://gitlab.cpsudevops.com/nuttachot/gitbranch.git 

ปรับแต่ง Jupyter Notebook ให้สามารถบันทึก Source Code แบบ Plain Text (ดูรายละเอียดได้จาก Part 4) และ Config Git ไม่ให้ไป Track ไฟล์ *.ipynb โดยเพิ่มข้อความ *.ipynb ในไฟล์ .gitignore แล้ว Check-In ด้วยคำสั่ง git add และ git commit

git add .
git commit -m 'add gitignore'

ใช้คำสั่ง git log --stat เพื่อดู Commit และการเปลี่ยนแปลงของไฟล์

จากภาพเราจะเห็น HEAD Pointer ชี้ไปที่ Master Branch ซึ่งมีการสร้างมันขึ้นมาขณะที่ใช้คำสั่ง git init

git log --stat --oneline

เราสามารถดู Branch บน Local Host โดยใช้คำสั่ง git branch

git branch

เมื่อถึงขั้นตอนนี้ก็ให้ Sync History ของ Master Branch กับ Remote Project โดยใช้คำสั่ง git push

git push -u origin master
Git Project บน Gitlab Server

ตามมาตรฐานการพัฒนา Software สมัยใหม่ จะมีการสร้าง Branch ลบ Branch และ Mearge Branch กันเป็นประจำทุกๆ วัน ซึ่งคำสั่งสร้าง Branch ก็คือ git branch ตามด้วยชื่อของ Branch

ดังคำสั่งด้านล่างเราจะสร้าง Branch ใหม่ชื่อ Dev บน Local Host

git branch dev

จะเห็นว่าขณะนี้เรายังคง Check-Out ที่ Master Branch ตาม HEAD Pointer

เพื่อ Switch ไปยัง Dev Branch เราต้องใช้คำสั่ง git checkout  เพื่อเคลื่อนย้าย HEAD Pointer

git checkout dev

เพื่อแสดงชื่อ Branch ทั้งบน Local Project และ Remote Project เราจะพิมพ์คำสั่ง git branch -a

git branch -a

Software Developers สามารถปรับปรุง Remote Branch Pointer ให้ Up-to-date ได้ตลอดเวลาโดยใช้คำสั่ง git fetch

สร้าง Test Branch บน Remote Project
git fetch

อย่างไรก็ตาม Git จะไม่สร้าง Test Branch บน Local Host จนกว่าจะใช้คำสั่ง git checkout  --track เพื่อเชื่อมโยงกับ Test Branch บน Remote Project

git checkout --track origin/test

การลบ Branch บน Local Host ที่ไม่ได้ใช้งานแล้ว เช่น Test Branch จะต้อง Check-Out ไปที่ Branch อื่น แล้วใช้สำสั่ง git branch -d

git checkout dev
git branch -d test

ขณะที่การลบ Test Branch บน Remote Project จะใช้คำสั่งด้านล่าง

git push --delete origin test

หลังจากลบ Branch แล้ว ถ้าพบไฟล์ .swp ใน gitbranch Folder ให้ลบมัน ดังตัวอย่าง

rm .test.swp

Fast-Forward Merge

ลำดับต่อไปจะทดลองเขียนฟังก์ชัน add และ subtract บน Jupyter Notebook และ Check-In Source Code ทั้งหมด 2 ครั้ง เพื่อดูการ Merge Branch

git add .
git commit -m 'add add file'
git add .
git commit -m 'add subtract file'

History ของ Dev Branch จะมีหน้าตาดังภาพด้านล่าง

เพื่อที่จะ Sync History กับ Remote Project ในครั้งแรกของการ Sync เราต้องสร้างการเชื่อมโยง ด้วยคำสั่ง git push --set-upstream

git push --set-upstream origin dev

History ของ Dev Branch ที่ Local Project ถูก Sync กับ Remote Project ดังภาพ

ถึงตรงนี้เราจะ Merge Dev Branch เข้ากับ Master Branch ครับ

ก่อนอื่น Check-Out ที่ Master Branch แล้วใช้คำสั่ง git merge

git checkout master
git merge dev

จากภาพจะเห็นการ Merge แบบ Fast-forward ซึ่งมีการเคลื่อนย้าย Master Branch Pointer ไปข้างหน้ายัง Commit บนสุดของ Timeline ที่ตำแหน่งเดียวกับ Dev Branch Pointer

Three Way Merge and Conflict

เรากลับไปยัง Dev Branch ด้วยคำสั่ง git checkout ซึ่งทำให้มีการเคลื่อนย้าย HEAD Pointer ไปที่ Dev Branch ดังภาพ

git checkout dev

สร้าง Branch ใหม่ชื่อ Testing แล้ว Check-Out ที่ Testing Branch ในคราวเดียวกัน ด้วยคำสั่ง git checkout -b

git checkout -b testing

จะได้ Testing Branch Pointer ที่ชี้ไปยัง Commit เดียวกับ Dev Branch Pointer ดังภาพ

เราจะแตก History ออกเป็น 2 Timeline ด้วยการแก้ไข Code และ Check-In ไฟล์ add.Rmd ที่ Testing Branch และ Master Branch เพื่อทำ Three Way Merge

ที่ Test Branch แก้ไข Code ในไฟล์ add .Rmd ดังภาพ จากนั้นกด Save แล้ว Check-In

git add .
git commit -m 'edit add from testing branch'

Checkout ที่ Master Branch และ Refresh Browser แล้วแก้ไข Code ในไฟล์ add .Rmd ดังภาพ จากนั้นกด Save แล้ว Check-In

git checkout master
Refresh Browser แล้วแก้ไข Code
git add .
git commit -m 'edit add from master branch'

ตอนนี้เราจะมี Source Code อยู่ 2 Timeline แต่เมื่อใช้คำสั่ง git log เราจะเห็น History เฉพาะใน Master Branch เท่านั้น

git log --stat --oneline

เราสามารถดู Source Code ทั้ง 2 Timeline พร้อมกัน ด้วยคำสั่ง git log --all --graph ครับ

git log --stat --oneline --all --graph
`

เมื่อ Software Developers แก้ไข Source Code เสร็จแล้วจึง Merge Branch ที่ Stable น้อยกว่ากลับมายัง Branch หลักเช่น Master Branch

ใช้คำสั่ง git branch เพื่อให้แน่ใจว่าเราอยู่ใน Branch หลัก

git branch

เมื่อ Merge Testing Branch จาก Timeline หนึ่งกลับมายัง Master Branch ในอีก Timeline หนึ่ง ด้วยคำสั่ง git merge แล้ว Git จะสร้าง Version ของ Code ที่เป็นผลลัพธ์จากการเปรียบเทียบแบบ 3 ทาง คือระหว่าง Commit ที่เป็น Root ของทั้ง 2 Timeline ได้แก่ Commit ID 5a61112 (Base), Commit สุดท้ายของ Testing Brancg ได้แก่ Commit ID da7cf69 (Source) และ Commit สุดท้ายของ Master Branch ได้แก่ Commit ID 6429e89 (Target)

git merge testing
ไฟล์ add.Rmd Version ใหม่ใน Working Directory ที่เกิดจากการ Merge แบบ 3 ทาง

จากภาพด้านบนแสดง Source Code จากทั้ง Base, Source และ Target ที่นำมา Merge ได้เป็น Version ใหม่ดังภาพด้านล่าง

Three Way Merge ช่วยให้เกิดการ Merge แบบอัตโนมัติเมื่อเกิด Conflict ดังต่อไปนี้

%autosave 0 ในบรรทัดที่ 1 ของ Cell แรกไม่เกิด Conflict เพราะมี Code เหมือนกันทั้ง Source, Target และ Base
def add ในบรรทัดที่ 1 ของ Cell ที่ 2 เกิด Conflict เพราะมี Code ต่างกันทั้ง Source, Target และ Base ดังนั้น Git จึงแจ้งให้เราแก้ไข Conflict ด้วยมือ
Code บรรทัดที่ 2 ของ Cell ที่ 2 เกิด Conflict เพราะ Git มองว่ามีการแทรกคำสั่ง print(a + b ) ระหว่างคำสั่ง def add และ return ที่ Source ดังนั้น Git จึงเพิ่มบรรทัดใหม่แทรกระหว่าง def add และ return ที่ Target ให้ชั่วคราว ทำให้ทั้ง Source, Target และ Base มี Code ต่างกัน ดังนั้น Git จึงแจ้งให้เราแก้ Conflict ด้วยมือ
Return a + b ในบรรทัดสุดท้ายของ Cell ที่ 2 ไม่เกิด Conflict เพราะ Git มองว่าเป็น Code ที่เหมือนกันทั้ง Source, Target และ Base
add(1, 2) ในบรรทัดที่ 1 ของ Cell ที่ 3 เกิด Conflict เพราะ Source และ Base มี Code เหมือนกัน แต่ Target มี Code ต่างกัน ดังนั้น Git จึงเลือก Code ที่ Target เพราะมองว่าเป็น Code ที่ใหม่กว่า

เราเลือก Code จาก Source เพื่อแก้ไข Conflict ด้วยมือดังภาพ จากนั้นกด Save แล้ว Check-In

Code ใหม่ที่แก้ไข Conflict แล้ว
git add .
git commit -m 'resolved merge conflict'

Testing  Branch จะถูก Merge เข้ากับ Master Branch ดังภาพด้านล่างครับ

ใน Part 7.1 นี้ เราได้ฝึกปฏิบัติเพื่อจัดการกับ Branch มาจนถึงหัวข้อ การแก้ปัญหาเมื่อเกิด Conflict  ดังนั้นเพื่อไม่ให้เนื้อหายาวเกินไป ตั้งแต่หัวข้อ การเคลื่อนย้าย Commit ระหว่าง Branch เราจะฝึกปฏิบัติกันต่อใน Part 7.2 ครับ

ขอขอบคุณ Nipa.Cloud ที่ให้การสนับสนุน Environment ในการเรียนการสอน
รายวิชา Dev-Ops and Cloud Engineering 101