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/eb/indicam os lados antigo e novo; não são nomes de branches.index f9ed85e..b551297conté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/nullno lado antigo, como mostra o arquivo seguinte emreview.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.