- O formato Unified Diff tradicional tem limitações por não refletir plenamente as necessidades dos ambientes de desenvolvimento
- DiffX é totalmente compatível com o formato existente e oferece uma estrutura pensada para o futuro, com extensibilidade de metadados
- Permite armazenar de forma estruturada várias informações de commits, arquivos binários, codificação de caracteres e metadados
- Com a adoção de regras de parsing padronizadas, diferentes ferramentas (patch, revisão de código etc.) podem se integrar com facilidade
- Pode ser usado sem problemas com ferramentas e fluxos de trabalho existentes, e apenas os novos recursos exigem suporte específico da ferramenta
Desenvolvedores e arquivos Diff
- Desenvolvedores de software normalmente verificam alterações de código por meio de arquivos diff em Git, Subversion, CVS e outros
- Arquivos diff têm uma estrutura que inclui inserções (
+) e remoções (-) de texto, além de nome de arquivo, caminho, timestamp e alguns metadados
- A maioria das ferramentas e dos usuários usa o formato Unified Diff, que visualiza as diferenças de forma relativamente simples
Limitações do Unified Diff
- O Unified Diff padroniza apenas a identificação de arquivos, o escopo das alterações e as linhas inseridas/removidas, mas não padroniza codificação, revisão e metadados estendidos
- Isso dificulta o suporte a diversos sistemas de controle de código-fonte, o parsing confiável e a extração de informações mais ricas
- Os seguintes problemas continuam ocorrendo:
- Não é possível representar vários commits de uma só vez
- Falta um formato padrão dedicado para arquivos binários
- Sem saber a codificação de caracteres, ocorrem perda de informação e confusão
- A falta de padronização para metadados arbitrários faz com que cada ferramenta use um formato diferente
Direção da melhoria
- O Unified Diff existente carece de estrutura e padronização, mas já está amplamente difundido por ser flexível e funcionar em diversos ambientes
- O Git Diff vem atuando como padrão de fato, mas ainda faltam uma especificação oficial do formato e extensibilidade genérica
- Cresce a necessidade de um novo formato que preserve as vantagens do Unified Diff, acrescentando extensibilidade e estrutura padronizada
O que é DiffX
- DiffX é um formato Diff extensível, totalmente compatível com as ferramentas existentes, mantendo a legibilidade humana e ao mesmo tempo permitindo armazenar metadados e organização de forma estruturada
- Exemplo de sintaxe:
- Metadados e conteúdo de arquivos, commits e do diff inteiro são armazenados de forma estruturada e extensível
- A saída de exemplo inclui sintaxes como
#diffx:, além de section, metadados (JSON), caminhos de arquivo e informações de commit
Principais características do DiffX
- Fornece regras de parsing padronizadas, permitindo que ferramentas leiam e gravem informações com confiabilidade
- Formaliza o armazenamento e gerenciamento de metadados: pode ser usado no diff inteiro, em commits e por arquivo
- É compatível com todas as ferramentas existentes, como parsers, patchers e revisão de código (novos recursos exigem suporte, mas os recursos existentes mantêm a compatibilidade)
- Em um único arquivo, pode representar de forma eficiente vários conteúdos, como múltiplos commits, diff binário e informações de codificação de texto
- Oferece suporte à mutabilidade, permitindo que ferramentas abram o diff, registrem e modifiquem as informações necessárias e depois salvem novamente
Objetivos e não objetivos do DiffX
- Não força todas as ferramentas a suportarem o formato nem cria problemas de compatibilidade
- Não gera dependência de fornecedor nem quebra os fluxos de trabalho existentes
- Resolve problemas dos arquivos Diff existentes e oferece uma experiência de uso consistente e confiável em ferramentas de desenvolvimento, revisão e análise
1 comentários
Comentários no Hacker News
Eu não gosto de formatos hierarquicamente complexos como
..metae...meta. Para deixar a expressão mais clara, acho que seria mais fácil entender se houvesse apenas três níveis com nomes diferentes: o diff inteiro, o arquivo e o chunk. Mesmo sem blocos de meta, daria para distinguir o alvo de relance, reduzindo enganos e erros. Também parece pouco racional que os metadados do diff inteiro e os metadados por arquivo usem os mesmos campos. E não entendo por que são necessários dois formatos, JSON ekey=value; se há pouca coisa para administrar, usar só um formato ajuda muito mais na implementação e na integração com ferramentas existentes (grep,sedoujq, bastando um deles). Além disso, seria bom permitir trailing comma em listas, e tenho curiosidade sobre como esse formato afeta a capacidade de aplicar o diff em partes (por exemplo, para aplicar apenas uma parte do diff, parece incômodo ter de copiar o preâmbulo e também copiar os blocos separadamente). Também fico em dúvida serevisioné um atributo do arquivo ou um checksum de commit.#<section_level><section_type>por simplicidade do ponto de vista do parser. Em cada metablock, basta verificar verticalmente o nível, e ao contar o número de pontos já dá para distinguir naturalmente de que nível é aquele meta. O formato de cabeçalho de chave/valor foi pensado para conter apenas propriedades simples já conhecidas pelo parser, enquanto os metadados livres foram projetados para ficar em um blocometaseparado. Além do JSON atual, também é possível explicitar no cabeçalho o formato para manter extensibilidade caso, no futuro, seja necessário outro método de serialização. Foi uma tentativa de equilibrar simplicidade e flexibilidade. Pessoalmente eu gostaria de permitir trailing comma, mas é difícil exigir obrigatoriamente um parser JSON5 por causa da compatibilidade com JSON no nível base. O diff continua podendo ser dividido, e como as informações foram colocadas em áreas que o Unified Diff ignora, ferramentas como o GNUpatchtambém as ignoram sem problemas. No entanto, se você dividir em dois arquivos DiffX, talvez precise adicionar novamente o cabeçalho, o que pode deixar um pouco mais complexo. Em alguns SCMs, mesmo ao dividir o diff, parte dos metadados pode se perder (por exemplo, informações do commit pai), ou pode haver perda de informação dependendo do alvo da aplicação.revisionvaria conforme o SCM: pode ser ID de commit, ID por arquivo, uma combinação dos dois, informações adicionais etc. A estrutura foi pensada para atender às diferentes necessidades de vários SCMs.Na minha visão, entre as quatro observações abaixo, a única realmente razoável como generalização de arquivo diff é a notação de patch binário. O resto são problemas de dados internos ou de protocolo de sistemas específicos de controle de versão (SCM), que só fazem sentido dentro dos respectivos clientes, servidores e sistemas de backup. Todo o resto me parece desnecessário.
Não é possível listar vários commits em um único diff
Não há padrão para patch binário
Não é possível reconhecer codificação de texto (e isso é um problema mais frequente do que parece)
Não existe um formato padrão para metadados arbitrários
Nós desenvolvemos por 20 anos um produto de code review que integra mais de 12 SCMs, e ao longo disso enfrentamos incontáveis formatos de diff e problemas específicos de cada SCM que ninguém imaginaria. De fato, não são problemas com que o usuário final precise se preocupar diretamente, mas para quem desenvolve ferramentas são pain points que precisam ser resolvidos. Alguns SCMs nem têm seu próprio formato de diff, ou trazem informação incompleta (por exemplo, não conseguem indicar arquivos removidos), o que muitas vezes impede outras ferramentas de identificar os arquivos corretamente. Por isso sentimos necessidade desse tipo de melhoria.
Hoje é menos comum, mas eu também ainda uso de vez em quando ferramentas parecidas com
patch(1). Quando desenvolvedores de várias plataformas colaboram, problemas com maiúsculas/minúsculas em nomes de arquivo, codificação de caracteres etc. ainda aparecem com frequência.Se o JSON for embutido com informação de comprimento em um formato self-delimitered, basta mudar um espaço em branco dentro do JSON para que ele continue válido, mas o DiffX inteiro corra o risco de quebrar. A combinação parece estruturalmente desajeitada e bagunçada (mistura de cabeçalhos próprios com payload JSON, impossibilidade de diferenciar outros blocos
metasem contar pontos, necessidade de dois parsers etc.). A ideia de padronizar um diff extensível para metadados é boa, mas esta implementação parece tentativa e erro.Acho que o formato patch já resolve todos esses problemas. Link da documentação do git format-patch
Hoje foi a primeira vez que descobri que esse formato existia, vou dar uma olhada (sou só um usuário comum da internet, não o autor).
No git isso pode até estar resolvido, mas em um produto como o Review Board é preciso integrar vários VCS, como SVN, CVS e Perforce, então parece daí que surgiu esse formato. Eu também já usei Review Board com SVN, e quando vários desenvolvedores misturavam
git-svnesvn, frequentemente havia problemas ao enviar diffs para review. Se existisse um formato diff padrão compatível com ambos, isso teria ajudado muito no uso da ferramenta.Eu sinceramente não consigo perceber bem que esses problemas existam de fato (tirando arquivos binários).
Mesmo que a codificação seja diferente, o algoritmo de patch é o mesmo, então não há motivo para se preocupar (nem é necessário que os caracteres sejam utf-8 válido).
Também não vejo por que alguém iria querer colocar vários commits em um único diff; dividir em vários diffs é mais intuitivo.
Acho que metadados só fazem sentido dentro do próprio sistema.
Sobre codificação, os dados de patch de qualquer forma precisam ser tratados como dados binários baseados em ascii. Como também pode haver edição de arquivos com codificação mista, fixar uma codificação não parece ter muito valor.
Eu realmente não acho que isso seja um problema; seria melhor ouvir a experiência prática de quem de fato usa muito diff. Não parece uma boa ideia overengineering em um formato que já funciona bem.
Acho que o problema de dados binários realmente existe.
Normalmente você só encontra esses problemas de verdade quando precisa criar ferramentas por conta própria ou fazer interface com um SCM específico.
patchquebrar por causa de BOM.O documento inteiro parece difícil de ler. Para mim, “diff” significa a diferença entre dois itens (arquivos, diretórios etc.), mas o diff de que a TFA fala é o que eu chamaria de “patch”. O que está sendo discutido aqui não é diff, e sim gerenciamento de metadados de patch. Se os metadados fossem padronizados como um formato obrigatório, como JSON, tudo bem; mas essa estrutura ambígua, autoexplicativa e delimitada por comprimento parece mais esconder o problema do que resolvê-lo. A padronização em si é boa, mas sinto que é preciso uma solução mais clara e melhor organizada. Também é curioso pensar que, no fim, o estilo
git diffestá mais próximo de um padrão de fato.Tenho curiosidade sobre que problema esse formato está tentando resolver. Dizem que os formatos de patch/diff não são bons o bastante, mas não fica claro para quem essa melhoria é destinada, se houve aumento de reclamações na comunidade do GNU Patch ou algo assim. Precisaria ficar mais específico. Continuo com a dúvida sobre por que exatamente seria necessário um formato de patch melhor.
Tenho um texto separado, longo demais para caber aqui, mas o resumo é: são preocupações voltadas a quem cria SCMs diretamente ou desenvolve ferramentas que se integram com SCMs. Usuários comuns não precisam se preocupar com isso. Os formatos diff também variam totalmente de um SCM para outro; alguns são muito bem feitos, outros são gravemente limitados ou nem têm formato algum. Para um produto como o Review Board, que precisa cobrir vários SCMs, um padrão de integração desse tipo é realmente necessário na prática. Foi uma tentativa de melhoria que incorporou feedback de colaboração com fornecedores de SCM.
Parece ser um formato usado principalmente pelo Review Board. Como o produto suporta vários VCS e o diff é peça central no code review, faz sentido que tenham adotado isso.
A forma mais geral e clara de representar um diff é simplesmente incluir os dois arquivos. Hoje em dia o volume de dados não é um problema, então em vez de
diff a b | patch c, poderia ser algo comoapply a b c, e a representação interna tanto faz. Diff é difícil para humanos lerem, e uma visualização colorida lado a lado é muito melhor, então o mais intuitivo seria já receber os dois arquivos e processar assim. Não vejo necessidade de transmitir um diff não padronizado.Não existe apenas um diff possível entre dois arquivos; podem surgir várias versões de diff para objetivos diferentes. Se houver um formato diff, dá para separar a lógica de geração da lógica de aplicação do diff, reduzindo um problema n*m para um problema n+m.
Em atualizações de programas, seria irritante ter de baixar de novo os 130 GB completos toda vez, mas arquivos quase idênticos também são fáceis de comprimir, então enviar apenas a diferença entre duas versões continua tendo vantagens práticas. Talvez até seja possível algo mais eficiente, como mandar só o hash do arquivo original e transmitir o corpo comprimido do arquivo.
Transmitir e administrar pares de arquivos sem um contêiner dedicado é difícil, e se você precisar trocar por e-mail várias mudanças (como 10 arquivos modificados + exclusões + adições), isso parece até um retrocesso para a era pré-VCS, virando uma estrutura complicada com
tar/zipe afins.Mesmo que enviar o arquivo completo seja mais intuitivo do que enviar um diff, na prática o diff continua sendo muito importante dependendo do uso e do ambiente. Hoje, por exemplo, ao gerar resultados com LLM para edição de código, pedir em formato diff economiza muitos tokens e pode reduzir a latência da resposta em 5 a 10 vezes, trazendo um ganho enorme de eficiência. Enviar os dois arquivos inteiros desperdiça tokens e aumenta o custo. Para aplicar rapidamente em um sandbox de código ou em uma máquina remota, o diff oferece uma grande vantagem de otimização. Se você tem o arquivo A, o arquivo A2 e o diff A×A2, fica fácil reconstruir A2 e também otimizar armazenamento. Se houver conflito no merge, você só intervém manualmente nesses casos. Resumindo: diff é excelente.
Ainda me incomoda o quanto as ferramentas de diff dependem de quebras de linha. Quando uma linha fica longa demais (por exemplo, JSON, arrays longos etc.), a revisão fica difícil.
Também concordo. Acho que ainda há espaço para explorar formas melhores de representar diff em dados estruturados (por exemplo, AST diff). Este formato (DiffX) se concentra em ser uma extensão do formato Unified Diff existente, mas foi projetado para que, se formatos mais específicos como AST se tornarem amplamente usados, também possam ser embutidos e suportados com facilidade.
A forma mais comum é um meio-termo estranho entre legibilidade humana e facilidade de parsing por ferramentas. Desta vez parece que tentaram resolver parte do problema com extensão de metadados, mas uma solução realmente boa talvez fosse definir um novo formato que não fosse texto puro, mas ainda assim fácil de ler e de parsear. Criar algoritmos de diff melhores para linhas longas ou dados estruturados é uma tarefa difícil, mas acho perfeitamente possível.
O git também oferece
word diff, mais detalhado do queline diff, e o separador padrão é espaço em branco.Tenho dúvidas sobre o fato de JSON ser o único formato de metadados suportado. Para um padrão de metadados projetado para uso geral, JSON me parece complexo demais.