- ทีม Gradle เคยพยายามนำ Jujutsu(jj) ซึ่งเป็น VCS ที่เข้ากันได้กับ Git มาใช้ในการพัฒนาประจำวัน แต่ต้องเลื่อนการนำมาใช้เพราะการจัดการอักขระขึ้นบรรทัดใหม่ของ
gradlew.bat บน Windows
- ปัญหาหลักคือ
jj อ่าน กฎ EOL รายไฟล์ใน .gitattributes ไม่ได้ จึงไม่สามารถใช้ *.bat text eol=crlf ได้
- Git จะ normalize
gradlew.bat เป็น LF ใน repository และ checkout เป็น CRLF ใน working directory แต่ jj มองสิ่งนี้เป็นการเปลี่ยนแปลงและสร้าง phantom modification ต่อเนื่อง
- สามารถเลี่ยงได้ด้วยการ commit
gradlew.bat เป็น CRLF ตามเดิมและเปลี่ยนเป็น *.bat -text แต่หาก editor เปลี่ยนเป็น LF Git จะไม่กู้คืนให้อัตโนมัติ ทำให้ ความเสถียรในการรันบน Windows อาจสั่นคลอน
- ทีม Gradle จะใช้
git worktree เพื่อจัดการ working directory หลายชุดไปก่อน และมีแผนจะลองอีกครั้งเมื่อ jj รองรับ eol แล้ว
สาเหตุจริงที่ขวางการนำมาใช้: gradlew.bat และ .gitattributes
- Jujutsu(
jj) เป็น ระบบจัดการเวอร์ชันที่เข้ากันได้กับ Git ซึ่งเมื่อใช้ Git backend จะเก็บ commit และไฟล์ไว้ใน Git และทำงานร่วมกับ repository Git เดิมได้
jj ตัด staging area ออก และแสดง working copy เป็น commit จริงที่ถูกอัปเดตอย่างต่อเนื่อง
- หลังจากแก้ไขหรือจัดลำดับ commit ใหม่ จะ rebase commit ถัดไปให้อัตโนมัติ และ conflict สามารถบันทึกไว้ในประวัติแทนที่จะหยุดงานได้ อีกทั้งยังย้อนกลับจาก operation log ได้ด้วย
- ความพยายามของวิศวกร Gradle ที่จะเปลี่ยนจาก
git ไปเป็น jj ไม่ได้ติดที่โมเดลของ jj แต่ติดที่รายละเอียดเล็ก ๆ ของวิธีที่โปรเจกต์ Gradle ถูกวางอยู่บนดิสก์
gradlew.bat ต้องใช้ CRLF บน Windows
- repository Gradle ทั่วไปจะมี
gradlew.bat มาพร้อมกับ Gradle Wrapper
gradlew.bat ใช้ label และ goto และ cmd.exe อาจจัดการ batch file ที่มีอักขระขึ้นบรรทัดใหม่แบบ LF อย่างเดียวผิดพลาดได้
- เพื่อให้รันได้อย่างเสถียรบน Windows
gradlew.bat ต้องถูก checkout เป็น CRLF
- โปรเจกต์ Gradle บังคับข้อกำหนดนี้ด้วย
.gitattributes
*.bat text eol=crlf
- ใน Git พฤติกรรมนี้เป็น flow มาตรฐาน
- ไฟล์จะถูกเก็บใน index ในรูปแบบที่ normalize แล้ว
- attribute
eol=crlf จะทำให้ working copy มี CRLF จริงตอน checkout
- เมื่อบันทึกเนื้อหาที่แก้ไขกลับเข้าไป จะถูก normalize อีกครั้ง
การไม่รองรับ .gitattributes ของ jj ทำให้เกิด phantom diff
jj ไม่อ่าน .gitattributes จึงไม่สามารถใช้กฎอักขระขึ้นบรรทัดใหม่รายไฟล์ได้
- ฟีเจอร์ที่เกี่ยวข้องถูกติดตามใน issue เก่าอย่าง jj-vcs/jj#53 และอย่างน้อยก็กำลังมีการพูดคุยเรื่องการรองรับ attribute
eol
- เครื่องมือที่ใช้ได้ใน
jj ตอนนี้มีเพียง working-copy.eol-conversion ซึ่งเป็นการตั้งค่าระดับ global เท่านั้น
- ใช้กับทุกไฟล์พร้อมกันเหมือน
core.autocrlf ของ Git
- ไม่สามารถระบุเฉพาะไฟล์
.bat ได้
- ใน colocated repo ปัญหาจะปรากฏทันที
- Git เก็บ
gradlew.bat เป็น LF และ checkout เป็น CRLF
- เพราะ
jj ไม่เคารพ attribute ดังกล่าว จึงบันทึก CRLF บนดิสก์เป็นการเปลี่ยนแปลงเมื่อเทียบกับ LF blob ของ Git
- ในโปรเจกต์ Gradle ที่ได้รับผลกระทบ จะเกิด การเปลี่ยนแปลงปลอมอย่างต่อเนื่อง ของ
gradlew.bat
- ปัญหานี้ไม่ได้เป็นกรณีของทีม Gradle เพียงทีมเดียว
workaround ที่ถูกเสนอและข้อจำกัด
- ผู้สร้าง
jj เสนอใน issue ให้ใช้ non-colocated workspace เป็นแนวทางหนึ่ง
- ตัวอย่าง: ใช้
jj git init --git-repo=<path> จากภายนอก Git working copy
- วิศวกร Gradle เห็นว่าวิธีนี้ก็ยังแก้ปัญหาไม่ได้
- หาก
jj materialize gradlew.bat จาก LF blob ที่เก็บไว้ ไฟล์บนดิสก์ก็จะออกมาเป็น LF
gradlew.bat แบบ LF จะรันบน Windows ไม่ได้
- สามารถบังคับ CRLF ได้ด้วยการตั้งค่า global
working-copy.eol-conversion = input-output
- แต่จะกลายเป็นปัญหาอีกแบบ เพราะทุกไฟล์ข้อความจะถูกเขียนใหม่เป็น CRLF ตอน checkout
วิธีแก้ที่ใช้งานได้แต่เปราะบาง
- วิธีที่ใช้งานได้จริงคือ commit
gradlew.bat เป็น CRLF ตามเดิม ใน repository และหยุดการ normalize
- กฎจะเปลี่ยนเป็นดังนี้
- เดิม:
*.bat text eol=crlf
- เปลี่ยนเป็น:
*.bat -text
- แบบนี้ Git และ
jj จะเห็นตรงกันว่า CRLF อยู่ทั้งใน tree ที่เก็บไว้และใน working copy
- ไม่มีการแปลงอีกต่อไป
- phantom diff ก็หายไป
- การรันบน Windows ก็ยังคงทำงานได้
- แต่
*.bat -text คือการยอมสละ การกู้คืนอัตโนมัติ ที่ text eol=crlf เคยให้
- ก่อนหน้านี้ ต่อให้เผลอมี LF เข้ามา ก็จะถูก normalize กลับอย่างเงียบ ๆ ตอน commit
- ตอนนี้ LF จะคงอยู่ตามเดิม และอาจทำให้
gradlew.bat พังบน Windows อย่างเงียบ ๆ
- การแปลงนี้ต้องทำเพียงครั้งเดียว และการแก้ไขที่ผิดพลาดมักถูกจับได้ในการ review
- ถึงอย่างนั้น ทีม Gradle เห็นว่ายากจะเอาทั้ง ecosystem ไปผูกกับสมมติฐานว่าโปรเจกต์ Gradle ทั้งหมดจะ commit CRLF โดยตรง และไม่มี editor ใดเปลี่ยนมันอย่างเงียบ ๆ
ทางเลือกที่ใช้ไปก่อน: git worktree
- workspace ของ
jj ให้ working directory หลายชุดที่แชร์ repository เดียวกัน ทำให้การรัน CI ที่ใช้เวลานานหรือการเปลี่ยนแปลงที่กำลังทำอยู่ไม่ขวางงานถัดไป
- ทีม Gradle ใช้
git worktree เพื่อรองรับความต้องการเดียวกันได้เพียงพออยู่แล้ว
git worktree add ../gradle-feature-x feature-x
git worktree add ../gradle-hotfix hotfix-7.6
- วิธีนี้ใช้ checkout สองชุดและ object store เดียว
- สามารถทำงานคู่ขนานและคง checkout ของ
main ระยะยาวไว้ได้โดยไม่ต้อง stash หรือ clone ชุดที่สอง
- ทีม Gradle ใช้ pattern นี้มาหลายปีแล้ว
ความเป็นไปได้ในการพิจารณา jj อีกครั้ง
- กรณีนี้ไม่ใช่คำตัดสินสุดท้ายต่อ
jj เองในโปรเจกต์ Gradle
- แค่ operation log ของ
jj ก็มีคุณค่าพอให้พิจารณาเชิงลึกต่อ
- การรองรับ
eol ถูกมองว่าเป็นปัญหาที่แก้ได้ และใน issue ยังมีการพูดคุยถึงการ implement ผ่าน gix-filter ด้วย
- เมื่อมีการรองรับดังกล่าว ทีม Gradle มีแผนจะลองอีกครั้ง
- ก่อนถึงตอนนั้น พวกเขาจะยังใช้ Git ต่อเพราะ
gradlew.bat และให้ git worktree ทำหน้าที่ที่จำเป็น
1 ความคิดเห็น
ความคิดเห็นจาก Lobste.rs
มองว่า การตั้งค่า
autocrlfของ Git และ การตั้งค่า.gitattributesที่ตามมานั้นเป็นความผิดพลาดตั้งแต่แรก และต่อไปก็ยังจะเป็นเช่นนั้นระบบควบคุมเวอร์ชันไม่ควรเก็บไฟล์ในรีโพซิทอรีให้ต่างจากไฟล์บนดิสก์
ถ้าต้องการบังคับรูปแบบไฟล์ นั่นเป็นหน้าที่ของ linter, commit hook, merge hook ไม่ใช่บทบาทของระบบควบคุมเวอร์ชัน
core.{eol, safecrlf, autocrlf}ควรถูกย้ายไปอยู่ในสถานะเตรียมเลิกใช้การแปลงท้ายบรรทัดถูกเพิ่มเข้ามาเพียงเพราะ Windows เท่านั้น ส่วนแบบ ␍ อย่างเดียวก็ตายไปก่อน Git แล้ว และทุกอย่างยกเว้น Windows ก็ลงตัวที่แบบ ␊ อย่างเดียว
ตอนนี้สิ่งสำคัญหลัก ๆ ที่ยังต้องสนใจ ␍␊ จริง ๆ ก็คงมีแค่ไฟล์ batch เท่านั้น
ทุกภาษาต่างพยายามรองรับ ␍␊ แต่ตอนนี้แทบไม่มีอะไรที่ต้องการหรือสร้างมันขึ้นมาแล้ว
ถ้า Notepad ยังใช้ ␍␊ เป็นค่าเริ่มต้น หรือรองรับแค่นั้น คนที่ใช้ Notepad ก็ต้องรับผลที่ตามมาเอง
พื้นที่ที่น่าสนใจกว่าในแง่ความเข้ากันได้กับ Windows คือโหมดไฟล์
Git นำโหมดของ Unix มาใช้และยังมีร่องรอยเหลืออยู่ ทำให้เวลาทำงานบน Windows บางทีก็เกิด ข้อผิดพลาดเรื่องโหมด โดยไม่ตั้งใจ
ถ้าเครื่องมือดีพอจนตอน checkout สามารถใช้ช่องว่างให้อัตโนมัติ หรือปรับให้ตรงกับจำนวนช่องว่างที่เฉพาะเจาะจงแปลก ๆ ซึ่งใครบางคนยืนกรานอย่างหนักได้ เราก็อาจหยุดเถียงกันเรื่องแท็บกับช่องว่างได้
ยิ่งไปกว่านั้น อาจมองได้ถึงขั้นว่าระบบควบคุมเวอร์ชันควรเก็บ abstract syntax tree (AST) ไม่ใช่ข้อความต้นฉบับ
ตอนนี้คงโน้มน้าวได้ยาก แต่ก็จินตนาการถึงเส้นเวลาอื่นหรืออนาคตที่แนวทางนี้เป็นธรรมชาติมากได้
ไม่รู้เหมือนกันว่าจะไปรันไฟล์
.batที่ไหนถ้าไม่ใช่บน Windowsและก็สงสัยด้วยว่ามีกรณีไหนที่พฤติกรรมอื่นจะพึงประสงค์แม้แต่นิดเดียว
ผมไม่เห็นด้วยกับข้อโต้แย้งที่ว่าไม่ควร commit ไฟล์ตามรูปแบบท้ายบรรทัดที่ต้องการ
ประเด็นหลักดูเหมือนจะเป็น “ใครบางคนทำท้ายบรรทัดพังแล้ว commit จากนั้นผ่าน code review จนมีสคริปต์ที่เสียเข้าไปได้”
ทั้งหมดนั้นถูกต้อง แต่เพราะอย่างนั้นถึงมี การตรวจ lint ใน CI ไม่ใช่หรือ?
ถ้าสแกนหาท้ายบรรทัดที่ไม่ใช่ CRLF ในไฟล์นั้น และหากพบก็ให้ CI ล้มเหลว การแก้ไขที่ทำท้ายบรรทัดพังก็จะผ่านขั้น pull request ไปไม่ได้
คำตอบว่า “ก็แค่เพิ่ม CI” ยังไม่พอ
โปรเจกต์ที่เกี่ยวข้องอาจเป็น Minecraft mod ที่มือใหม่ค่อนข้างมากเป็นคนสร้าง และใครคนใดคนหนึ่งในนั้นอาจทำ
gradle.batพังโดยไม่รู้ตัวก็ได้ผมเองก็มีส่วนรับผิดชอบ เพราะเคยโปรโมต jj ว่าเป็นทางเลือกที่ดีกว่า CLI Git มากสำหรับผู้เริ่มต้น
ทางเลือกในการย้ายจาก
gradlew.batไปเป็นgradlew.ps1หรือก็คือย้ายจากcmd.exe/ไฟล์ batch ไปเป็น PowerShell เป็นไปได้ไหม?ตอนนี้ MAYBE อาจพอสมมติได้ว่า 90% ของฐานการติดตั้ง Windows มี PowerShell ที่ใหม่พอ
แต่ยังมีกรณีที่ต้องยกระดับสิทธิ์การรันตามค่าเริ่มต้น และในสภาพแวดล้อมองค์กรก็อาจถูกล็อกไว้
ไม่รู้ว่าคุณเคยใช้ PowerShell แค่ไหน แต่ความรู้สึกมันไม่ใช่แบบ “มี bash อยู่แล้ว ก็รันไฟล์
.shได้เลย”ชุดฟีเจอร์ในแต่ละเวอร์ชันของ Windows ต่างกัน จนให้ความรู้สึกเหมือนกำลังเทียบ
kshกับbash4