รู้จักกับ Detached Head และวิธีแก้ไข
ก่อนอื่นมาเริ่มกันที่ message ด้านล่างนี้ที่เราอาจได้พบเจอหลังจากที่เราทำการ checkout แล้วก็เกิดความสงสัยว่า เอ๊ะ! ที่ git บอกว่าเราอยู่ใน detached HEAD state นั้นหมายความว่ายังไง คราวนี้เรามาลองดูที่มาของ detached HEAD กันว่าเกิดจากอะไรและจะจัดการกับมันยังไง
You are in 'detached HEAD' state. You can look around,
make experimental changes and commit them,
and you can discard any commits you make in this state without
impacting any branches by switching back to a branch
Detached HEAD เป็น state ไม่ใช่ Error
สิ่งที่ต้องทำความเข้าใจเป็นอย่างแรกเลยคือ detached HEAD นั้นไม่ได้เป็น error แต่อย่างใด เพียงแค่เป็น state ที่เราอาจไม่ค่อยได้เจอบ่อยนัก ดังนั้น detached HEAD ไม่ได้เป็็นปัญหาแต่เราแค่ต้องเข้าใจมันเท่านั้นเอง การเปลี่ยน state จาก detached HEAD กลับมาเป็น attched HEAD นั้นง่ายมากถ้าคุณเข้าใจที่มาที่ไป
HEAD คืออะไร
HEAD นั้นเป็นเหมือน pointer ที่คอยชี้ว่าตอนนี้คุณกำลังทำงานอยู่บน commit ไหนใน branch ไหน นั่นคือเมื่อคุณ commit code เข้าไปใน repository มันจะความแตกต่างของ code ที่เราจะ commit เข้าไปกับ HEAD ดังนั้นตอนทำงานต้องรู้ว่าตอนนี้เรากำลังจะทำอะไรใน branch ไหนคุณต้องอยู่ใน branch ที่ถูกต้องก่อน ซึ่ง HEAD จะมี 2 state คือ
- Attached HEAD HEAD ชี้ที่ commit ล่าสุดใน branch นั้นๆ
- Detached HEAD HEAD ชี้ที่ commit อื่นที่ไม่ใช่ commit ล่าสุด
Git Detached HEAD เกิดขึ้นตอนไหน
เราจะลองสร้างให้เกิด detached HEAD โดย run คำสั่งต่อไปนี้่
mkdir git-head-demo
cd git-head-demo
git init
touch file.txt # สร้างไฟล์แรกขึ้นมา เป็นไฟล์ที่ยังไม่มีข้อความใดๆ
git add .
git commit -m "Create file" # commit file.txt เข้ามาใน repository ครั้งแรก
# เพิ่มข้อความ Hello world! เข้าไปใน file.txt
echo "Hello World" > file.txt
git commit -a -m "Add line to the file" # commit file.txt ครั้งที่ 2
# เพิ่ม file ที่ 2 เข้าไปใน repository
echo "Second file" > file2.txt
git add .
git commit -m "Create second file" # commit file2.txt ครั้งแรก
หลังจาก run git command ด้านบนเรียบร้อยให้ทำการ แสดง log message ออกมาด้วยคำสั่ง
git log --oneline
ค่า Hash ที่ได้จากการ commit ของเราจะไม่เหมือนกัน ให้อ้างอิงจาก commit message เป็นหลัก
ในบรรทัดแรกที่ 049f6e0 จะมีสัญลักษณ์ (HEAD -> master) แสดงว่าตอนนี้ HEAD จะอยู่ในสถานะ attached เรียกว่าเป็น attached HEAD state ซึ่งเป็น state ปกติ
หลังจากนั้นให้เรา run คำสั่ง
git checkout 6008e58 # commit ก่อนสุดท้าย ที่มี message ว่า Add line to the file
หลังจากนี้คุณจะได้รับ message จาก git บอกว่าคุณได้เปลี่ยนไปอยู่ใน detached HEAD state เรียบร้อยแล้ว
You are in 'detached HEAD' state. You can look around,
make experimental changes and commit them,
and you can discard any commits you make in this state without
impacting any branches by switching back to a branch
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 6008e58 Add line to the file
หลังจากเข้าสู่ detached HEAD แล้วลอง commit เพิ่มเข้าไปด้วยคำสั่งนี้
# สร้างไฟล์ใหม่ชื่ีอว่า new-file.txt
echo "Welcome to the alternate timeline" > new-file.txt
git add .
git commit -m "Create new file" # commit new-file.txt เข้าไปใน repository
# เพิ่มบรรทัดใหม่เข้าไปใน new-file.txt
echo "Another line" >> new-file.txt
# commit new-file.txt เป็นครั้งทีี่ 2
git commit -a -m "Add a new line to the file"
หลังจาก run git command ด้านบนเรียบร้อยให้ทำการ แสดง log message ออกมาด้วยคำสั่ง
git log --oneline
เราจะเห็นว่าในบรรทัดแรก add847b นั้นเปลี่ยนจาก (HEAD -> master) ไปเป็น (HEAD) แสดงว่ามีการแก้ไขตอนที่เป็น detached HEAD ดังรูป
วิธีการแก้ไข Detached HEAD
จริงๆแล้วเราไม่ได้แก้ไขเพราะ detached HEAD เป็น state ไม่ใช่ error แต่ในหัวข้อนี้ผมจะแยกออกเป็น 3 กรณีดังนี้
กรณีที่ 1 เราเข้าสู่ detached HEAD โดยไม่ได้ตั้งใจ
ในกรณีนี้เราอาจเข้าสู่ detached state โดยไม่ตั้งใจ ให้เราทำการ checkout หรือ switch กลับไปที่ branch เดิมได้เลยโดยใช้คำสั่ง git checkout ซึ่งเราต้องระบุ branch ที่เราอยุ่ก่อนหน้านี้
git checkout [branch-name] # ระบุ branch ที่เราอยุ่ก่อนหน้านี้
# หรือ
git switch -
เช่นเราอยากจะกลับไปที่ master branch เราจะใช้คำสั่ง checkout หรือ switch เพื่อกลับไปที่จุดเริ่มต้นดังรูป
git checkout master
# หรือ
git switch -
กรณีที่ 2 เราลองแก้ไข code แล้วแต่ไม่ work
ในกรณีนี้เราได้เข้าสู่ detached HEAD state แล้วลองแก้ไข code โดยการ commit code ใหม่เข้าไปแล้ว แต่ไม่ work อยากย้อนกลับไปที่จุดเริ่มต้น โดยที่เราจะไม่เก็บ commit หลังจาก detached HEAD ไว้ ให้เรา run คำสั่งเดียวกับในกรณีที่ 1
git checkout [branch-name] # ระบุ branch ที่เราอยุ่ก่อนหน้านี้
# หรือ
git switch -
ถ้าคุณอยากกลับไปที่ attached HEAD state แบบปกติให้ใช้คำสั่ง git checkout ย้อนกลับไปยัง branch ที่เรามาได้เลย
กรณีที่ 3 เราลองแก้ไข code แล้ว work
กรณีนี้เราจะได้ลองแก้ไข code โดยการ commit เข้าไปแล้วปรากฎว่า work เราต้องการเก็บ code ชุดใหม่นี้ไว้ใน repository ให้เรา run คำสั่งนี้
git branch [ตั้งชื่อ branch ใหม่]
git checkout [ชื่อ branch ใหม่]
# หรือ
git switch -c [ชื่อ branch ใหม่]
ในตัวอย่างยี้เราจะใช่คำสั่งนี้ เพื่อสร้าง branch alt-history ขึ้นมาใหม่
git switch -c alt-history
หลังจากที่เราได้ run command ด้านบนเรียนร้อยแล้วให้ลองเปิด log message ดูด้วยคำสั่ง
git log --oneline
เราจะเห็นว่าใน log บรรทัดแรก add847b นั้นเปลี่ยนจาก (HEAD) เป็น (HEAD -> alt-history) แสดงว่าเรากลับสู่ attached HEAD state เรียบร้อยแล้ว แต่อยู่คนละ branch กับตอนเริ่มต้น ดังรูป
หลังจากนี้เราสามารถเลือก merge กลับเข้าสู่ main หรือ master branch ได้ตามปกติ
ข้อดีของการมี Detached HEAD state
การที่เรามี detached HEAD นั้นเหมือนกับ multiverse เมื่อเรานั่ง time machine ย้อนเวลากลับไปแก้ไขอดีตมันจะทำให้เกิดการแตกเส้นเวลาออกมาใหม่ นั่นคือเส้นเดิมจะยังอยู่และไม่ได้รับผลกระทบจากสิ่งที่คุณแก้ไข คุณสามารถเลือกได้ว่าคุณจะใช้ชีวิตบนเส้นเวลาที่เกิดขึ้นมาใหม่(จากการแก้ไขอดีต) หรือคุณอาจอยากย้อนกลับไปอยู่ในเส้นเวลาเดิมก็ได้
เส้นเวลาที่แตกออกมาคือ git branch นั่นเอง
ตัวอย่าง case ที่อาจเกิดขึ้นจริงคือ คุณพบว่ามี bug เกิดขึ้นมาจากการแก้ไข code เมื่อหลายวันก่อน คุณต้อง checkout code ที่มีปัญหาแล้วมาลองแก้ไขใหม่ ซึ่ง ณ จุดนี้จะกลายเป็น detached HEAD ทันทีที่เรา checkout ซึ่งเมื่อลองแก้ไขแล้วคุณจะมีทางเลือก 2 ทางคือ
- ถ้า commit ใหม่นี้ work เราก็จะแตก branch ออกและ merge กลับเข้ามาที่ master หรือ main branch ใหม่
- ถ้า commit ที่ลองแก้แล้วไม่ work คุณสามารถ switch กลับไปที่ branch เดิมเพื่อยกเลิกการเปลี่ยนแปลงนี้ได้เลย
ปิดข้อความแจ้งเตือน
ในกรณีที่เราต้องการปิด message ที่แจ้งเตือนว่าตอนนี้เราได้เปลี่ยนไปอยู่ใน detached HEAD state แล้ว ให้เราทำการปรับ config ด้วยคำสั่งนี้
git config advice.detached head false
ตามไปดูวิธีการปรับ config ใน scope ต่างๆได้ที่บทความ การจัดการ git config
การปิด Git Detached HEAD message นี้ผู้ใช้ควรมีความคุ้นเคยและความเข้าใจใน Detached HEAD และ Attached HEAD เป็นอย่างดี