Desenvolvimento

9 out, 2012

Dica Git da semana: patches por e-mail

Publicidade

A dica Git da Semana é sobre como o git lida com os patches por e-mail.

Um dos principais benefícios de um sistema de controle de versão distribuído é que as mudanças de código podem ser empurradas de um repositório para outro clone, e todas as alterações dependentes são empurradas também. No entanto, isso só funciona quando você tem acesso de gravação para o repositório remoto, que, em muitos casos, você não tem. Uma maneira de conseguir as mudanças é por meio do fornecimento de um patch, ou um conjunto de alterações que pode ser aplicado a um repositório remoto na outra extremidade.

O Git começou como um sistema distribuído de controle de versão para o projeto Linux, que usa ativamente listas de e-mail tanto como um mecanismo de discussão e também como um mecanismo de distribuição de patches (mudanças) para uma base de código existente. (Os novos recursos são apenas um caso especial de não aplicar o patch para adicionar o novo código.).

Para acelerar o processamento dos patches pelo e-mail, o git desenvolveu uma forte integração com os clientes (linha de comando) de e-mail e do formato genérico mbox do Unix. Os patches podem ser gerados sob a forma de mensagens de e-mail, e o terminal remoto pode processá-las com um comando específico para reconstituir as alterações no repositório git.

Enquanto a maioria dos projetos não usa patches por e-mail como um mecanismo de distribuição de mudança, ele é útil quando um patch precisa ser gerado e anexado a um sistema de rastreamento de bugs, ou quando as mudanças precisam ser enviadas para um desenvolvedor remoto que não tem acesso direto (como por um firewall).

A convenção adotada pelos desenvolvedores git é formatar um patch por mensagem de e-mail. O assunto da mensagem, então, possui a primeira linha do git commit, prefixado com um prefixo que pode ser substituído na linha de comando, mas que o padrão é [PATCH x/y] como um meio de fazer threads juntos. (Entre outras razões, é por isso que é sugerido que a linha inicial de uma mensagem Git commit seja relativamente curta, de modo que se encaixe com uma visão do cliente de e-mail do assunto e sugeriu prefixo.).

Gerar e enviar patches

Como podemos gerar esses patches? O git patch-format irá gerar um patch de arquivo por commit na faixa exigida, formatado pronto para ir como mensagens de e-mail no formato mbox. O –to para pode ser especificado para qual endereço de e-mail os patches devem ser enviados, mas o envio é feito separadamente.

(master) $ git format-patch --to cdt-dev@eclipse.org HEAD~..HEAD
0001-bug-333001-Description-Scanner-Info-doesn-t-release.patch
From 9c9c692df50e5a9eb91b41cc86f57212afd78ef9 Mon Sep 17 00:00:00 2001
From: Andrew Gvozdev …
Date: Sat, 16 Jul 2011 15:16:21 -0400
Subject: [PATCH] bug 333001: Description Scanner Info doesn't release
ICProjectDescription
To: cdt-dev@eclipse.org

---
.../cdt/internal/core/model/CModelManager.java     |    2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
⋮

Se a mensagem de commit tiver mais detalhes do que uma única linha, o detalhe será incluído abaixo do cabeçalho do assunto do e-mail. É possível acrescentar comentários adicionais abaixo da mensagem de commit, antes de o patch ser mostrado, e qualquer texto até uma combinação de – ou >8 ou 8<(AKA ‘scissorlines’) é ignorada pelo aplicativo do sistema na outra extremidade.

Esses arquivos de patch podem então ser transmitidos via e-mail usando o comando git send-email. Ele se conecta ao servidor SMTP dado (ou ao de sua global ~/.gitconfig ou ao do projeto gitconfig, ou ao especificado na linha de comando.). E, em seguida, envia cada arquivo de patch como um e-mail separado:

(master) $ gitsend-email --smtp-server=smtp.gmail.com *.patch

Bem como a utilização de format-patch em uma estágio separado, é possível usar send-mail para gerar os patches e depois enviá-los imediatamente. (Você também pode configurar send-email para solicitar a abertura de um editor, de modo que você possa personalizar as mensagens antes de serem enviadas.)

Aplicando patches

Uma vez que os patches foram criados, como você os aplica a um clone local? Se você tem um arquivo de patch, você pode aplicá-lo com git apply:

(master) $ git apply 0001-bug-333001-Description-Scanner-Info-doesn-t-release.patch

Note, no entanto, que essa abordagem não recria a situação global como era no repositório do remetente. Em vez disso, o patch é aplicado, mas só faz alterações locais para o conteúdo do repositório; ele não recria o commit (e, mais especificamente, o hash que faz o commit). Você pode especificar git apply –index e git apply –cached para obter as mudanças colocadas na área de teste, mas isso não recria o mesmo commit como antes.

Para recriar o commit exatamente como ele era, é necessário o uso de git am, o que significa aplicar mailbox. Este é executado através de uma caixa de correio (que pode ter um ou mais patches) e recria um commit para cada um desses patches.

Felizmente, a saída gerada pelo git  patch já está no formato mbox; é a finalidade da outra linha fictícia From 9c9c692df50e5a9eb91b41cc86f57212afd78ef9 Mon Sep 17 00:00:00 2001 no topo do arquivo de patch. Como resultado, os patches podem ser tratados como uma mensagem por mbox e, em seguida, aplicada em lotes para as mudanças que são enviadas.

De fato, uma vez que os elementos de mbox podem ser concatenados juntos, isso permite que os arquivos de patch sejam concatenados para formar um arquivo de patch maior, que pode ser enviado como uma única unidade por meio de um outro mecanismo de transferência e, em seguida, aplicado no lado remoto.

Bundles

Os patches fornecem uma maneira de reconstituir um repositório sobre um mecanismo não-conectado diretamente, mas o propósito deles é permitir às pessoas investigarem o conjunto de alterações, tanto quanto conseguir a mudança lá. Se, contudo, o desejo é passar commits de uma máquina para outra, sem conexão direta, a melhor alternativa é usar git bundle.

(master) $ git bundle create changes.bundle HEAD~..HEAD
Counting objects: 23, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (12/12), 935 bytes, done.
Total 12 (delta 5), reused 6 (delta 0)

O formato do bundle utiliza o mesmo formato usado pela rede de transmissão que o Git usa por meio da rede quando está clonando. Como resultado, as referências contidas são apenas aquelas listadas na lista de referência.

Tipicamente, uma tag será utilizada para marcar o local onde o último ponto conhecido foi para a fonte remota e, depois, a diferença entre HEAD e a tag é usada para criar o bundle para a extremidade remota. Como alternativa, os branches podem ser utilizados para simular o branch na extremidade remota.

Uma vez que o arquivo de bundle foi gerado, ele pode ser enviado por qualquer transporte para o host remoto para reconstituição. Isso pode envolver a gravação de CD, através de um stick USB ou algum outro protocolo de rede.

No lado do cliente, ele pode executar git verify para determinar se todos os commits pais exigidos estão presentes no repositório local. Isso deve ser executado a partir do repositório git cliente em que você deseja fazer o fetch.

O cliente vê o bundle como um remoto do qual pode extrair, como um caminho para um diretório pode ser usado para puxar de um repositório baseado em arquivo local. Você pode adicioná-lo como um remote (por exemplo, git remote add changes /tmp/changes.bundle) ou você pode buscar do caminho para o bundle em si:

(master) $ git verify /tmp/changes.bundle
The bundle contains 1 ref
b707c559636bf8e6dffb3145bd44b03de18868b3 HEAD
The bundle requires these 1 ref
3580c1087c2860fbe6ca4c1a7a6d6e1eb1669aa3 Bug 333599 - [C++0x] Initializer lists & return without type
/tmp/changes.bundle is okay
(master) git fetch /tmp/changes.bundle
From /tmp/foo.bundle
* branch            HEAD       -> FETCH_HEAD

Uma vez que as referências foram buscadas no repositório (que podem ser referidas como FETCH_HEAD), então você pode inspecionar as mudanças, fetch/merge nos branches locais ou redefinir o seu branch master com FETCH_HEAD.

Resumo

Nem sempre é possível ter acesso de gravação ao repositório para o qual você deseja enviar alterações. Nesses casos, você pode enviar alterações por fora ou via e-mail (se quiser opiniões humanas) ou como um bundle (se você quiser apenas enviar o commit).

***

Texto original disponível em http://alblue.bandlem.com/2011/12/git-tip-of-week-patches-by-email.html