Por dentro do
motor do Git.
Chegamos ao nível mais profundo de Pro Git: levante servidores bare blindados com git-shell, automatize políticas inquebráveis com Server-Side Hooks, construa commits manualmente com comandos plumbing e opere objetos, packfiles e resgates na raiz do diretório .git.
Trilha de formação: Domínio do Git do básico à arquitetura interna
Você está na Etapa 4 (último curso da formação), onde conectamos a administração de servidores, customização avançada, migração e a engenharia interna do banco de objetos do Git.
Percurso da Etapa 4 (Capítulos 4, 8, 9 e 10)
Este curso revela como o Git funciona como um sistema de arquivos endereçável por conteúdo. Todos os laboratórios podem ser executados localmente no seu terminal.
Compare Local, Smart HTTP, SSH e Git, crie repositórios bare e blinde o acesso com git-shell.
OFICINA 02Configuração de Cliente e Servidor ↗Domine core.autocrlf, core.whitespace e proteja o servidor com fsckObjects e denyNonFastForwards.
OFICINA 03Git Attributes e Filtros ↗Compare arquivos .docx e imagens no git diff, crie filtros smudge/clean e controle o git archive.
OFICINA 04Git Hooks e Políticas ↗Implemente hooks de cliente (pre-commit, commit-msg) e políticas no servidor (pre-receive e update).
OFICINA 05Interoperabilidade e Migração ↗Use git svn / git p4, conduza migrações preservando autores com fast-import e simule ganhos de infraestrutura.
OFICINA 06Plumbing e Objetos Internos ↗Construa blobs, trees e commits na mão com hash-object, update-index, write-tree e commit-tree.
OFICINA 07Referências, Packfiles e Refspec ↗Manipule refs com symbolic-ref, entenda a compressão delta reversa dos packfiles e domine o Refspec.
OFICINA 08Fsck, Expurgo de Blobs e Trace ↗Resgate commits sem reflog via git fsck, expurgue arquivos gigantes do packfile e depure com GIT_TRACE.
“Na sua essência, o Git é um sistema de arquivos endereçável por conteúdo com uma interface de controle de versão construída por cima.”
No Capítulo 10 de Pro Git, Scott Chacon e Ben Straub mostram que toda a mágica do Git repousa sobre apenas quatro tipos de objetos imutáveis (blob, tree, commit e tag) armazenados por chave-valor em.git/objects.Escolha o protocolo certo e levante um servidor Git seguro com git-shell
Uma equipe decide hospedar seu próprio servidor Git via SSH em uma máquina Linux interna, mas percebe que qualquer desenvolvedor com chave cadastrada consegue abrir um terminal interativo no servidor e navegar pelo sistema operacional. Como permitir git push e git pull bloqueando o login de shell?
Os quatro protocolos de transporte e o Repositório Bare
No Capítulo 4 (Seção 4.1), Chacon e Straub comparam os quatro protocolos que o Git usa para conversar com um servidor: o Protocolo Local (quando o remoto está em uma pasta ou montagem NFS como `/srv/git/projeto.git`), o HTTP (dividido entre o moderno Smart HTTP, que autentica e negocia pacotes de forma inteligente na mesma URL `https://`, e o Dumb HTTP, que expõe o repositório como arquivos estáticos somente leitura exigindo o hook `post-update` com `git update-server-info`), o SSH (autenticado, criptografado e ubiquamente disponível) e o Protocolo Git (`git://` na porta 9418) (o mais veloz por não ter criptografia nem autenticação, usado para leitura pública quando existe o arquivo `git-daemon-export-ok`).
Para hospedar um repositório no servidor que receberá `git push` de várias pessoas, ele nunca deve ter uma Working Tree (caso contrário, um push para a branch ativa deixaria a cópia de trabalho do servidor defasada em relação ao índice). Por isso, todo repositório de servidor é um Bare Repository (Repositório Puro), criado com git init --bare --shared ou clonado a partir de um projeto existente com git clone --bare meu_projeto meu_projeto.git (que copia apenas o conteúdo interno da pasta `.git`).
| Protocolo | Exemplo de URL | Pontos fortes | Limitações principais |
|---|---|---|---|
| Local | /srv/git/proj.git ou file:///srv/git/proj.git | Simples para equipes que já compartilham sistema de arquivos; sem `file://` usa hardlinks rápidos. | Exige montar o disco na rede; sem `file://` não limpa objetos quebrados na cópia. |
| Smart HTTP | https://git.empresa.com/proj.git | Uma única URL para leitura pública e escrita autenticada; atravessa qualquer firewall corporativo (porta 443). | Exige configurar servidor web (Apache/Nginx + `git-http-backend`). |
| SSH | git@servidor:/srv/git/proj.git | Fácil de levantar, criptografado e com compressão de dados nativa. | Não permite acesso anônimo direto sem criar chaves ou contas. |
| Git (9418) | git://servidor/proj.git | Maior velocidade de transferência pura sem custo de CPU de criptografia. | Zero autenticação nativa; exige porta `9418` aberta. |
Blindando a conta git no servidor com git-shell e authorized_keys
Na Seção 4.4, ao configurar o acesso SSH para a equipe criando um usuário `git` no servidor e adicionando as chaves públicas (`~/.ssh/id_rsa.pub` ou `id_ed25519.pub`) ao arquivo `/home/git/.ssh/authorized_keys`, você deve aplicar duas travas de segurança imediatamente: primeiro, trocar o shell de login do usuário `git` para o utilitário restrito git-shell (que aceita apenas conexões de `git push`/`git fetch` e rejeita login interativo); segundo, prefixar cada chave no `authorized_keys` bloqueando redirecionamento de portas e terminal:
# 1. Transforme um repositório normal em um repositório BARE (.git puro) de servidor: mkdir -p ~/lab-git-etapa4 && cd ~/lab-git-etapa4 git init -b main projeto-origem cd projeto-origem && echo "codigo v1" > main.py && git add main.py && git commit -m "Commit inicial" && cd .. git clone --bare projeto-origem servidor-bare.git ls -F1 servidor-bare.git # 2. Comandos de administração para restringir o usuário "git" no servidor Linux (Cap. 4.4): # which git-shell # sudo chsh git -s $(which git-shell) # 3. Formato blindado para cada linha do arquivo ~/.ssh/authorized_keys no servidor: # no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA... dev@empresa
Qual a diferença entre clonar /srv/git/proj.git vs. file:///srv/git/proj.git?
Como detalha a Seção 4.1, se você especificar apenas o caminho do diretório (`git clone /srv/git/proj.git`), o Git usa links físicos (*hardlinks*) ou copia os arquivos diretamente no sistema operacional — o que é extremamente rápido. Já se você adicionar **`file://`**, o Git dispara os processos normais de transferência de rede (`upload-pack`), o que é um pouco mais lento, mas excelente quando você quer gerar uma cópia limpa deixando para trás objetos órfãos ou referências externas!
Entrega da etapa
Crie um repositório `--bare` local conforme o comando acima, inspecione os arquivos na raiz dele (notando a ausência de Working Tree), adicione-o como remoto de um segundo clone e documente como sua equipe protege chaves SSH com `git-shell` e restrições de `authorized_keys`.
Conexão com o livro · Capítulo 4 (Seções 4.1 a 4.10) · protocolos Local, Smart/Dumb HTTP, SSH e Git (9418), git clone --bare, init --bare --shared, git-shell, authorized_keys e GitLab.
Padronize quebras de linha e blinde o servidor contra pushes destrutivos
Um desenvolvedor no Windows abre um arquivo criado no Linux e, sem alterar nenhuma linha de código, o git diff acusa que o arquivo inteiro foi apagado e reescrito por causa do retorno de carro (CRLF). Além disso, alguém roda git push --force na main e apaga commits do servidor. Como resolver os dois problemas via configuração?
Configurações de Cliente: templates, excludesfile, autocrlf e whitespace
Na Seção 8.1, Chacon e Straub exploram as chaves de configuração que elevam o padrão técnico de uma estação de trabalho:
- Template de mensagem e exclusão global: aponte
commit.templatepara um arquivo `~/.gitmessage.txt` (para abrir todo `git commit` já com a estrutura de resumo, motivação e número de ticket pronta para preencher) e apontecore.excludesfilepara `~/.gitignore_global` (para ignorar arquivos do seu sistema operacional e editor, como `.DS_Store`, `*~` e `.swp`, em todos os projetos da máquina sem sujar o `.gitignore` compartilhado da equipe). - Fim da guerra entre CRLF (Windows) e LF (Linux/macOS): configure
core.autocrlfde acordo com o sistema operacional: no Windows, usegit config --global core.autocrlf true(converte LF para CRLF ao colocar arquivos no disco e converte de volta para LF ao gravar no Git); no Linux e macOS, usegit config --global core.autocrlf input(não converte nada no checkout, mas limpa qualquer CRLF acidental transformando em LF na hora do commit!). - Detecção automática de problemas de espaçamento (
core.whitespace): o Git vem com quatro regras ativadas por padrão (`blank-at-eol`, `blank-at-eof`, `space-before-tab` e `cr-at-eol`) e duas opcionais que você pode ligar comindent-with-non-tabetab-in-indent— permitindo até corrigir automaticamente patches recebidos rodandogit apply --whitespace=fix arquivo.patch.
As três travas de integridade no Servidor Git
Ainda na Seção 8.1, três chaves configuradas no arquivo `config` do repositório `--bare` no servidor protegem toda a equipe contra corrupção de objetos e reescrita forçada:
| Chave no Servidor | Valor recomendado | Proteção que ativa imediatamente |
|---|---|---|
receive.fsckObjects | true | Obriga o servidor a verificar a integridade SHA-1 e a validade do grafo de todos os objetos em cada git push antes de aceitá-los. |
receive.denyNonFastForwards | true | Bloqueia git push --force em commits que já estão no servidor, exigindo que toda atualização seja um avanço limpo (fast-forward). |
receive.denyDeletes | true | Proíbe apagar branches e tags remotas via push (evitando que alguém contorne o denyNonFastForwards apagando e recriando a branch!). |
# 1. Configure o tratamento correto de quebras de linha no Linux/macOS (input) e ignore arquivos globais: git config --global core.autocrlf input # No Windows, use: git config --global core.autocrlf true # 2. Ative as três travas de proteção diretamente no nosso servidor bare de laboratório: cd ~/lab-git-etapa4/servidor-bare.git git config --local receive.fsckObjects true git config --local receive.denyNonFastForwards true git config --local receive.denyDeletes true # 3. Confira as configurações gravadas no servidor: git config --local --list
Cuidado com help.autocorrect em scripts ou digitação rápida
A chave help.autocorrect aceita um número inteiro representando décimos de segundo. Se você digitar `git chekcout main` com `help.autocorrect = 0` (padrão), o Git apenas sugere o comando correto e para. Se você definir `help.autocorrect = 20`, ele avisa do erro, espera 2,0 segundos para dar tempo de cancelar com `Ctrl+C` e executa automaticamente o comando mais parecido!
Entrega da etapa
Aplique `receive.fsckObjects`, `receive.denyNonFastForwards` e `receive.denyDeletes` no seu repositório `servidor-bare.git` e tente fazer um `git push --force` ou `git push origin --delete` a partir do repositório cliente para ver o servidor bloqueando a operação Destrutiva em tempo real.
Conexão com o livro · Capítulo 8 (Seção 8.1) · commit.template, core.pager, core.excludesfile, help.autocorrect, color.*, extMerge/extDiff, core.autocrlf, core.whitespace e receive.fsckObjects/denyNonFastForwards/denyDeletes.
Domine o .gitattributes: diff de arquivos binários, filtros smudge/clean e exportação
Quando alguém altera um documento Word (.docx) ou a resolução de uma imagem .png no projeto, o git diff padrão diz apenas Binary files differ. Como ensinar o Git a mostrar exatamente quais palavras do .docx ou quais metadados da imagem mudaram?
Transformando binários em diffs legíveis com textconv
Na Seção 8.2, Chacon e Straub mostram o poder do arquivo .gitattributes. Diferente do `.git/config`, o `.gitattributes` é versionado na raiz do projeto para que toda a equipe compartilhe as mesmas regras por tipo de arquivo. Você pode declarar explicitamente que um arquivo gerado não deve sofrer conversão de linha nem diff com *.pbxproj binary, ou fazer muito melhor: associar um filtro diff com textconv para arquivos binários estruturados!
Quando você coloca *.docx diff=word no `.gitattributes` e configura `git config diff.word.textconv docx2txt` (ou `*.png diff=exif` com `exiftool`), o Git continua guardando o arquivo binário original intacto nos commits, mas, toda vez que você roda git diff, ele passa as duas versões pelo conversor em memória e imprime o diff linha por linha do texto ou dos metadados!
Filtros de conteúdo (Smudge / Clean), export-ignore e merge=ours
O `.gitattributes` também habilita três recursos avançados de engenharia de releases:
Atributo no .gitattributes | Como funciona nos bastidores | Aplicação prática imediata |
|---|---|---|
*.c filter=indent | Executa o comando clean ao entrar na Staging Area (git add) e o comando smudge ao sair para a Working Tree (checkout). | Garantir que todo arquivo `.c` entre no repositório formatado pelo `indent` antes do commit. |
test/ export-ignore | Instrui o comando git archive a omitir aquele arquivo ou diretório ao gerar pacotes `.zip` / `.tar.gz`. | Entregar o pacote de produção sem a pasta de testes automatizados nem arquivos de CI. |
LAST_COMMIT export-subst | Substitui marcadores $Format:...$ dentro do arquivo usando a formatação do git log durante o git archive. | Gravar automaticamente o hash, a data e o autor da release dentro de um arquivo de versão no pacote exportado. |
database.xml merge=ours | Aciona a estratégia `ours` (`git config --global merge.ours.driver true`) apenas para aquele arquivo específico em caso de conflito. | Manter o arquivo de banco de dados da branch de produção intacto ao mesclar uma branch de homologação. |
# 1. Configure export-ignore e export-subst no .gitattributes para automatizar metadados de release: cd ~/lab-git-etapa4/projeto-origem mkdir -p test && echo "teste interno" > test/suite.py echo 'Versao empacotada do commit: $Format:%h por %aN em %cd$' > VERSAO.txt cat << 'EOF' > .gitattributes test/ export-ignore VERSAO.txt export-subst database.xml merge=ours EOF git add test/suite.py VERSAO.txt .gitattributes git commit -m "Configura atributos de exportacao e versao automatica" # 2. Exporte o pacote com git archive e veja como VERSAO.txt ganha o hash real e a pasta test/ some! mkdir -p ~/lab-git-etapa4/pacote-extraido git archive HEAD | tar -x -C ~/lab-git-etapa4/pacote-extraido ls -la ~/lab-git-etapa4/pacote-extraido cat ~/lab-git-etapa4/pacote-extraido/VERSAO.txt
Como funcionam exatamente as direções smudge e clean em um filtro?
Pense na roupa voltando para o armário: quando você faz git add para guardar o arquivo no Index do Git, roda o filtro clean (limpando o arquivo para o formato canônico do repositório). Quando você faz git checkout para trazer o arquivo para a bancada de trabalho (Working Tree), roda o filtro smudge (adaptando o arquivo para o ambiente local do desenvolvedor).
Entrega da etapa
Execute o laboratório acima na sua máquina e comprove que o arquivo `VERSAO.txt` extraído pelo `git archive` contém o hash curto e o seu nome preenchidos automaticamente, enquanto a pasta `test/` foi deixada de fora do pacote de distribuição.
Conexão com o livro · Capítulo 8 (Seção 8.2) · Git Attributes, arquivos binários, diff textconv (docx2txt, exiftool), ident ($Id$), filtros smudge/clean, export-ignore, export-subst e merge=ours.
Automatize regras com Git Hooks de Cliente e Políticas no Servidor
Você configurou um hook local `commit-msg` nas máquinas da equipe para exigir o número do chamado em todo commit, mas um desenvolvedor apressado rodou `git commit --no-verify` e subiu cinco commits fora do padrão. Como impor políticas que ninguém consegue burlar?
Hooks do Lado do Cliente vs. Hooks do Lado do Servidor
Nas Seções 8.3 e 8.4, aprendemos que a pasta .git/hooks/ contém scripts de exemplo terminados em `.sample`. Para ativar qualquer hook, basta criar um script executável (`chmod +x`) com o nome exato do evento (sem `.sample`): se o script sair com código `0`, o Git prossegue; se sair com qualquer código diferente de zero, o Git aborta a ação e exibe sua mensagem de erro.
Os Client-Side Hooks rodam na estação do desenvolvedor para acelerar o feedback: pre-commit (verifica lint, testes rápidos e espaços em branco antes mesmo de pedir a mensagem), prepare-commit-msg (injeta textos dinâmicos no template antes de abrir o editor), commit-msg (recebe o caminho do arquivo temporário da mensagem em `$1` e aborta o commit se ela não seguir a expressão regular da equipe), post-commit, `pre-rebase`, `post-checkout` e `pre-push`. Porém, como os hooks locais não são copiados no `git clone` e podem ser ignorados com `--no-verify`, a fiscalização definitiva ocorre nos três Server-Side Hooks dentro do repositório `--bare`:
| Hook no Servidor | Quando roda e o que recebe | Como atua se retornar erro (exit != 0) |
|---|---|---|
pre-receive | Roda primeiro no `push`; lê do `stdin` uma linha `oldrev newrev refname` para cada ref enviada. | Tudo ou nada: rejeita o `git push` inteiro se qualquer commit ou branch violar a política. |
update | Roda uma vez para cada branch enviada, recebendo `refname`, `oldrev` e `newrev` nos argumentos `$1`, `$2` e `$3`. | Seletivo por branch: rejeita apenas a branch que violou a regra (ex.: ACL de diretório) e aceita as demais branches do push. |
post-receive | Roda depois que todos os objetos e referências já foram gravados no servidor (mesmo `stdin` do `pre-receive`). | Não aborta o push; usado para disparar CI/CD, e-mails e integração com rastreadores de chamados. |
# 1. Crie um hook "commit-msg" local que exige "[ref: NUMERO]" na mensagem de todo commit (Cap. 8.4): cd ~/lab-git-etapa4/projeto-origem cat << 'EOF' > .git/hooks/commit-msg #!/bin/sh if ! grep -iqE "\[ref: [0-9]+\]" "$1"; then echo "[POLITICA GIT] Sua mensagem deve conter a referencia do chamado, ex: [ref: 132]" >&2 exit 1 fi EOF chmod +x .git/hooks/commit-msg # 2. Teste fazer um commit fora do padrão (será bloqueado!) e depois um commit válido: echo "nova linha" >> main.py git commit -a -m "Ajuste sem chamado" || true git commit -a -m "Ajuste validado pelo hook [ref: 132]" # 3. Como um hook pre-receive no servidor lista todos os commits novos enviados em um push: # git rev-list $oldrev..$newrev
Como o hook de servidor verifica os arquivos e a mensagem sem ter Working Tree?
Como o repositório no servidor é `--bare` (não tem arquivos abertos em pastas comuns), na Seção 8.4 os scripts de servidor usam comandos *plumbing* para inspecionar o push diretamente no banco de objetos: git rev-list $oldrev..$newrev lista todos os hashes SHA-1 que estão tentando entrar; git cat-file commit $sha | sed '1,/^$/d' extrai a mensagem de cada commit para validar a regex `[ref: NNN]`; e git log -1 --name-only --pretty=format:'' $sha lista quais arquivos foram alterados para bloquear caso um usuário tente modificar uma pasta restrita!
Entrega da etapa
Ative o hook `commit-msg` no seu laboratório local, confirme que ele bloqueia mensagens sem `[ref: NNN]` e desenhe a regra do seu hook `pre-receive` no servidor para garantir que nem mesmo um `git commit --no-verify` passe sem referência de chamado.
Conexão com o livro · Capítulo 8 (Seções 8.3 e 8.4) · Client-Side Hooks (pre-commit, prepare-commit-msg, commit-msg, pre-push), Server-Side Hooks (pre-receive, update, post-receive) e política ACL/formato.
Conduza migrações de outros sistemas e simule o ganho de infraestrutura Git
Da ponte bidirecional com sistemas legados à migração definitiva com fast-import
No Capítulo 9, Chacon e Straub resolvem o desafio de empresas que ainda possuem sistemas críticos em Subversion (SVN), Mercurial ou Perforce, mas querem dar à equipe toda a produtividade local do Git — ou migrar o acervo histórico inteiro de vez para um servidor Git moderno.
Regras de ouro ao operar como cliente (git svn / git p4) e ao migrar
Quando você usa o Git como cliente de um servidor Subversion via git svn clone <url> -s (onde `-s` reconhece o layout padrão `trunk`, `branches` e `tags`), você pode criar branches locais, usar `git add -p`, `stash` e `rebase -i` normalmente. Porém, ao sincronizar com o servidor SVN, duas regras da Seção 9.1 são vitais:
- Mantenha o histórico linear antes do
dcommit: usegit svn rebasepara buscar novidades do servidor SVN egit svn dcommitpara enviar seus commits locais (cada commit vira uma revisão no SVN e é reescrito localmente com o metadado `git-svn-id`). Nunca publique merges não-lineares do Git diretamente para o SVN, pois o modelo do SVN não registra múltiplos pais como o Git. - Mapeie os usuários antes de migrar em definitivo (Seção 9.2): sistemas como SVN gravam apenas o login de rede (ex.: `jsilva`), enquanto o Git exige `Nome Completo
`. Na migração definitiva (`git svn clone file:///... --authors-file=users.txt --no-metadata`), fornecer o arquivo `users.txt` converte cada revisão histórica em um commit Git limpo, e um script de pós-processamento transforma as referências `refs/remotes/origin/tags/*` em tags anotadas reais do Git. - Importação de alta velocidade com
git fast-import: para sistemas proprietários ou pastas de versões arquivadas, o comando plumbinggit fast-importlê um fluxo de texto simples na entrada padrão (`blob`, `mark :1`, `data`, `commit refs/heads/main`) e grava dezenas de milhares de commits diretamente em packfiles em poucos segundos.
# Demonstração prática do protocolo "git fast-import" (Cap. 9.2) criando um commit # completo a partir de um fluxo de texto puro em milissegundos, sem tocar na Working Tree: mkdir -p ~/lab-git-etapa4/importado && cd ~/lab-git-etapa4/importado git init -b main cat << 'EOF' | git fast-import blob mark :1 data 29 Sistema legado migrado v1.0 commit refs/heads/main mark :2 author Engenheiro Migracao1700000000 -0300 committer Engenheiro Migracao 1700000000 -0300 data 38 Importa revisao inicial via fast-import M 100644 :1 legado.txt done EOF git checkout main git log --stat -1
Meça o impacto operacional da governança de servidor e compressão Git
Henrique administra a plataforma de repositórios de uma indústria em Joinville que migrou 14 projetos de um servidor legado para um servidor Git centralizado com hooks `pre-receive` (bloqueando commits sem ticket e arquivos binários gigantes) e rotinas de compactação `git gc`. Cada projeto migrado economiza R$ 190 mensais em tempo de build, armazenamento e sincronização da equipe. O custo mensal de manutenção, backup e execução dos hooks por projeto é de R$ 40. O trabalho inicial de migração com mapeamento de autores e criação dos scripts de servidor custou R$ 900. Já cada incidente em que um repositório sem governança aceita um arquivo gigante ou push forçado indevido e exige intervenção manual do administrador custa R$ 110.
| Indicador mensal | Valor por projeto |
|---|---|
| Economia em tempo de clone, build e armazenamento (Packfiles) | R$ 190 |
| Custo de operação, backup e validação por repositório | R$ 40 |
| Margem operacional líquida por repositório | R$ 150 |
A economia líquida é de 190 − 40 = R$ 150 por repositório. Com 6 projetos migrados (6 × 150 = R$ 900), o investimento inicial de migração se paga integralmente. Com os 14 projetos migrados e apenas 1 incidente de limpeza de histórico no período (R$ 110), o saldo líquido fecha em 14 × 150 − 900 − 110 = R$ 1.090 positivos.
Sua decisão ao planejar a migração de um repositório legado
Se uma equipe pedir para migrar um repositório Subversion de oito anos para o Git hoje à tarde apenas copiando os arquivos da última versão (perdendo todo o histórico de quem escreveu cada linha), que argumento técnico das Etapas 3 e 4 você apresentaria para mostrar por que vale usar `git svn clone --authors-file`?
Conferir uma decisão possível
Copiar apenas a última fotografia destrói a capacidade de usar `git blame -C`, `git log -S` e `git log -L` para descobrir por que uma regra de negócio crítica foi escrita há dois anos. Usar `git svn clone` com `--authors-file` e `--no-metadata` preserva toda a inteligência forense da história em um pacote compactado (packfile) muitas vezes menor que o repositório SVN original.
Simule o retorno da migração e blindagem de repositórios
Os valores iniciais reproduzem o projeto de Henrique. Altere um parâmetro por vez para avaliar o ponto de equilíbrio da migração e o impacto de bloquear incidentes com Server-Side Hooks.
Saldo = repositórios × (economia − custo operacional) − investimento inicial − incidentes × custo por incidente. O ponto de equilíbrio exibido considera o investimento inicial antes dos incidentes.
Conexão com o livro · Capítulo 9 (Seções 9.1 e 9.2) · git svn (clone, rebase, dcommit), git-remote-hg, git p4, migração com authors-file e importador customizado com git fast-import.
Construa blobs, trees e commits manualmente com comandos Plumbing
Abra a caixa-preta: como o Git grava objetos por chave-valor em .git/objects
Chegamos ao coração do Capítulo 10 (Seções 10.1 e 10.2). Chacon e Straub explicam que os comandos que usamos no dia a dia (`add`, `commit`, `status`, `checkout`) são chamados de Porcelain (Porcelana) porque foram criados como uma camada amigável sobre os comandos de baixo nível chamados de Plumbing (Encanamento).
Por baixo do capô, o Git é um sistema de arquivos endereçável por conteúdo: um banco de dados simples de chave-valor onde você entrega qualquer conteúdo, o Git devolve uma chave única de 40 caracteres hexadecimais (SHA-1) e grava o objeto comprimido em .git/objects/ (usando os 2 primeiros caracteres do hash como nome da subpasta e os 38 caracteres restantes como nome do arquivo para evitar diretórios gigantescos no sistema operacional).
Construindo um commit completo na mão sem usar git add nem git commit
Existem três objetos fundamentais para registrar a história de um projeto, e você pode criar todos eles manualmente no terminal agora mesmo:
- Objeto
blob(o conteúdo puro do arquivo, sem nome!): um blob armazena exclusivamente os bytes do arquivo — ele não sabe o nome do arquivo nem suas permissões! Você grava um blob comgit hash-object -w <arquivo>e lê seu conteúdo comgit cat-file -p <hash>(ou descobre seu tipo com `git cat-file -t`). - Objeto
tree(o diretório que dá nome e permissão aos blobs): é a árvore que guarda o nome de cada arquivo (`test.txt`), o modo de permissão (`100644` para arquivo normal, `100755` para executável, `120000` para symlink e `040000` para outra sub-tree) e o ponteiro para o hash do blob correspondente. Você registra o blob no Index comgit update-index --add --cacheinfo 100644 <blob-hash> test.txte grava o objeto tree no banco comgit write-tree! - Objeto
commit(quem salvou, quando, por quê e qual era a tree): você passa o hash da tree paraecho 'Mensagem' | git commit-tree <tree-hash> [-p <commit-pai>]e o Git grava o objeto commit apontando para aquela árvore!
# Laboratório clássico da Seção 10.2: crie um repositório vazio e construa um commit # inteiro usando EXCLUSIVAMENTE comandos plumbing (sem git add e sem git commit!): mkdir -p ~/lab-git-etapa4/plumbing-puro && cd ~/lab-git-etapa4/plumbing-puro git init -b main # Passo 1: Grave um objeto BLOB diretamente no banco .git/objects e inspecione-o: BLOB_HASH=$(echo "versao 1 construida na mao" | git hash-object -w --stdin) echo "Hash do Blob: $BLOB_HASH" find .git/objects -type f git cat-file -t "$BLOB_HASH" git cat-file -p "$BLOB_HASH" # Passo 2: Adicione o blob ao Index com o nome "manual.txt" e grave o objeto TREE: git update-index --add --cacheinfo 100644 "$BLOB_HASH" manual.txt TREE_HASH=$(git write-tree) echo "Hash da Tree: $TREE_HASH" git cat-file -p "$TREE_HASH" # Passo 3: Grave o objeto COMMIT apontando para a Tree e aponte a branch main para ele: COMMIT_HASH=$(echo "Primeiro commit via plumbing" | git commit-tree "$TREE_HASH") git update-ref refs/heads/main "$COMMIT_HASH" # Comprove que o git log normal enxerga o commit que você montou peça por peça! git log --stat -1
A fórmula exata do SHA-1 do Git · Por que sha1sum direto no arquivo dá um hash diferente?
Na Seção 10.2 (“Object Storage”), os autores revelam a fórmula exata: antes de calcular o SHA-1, o Git constrói um cabeçalho em texto com o tipo do objeto, um espaço, o tamanho do conteúdo em bytes e um byte nulo (\0), concatenando o conteúdo logo depois: "blob 16\0what is up, doc?". É sobre essa string inteira (cabeçalho + `\0` + conteúdo) que o SHA-1 é calculado (`bd9dbf5aae1a3862dd1526723246b20206e5fc37`), e em seguida o conjunto é comprimido com zlib antes de ir para `.git/objects/bd/9dbf5...`!
| Tipo (`cat-file -t`) | Criado pelo comando plumbing | O que contém internamente (`cat-file -p`) |
|---|---|---|
blob | git hash-object -w | Apenas o conteúdo bruto de um arquivo (sem nome de arquivo e sem data). |
tree | git write-tree | Lista de entradas modo tipo hash nome apontando para blobs e sub-trees. |
commit | git commit-tree | Ponteiro para a tree raiz, ponteiros parent, author, committer e mensagem. |
tag | git mktag / git tag -a | Ponteiro para o objeto marcado, tipo, nome da tag, tagger, mensagem e assinatura GPG. |
Faça o raio-X de um commit real
Execute o laboratório acima e, em seguida, rode git cat-file -p HEAD, copie o hash da linha `tree` e rode `git cat-file -p
Conexão com o livro · Capítulo 10 (Seções 10.1 e 10.2) · Plumbing vs. Porcelain, hash-object, cat-file (-p, -t, -s), update-index, write-tree, read-tree, commit-tree e Object Storage (cabeçalho + \0 + zlib).
Domine Referências Simbólicas, Packfiles (Delta Reverso) e o Refspec
Se o Git guarda uma fotografia completa (blob) toda vez que você altera uma linha de um arquivo de 22 KB, por que dez versões desse arquivo não ocupam 220 KB depois que você roda git gc — e qual versão (a mais antiga ou a mais recente) o Packfile guarda inteira?
update-ref, symbolic-ref e a física das Tags Anotadas
Na Seção 10.3, vimos no laboratório anterior que nunca devemos editar arquivos dentro de `.git/refs/` na mão em produção: o comando plumbing seguro para mover qualquer referência é git update-ref refs/heads/main <hash>. Já o arquivo `.git/HEAD` geralmente não contém um hash direto, e sim uma referência simbólica (`ref: refs/heads/main`), lida e alterada de forma segura com git symbolic-ref HEAD refs/heads/main. E quando você cria uma tag anotada (`git tag -a v1.1`), o arquivo `.git/refs/tags/v1.1` aponta para um objeto do tipo `tag`, que por sua vez aponta para o commit (ou para qualquer outro objeto, como o blob da chave pública GPG do mantenedor!).
Packfiles e a sacada genial da Compressão Delta Reversa
Na Seção 10.4, Chacon e Straub respondem a uma dúvida clássica: inicialmente, cada versão de um arquivo modificado é salva como um objeto solto (*loose object*) comprimido com zlib. Se um arquivo `repo.rb` de 22 KB ganhar apenas uma linha nova, o novo blob ocupará outros 22 KB! Porém, quando você roda git gc (ou quando envia objetos pela rede num `git push`), o Git analisa nomes e tamanhos de arquivos parecidos e empacota tudo em um único Packfile (`.pack`) acompanhado de um índice (`.idx`).
O detalhe brilhante do design do Git está em quem vira delta de quem: como na imensa maioria das vezes você precisa acessar rápido a versão mais recente (HEAD) do arquivo, o packfile do Git armazena o objeto mais recente inteiro e transforma as versões antigas em deltas (diferenças) apontando para a versão nova! Você comprova isso inspecionando o índice do pacote com git verify-pack -v .git/objects/pack/pack-*.idx.
# 1. No laboratório plumbing, crie duas versões de um arquivo grande e veja os objetos soltos: cd ~/lab-git-etapa4/plumbing-puro seq 1 1000 > numeros.txt git add numeros.txt && git commit -m "Adiciona 1000 linhas" echo "1001" >> numeros.txt git commit -a -m "Adiciona linha 1001" find .git/objects -type f # 2. Execute o coletor/empacotador (git gc) e inspecione a cadeia de deltas no Packfile: git gc git verify-pack -v .git/objects/pack/pack-*.idx # 3. Manipule referências com comandos plumbing seguros (Cap. 10.3): git symbolic-ref HEAD git rev-parse HEAD
Teste de raciocínio: A Gramática do Refspec (+?<src>:<dst>)
Na Seção 10.5, todo mapeamento entre referências remotas e locais é governado pelo Refspec, escrito no formato +<src>:<dst> (onde `+` autoriza atualização mesmo que não seja fast-forward, `
Consultar uma pista
No `fetch =`, substitua o `*` geral por `qa*` nos dois lados dos dois-pontos (`:`). No `git push`, lembre-se de que `
Conferir a solução comentada
Para buscar apenas as branches de QA no `.git/config`, você usa fetch = +refs/heads/qa*:refs/remotes/origin/qa* (nota: o Git aceita um único curinga `*` por lado, mas permite namespaces como `refs/heads/qa/*:refs/remotes/origin/qa/*`). E para enviar sua `main` local para a branch `qa/main` do servidor, basta rodar git push origin main:refs/heads/qa/main! (É também por isso que `git push origin :topic` — com `
Para testar a transferência do conceito (Seção 10.6): quando o Git transfere dados pelo protocolo inteligente (*Smart Protocol*), quais são os dois processos executados no servidor para enviar dados ao cliente (`fetch`/`clone`) e para receber dados do cliente (`push`)?
Conferir a transferência
Quando você faz `git push` (enviando dados ao servidor), o servidor executa o processo git-receive-pack. Quando você faz `git fetch` ou `git clone` (baixando dados do servidor), o servidor executa o processo git-upload-pack, trocando metadados em pacotes prefixados pelo tamanho em hexadecimal de 4 dígitos (o formato pkt-line).
Entrega da etapa
Rode `git verify-pack -v .git/objects/pack/pack-*.idx` no seu laboratório após o `git gc`, identifique na listagem qual blob de `numeros.txt` ficou armazenado por inteiro (`non delta`) e qual virou delta apontando para o hash do outro.
Conexão com o livro · Capítulo 10 (Seções 10.3 a 10.6) · update-ref, symbolic-ref, objetos tag, Packfiles (git gc, verify-pack -v, delta reverso), Refspec (+src:dst) e Smart Protocol (receive-pack / upload-pack).
Resgate objetos órfãos com git fsck, expurgue arquivos gigantes e depure com GIT_TRACE
Um colega apagou uma branch com `git branch -D` e, para piorar, apagou também a pasta `.git/logs/` (destruindo todo o `reflog`). Em outro projeto, alguém comitou um arquivo `.tar.gz` de 50 MB no mês passado e, mesmo depois de rodar `git rm`, o repositório continua pesando 50 MB. Como resolver os dois casos na camada mais profunda do Git?
Resgate extremo sem Reflog: git fsck --full e dangling commits
Na Seção 10.7, Chacon e Straub mostram o que fazer quando até o `git reflog` não está disponível (ou quando você adicionou um arquivo com `git add` e o sobrescreveu antes de commitar). Enquanto o `git gc` não tiver podado objetos antigos expirados, os objetos continuam salvos em `.git/objects`! Basta executar git fsck --full: ele verifica a integridade de todo o banco e lista todos os objetos que não são alcançáveis por nenhuma referência atual como dangling commit <sha> ou dangling blob <sha>. Para recuperar a branch inteira, basta apontar uma branch nova para aquele hash: git branch resgate <sha-do-dangling-commit>!
O roteiro de 6 passos para expurgar um arquivo gigante do Packfile
Quando alguém adiciona um arquivo binário imenso no commit A e roda `git rm` no commit B, o arquivo some da Working Tree atual, mas o blob gigante continua dentro do Packfile para permitir fazer checkout do commit A! Na Seção 10.7, o livro ensina o procedimento forense completo para encontrar o blob pesado sem saber o nome dele e expurgá-lo definitivamente:
# --- PARTE A: Resgatar um commit apagado mesmo SEM reflog (git fsck --full) --- cd ~/lab-git-etapa4/plumbing-puro git checkout -b secreta echo "trabalho valioso" > joia.txt && git add joia.txt && git commit -m "Commit valioso" git checkout main git branch -D secreta rm -rf .git/logs/ # O reflog sumiu! Mas o git fsck encontra o "dangling commit" no banco de objetos: git fsck --full # --- PARTE B: Roteiro de 6 passos do Cap. 10.7 para achar e expurgar o maior arquivo do Packfile --- # 1. Veja o tamanho real do packfile (size-pack em KB): git count-objects -v # 2. Liste os 3 maiores objetos dentro do packfile ordenando pela 3ª coluna (tamanho em bytes): git verify-pack -v .git/objects/pack/pack-*.idx | sort -k 3 -n | tail -3 # 3. Descubra qual nome de arquivo corresponde ao hash do objeto gigante: # git rev-list --objects --all | grep# 4. Reescreva a história removendo do índice e limpe os logs/refs antes do prune imediato: # git filter-branch --index-filter 'git rm --ignore-unmatch --cached ' -- ^.. # rm -rf .git/refs/original/ .git/logs/ # git gc && git prune --expire now # --- PARTE C: Variáveis de ambiente de diagnóstico profundo (Cap. 10.8) --- GIT_TRACE=true GIT_TRACE_PERFORMANCE=true git status -s
Variáveis de Ambiente do Git (Seção 10.8)
Para fechar o Capítulo 10 e o livro, a Seção 10.8 reúne as variáveis de ambiente que alteram ou radiografam qualquer execução do Git sem tocar nos arquivos de configuração:
| Variável de Ambiente | O que ela faz na execução do comando |
|---|---|
GIT_TRACE=true | Imprime no terminal cada comando interno e expansão de alias ou subprocesso que o Git executa. |
GIT_TRACE_PACK_ACCESS=true | Mostra exatamente quais packfiles e deslocamentos de bytes estão sendo lidos no disco. |
GIT_TRACE_PACKET=true | Exibe pacote por pacote (em formato pkt-line) a conversa de rede durante `fetch`, `pull` ou `push`. |
GIT_TRACE_PERFORMANCE=true | Mede e imprime o tempo exato em segundos gasto por cada subcomando interno do Git. |
GIT_DIR e GIT_WORK_TREE | Permite apontar onde fica a pasta `.git` e onde fica a árvore de arquivos quando elas moram em pastas separadas. |
GIT_SSH_COMMAND | Permite passar flags customizadas de SSH (como `ssh -i ~/.ssh/chave_especifica -p 2222`) para um comando Git. |
Entrega da etapa
Execute a Parte A do bloco de comandos acima para ver o `git fsck --full` encontrar o `dangling commit` mesmo depois de apagar a pasta `.git/logs/`, recupere-o com `git branch resgate
Conexão com o livro · Capítulo 10 (Seções 10.7 a 10.9) · git gc --auto, .git/packed-refs, git fsck --full, git count-objects -v, expurgo com verify-pack + rev-list + filter-branch + prune e variáveis GIT_TRACE*.
Construa e blinde uma plataforma Git de ponta a ponta.
Reúna os conhecimentos dos Capítulos 4, 8, 9 e 10 para desenhar a governança, os hooks, os atributos e o protocolo de manutenção e recuperação de dados dos repositórios da sua equipe.
Roteiro de prática de uma semana
- Dia 1. Crie um repositório `--bare` de teste, ative `receive.fsckObjects`, `receive.denyNonFastForwards` e `receive.denyDeletes` e documente o bloqueio de login interativo com `git-shell`.
- Dias 2 e 3. Configure `core.autocrlf`, `core.excludesfile` e `commit.template` na sua estação, e crie o arquivo `.gitattributes` com regras de `export-ignore`, `export-subst` e `textconv`.
- Dias 4 e 5. Implemente um hook `commit-msg` no cliente e um hook `pre-receive` ou `update` no servidor bare para validar o padrão de mensagens e permissões de branch.
- Dia 6. Em uma pasta vazia, construa um commit do zero usando apenas comandos plumbing (`hash-object -w`, `update-index`, `write-tree`, `commit-tree` e `update-ref`).
- Dia 7. Simule o resgate de um commit órfão com `git fsck --full`, audite os maiores objetos de um packfile com `git verify-pack -v` + `git rev-list --objects --all` e exporte seu caderno final.
Decisões para praticar
Escolha uma resposta em cada desafio de arquitetura interna e administração do Git. A correção detalha os mecanismos dos Capítulos 4, 8, 9 e 10.
Minha síntese da Etapa 4
Checklist de domínio da Etapa 4 (Formação Completa)
Consultar anotações das oito oficinas
Livro-base e mapa das 4 etapas do curso
Scott Chacon e Ben Straub. Pro Git, 2ª edição. Apress, 2014 (com atualizações contínuas da comunidade). Esta Etapa 4: Administração e Arquitetura Interna cobre integralmente os Capítulos 4 (Git on the Server), 8 (Customizing Git), 9 (Git and Other Systems) e 10 (Git Internals) em uma adaptação didática e prática em português brasileiro. Edição oficial aberta em git-scm.com.
Mapa completo das 4 etapas desta formação: Etapa 1: O Básico Essencial (Caps. 1 e 2) · Etapa 2: Trabalho em Equipe e Fluxos (Caps. 3, 5 e 6) · Etapa 3: Produtividade e Limpeza de Histórico (Cap. 7) · Etapa 4: Administração e Arquitetura Interna (Caps. 4, 8, 9 e 10 — Atual).