1 điểm bởi GN⁺ 9 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Đội ngũ Gradle đã muốn đưa Jujutsu (jj), một VCS tương thích với Git, vào quy trình phát triển hằng ngày, nhưng đã hoãn việc áp dụng vì cách gradlew.bat xử lý xuống dòng trên Windows
  • Vấn đề cốt lõi là jj không đọc được quy tắc EOL theo từng tệp trong .gitattributes, nên không thể áp dụng *.bat text eol=crlf
  • Git chuẩn hóa gradlew.bat thành LF trong kho lưu trữ và checkout thành CRLF trong thư mục làm việc, nhưng jj lại coi đây là thay đổi và liên tục tạo ra phantom modification
  • Có thể lách bằng cách commit gradlew.bat với CRLF nguyên trạng và đổi sang *.bat -text, nhưng nếu trình soạn thảo đổi sang LF thì Git sẽ không tự khôi phục, khiến độ ổn định khi chạy trên Windows có thể bị ảnh hưởng
  • Trong thời gian tới, đội Gradle sẽ tiếp tục dùng git worktree để vận hành nhiều thư mục làm việc, và sẽ thử lại khi jj có hỗ trợ eol

Nguyên nhân thực sự cản trở việc áp dụng: gradlew.bat.gitattributes

  • Jujutsu (jj) là một hệ thống quản lý phiên bản tương thích Git; khi dùng backend Git, nó lưu commit và tệp vào Git và hoạt động cùng các kho Git hiện có
  • jj loại bỏ staging area và biểu diễn working copy bằng các commit thực được cập nhật liên tục
  • Sau khi chỉnh sửa hoặc sắp xếp lại commit, nó tự động rebase các commit phía sau; xung đột có thể được ghi lại thay vì buộc dừng công việc, và cũng có thể hoàn tác qua operation log
  • Việc kỹ sư Gradle cố thay git bằng jj không bị chặn bởi mô hình của jj, mà bởi một chi tiết nhỏ trong cách dự án Gradle được đặt trên đĩa

Trên Windows, gradlew.bat cần CRLF

  • Một kho Gradle thông thường có gradlew.bat đi kèm Gradle Wrapper
  • gradlew.bat dùng label và goto, còn cmd.exe có thể xử lý sai các batch file chỉ có xuống dòng LF
  • Để chạy ổn định trên Windows, gradlew.bat phải được checkout với CRLF
  • Dự án Gradle cưỡng chế yêu cầu này bằng .gitattributes
*.bat text eol=crlf
  • Trong Git, đây là luồng xử lý tiêu chuẩn
    • Tệp được lưu vào index ở dạng đã chuẩn hóa
    • Thuộc tính eol=crlf sẽ hiện thực hóa CRLF vào working copy khi checkout
    • Nội dung chỉnh sửa sẽ lại được chuẩn hóa khi lưu trở lại

Việc jj không hỗ trợ .gitattributes tạo ra phantom diff

  • jj không đọc .gitattributes, nên không thể áp dụng quy tắc xuống dòng theo từng tệp
  • Tính năng liên quan đang được theo dõi trong issue cũ jj-vcs/jj#53, trong đó tối thiểu là hỗ trợ thuộc tính eol đang được bàn tới
  • Hiện tại, lựa chọn có thể dùng trong jj chỉ là cấu hình toàn cục working-copy.eol-conversion
    • Tương tự core.autocrlf của Git, nó áp dụng đồng loạt cho mọi tệp
    • Không thể chỉ định riêng cho tệp .bat
  • Với colocated repo, vấn đề lộ ra ngay lập tức
    • Git lưu gradlew.bat dưới dạng LF và checkout thành CRLF
    • jj không tôn trọng thuộc tính đó, nó ghi nhận CRLF trên đĩa là thay đổi so với blob LF trong Git
    • Trong các dự án Gradle bị ảnh hưởng, gradlew.bat sẽ xuất hiện thay đổi giả liên tục
  • Đây không chỉ là trường hợp riêng của đội Gradle
    • Một người dùng cho biết phantom change đã xảy ra trên hàng trăm nghìn tệp, khiến họ từ bỏ jj

Các cách lách được đề xuất và giới hạn của chúng

  • Tác giả của jj đã đề xuất trong issue dùng non-colocated workspace như một hướng đi
    • Ví dụ: dùng jj git init --git-repo=<path> từ bên ngoài working copy của Git
  • Kỹ sư Gradle cho rằng cách này cũng không giải quyết được vấn đề
    • Khi jj hiện thực hóa gradlew.bat từ blob LF đã lưu, tệp trên đĩa vẫn sẽ là LF
    • gradlew.bat với LF sẽ không chạy được trên Windows
  • Có thể ép CRLF bằng cấu hình toàn cục working-copy.eol-conversion = input-output
    • Nhưng điều này sẽ ghi lại mọi tệp văn bản thành CRLF khi checkout, tạo ra một vấn đề khác

Giải pháp hoạt động được nhưng mong manh

  • Cách thực sự hoạt động là commit gradlew.bat vào kho với CRLF nguyên trạng và dừng việc chuẩn hóa
  • Quy tắc sẽ đổi như sau
    • Trước đây: *.bat text eol=crlf
    • Đổi thành: *.bat -text
  • Khi đó, cả Git và jj đều thống nhất dùng CRLF ở cả cây lưu trữ lẫn working copy
    • Không còn chuyển đổi
    • Phantom diff biến mất
    • Khả năng chạy trên Windows vẫn được giữ nguyên
  • Đổi lại, *.bat -text sẽ từ bỏ cơ chế tự khôi phụctext eol=crlf từng cung cấp
    • Trước đây, nếu vô tình có LF lọt vào, nó sẽ âm thầm được chuẩn hóa lại khi commit
    • Giờ đây LF sẽ còn nguyên, và gradlew.bat có thể âm thầm hỏng trên Windows
  • Việc chuyển đổi này chỉ cần làm một lần, và các chỉnh sửa sai thường có thể bị phát hiện trong quá trình review
  • Dù vậy, đội Gradle cho rằng khó có thể đặt cược cả hệ sinh thái vào giả định rằng mọi dự án Gradle đều sẽ commit CRLF trực tiếp và không có trình soạn thảo nào lặng lẽ đổi chúng đi

Giải pháp thay thế tiếp tục dùng trong lúc này: git worktree

  • Workspace của jj cung cấp nhiều thư mục làm việc dùng chung một kho, giúp các lượt chạy CI dài hoặc thay đổi đang dang dở không chặn công việc tiếp theo
  • Đội Gradle hiện đã xử lý tốt nhu cầu này bằng git worktree
git worktree add ../gradle-feature-x feature-x
git worktree add ../gradle-hotfix    hotfix-7.6
  • Cách này dùng hai checkout nhưng chỉ một object store
  • Có thể vừa làm việc song song vừa giữ một checkout main tồn tại lâu dài mà không cần stash hay clone thứ hai
  • Đội Gradle đã dùng mô hình này trong nhiều năm

Khả năng xem xét lại jj

  • Trường hợp này không phải là phán quyết cuối cùng của dự án Gradle đối với jj
  • Chỉ riêng operation log của jj cũng đã đủ đáng để xem xét sâu hơn
  • Hỗ trợ eol đang được xử lý như một vấn đề có thể giải quyết, và trong issue cũng có thảo luận về việc triển khai qua gix-filter
  • Khi hỗ trợ đó xuất hiện, đội Gradle sẽ thử lại
  • Còn trước mắt, họ vẫn giữ Git vì gradlew.bat, còn git worktree sẽ đảm nhiệm vai trò cần thiết

1 bình luận

 
Các ý kiến trên Lobste.rs
  • Tôi cho rằng thiết lập autocrlf của Gitthiết lập .gitattributes sau đó ngay từ đầu đã là sai lầm, và về sau cũng sẽ vẫn như vậy
    Hệ thống quản lý phiên bản không nên lưu trữ các tệp trong kho khác với các tệp trên đĩa
    Nếu muốn áp đặt định dạng tệp, đó là việc của linter, commit hook, merge hook, chứ không phải vai trò của hệ thống quản lý phiên bản

    • Tôi nghiêng về phía cho rằng ngay từ đầu đó đã là sai lầm, nhưng giờ thì nên tuyên bố rõ ràng là sai lầm và đặc biệt nên đưa core.{eol, safecrlf, autocrlf} vào diện dự kiến loại bỏ
      Chuyển đổi ký tự kết thúc dòng được thêm vào chỉ vì Windows; kiểu chỉ dùng ␍ thì đã chết từ trước Git, còn mọi nơi trừ Windows đều đã ổn định với kiểu chỉ dùng ␊
      Giờ đây có lẽ đối tượng chính thực sự còn quan tâm đến ␍␊ chỉ là các tệp batch
      Mọi ngôn ngữ đều cố gắng hỗ trợ ␍␊, nhưng bây giờ hầu như không còn gì yêu cầu hoặc tạo ra nó nữa
      Nếu Notepad vẫn dùng ␍␊ làm mặc định hoặc chỉ hỗ trợ nó, thì người dùng Notepad phải tự chịu hậu quả
      Một lĩnh vực thú vị hơn trong khả năng tương thích Windows là file mode
      Git đã mang theo mode của Unix và dấu vết đó vẫn còn, nên khi làm việc trên Windows đôi khi có thể phát sinh lỗi mode ngoài ý muốn
    • Ngược lại, tôi lại cảm thấy quản lý phiên bản không nên làm ít những việc như vậy hơn, mà nên làm nhiều hơn
      Nếu công cụ đủ tốt để khi checkout có thể tự động dùng khoảng trắng, hoặc căn chỉnh theo một số lượng khoảng trắng cụ thể kỳ quặc mà ai đó cực kỳ cố chấp, thì ta có thể chấm dứt tranh cãi tab và space
      Xa hơn nữa, thậm chí có thể cho rằng hệ thống quản lý phiên bản nên lưu cây cú pháp trừu tượng (AST) thay vì văn bản gốc
      Hiện tại có thể khó thuyết phục, nhưng cũng có thể tưởng tượng ra một dòng thời gian khác hoặc một tương lai nơi cách làm này rất tự nhiên
  • Tôi không biết tệp .bat thì ngoài Windows ra còn chạy ở đâu nữa
    Cũng thắc mắc là có trường hợp nào mà một hành vi khác dù chỉ một chút lại đáng mong muốn hay không

  • Tôi không bị thuyết phục bởi lập luận rằng không nên commit tệp đúng nguyên định dạng kết thúc dòng cần thiết
    Điểm cốt lõi có vẻ là “ai đó có thể làm hỏng ký tự kết thúc dòng rồi commit, qua được review code và đưa vào một script bị hỏng”
    Tất cả đều đúng, nhưng chẳng phải vì thế mới có kiểm tra lint trên CI sao?
    Nếu quét các ký tự kết thúc dòng không phải CRLF trong tệp đó và khi phát hiện thì cho CI fail, thì thay đổi làm hỏng ký tự kết thúc dòng không thể vượt qua giai đoạn pull request

    • Mục đích của bài này có vẻ không chỉ nói về jj trong Gradle, mà còn cho thấy vấn đề này liên quan đến mọi người và mọi dự án muốn dùng jj trong hệ sinh thái JVM
      Câu trả lời “chỉ cần thêm CI là được” là chưa đủ
      Dự án liên quan có thể là một mod Minecraft do người khá mới làm, và bất kỳ ai trong số đó cũng có thể làm hỏng gradle.bat mà không nhận ra
      Tôi cũng có trách nhiệm vì đã quảng bá jj như một lựa chọn thay thế tốt hơn nhiều so với CLI Git cho người mới bắt đầu
  • Liệu có khả thi không nếu chuyển từ gradlew.bat sang gradlew.ps1, tức là từ cmd.exe/tệp batch sang PowerShell?

    • Tôi không tham gia Gradle, nhưng vài năm trước đã thử làm vậy với Leiningen, và khi đó mọi thứ hoàn toàn hỗn loạn nên hoàn toàn không thực tế
      Giờ thì MAYBE có thể giả định 90% nền tảng cài đặt Windows đã có PowerShell đủ mới
      Nhưng vẫn có trường hợp phải nâng quyền thực thi mặc định, và trong môi trường doanh nghiệp thì có thể bị khóa
      Không biết bạn đã dùng PowerShell nhiều đến đâu, nhưng nó không có cảm giác kiểu “có bash rồi thì chỉ cần chạy tệp .sh
      Tập tính năng khác nhau giữa các phiên bản Windows, cảm giác giống như so sánh ksh với bash4 vậy