1 คะแนน โดย GN⁺ 9 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ทีม 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 ความคิดเห็น

 
GN⁺ 9 시간 전
ความคิดเห็นจาก 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 ไปไม่ได้

    • ดูเหมือนจุดประสงค์ของบทความนี้ไม่ได้พูดแค่ jj ใน Gradle แต่ต้องการชี้ให้เห็นว่าปัญหานี้เกี่ยวข้องกับทุกคนและทุกโปรเจกต์ใน ระบบนิเวศ JVM ที่อยากใช้ jj
      คำตอบว่า “ก็แค่เพิ่ม CI” ยังไม่พอ
      โปรเจกต์ที่เกี่ยวข้องอาจเป็น Minecraft mod ที่มือใหม่ค่อนข้างมากเป็นคนสร้าง และใครคนใดคนหนึ่งในนั้นอาจทำ gradle.bat พังโดยไม่รู้ตัวก็ได้
      ผมเองก็มีส่วนรับผิดชอบ เพราะเคยโปรโมต jj ว่าเป็นทางเลือกที่ดีกว่า CLI Git มากสำหรับผู้เริ่มต้น
  • ทางเลือกในการย้ายจาก gradlew.bat ไปเป็น gradlew.ps1 หรือก็คือย้ายจาก cmd.exe/ไฟล์ batch ไปเป็น PowerShell เป็นไปได้ไหม?

    • ผมไม่ได้เกี่ยวข้องกับ Gradle แต่เมื่อหลายปีก่อนเคยพยายามทำแบบนั้นกับ Leiningen และตอนนั้นมันเละเทะมากจนใช้จริงไม่ได้เลย
      ตอนนี้ MAYBE อาจพอสมมติได้ว่า 90% ของฐานการติดตั้ง Windows มี PowerShell ที่ใหม่พอ
      แต่ยังมีกรณีที่ต้องยกระดับสิทธิ์การรันตามค่าเริ่มต้น และในสภาพแวดล้อมองค์กรก็อาจถูกล็อกไว้
      ไม่รู้ว่าคุณเคยใช้ PowerShell แค่ไหน แต่ความรู้สึกมันไม่ใช่แบบ “มี bash อยู่แล้ว ก็รันไฟล์ .sh ได้เลย”
      ชุดฟีเจอร์ในแต่ละเวอร์ชันของ Windows ต่างกัน จนให้ความรู้สึกเหมือนกำลังเทียบ ksh กับ bash4