คำสั่งจัดการ change (เวลามันพัง)
เมื่อ change เกิดปัญหา เราทำอะไรได้บ้าง ?
เคสในหมวดหมู่นี้เราจะใช้ในเคสที่จะต้องมีการจัดการ history เพิ่มเติม เช่น
- ต้องการย้อน commit
- ต้องการสร้าง branch ใหม่จากจุดอื่นใน commit (ไม่ใช่จาก branch โดยตรง)
- ต้องการ shift history git เพื่อให้ history up to date จาก branch main
- สร้าง history มาเยอะจัด (เช่น change 1, change 2, change 3) ต้องการรวม history ให้สวยงามขึ้น
ในหมวดหมู่นี้จะสามารถแก้ไขได้
- จริงอยู่ที่ git เป็นลักษณะ snapshot แต่ก็สามารถที่จะเปลี่ยนแปลง history ได้
- แต่ก็ต้องอยู่บนพื้นฐานของการต่อ history หรือการเปลี่ยนแปลงจากจุดในอดีต history อยู่ดี = คือเราไม่สามารถเข้าไปแก้ history commit ตรงๆได้ แต่สามารถย้อนกลับไปก่อนหน้า เพื่อต่อ history ใหม่ออกมาได้
ก่อนไปต่อลง Extension "GitLens" ใน VScode
คำสั่งช่วยเหลือมีอะไรบ้าง
1. git checkout <commit hash>
= ย้อนเวลา
วิธีแรกคือการย้อนเวล ากลับไปยัง commit ในอดีต เพื่อสร้าง commit change ใหม่ต่อออกมา
สมมุติว่า history ของ git เป็นแบบนี้
ทีนี้ เราอาจจะอยากเพิ่ม feature บางอย่าง โดยที่ยังไม่อยากใช้ประวัติของ C, D (อาจจะเพื่อตรวจสอบบางอย่างให้แน่ใจว่า ไม่ได้กระทบจาก C,D ใช่ไหม) = ต้องย้อนกลับไปที่ B แล้วสร้าง branch ใหม่แทน
- ดังนั้น เมื่อใช้คำสั่ง
git checkout B
และสร้าง branch ใหม่เป็นgit checkout -b develop
- เสร็จแล้วลอง commit C1, D1 เพิ่มหน้าตา history ก็จะประมาณนี้ = branch develop ที่สร้างมาใหม่ ก็จะทำการย้อนเวลากลับไปยังประวัติ B และทำประวัติ C1, D1 ต่อ
- และจะไม่ได้ประวัติของ C, D ออกมา
ตัวอย่างการใช้ checkout สำหรับการย้อนเวลา
# command
git checkout <commit history ที่ต้องการย้อน อย่างน้อย 6 ตัว>
git checkout -b <ชื่อ branch ใหม่ที่ต้องการสร้างจาก history>
# ตัวอย่าง
git checkout 13eda488b
git checkout -b develop
2. git reset (ย้อน commit), revert (กลับ commit)
คำสั่ง set แรกคือ คำสั่งที่ใช้สำหรับแก้ไข change commit ซึ่งปกติจะมี 2 วิธีที่สามารถ ทำได้
Ref: https://gcapes.github.io/git-course-mace/07-undoing/
2.1 git reset = ย้อนกลับไปยัง commit ที่ระบุ (โดยจะล้างประวัติ)
สมมุติ history ของ git หน้าตาประมาณนี้
แล้วถ้าเราทำการ git reset B
= ประวัติของ git จะเหลือเพียง
และ git reset B
จะต้องกลายเป็นประวัติใหม่ เพื่อเป็นการ confirm ว่า จะย้อนกลับมา commit B จริงๆใช่ไหม = เกิด commit ใหม่ที่บอกว่า เราย้อนกลับมาที่ commit "B" ประวัติจะกลายเป็นแบบนี้แทน หลังจาก commit ใหม่ (เป็นแบบ C1)
ออกมาแทน ตัวอย่าง command git reset
# command
git reset <commit history ที่ต้องการย้อน อย่างน้อย 6 ตัว>
# ตัวอย่าง
git reset 13eda488b
git commit -m "reset history to B"
2.2 git revert = กลับ change commit ที่ไม่ต้องการออก
สมมุติ history ของ git หน้าตาประมาณนี้
แล้วเราบอกว่า เราไม่ต้องการ change ของ commit B และ ต้องการเอา code ตรงนี้ออก (ซึ่งใน commit B มีการเพิ่ม code เอาไว้) = เราสามารถกลับเฉพาะ commit B แล้วสร้าง history ใหม่เป็น B' (ที่ย้อนกลับ B) แทนได้ โดยใช้ ```git revert B``
- ซึ่งอย่างที่เห็นในภาพนี้ แม้ผมจะใส่ x ตรง commit B ก็จริง แต่ประวัติ B ก็ไม่ได้หายไป เป็นเพียงประวัติใหม่ที่บอกว่า "ขอย้อนกลับ change commit B" ออกมาแทน
ตัวอย่าง command git revert
# command
git revert <commit history ที่ต้องการย้อน อย่างน้อย 6 ตัว>
# ตัวอย่าง
git revert 13eda488b
git commit -m "revert history B"
ท่านี้จะแตกต่างกับ checkout ท่าของข้อ 1. คือ
- ท่า checkout คือย้อนประวัติ เพื่อสร้าง branch ใหม่ = ประวัติ branch ที่ checkout จะยังไม่โดนแก้ไข
- ท่าของ reset, revert คือการจัดการ commit แล้วสร้าง commit ใหม่ = history ของ git จะโดนเปลี่ยนไปทันทีหลังจาก commit หลังคำสั่ง reset, revert
3. git cherry-pick > แล้วสร้าง branch ใหม่
เคสนี้ คือการหยิบ commit ใน history หนึ่งอันมาใช้ได้ ปกติจะใช้ในเคสต้องการ code แบบ เจาะจงจาก branch อื่นๆเข้ามา
สมมุติว่า ปัจจุบัน history ของ git เป็นแบบนี้ และปัจจุบันเราอยู่ Branch develop อยู่
เราเจอว่า เราอยากได้ commit "E" จาก branch main มา เนื่องจากมี code ที่จำเป็นสำหรับการ develop feature เราต่อ แต่อยากได้แค่เฉพาะ commit "E" เท่านั้น (ถ้า pull main มา = มันจะได้ history ทั้ง "C", "D", "E" มาหมด)
- เคสนี้เราสามารถใช้ cherry pick ได้ โดยคำสั่ง
git cherry-pick E
จะสามารถทำเคสนี้ได้ - แต่จะไม่เท่ากับการเอาประวัติ "E" มานะครับ = การสร้างประวัติใหม่โดยเอา change code "E" มาเท่านั้น จะกลายเป็นประวัติใหม่แทน
และประวัติก็จะกลายเป็นแบบข้างล่างแทน
ตัวอย่าง command cherry-pick
# command
git cherry-pick <commit hash ที่ต้องการหยิบ>
# example
git cherry-pick c144e458fe41
# ถ้าไม่เกิด conflict = auto merge เป็น commit ใหม่, ถ้ามีต้องแก้ conflict และ commit message เพิ่ม
git commit -m "Use code from E"
4. git rebase
Ref: https://tecadmin.net/git-rebase/
เคสนี้จะใช้สำหรับการ shift history ของ branch ที่กำลังทำอยู่ ทำให้ branch ที่กำลังทำอยู่ up to date ได้โดย
- เลื่อน history ของ branch ที่แยกออกมา ให้ shift ตาม ประวัติของ branch ที่ระบุ (ส่วนใหญ่จะใช้กับ main)
- หลังจากเลื่อน history เราจะได้ code ส่วนที่มี history เพิ่มมาเช่นกัน = เป็นการ update code branch ปัจจุบันที่เราอยู่ด้วย
สังเกต วิธีนี้ใช้สำหรับการ update branch ปัจจุบัน เหมือนการ merge branch (git pull จากบทก่อน) เลย คำถามก็คือ แล้ว 2 วิธีนี้ต่างกันยังไง
- ถ้าเป็น merge (หรือใช้ git pull มา update branch) = เป็นการนำ code จาก branch นั้นมา update ที่ branch ปัจจุบัน + commit ใหม่
- ถ้าเป็น rebase = เป็นการนำ code จาก branch นั้นมา update ที่ branch ปัจจุบัน อย่างเดียวโดยไม่สร้างประวัติใหม่ออกมา (หาก conflict อันไหน = จะเปิดเป็นประวัติใหม่)
สมมุติประวัติของ git ปัจจุบันเราเป็นแบบนี้
เมื่อเราลองใช้ git rebase main
จาก branch develop = ประวัติจะโดน shift ไปหลัง C, D, E และได้ code C, D, E เพิ่มมาใน branch develop เพิ่มเติม
history จะกลายเป็นแบบนี้แทน
ลองมาดูตัวอย่างคำสั่ง git rebase กัน
# command
git rebase <branch ที่จะนำ code เข้ามา>
# example
git rebase main
5. git squash
Ref: https://devopscounsel.com/git-squash-commits/
เคสที่เราจะใช้เมื่อทำการ "สรุป" commit ที่สร้างออกมาจากกันมาเยอะเกินความจำเป็น ปกติมีบางคนชอบใช้สำหรับ
- กำจัด commit ที่ไม่จำเป็นออก
- รวม commit ก่อนเข้า PR เพื่อให้สามารถ revert กลับมาได้ง่าย
สมมุติ history ของ git เป็นแบบนี้
แล้วตอนนี้เราอยู่ที่ develop และ เรารู้และว่า ที่ branch: develop add 1, add 2, add 3 มันคือการ "add feature A" เข้ามา เราเลยตัดสินใจ สรุป commit ใหม่โดยการใช้ git rebase -i HEAD~3
= squash 3 commit ล่าสุดใหม่
ประวัติจะสามารถเปลี่ยนเป็นแบบนี้ได้แทน
# command
git rebase -i HEAD~<n>
# example
git rebase -i HEAD~3
หลังจากที่เราใช้คำสั่งนี้ ตามปกติจะเจอหน้าจอ ที่ list commit ทั้งหมดออกมา หน้าตาประมาณนี้
หลังจากนั้น เราจะทำการเลือก commit ที่ต้องการเอาไว้โดย
- ถ้า commit ไหนเอา ให้คง pick ไว้
- ถ้า commit ไหนไม่เอา ให้เปลี่ยนเป็นคำว่า squash (หรือ s ตามภาพนี้)
หลังจากนั้นมันจะให้ทำการ commit ซ้ำอีกรอบ เพื่อเป็นการ confirm commit message
- หลังจากทำเรียบร้อย เราจะได้ commit hash อันใหม่พร้อมกับประวัติใหม่ออกมา
อื่นๆเพิ่มเติม
1. git stash (เก็บไว้)
Ref: https://www.javatpoint.com/git-stash
คำสั่งนี้มีไว้สำหรับฝาก git change เอาไว้ในกรณีที่เรามีเหตุจำเป็นต้อง
- update ของบางอย่างก่อนจะแก้ต่อ
- ย้ายไป branch อื่นเพื่อเช็คงาน แต่ยังไม่อยากเสีย change ไป
git stash
นั้นคือคำสั่งที่จะนำ git change ทั้งหมดไปฝากไว้เป็น node หนึ่งเอาไว้ และสามารถเรียก change กลับมาใช้ได้ด้วย git stash pop
ตัวอย่าง command git stash
# command สำหรับเก็บ change
git stash
# command สำหรับดึง change กลับมา
git stash pop
ตัวอย่างการใช้งานจะเป็นตาม video ด้านล่างนี้
- ตอนแรก เราจะมี change file ที่มีคำว่า "new change in file" อยู่
- เมื่อเราใช้
git stash
สังเกตเห็นว่า change จะหายไป (ถูกนำไปเก็บไว้เป็น node ใน git stash) - หลังจากที่เราใช้
git stash pop
สังเกตเห็นว่า change จะกลับมาตามเดิมได้
2. git tag (เอาไว้สำหรับสร้าง release version)
git tag คือการสร้าง reference point สำหรับ commit change เอาไว้ เพื่อเป็นการ capture เอาไว้ว่า commit ตรงไหนที่อยากทำการ mark เอาไว้บ้าง โดยปกติจะใช้สำหรับ
- การ tag version ที่จะนำขึ้น production (เพื่อให้แน่ใจว่าใช้ commit ไหนขึ้น code ไป)
เช่น เคสตามภาพด้านล่างนี้ มีการสร้าง tag เอาไว้ที่ commit "B" (v1.0.0) และ commit "F" (v1.0.1) เอาไว้ = เมื่อเกิดปัญหากับ tag v1.0.0, v1.0.1 เราสามารถกลับมายัง reference point นี้ได้เลย
ตัวอย่าง command
# command
git tag -a <tag version ที่ต้องการตั้ง> -m "<message ประกอบการ tag>"
# example
git tag -a v1.0 -m "Release version 1.0"
# หากต้องการย้อนกลับไปยัง tag version นั้น
# command
git checkout tags/<tag version>
#example
git checkout tags/v1.0
โดยเมื่อไหร่ที่มีการใช้คำสั่ง git tag = เป็นการ tag version ตอน commit ที่กำลังอยู่เลย
สรุปเรื่อง git
นี่ก็คือทั้งหมดของพื้นฐาน git ที่คิดว่าน่าจะเพียงพอสำหรับการ development ระดับนึงแล้ว git ต้องอาศัยความเคยชินของการใช้งาน จะทำให้สามารถชินมือและนึกเคสออกเวลาต้องแก้ไขเคสต่างๆได้เช่นกัน