Um assistente de IA diz ter adicionado busca por título a uma lista de livros. git diff mostra um import novo e poucas linhas alteradas. A implementação da busca não aparece, e o código proposto não encontra um livro presente no catálogo. Antes de decidir se o patch está correto, descubra o que o Git realmente comparou.

Baixe o laboratório Git diff: uma aplicação com seis livros, um patch e testes criados para este tutorial. Você precisa de Node.js 22 ou posterior e Git, sem dependências ou acesso à rede.

Escolha os dois estados antes de ler as linhas

O Git mantém uma árvore de trabalho, um índice com o estado preparado e estados commitados. Estes comandos respondem a perguntas diferentes:

Comando O que compara
git diff Índice → árvore de trabalho: mudanças não preparadas em arquivos rastreados
git diff --staged HEAD → índice: o que está preparado para o próximo commit
git diff HEAD HEAD → árvore de trabalho: mudança líquida rastreada, com edições preparadas e não preparadas
git diff base HEAD Os estados commitados identificados por base e HEAD
git diff base...HEAD Ancestral comum → HEAD: mudanças da branch head desde a divergência

Arquivos comuns não rastreados ficam fora das três primeiras comparações. Um arquivo também pode ter edições preparadas e não preparadas. Veja essas formas na referência de Git diff.

O diff descreve estados, não autoria. Se mudanças manuais anteriores se misturam às do assistente, o patch não identifica quais linhas ele escreveu. Guarde o estado anterior ou os IDs dos commits se isso importar. Uma comparação de branches também exige a base pretendida; outra base muda a pergunta.

Encontre o arquivo que o diff simples omite

Descompacte e entre em git-diff-reading-lab. Crie um repositório descartável:

node scripts/create-workspace.mjs review-workspace
cd review-workspace
git status --short --untracked-files=all

O script recusa um destino existente. Commita o código original de listagem, marca o commit como base e deixa a proposta sem commit. Sua saída é reproduzível sem tocar em um repositório existente.

 M src/list.mjs
?? src/search.mjs
?? tests/search.test.mjs

Neste status comum, sem conflitos, a primeira coluna descreve o índice e a segunda a árvore de trabalho. O espaço inicial em M importa: list.mjs está modificado, mas não preparado. ?? identifica arquivos não rastreados. As convenções estão no formato curto de Git status.

Agora execute:

git diff --name-status
git diff --stat
git diff -- src/list.mjs

Só aparece src/list.mjs. Leia src/search.mjs e tests/search.test.mjs separadamente no editor. No macOS ou Linux, exiba o novo código como adição com git diff --no-index -- /dev/null src/search.mjs; o código 1 significa que os arquivos diferem. O patches/review.patch do download inclui os três arquivos alterados; o diff simples, neste estado, não.

git diff --staged está vazio porque nada foi preparado. git diff base...HEAD também está vazio porque a proposta não foi commitada e base ainda equivale a HEAD. Nenhum deles revisou a proposta de trabalho.

Leia um hunk completo

A parte de list.mjs no patch fornecido é:

diff --git a/src/list.mjs b/src/list.mjs
index f9ed85e..b551297 100644
--- a/src/list.mjs
+++ b/src/list.mjs
@@ -1,6 +1,8 @@
 import { pageBooks } from "./page.mjs";
+import { searchBooks } from "./search.mjs";
 // Keep the caller's array unchanged.
-export function listBooks(books, { page = 1, pageSize = 2 } = {}) {
+export function listBooks(books, { page = 1, pageSize = 2, query = "" } = {}) {
   const sorted = [...books].sort((a, b) => a.title.localeCompare(b.title, "en"));
-  return pageBooks(sorted, { page, pageSize });
+  const currentPage = pageBooks(sorted, { page, pageSize });
+  return searchBooks(currentPage, query);
 }

Leia os cabeçalhos antes da função:

  • a/ e b/ indicam os lados antigo e novo; não são nomes de branches.
  • index f9ed85e..b551297 contém IDs abreviados dos objetos de conteúdo dos arquivos, não dos commits. 100644 é o modo inalterado de arquivo comum, não executável.
  • --- identifica o arquivo antigo e +++ o novo. Um arquivo adicionado usa /dev/null no lado antigo, como mostra o arquivo seguinte em review.patch.
  • @@ -1,6 +1,8 @@ começa na linha antiga 1, cobre 6 linhas antigas, depois começa na nova linha 1 e cobre 8 novas linhas. Os números são intervalos, não totais de exclusões e adições.
  • Dentro do hunk, espaço indica contexto preservado, - linha removida e + linha adicionada. Aqui, 4 linhas de contexto e 2 remoções compõem o intervalo antigo; 4 de contexto e 4 adições compõem o novo. O comentário preservado também conta como contexto.

A documentação do formato de patch Git explica os cabeçalhos. Confira a aritmética no arquivo base de seis linhas e no proposto de oito linhas. São hunks comuns de dois lados; um diff combinado de merge usa outro formato.

Siga a nova chamada até sua entrada

O pedido é específico: buscar por título no catálogo inteiro, sem diferenciar maiúsculas, e então paginar os livros encontrados. Preserve a ordem por título e não altere a entrada.

A nova função filtra a lista que recebe:

export function searchBooks(books, query) {
  const needle = query.toLowerCase();
  return books.filter((book) => book.title.toLowerCase().includes(needle));
}

A função pode funcionar corretamente enquanto o chamador fornece a lista errada. Abra src/page.mjs, que não mudou: ele reduz os livros ordenados a uma página. No hunk proposto, currentPage já contém esse recorte ao chegar a searchBooks.

Com páginas de dois livros, a primeira página original contém Amber Atlas e Moon Garden. Star Notes está adiante no catálogo. Buscar sTaR na primeira página retorna [], embora a primeira página dos resultados devesse conter b5. A correção em patches/fix.patch busca primeiro em sorted e passa os resultados a pageBooks.

Esse é o comportamento a verificar. Para mudanças maiores com muitos consumidores, continue com análise de impacto; a lista de arquivos alterados não os enumera sozinha.

Confira o que o teste verde realmente chamou

Volte de review-workspace à raiz do laboratório e execute os comandos separadamente:

cd ..
node scripts/run-tests.mjs proposed author
node scripts/run-tests.mjs proposed requirement
node scripts/run-tests.mjs fixed requirement
node scripts/example.mjs

O teste do autor da proposta passa: fornece um array completo de dois livros a searchBooks e verifica uma consulta com maiúsculas e minúsculas. Nunca chama listBooks, portanto não exercita a paginação antes da busca. Nenhuma asserção antiga foi removida ou enfraquecida.

Os mesmos três requisitos da listagem pública rodam contra cada versão. O download também fornece duas verificações de regressão inalteradas para ordenação, paginação e ausência de mutação:

Versão Verificações existentes Novos requisitos públicos Código de saída dos requisitos
base: sem busca 2 passaram 0 passaram, 3 falharam 1
proposed: busca após paginar 2 passaram 1 passou, 2 falharam 1
fixed: busca antes de paginar 2 passaram 3 passaram 0

Execute node scripts/run-tests.mjs base requirement ou substitua versão e suíte para reproduzir as outras linhas. As falhas da base indicam ausência do recurso novo, não regressão da listagem antiga. As falhas propostas envolvem uma página completa de resultados e uma correspondência além da primeira página original. Ambas as suítes realmente saem com 1; uni-las com && interromperia os comandos restantes.

scripts/example.mjs imprime um objeto JSON por versão. Em results, o valor de sTaR é ["b1","b2"] na base, [] na proposta e ["b5"] na corrigida. Essas verificações respondem à questão da ordem de busca, sem certificar uma implementação de produção. Se o patch também mudar asserções, use o guia de revisão de testes gerados por IA.

Entenda por que um diff vazio ainda pode deixar um patch para revisar

Depois de examinar os três arquivos, prepare-os somente no repositório descartável:

cd review-workspace
git add -- src/list.mjs src/search.mjs tests/search.test.mjs
git diff
git diff --staged --stat
git diff --staged

O diff simples agora está vazio porque os arquivos correspondem ao índice. O diff preparado contém os três, incluindo a função e o teste novos. O exercício muda a área de preparação do laboratório; não commita a proposta. Em uma revisão real, evite preparar arquivos alheios apenas para fazê-los aparecer no diff.

Compare a proposta como duas versões commitadas

Para usar o exemplo no visualizador de diff Git do Yenpo para Mac, crie outro repositório de exercício com ambas as versões commitadas. Volte à pasta git-diff-reading-lab extraída — cd .. se ainda estiver em review-workspace — e execute:

node scripts/create-workspace.mjs committed-workspace --committed
cd committed-workspace
git status --short --untracked-files=all
git rev-parse base
git rev-parse proposal
git diff base proposal --name-status
git diff base proposal

O novo modo commita os arquivos originais como base, copia e prepara as três mudanças propostas e cria um segundo commit marcado como proposal. Imprime os IDs reais de ambos. proposal é HEAD; base é seu pai. O review-workspace anterior permanece intacto.

O comando de status não deve imprimir nada. Como ambos os estados estão commitados, git diff e git diff --staged também estão vazios. A comparação explícita base → proposal ainda contém src/list.mjs modificado, src/search.mjs adicionado e tests/search.test.mjs adicionado. Abra também src/page.mjs, inalterado: a listagem ainda passa um subconjunto paginado à busca nova.

Esses commits contêm o mesmo código de versions/base e versions/proposed. A observação de sTaR continua ["b1","b2"] antes da proposta e [] depois; o resultado desejado é ["b5"]. Commitar preserva o problema de ordem. O estado fixed e seus requisitos passando continuam no laboratório; este modo não cria commit fixed.

A preparação só aceita um novo diretório filho e recusa arquivos, pastas ou links simbólicos existentes. Usa identidade didática fictícia explícita e datas fixas, ignora identidade/configuração Git herdadas e grava opções apenas no novo repositório. Não muda sua identidade global nem outro repositório.

Abra committed-workspace no Yenpo. Use os IDs impressos para conferir versões antiga e nova conforme o guia de comparação Git: lado antigo base, novo proposal. A comparação trata desses estados commitados. Não inclui edições preparadas ou não preparadas nem identifica mudanças feitas em uma rodada de conversa com IA.