Pular para conteúdo

Atualizações do template

Um scaffold é uma cópia única. Uma correção que entra em templates/ depois disso só chega aos projetos gerados depois dela. python bin/check_template_drift.py é o relator que fecha essa lacuna (blueprintx#872).

O que ele compara

Para um projeto carimbado com tier e blueprintx_commit em .blueprintx-provenance.yaml, existem três versões de cada arquivo gerenciado pelo template:

Nome De onde vem
base o arquivo do template no blueprintx_commit carimbado
theirs o arquivo do template no checkout do BlueprintX agora
ours o arquivo do projeto

O conjunto de arquivos gerenciados nunca é listado à mão. A entrada de scaffold de cada tier responde a --list-copies executando em modo simulado os seus próprios passos de cópia (bin/lib/scaffold_python_templates.sh para os tiers de serviço, bin/scaffold/python_lib_minimal.sh para o lib-minimal) e imprimindo uma linha COPY ou RM por cópia. A simulação substitui cp, rm, mkdir, touch, chmod, envsubst e print_status por meio de scaffold_dry_run em bin/lib/scaffold_provenance.sh, de modo que a saída não pode divergir do que o scaffold faz.

A simulação aceita --roster true|false para corresponder à escolha do projeto sobre o roster de bots de revisão, e limpa GITGUARDIAN_API_KEY para que a saída nunca dependa do ambiente de quem chama. Arquivos renderizados em vez de copiados (README.md, pyproject.toml) não aparecem. O CI do próprio BlueprintX gera cada tier de verdade e falha quando --list-copies e a árvore gerada divergem.

O que ele reporta

  • Ausente: o template entrega um caminho que o projeto não tem.
  • Template mudou desde o scaffold: ours é igual a base e theirs difere.
  • Ambos mudaram: ours e theirs diferem de base e entre si.
  • Não comparado: o projeto tem o arquivo, mas a fonte no checkout do BlueprintX não é um arquivo (por exemplo, foi apagada localmente); a execução não prova nada sobre ele e nunca termina em veredito limpo.

Um arquivo que o projeto editou enquanto o template ficou parado não é deriva. Quebras de linha CRLF (checkout com core.autocrlf) não contam como edição: o texto é normalizado para LF antes de comparar; conteúdo binário não é alterado. Arquivos que o scaffold renderiza com envsubst (pyproject.toml, LICENSE, README.md, SECURITY.md) não são cópias literais e ficam fora da comparação, assim como os arquivos copiados de bin/ci/. Cópias condicionadas a uma resposta dada no scaffold (o roster de bots de revisão) são listadas exatamente quando a simulação recebe o valor correspondente, lido do carimbo. As demais cópias condicionadas (chave do GitGuardian, destinos de publicação) ainda não são policiadas. Um arquivo ausente no commit carimbado que o projeto escreveu por conta própria é reportado como "ambos mudaram", a menos que o conteúdo já seja igual ao template atual, caso em que é "igual".

Arquivos que o template removeu

Uma terceira classe cobre o sentido oposto: um arquivo que o template entregava no commit carimbado, não entrega mais, e que o projeto ainda tem. Sem ela ficaria invisível, porque todas as outras classes partem da lista de cópias atual. Um projeto gerado antes de uma remoção guarda o órfão (Makefile, tasks.sh, docs/api.md) e nada avisa. Cada um é reportado como removed ou replaced by <substituto>:

🗑️ removed: docs/api.md
🔁 replaced by poe_tasks.toml: Makefile

A lista de cópias carimbada é calculada com o mesmo --list-copies em modo simulado, executado no commit carimbado: git archive exporta bin/ e templates/ desse commit para um diretório temporário e a entrada de scaffold dele responde ali. Um caminho que está nessa lista, não está na atual e existe no projeto é removido. O substituto vem só de duas fontes:

  • Um rename do git. git diff -M entre o commit carimbado e o checkout mapeia a fonte antiga do template para o novo caminho, e o substituto é cada caminho do projeto que a lista atual entrega a partir dela.
  • Uma tabela explícita de regras. Reescrita não é rename, então o git não diz que Makefile e tasks.sh viraram poe_tasks.toml, nem que make/library.mk virou poe_tasks.library.toml. _SUCCESSOR_RULES em bin/check_template_drift.py lista esses pares. Uma regra só vale quando o substituto está na lista atual, de modo que a tabela nunca nomeia um arquivo que o template não entrega.

Sem nenhuma das duas, o arquivo é apenas removed. Um substituto chutado seria pior que nenhum.

Quando o commit carimbado não consegue listar as suas cópias (é anterior ao --list-copies, ou a exportação falha) a seção imprime SKIPPED removed-file check e diz o motivo, como a seção de conteúdo. Nada é reportado como removido a partir de uma comparação que não rodou.

python bin/template_update.py imprime as mesmas linhas, na simulação e em --apply, e nunca apaga nem move uma: remover um arquivo do projeto continua sendo decisão humana. O carimbo blueprintx_commit ainda avança quando os demais arquivos foram aplicados sem pendências, porque um órfão não é conflito. Os órfãos são medidos a partir de scaffold_commit, que --apply nunca move, e por isso continuam listados até você apagá-los. Um carimbo escrito antes desse campo existir usa blueprintx_commit.

Por que ele se auto-ignora

Cada pré-requisito que falta tem a sua própria saída, e nenhuma delas é um veredito limpo:

  • Sem carimbo (.blueprintx-provenance.yaml) ou sem checkout do BlueprintX, a ferramenta imprime uma linha SKIPPED: ... genérica explicando o que falta e encerra antes de qualquer comparação.
  • Com checkout e carimbo, mas com blueprintx_commit ausente, unknown ou fora do histórico de HEAD do checkout (um commit divergente ou mais novo que o checkout não serve de base de mesclagem), a seção de conteúdo imprime SKIPPED content drift e diz o motivo. A verificação de caminhos ausentes ainda roda.

A ferramenta sempre encerra com 0.

Aplicando atualizações: python bin/template_update.py

python bin/template_update.py lê os mesmos dados base, ours e theirs e age sobre eles. É uma simulação, a menos que --apply seja passado. O checkout vem de --blueprintx-root ou de BLUEPRINTX_TEMPLATE_ROOT, como no relator, e a resolução dos caminhos gerenciados é reaproveitada (managed_sources em bin/check_template_drift.py).

Arquivo do projeto Ação
ausente adiciona, com o modo de arquivo do template
igual a base avança (fast-forward) para theirs
igual a theirs, ou theirs igual a base nada a fazer
editado localmente e o template também mudou git merge-file base, ours, theirs
um diretório no lugar do arquivo listado como não mesclável e deixado intacto

Uma mesclagem limpa é escrita como está. Um conflito é escrito com marcadores <<<<<<< ours, ======= e >>>>>>> template e listado na saída. Um arquivo que o git não consegue mesclar (conteúdo binário, ou o git merge-file encerrado por sinal) é listado como não mesclável e deixado intacto. Uma edição local nunca é sobrescrita em silêncio, e um arquivo cuja base é desconhecida (adicionado ao template depois do commit carimbado) é mesclado contra uma base vazia, de modo que uma cópia local diferente aparece como conflito.

--apply primeiro confere que --root é a raiz de uma árvore de trabalho git (um projeto dentro de outro repositório não tem o HEAD desse repositório movido) e então executa git switch -c chore/template-update-<short-sha>, em que o sha curto é o HEAD do checkout. Nunca escreve na branch em que você estava e para antes de alterar qualquer coisa quando a branch não pode ser criada. Depois escreve os arquivos e os deixa sem commit para revisão. blueprintx_commit no carimbo passa para o HEAD do checkout somente quando todos os arquivos foram aplicados sem pendências; após um conflito o carimbo não muda e a ferramenta encerra com 1, de modo que resolver os marcadores e rodar de novo continua a partir da mesma base.

Sem carimbo, checkout ou blueprintx_commit utilizável (inclusive um commit que não é ancestral do HEAD do checkout), a ferramenta imprime SKIPPED update (ou o SKIPPED do relator) e encerra com 0 sem tocar no projeto.

Tentando de novo após um conflito

Rode python bin/template_update.py --apply outra vez enquanto a branch de atualização continua em checkout. A branch é reaproveitada, arquivos já escritos não são reescritos, e um arquivo conflitado ou mesclado conta como resolvido assim que não tem linha de marcador <<<<<<< ou >>>>>>>. Só então blueprintx_commit avança. Um arquivo não mesclável continua bloqueando até ser tratado à mão.

Retomar só vale quando uma execução anterior registrou que escreveu o plano (um arquivo template-update-<short-sha>.written dentro do diretório .git). Uma branch de mesmo nome sem esse registro (execução interrompida ou criada à mão) recebe o plano completo, para que mesclagens não escritas nunca sejam puladas e carimbadas como aplicadas.

--apply também recusa um checkout do BlueprintX com alterações não commitadas ou não rastreadas, porque theirs é lido da árvore de trabalho enquanto o commit carimbado vem do histórico.

Neste repositório

O opendata-cvm carrega um carimbo (.blueprintx-provenance.yaml, tier lib-minimal). As tarefas poe template_drift e template_update chegam com a migração para o poe; até lá, rode os scripts diretamente, apontando para um checkout do BlueprintX com --blueprintx-root ou com a variável de ambiente BLUEPRINTX_TEMPLATE_ROOT:

python bin/check_template_drift.py --root . --blueprintx-root ~/github/blueprintx
python bin/template_update.py --root . --blueprintx-root ~/github/blueprintx   # dry run

blueprintx_commit no carimbo não é o commit do qual o repositório foi literalmente gerado. Foi escolhido por medição: o commit do BlueprintX em que os arquivos do template que ninguém aqui tocou ainda são iguais às cópias do projeto, de modo que "template mudou" e "ambos mudaram" sejam classificados corretamente. Apenas tier e review_bot_roster são respostas de scaffold que o carimbo registra.

Limites conhecidos

  • Arquivos renderizados (pyproject.toml, README.md, mkdocs.yml, SECURITY.md, LICENSE) e as cópias de bin/ci/ ficam fora da comparação.
  • A árvore do pacote vendorizado aparece sob o nome de espaço reservado do tier, src/project/...; neste repositório, todo o src/ é reportado como ausente. Ignore essas linhas.
  • Respostas de scaffold além do roster de bots de revisão não são registradas no carimbo.