Guide⏱ อ่านประมาณ 13 นาที

Git บน Windows แปลง CRLF เงียบ ๆ แล้วทำให้ SHA-256 ของคุณพัง

ถ้าคุณเก็บ SHA-256 ของไฟล์ข้อความไว้ตรวจความถูกต้องของงาน Git บน Windows อาจทำให้แฮชไม่ตรงทั้งที่ไฟล์ไม่ได้เสียหาย นี่คือวิธีพิสูจน์ใน 3 คำสั่ง

ข้อมูลในคู่มือนี้ได้รับการตรวจสอบและทดสอบซ้ำล่าสุดเมื่อ: 28 สิงหาคม 2569

ประเด็นสำคัญที่คุณจะได้จากบทความนี้

  • อาการ: แฮชไม่ตรง ทั้งที่ไม่มีใครแก้ไฟล์
  • สาเหตุ: 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)

  1. ปฐมภูมิgitattributes Documentation— Git (ตรวจสอบเมื่อ 2026-08-28)
การใช้งาน AI ในบทความนี้

AI ช่วยเรียบเรียงและจัดรูปแบบ หลักฐานไบต์และแฮชทั้งหมดมาจากการตรวจสอบจริงในรีโพของ SXP เมื่อ 20 สิงหาคม 2026 ผู้เขียนตรวจสอบซ้ำกับเอกสารทางการของ Git ด้วยตัวเอง

สิทธา อินทชัย

ผู้ก่อตั้ง SXP