1 pontos por GN⁺ 9 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • A equipe da Gradle tentou adotar Jujutsu (jj), um VCS compatível com Git, no desenvolvimento cotidiano, mas adiou a adoção por causa do tratamento de quebras de linha no gradlew.bat para Windows
  • O problema central é que o jj não consegue ler as regras de EOL por arquivo em .gitattributes, portanto não consegue aplicar *.bat text eol=crlf
  • O Git normaliza gradlew.bat como LF no repositório e o faz checkout como CRLF no diretório de trabalho, mas o jj enxerga isso como uma alteração e continua criando phantom modifications
  • É possível contornar fazendo commit de gradlew.bat já em CRLF e mudando para *.bat -text, mas, se um editor trocar para LF, o Git não recupera automaticamente, o que pode abalar a estabilidade de execução no Windows
  • Por enquanto, a equipe da Gradle vai operar vários diretórios de trabalho com git worktree e planeja tentar novamente quando o suporte a eol no jj for implementado

A causa real que impediu a adoção: gradlew.bat e .gitattributes

  • Jujutsu (jj) é um sistema de controle de versão compatível com Git que, ao usar o backend Git, armazena commits e arquivos no Git e funciona junto com repositórios Git existentes
  • O jj elimina a área de staging e representa a cópia de trabalho como um commit real que é atualizado continuamente
  • Depois de editar ou reordenar commits, ele faz rebase automático dos commits subsequentes; conflitos podem ficar registrados em vez de interromper o trabalho; e também é possível reverter a partir do log de operações
  • A tentativa de um engenheiro da Gradle de substituir git por jj não foi bloqueada pelo modelo do jj, mas por um pequeno detalhe da forma como o projeto Gradle fica no disco

No Windows, gradlew.bat precisa de CRLF

  • Um repositório Gradle típico inclui gradlew.bat junto com o Gradle Wrapper
  • gradlew.bat usa labels e goto, e o cmd.exe pode processar incorretamente arquivos batch que tenham quebras de linha apenas LF
  • Para executar de forma confiável no Windows, gradlew.bat precisa ser feito checkout em CRLF
  • Projetos Gradle impõem esse requisito com .gitattributes
*.bat text eol=crlf
  • No Git, esse comportamento é um fluxo padrão
    • O arquivo é armazenado no índice em uma forma normalizada
    • O atributo eol=crlf materializa CRLF na cópia de trabalho durante o checkout
    • O conteúdo editado é normalizado novamente quando é salvo

O phantom diff criado pela falta de suporte a .gitattributes no jj

  • O jj não lê .gitattributes, então não consegue aplicar regras de quebra de linha por arquivo
  • A funcionalidade relacionada é acompanhada no issue antigo jj-vcs/jj#53, e pelo menos o suporte ao atributo eol está em discussão
  • Atualmente, o único recurso disponível no jj é a configuração global working-copy.eol-conversion
    • Ela é aplicada a todos os arquivos de uma vez, como o core.autocrlf do Git
    • Não é possível especificar apenas arquivos .bat separadamente
  • Em um repositório colocated, o problema aparece imediatamente
    • O Git armazena gradlew.bat como LF e faz checkout como CRLF
    • Como o jj não respeita esse atributo, ele registra o CRLF no disco como uma alteração em relação ao blob LF do Git
    • Nos projetos Gradle afetados, surge uma alteração falsa persistente em gradlew.bat
  • Esse problema não é um caso exclusivo da equipe da Gradle
    • Um usuário desistiu de usar jj depois que phantom changes ocorreram em um volume de arquivos na casa das centenas de milhares

Soluções alternativas propostas e seus limites

  • O autor do jj sugeriu no issue um non-colocated workspace como um caminho possível
    • Ex.: usar jj git init --git-repo=<path> fora da cópia de trabalho do Git
  • O engenheiro da Gradle considera que esse método também não resolve o problema
    • Se o jj materializar gradlew.bat a partir do blob LF armazenado, ele chega ao disco como LF
    • Um gradlew.bat em LF não executa no Windows
  • É possível forçar CRLF com a configuração global working-copy.eol-conversion = input-output
    • Mas isso vira outro problema, porque todos os arquivos de texto são reescritos como CRLF no checkout

Uma solução que funciona, mas é frágil

  • O método que de fato funciona é fazer commit de gradlew.bat no repositório mantendo CRLF e interromper a normalização
  • A regra muda assim
    • Antes: *.bat text eol=crlf
    • Depois: *.bat -text
  • Com isso, Git e jj concordam com CRLF tanto na árvore armazenada quanto na cópia de trabalho
    • A conversão desaparece
    • O phantom diff também desaparece
    • A execução no Windows é preservada
  • Em contrapartida, *.bat -text abre mão da recuperação automática que text eol=crlf fornecia
    • Antes, mesmo que LF entrasse por engano, ele era silenciosamente normalizado de novo no commit
    • Agora, LF permanece como está, e gradlew.bat pode quebrar silenciosamente no Windows
  • Essa conversão só precisa ser feita uma vez, e edições incorretas em geral podem ser detectadas em review
  • Mesmo assim, a equipe da Gradle concluiu que é difícil apostar o ecossistema na premissa de que todos os projetos Gradle vão commitar CRLF diretamente e de que nenhum editor vai alterá-lo silenciosamente

Alternativa mantida por enquanto: git worktree

  • O workspace do jj oferece vários diretórios de trabalho compartilhando um único repositório, para que execuções longas de CI ou mudanças em andamento não bloqueiem a próxima tarefa
  • A equipe da Gradle já atende suficientemente bem a essa mesma necessidade com git worktree
git worktree add ../gradle-feature-x feature-x
git worktree add ../gradle-hotfix    hotfix-7.6
  • Esse método usa dois checkouts e um único armazenamento de objetos
  • Ele permite manter trabalho paralelo e um checkout de main de longa duração sem stash nem um segundo clone
  • A equipe da Gradle usa esse padrão há anos

Possibilidade de reavaliar o jj

  • Este caso não é uma avaliação final sobre o próprio jj nos projetos Gradle
  • Só o operation log do jj já vale uma análise mais aprofundada
  • O suporte a eol está sendo tratado como um problema solucionável, e o issue também inclui discussões de implementação via gix-filter
  • Quando esse suporte chegar, a equipe da Gradle pretende tentar novamente
  • Até lá, eles mantêm o Git por causa de gradlew.bat, e git worktree cumpre o papel necessário

1 comentários

 
GN⁺ 9 시간 전
Opiniões no Lobste.rs
  • A configuração autocrlf do Git e a posterior configuração .gitattributes foram um erro desde o começo e continuarão sendo
    Um sistema de controle de versão não deveria armazenar os arquivos do repositório de forma diferente dos arquivos no disco
    Se quiser impor formato de arquivo, isso é tarefa de linter, hook de commit e hook de merge, não do sistema de controle de versão

    • Concordo mais com a visão de que foi um erro desde o início, mas agora isso deveria ser declarado explicitamente como erro, e especialmente core.{eol, safecrlf, autocrlf} deveria entrar em processo de descontinuação
      A conversão de fim de linha só foi adicionada por causa do Windows, o formato apenas com ␍ já estava morto antes mesmo do Git, e fora do Windows todo mundo se consolidou em ␊
      Hoje, o principal caso que realmente ainda se importa com ␍␊ provavelmente são arquivos em lote
      Todas as linguagens se esforçam para oferecer suporte a ␍␊, mas hoje quase nada exige ou gera isso
      Se o Notepad ainda usa ␍␊ como padrão ou só oferece suporte a isso, quem usa o Notepad precisa arcar com as consequências
      Uma área mais interessante da compatibilidade com Windows é o modo de arquivo
      O Git trouxe os modos do Unix e ainda carrega esse legado, então ao trabalhar no Windows às vezes surgem erros de modo sem querer
    • Eu penso o contrário: controle de versão deveria fazer mais desse tipo de coisa, não menos
      Se a ferramenta fosse boa o bastante para, no checkout, usar espaços automaticamente ou ajustar para aquela quantidade estranhamente específica de espaços que alguém insiste em usar, daria para encerrar a discussão entre tabs e espaços
      Indo além, dá até para defender que o sistema de controle de versão deveria armazenar não o texto-fonte, mas a árvore de sintaxe abstrata (AST)
      Pode ser difícil convencer alguém disso hoje, mas é fácil imaginar outra linha do tempo ou um futuro em que isso pareça muito natural
  • Não entendo onde alguém executaria um arquivo .bat fora do Windows
    Também duvido que exista algum caso em que um comportamento diferente seja minimamente desejável

  • Não me convence o argumento de que não se deve fazer commit do arquivo já com o formato de fim de linha necessário
    O ponto central parece ser: “alguém pode quebrar os fins de linha e fazer commit, a revisão de código pode deixar passar, e um script quebrado pode entrar”
    Tudo isso é verdade, mas não é exatamente para isso que existe lint no CI?
    Se o CI verificar fins de linha que não sejam CRLF nesse arquivo e falhar ao encontrar um, uma mudança que quebre os fins de linha não passa da etapa de pull request

    • O objetivo deste texto parece ser não apenas tratar do jj no Gradle, mas mostrar que esse problema afeta qualquer pessoa e projeto que queira usar jj no ecossistema JVM
      A resposta “é só adicionar CI” não é suficiente
      O projeto em questão pode ser um mod de Minecraft feito por iniciantes, e qualquer um deles pode quebrar o gradle.bat sem perceber
      Eu também tenho responsabilidade por ter promovido o jj como alternativa, dizendo que ele é muito melhor que o Git via CLI para iniciantes
  • Seria viável migrar de gradlew.bat para gradlew.ps1, ou seja, sair de cmd.exe/arquivos em lote e ir para PowerShell?

    • Não trabalhei no Gradle, mas tentei fazer isso no Leiningen alguns anos atrás, e na época foi um desastre completo, nada prático
      Talvez agora seja possível assumir que 90% da base instalada do Windows já tenha uma versão suficientemente nova do PowerShell
      Mas ainda pode ser necessário elevar permissões de execução por padrão, e em ambientes corporativos isso pode continuar bloqueado
      Não sei o quanto você já usou PowerShell, mas não era algo como “tem bash, então é só executar o arquivo .sh
      Cada versão do Windows tinha um conjunto diferente de recursos, então a sensação era mais parecida com comparar ksh e bash4