Git บน Windows แปลง CRLF เงียบ ๆ แล้วทำให้ SHA-256 ของคุณพัง
ถ้าคุณเก็บ SHA-256 ของไฟล์ข้อความไว้ตรวจความถูกต้องของงาน Git บน Windows อาจทำให้แฮชไม่ตรงทั้งที่ไฟล์ไม่ได้เสียหาย นี่คือวิธีพิสูจน์ใน 3 คำสั่ง
ประเด็นสำคัญที่คุณจะได้จากบทความนี้
- อาการ: แฮชไม่ตรง ทั้งที่ไม่มีใครแก้ไฟล์
- สาเหตุ: Git บน Windows แปลงท้ายบรรทัดตอน checkout ทำให้ไบต์บนดิสก์ต่างจากไบต์ใน Git
- วิธีพิสูจน์ใน 3 คำสั่ง ที่แยกได้ว่าเป็นเรื่องท้ายบรรทัดหรือไฟล์เสียจริง
- ห้าอย่างที่ห้ามทำเด็ดขาดเมื่อเจออาการนี้
- ทำไมไฟล์ภาพไม่เป็น แต่ไฟล์ข้อความเป็น
ปัญหาที่บทความนี้แก้
คุณเก็บค่า SHA-256 ของไฟล์ไว้เพื่อยืนยันว่าไฟล์ไม่ถูกแก้ วันหนึ่งการตรวจสอบล้มเหลว ทั้งที่ไม่มีใครแตะไฟล์นั้นเลย
ก่อนจะไปไล่หาว่าไฟล์เสียตรงไหน ให้เช็กเรื่องนี้ก่อน เพราะมันใช้เวลาสามคำสั่ง และในกรณีของเรา มันคือคำตอบ
ทำไมเรื่องนี้เกี่ยวกับงาน AI
เวลาทำงานกับของที่ AI สร้าง ไม่ว่าจะเป็นภาพ เสียง สคริปต์ หรือ manifest คำถามที่ต้องตอบได้ตลอดคือ “ไฟล์ที่คุณอนุมัติไป กับไฟล์ที่กำลังจะเผยแพร่ เป็นไฟล์เดียวกันจริงไหม”
วิธีตอบที่ตรงไปตรงมาที่สุดคือแฮชไฟล์ตอนอนุมัติ แล้วเทียบแฮชอีกครั้งก่อนใช้ เราทำแบบนี้กับทั้งไฟล์ภาพและไฟล์ข้อความที่บันทึกการอนุมัติ
พอเริ่มมีเครื่องหลายเครื่อง หรือมี coding agent มาช่วยทำงานใน worktree ของตัวเอง สมมติฐานที่ว่า “ไฟล์เดียวกันย่อมได้แฮชเดียวกัน” จะเริ่มพัง และมันพังแบบเงียบ ๆ
อาการ
ในกรณีของเรา มีการตรวจสอบสองจุดล้มเหลวพร้อมกัน ทั้งสองจุดทำสิ่งเดียวกันคือ อ่านไบต์ดิบของไฟล์ แล้วเทียบ SHA-256 กับค่าที่บันทึกไว้ที่อื่น
หน้าตาของโค้ดคือแบบนี้ ซึ่งเป็นวิธีที่ถูกต้องแล้ว
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
ไม่มีอะไรผิดในโค้ดนี้เลย และนั่นคือส่วนที่ทำให้หาสาเหตุยาก
สาเหตุ
Git มีตัวเลือกชื่อ core.autocrlf เมื่อตั้งเป็น true ซึ่งเป็นค่าที่ตัวติดตั้งบน Windows มักแนะนำ Git จะ แปลงท้ายบรรทัดของไฟล์ข้อความจาก LF เป็น CRLF ตอน checkout และแปลงกลับตอน commit
ผลคือไบต์ที่อยู่ในที่เก็บของ Git กับไบต์ที่อยู่บนดิสก์ ไม่เหมือนกัน ทั้งที่เนื้อหาเหมือนกันทุกประการ
LF กับ CRLF คืออะไร — คือวิธีบอกว่า “จบบรรทัดแล้ว” ระบบตระกูล Unix ใช้อักขระเดียวคือ LF ส่วน Windows ใช้สองอักขระคือ CR ตามด้วย LF ไฟล์ที่มี 55 บรรทัดจึงมีขนาดต่างกัน 55 ไบต์ระหว่างสองแบบ
SHA-256 ไม่สนใจว่าเนื้อหาเหมือนกันไหม มันสนใจแค่ไบต์ พอไบต์ต่าง แฮชก็ต่างทันที
เอกสารทางการของ Git อธิบายลำดับความสำคัญไว้ว่า ถ้าไฟล์ไม่ได้ถูกกำหนดค่า eol ไว้ ท้ายบรรทัดในไดเรกทอรีทำงานจะถูกกำหนดโดยค่า core.autocrlf หรือ core.eol และถ้าตั้ง text ไว้แต่ไม่ได้ตั้งสองตัวนั้น ค่าเริ่มต้นคือ eol=crlf บน Windows
พิสูจน์ใน 3 คำสั่ง
ทำตามลำดับ แต่ละข้อเป็นคำสั่งเดียว
1. ดูว่าตัวการอยู่ในเครื่องไหม
git config core.autocrlf
ถ้าได้ true แปลว่าเงื่อนไขครบแล้ว
2. เทียบขนาดไฟล์บนดิสก์กับขนาดใน Git
wc -c < ไฟล์ของคุณ
git cat-file -s HEAD:ไฟล์ของคุณ
ถ้าสองค่าต่างกัน เท่ากับจำนวนบรรทัดของไฟล์พอดี นั่นคือหลักฐานที่ค่อนข้างชี้ชัดว่าเป็นเรื่องท้ายบรรทัด ไม่ใช่ไฟล์เสีย
ในกรณีของเรา ไฟล์หนึ่งมีขนาดบนดิสก์ 8,557 ไบต์ ใน Git 8,502 ไบต์ ต่างกัน 55 ไบต์ และไฟล์นั้นมี 55 บรรทัดพอดี
3. ลองแฮชแบบตัด CR ออกก่อน
tr -d '\r' < ไฟล์ของคุณ | sha256sum
ถ้าค่าที่ได้ ตรง กับแฮชที่บันทึกไว้ แปลว่าเนื้อหาที่ commit ไว้ยังสมบูรณ์ดี มีแค่ไฟล์ที่ checkout ออกมาเท่านั้นที่ผิด
ถ้าค่าที่ได้ ยังไม่ตรง บทความนี้ไม่ใช่คำตอบของคุณ ไฟล์นั้นต่างจริง ให้ไปไล่หาสาเหตุแบบอื่น
หลักฐานที่ตัดตัวเลือกอื่นทิ้ง
สิ่งที่ทำให้เรามั่นใจว่าเป็นเรื่องท้ายบรรทัด ไม่ใช่ไฟล์เสีย คือรูปแบบของมันสะอาดเกินกว่าจะเป็นความบังเอิญ
ไฟล์ภาพไม่เป็น ไฟล์ข้อความเป็น เราตรวจไฟล์ 19 ไฟล์ที่มีแฮชบันทึกไว้ ผลออกมาแบ่งตามชนิดไฟล์อย่างสมบูรณ์
| ชนิดไฟล์ | จำนวน | ผล |
|---|---|---|
| SVG (ไฟล์ข้อความ) | 10 | ไบต์ดิบไม่ตรง แต่ตรงเมื่อตัด CR ออก |
| PNG (ไฟล์ไบนารี) | 9 | ตรงตั้งแต่แรก |
Git ไม่แปลงท้ายบรรทัดในไฟล์ไบนารี ไฟล์ PNG จึงรอด ส่วน SVG ซึ่งจริง ๆ แล้วเป็นไฟล์ข้อความ ไม่รอด
ค่าที่บันทึกไว้ถูกต้องทุกค่า ไบต์บนดิสก์ต่างหากที่ผิด ทุกแฮชที่บันทึกไว้ตรงกับเนื้อหาที่ commit ไว้จริง
คู่ที่ชี้ขาด ในโฟลเดอร์เดียวกันมีไฟล์สองไฟล์ที่การตรวจสอบเดียวกันอ่านทั้งคู่ ไฟล์หนึ่งถูกกำหนดค่าท้ายบรรทัดไว้ใน .gitattributes อีกไฟล์ไม่ได้กำหนด ผลคือไฟล์ที่กำหนดไว้ผ่าน ไฟล์ที่ไม่ได้กำหนดล้มเหลว
ในรีโพของเราตอนนั้น มีไฟล์ข้อความที่ติดตามอยู่ราว 1,223 ไฟล์ที่ขนาดบนดิสก์ต่างจากขนาดใน Git ส่วนใหญ่ไม่มีผลอะไร ส่วนที่มีผลคือไฟล์ข้อความที่ มีแฮชถูกบันทึกไว้เป็นหลักฐาน เท่านั้น
ห้าอย่างที่ห้ามทำ
นี่คือส่วนที่สำคัญที่สุดของบทความ เพราะทุกข้อข้างล่างนี้ “แก้ปัญหาได้” ในความหมายที่ว่าทำให้การตรวจสอบผ่าน และทุกข้อทำลายสิ่งที่การตรวจสอบมีไว้เพื่อ
1. อย่าคำนวณแฮชใหม่แล้วเขียนทับค่าเดิม
นี่คือสิ่งที่ล่อใจที่สุดและแย่ที่สุด ค่าที่บันทึกไว้ถูกต้องอยู่แล้ว การเขียนทับด้วยค่าจากไฟล์ที่เพี้ยนในเครื่องคุณ เท่ากับย้ายความเพี้ยนนั้นเข้าไปเป็นหลักฐานถาวรให้ทุกคน
2. อย่าแก้ไฟล์ต้นฉบับให้ “ปกติ”
เนื้อหาที่ commit ไว้ถูกต้องแล้ว ปัญหาอยู่ที่การ checkout ไม่ใช่ที่ไฟล์
3. อย่าผ่อนหรือปิดการตรวจสอบ
การตรวจสอบทำงานถูกต้อง มันเจอความต่างของไบต์จริง ๆ การปิดมันคือการทิ้งสิ่งเดียวที่จับเรื่องนี้ได้
4. อย่าแก้โค้ดให้ตัด CR ออกก่อนแฮช
ฟังดูสมเหตุสมผล แต่จะทำให้ไฟล์ข้อความกับไฟล์ไบนารีถูกตรวจด้วยมาตรฐานคนละแบบ และจะยอมรับไฟล์ข้อความที่ถูกแก้จริง ๆ อย่างเงียบ ๆ
5. อย่า commit ไฟล์เวอร์ชัน CRLF เข้าไป
จะย้ายปัญหาไปให้ทุกคนที่ใช้รีโพนี้
ทางแก้ที่ถูกต้อง
กำหนดท้ายบรรทัดของไฟล์ข้อความที่มีแฮชเป็นหลักฐาน ให้ตายตัวใน .gitattributes แล้ว checkout ใหม่
*.svg text eol=lf
*.manifest.json text eol=lf
ตามเอกสารของ Git เมื่อกำหนด text หรือ text=auto ไว้ ค่า eol จะมีลำดับความสำคัญเหนือ core.autocrlf สำหรับไฟล์นั้น ดังนั้นการกำหนดแบบนี้จะบังคับ LF ไม่ว่าเครื่องปลายทางจะตั้งค่าอะไรไว้
หลังแก้ .gitattributes ต้องบังคับให้ Git เขียนไฟล์ใหม่ ไม่งั้นไฟล์เดิมยังเพี้ยนอยู่
git rm --cached -r .
git reset --hard
ก่อนรัน
git reset --hardให้ commit หรือ stash งานที่ยังไม่ได้บันทึกให้หมดก่อน คำสั่งนี้ทิ้งการแก้ไขที่ยังไม่ได้ commit ทั้งหมด
จากนั้นรันการตรวจสอบอีกครั้ง แฮชควรตรงโดยที่คุณไม่ได้แตะค่าที่บันทึกไว้เลยแม้แต่ค่าเดียว
วิธีป้องกันตั้งแต่แรก
ถ้าคุณกำลังเริ่มโปรเจกต์ที่จะใช้แฮชเป็นหลักฐาน ให้ใส่ .gitattributes ตั้งแต่วันแรก
* text=auto eol=lf
*.png binary
*.jpg binary
*.wav binary
*.mp4 binary
บรรทัดแรกทำให้ไฟล์ข้อความทั้งหมดเป็น LF เหมือนกันทุกเครื่อง ส่วนบรรทัดที่เหลือกันไม่ให้ Git ไปยุ่งกับไฟล์ไบนารี
ถ้าคุณให้ coding agent ทำงานใน worktree แยก ข้อนี้ยิ่งสำคัญ เพราะแต่ละ worktree ก็ checkout ไฟล์ของตัวเองออกมา และจะเจอการแปลงแบบเดียวกัน
ข้อจำกัดของบทความนี้
- หลักฐานทั้งหมดมาจากรีโพเดียวบนเวิร์กสเตชัน Windows เครื่องเดียว เมื่อ 20 สิงหาคม 2026 เราไม่ได้ทดสอบซ้ำบนหลายเครื่องหรือหลายเวอร์ชันของ Git
- เราพิสูจน์ได้ว่า อาการนี้เกิดจากการแปลงท้ายบรรทัดตอน checkout ด้วยหลักฐานไบต์และแฮช แต่เราไม่ได้พิสูจน์ว่า อะไรทำให้เครื่องนั้นตั้งค่าแบบนั้นตั้งแต่แรก สภาพเดียวกันนี้เกิดได้กับการ clone ใหม่บน Windows ที่ใช้ค่าเริ่มต้น
- คำสั่งในบทความเขียนแบบ Unix shell ถ้าใช้ PowerShell ล้วน ๆ ต้องปรับ
wc,trและsha256sumเป็นคำสั่งเทียบเท่า - บทความนี้พูดถึงกรณีที่ เนื้อหายังถูกต้อง เท่านั้น ถ้าคำสั่งที่ 3 ยังให้ค่าไม่ตรง แปลว่าคุณเจอปัญหาคนละเรื่อง
เหมาะกับใคร
เหมาะ ถ้าคุณเก็บ SHA-256 ของไฟล์ข้อความไว้เป็นหลักฐานการอนุมัติหรือความถูกต้อง ถ้าคุณทำงานข้ามระหว่าง Windows กับ Linux หรือถ้าคุณให้ coding agent ทำงานในหลาย worktree
ไม่เหมาะ ถ้าคุณแฮชเฉพาะไฟล์ไบนารี เพราะ Git ไม่แปลงไฟล์เหล่านั้นอยู่แล้ว และถ้าคุณไม่ได้ใช้แฮชเป็นหลักฐานเลย เรื่องนี้ก็จะไม่กระทบคุณ
สรุป
แฮชไม่ตรง ไม่ได้แปลว่าไฟล์เสียเสมอไป บนวินโดวส์ให้เช็กเรื่องท้ายบรรทัดก่อนเสมอ เพราะมันใช้เวลาสามคำสั่ง
และถ้าใช่ ให้แก้ที่ .gitattributes อย่าแก้ที่ค่าแฮชที่บันทึกไว้ ค่าเหล่านั้นคือสิ่งเดียวที่บอกคุณได้ว่ามีอะไรผิดปกติ
แหล่งอ้างอิงและเอกสารปฐมภูมิ (Citations)
- ปฐมภูมิgitattributes Documentation
AI ช่วยเรียบเรียงและจัดรูปแบบ หลักฐานไบต์และแฮชทั้งหมดมาจากการตรวจสอบจริงในรีโพของ SXP เมื่อ 20 สิงหาคม 2026 ผู้เขียนตรวจสอบซ้ำกับเอกสารทางการของ Git ด้วยตัวเอง