Git attributes: controlando line endings, diff drivers e export-ignore

1. Introdução ao .gitattributes e sua sintaxe

O arquivo .gitattributes é um mecanismo de configuração do Git que permite definir como arquivos específicos devem ser tratados durante operações como commit, checkout, merge e diff. Diferente do .gitignore, que apenas exclui arquivos do versionamento, o .gitattributes controla o comportamento do Git em relação ao conteúdo dos arquivos.

Por padrão, o arquivo deve ser colocado na raiz do repositório, mas também pode ser definido em subdiretórios para sobrescrever configurações anteriores. A sintaxe básica segue o padrão:

*.txt text
*.jpg binary
docs/** linguist-detectable=false

Cada linha contém um padrão de nome de arquivo seguido por um ou mais atributos. Os atributos podem ser booleanos (presentes ou ausentes), com valor específico (ex.: text=auto) ou com valor vazio para desativar o atributo.

A diferença fundamental entre .gitattributes e configurações locais ou globais (git config) é que o arquivo versionado é compartilhado com toda a equipe, garantindo consistência independentemente das configurações individuais de cada desenvolvedor.

2. Controlando line endings com text e eol

Um dos problemas mais comuns em equipes com sistemas operacionais mistos é a inconsistência no final de linha (line endings). O Git oferece três atributos principais para controlar esse comportamento:

  • text: Define se o arquivo deve ser tratado como texto. Quando ativado, o Git converte automaticamente os line endings durante checkout e commit. Valores possíveis: text, -text (desativa), text=auto (deixa o Git decidir).

  • eol: Força um tipo específico de line ending. Valores: lf (Unix/macOS), crlf (Windows).

Exemplo prático de configuração:

# Força LF para todos os arquivos de texto
* text=auto eol=lf

# Arquivos específicos com CRLF (Windows)
*.bat text eol=crlf
*.cmd text eol=crlf

# Arquivos binários que não devem ser convertidos
*.png -text
*.jpg -text
*.pdf -text

A melhor prática para equipes mistas é usar * text=auto na primeira linha. Isso instrui o Git a detectar automaticamente arquivos de texto e converter seus line endings para LF ao fazer commit, mantendo o line ending do sistema local durante o checkout.

# Configuração recomendada para equipes mistas
* text=auto
*.sh text eol=lf
*.bat text eol=crlf

3. Customizando diff drivers para arquivos especializados

O atributo diff permite associar arquivos a drivers de diff personalizados. Isso é especialmente útil para arquivos que não são texto puro, como documentos do Microsoft Word, PDFs ou arquivos de serialização.

Para configurar um driver de diff, primeiro defina o atributo no .gitattributes:

*.md diff=markdown
*.pdf diff=pdf
*.docx diff=word

Em seguida, configure o driver no Git (cada desenvolvedor precisa fazer isso localmente ou via configuração global):

git config --global diff.markdown.textconv "pandoc -t plain"
git config --global diff.pdf.textconv "pdftotext -layout -nopgbrk -"
git config --global diff.word.textconv "pandoc -t plain"

Exemplo prático: comparar versões de um arquivo PDF usando pdftotext:

# Configuração do driver
git config --global diff.pdf.textconv "pdftotext -layout -nopgbrk -"

# No .gitattributes
*.pdf diff=pdf

Agora, ao executar git diff, o Git converterá automaticamente o PDF para texto antes de comparar, mostrando mudanças significativas no conteúdo.

4. Ignorando arquivos em exportações com export-ignore

O atributo export-ignore é usado exclusivamente com o comando git archive. Ele remove arquivos ou diretórios do arquivo gerado, sendo útil para excluir conteúdo que não deve fazer parte de distribuições.

Casos comuns de uso:

# Excluir diretórios de desenvolvimento
.gitignore export-ignore
.github/ export-ignore
tests/ export-ignore
docs/ export-ignore

# Excluir arquivos específicos
Makefile export-ignore
docker-compose.yml export-ignore
.editorconfig export-ignore

Importante: export-ignore não afeta o repositório normal. Os arquivos continuam versionados e aparecem em checkouts comuns. A diferença crucial para .gitignore é que este último impede que arquivos sejam versionados, enquanto export-ignore apenas os exclui de arquivos gerados por git archive.

Exemplo de geração de arquivo sem os diretórios ignorados:

git archive --format=zip --output=projeto-v1.0.zip HEAD

5. Outros atributos essenciais: merge, binary e working-tree-encoding

Atributo merge

Define drivers de merge customizados para resolver conflitos em arquivos específicos:

*.json merge=union
*.lock merge=ours

Atributo binary

Atalho que combina -diff -merge -text, indicando que o arquivo não deve ser tratado como texto:

*.zip binary
*.exe binary
*.dll binary

Atributo working-tree-encoding

Permite especificar codificação de caracteres diferente de UTF-8:

*.csv working-tree-encoding=ISO-8859-1
*.txt working-tree-encoding=Shift_JIS

O Git converte automaticamente para UTF-8 ao fazer commit e para a codificação especificada ao fazer checkout, facilitando o trabalho com arquivos legados.

6. Depuração e verificação de atributos

Para verificar quais atributos se aplicam a um arquivo específico, use git check-attr:

git check-attr -a src/main.js
# src/main.js: text: auto
# src/main.js: diff: java
# src/main.js: export-ignore: unspecified

Para verificar atributos específicos:

git check-attr text eol export-ignore -- config.json
# config.json: text: set
# config.json: eol: lf
# config.json: export-ignore: unspecified

git check-ignore verifica se um arquivo seria ignorado pelo .gitignore:

git check-ignore -v node_modules/package.json
# .gitignore:2:node_modules/   node_modules/package.json

7. Boas práticas e armadilhas comuns

Boas práticas:

  1. Versionar .gitattributes: Sempre inclua o arquivo no repositório para garantir consistência entre todos os membros da equipe.

  2. Usar text=auto como padrão: Isso evita a maioria dos problemas com line endings.

  3. Especificar binários explicitamente: Use binary para arquivos que nunca devem ser tratados como texto.

  4. Testar antes de aplicar: Execute git check-attr em arquivos suspeitos antes de fazer commit.

Armadilhas comuns:

  1. Misturar text=auto com eol explícito: Quando text=auto está ativo, definir eol=lf pode causar comportamento imprevisível. Prefira usar um ou outro.

  2. Esquecer binary para binários: Arquivos binários sem o atributo -text podem ser corrompidos pelo Git ao tentar converter line endings.

  3. Sobrescrita em subdiretórios: Atributos definidos em subdiretórios sobrescrevem completamente os definidos na raiz, não fazendo merge.

  4. export-ignore não afeta git clone: Lembre-se que este atributo só funciona com git archive.

Exemplo de configuração robusta para projetos modernos:

# Configuração base
* text=auto

# Documentação
*.md text diff=markdown
*.rst text

# Scripts
*.sh text eol=lf
*.bat text eol=crlf

# Binários
*.png binary
*.jpg binary
*.ico binary
*.woff2 binary

# Exportação
.gitattributes export-ignore
.github/ export-ignore
tests/ export-ignore

Referências