Criando e Aplicando Patches

Autoria: Ciaran O'Riordan, Stephen Compall, Brian Gough (Network Theory Ltd)
Original: http://www.network-theory.co.uk/articles/patchintro.html
Tradução livre: Cárlisson Galdino

Introdução

A proposta deste artigo é prover um tutorial introdutório sobre criação e uso de patches com os comandos GNU diff e patch.

Para mais informações veja o manual impresso (em inglês) "Comparing and Merging Files with GNU diff and patch" (ISBN 0-9541617-5-0).

Um patch (remendo) é um conjunto de diferenças entre dois arquivos ou dois conjuntos de arquivos, usualmente um sendo o original e o outro uma versão modificada do original. A proposta de um patch é permitir que outros modifiquem seus arquivos originais da mesma forma que você modificou os seus. Assim, qualquer um pode aplicar um patch à sua própria cópia do arquivo original (ou dos arquivos originais), obtendo como resultado um arquivo ou conjunto de arquivos que estará idêntico à versão modificada modificada por você.

Patches são uma forma padrão de desenvolvedores de software livre compartilharem correções de falhas e melhorias com outros desenvolvedores.

Aplicando Patches

Patches são aplicados com o comando patch. Para especificar o nome do patch a aplicar, o comando patch pode ser chamado com a opção --input, ou -i, seguida pelo nome do arquivo com o patch. Se nenhum nome for fornecido, o comando patch lerá a partir da entrada padrão (que pode ser a saída de outro comando enviado via pipe |). Patches são usualmente armazenados com a extensão .diff e referenciados como arquivos diff, por conta de eles serem criados com uso do comando diff.

Quando novas versões de pacotes GNU são liberadas, elas são trabalhadas pelo mantenedor para prover tanto um arquivo tar da nova versão como um arquivo patch que possa ser utilizado para atualizar o código-fonte da versão anterior. Desse modo, se você tem o código-fonte do GNU Bash 2.04 e quer atualizar para o 2.05, ao invés de baixar o arquivo bash-2.05.tar.gz inteiro, você pode pegar o arquivo de patch bash-2.04-2.05.diff.gz, que é bem menor, a partir do endereço ftp.gnu.org. Por exemplo, os comandos seguintes mostram como aplicar um patch para atualizar do Bash 2.04 para o Bash 2.05, lendo o patch diretamente a partir do arquivo compactado via pipe:

$ ls
bash-2.04-2.05.diff.gz bash-2.04/
$ cd bash-2.04/
$ gunzip -c ../bash-2.04-2.05.diff.gz | patch -p1
patching file CHANGES
patching file COMPAT
patching file CWRU/POSIX.NOTES
...
patching file y.tab.h

O mesmo efeito pode ser atingido com a opção -i sendo passada para o comando patch, depois de descompactar o arquivo de patch:

$ gunzip bash-2.04-2.05.diff.gz
$ cd bash-2.04/
$ patch -p1 -i ../bash-2.04-2.05.diff
patching file CHANGES
patching file COMPAT
patching file CWRU/POSIX.NOTES
...
patching file y.tab.h

251 linhas de mensagens tipo "patching file XXX" serão mostradas. Quando o comando completar, a sua cópia do bash será idêntica à versão 2.05. Ao final da próxima sessão, eu mostrarei um comando que você pode utilizar para verificar isso. Depois de aplicar o patch, você provavelmente desejará renomear o diretório de bash-2.04 para bash-2.05 de modo a refletir as mudanças.

O parâmetro -p1 dado ao comando patch indica que exatamente um diretório deve ser ignorado no início de cada nome de arquivo constante no patch. Os nomes de arquivo no arquivo de patch se parece com isso:

$ more bash-2.04-2.05.diff
...
*** bash-2.04/CHANGES Tue Mar 14 11:40:08 2000
--- bash-2.05/CHANGES Tue Apr 3 10:33:50 2001
...

Uma vez que começamos a entrar no diretório directory bash-2.04, precisamos ignorar um diretório desses caminhos para que obtenhamos o endereço relativo correto do arquivo CHANGES no diretório atual.

A necessidade de usar esse parâmetro vai depender de como o patch foi gerado. A maioria dos patches precisa do -p1, mas alguns não, de modo que se você receber alertas ou erros quando estiver aplicando o patch, tente aplicá-lo sem o parâmetro -p1, ou procure o arquivo README na distribuição do código-fonte para ver se o -p1 é mencionado. Se estiver inseguro sobre o uso correto, faça uma cópia de segurança de quaisquer arquivos importantes em que esteja trabalhando.

Se um patch é aplicado com a opção -p usada de maneira inapropriada, partes dele podem ser rejeitadas, deixando você com uma estrutura de código-fonte quebrada. Você pode utilizar a opção –dry-run para testar se as mudanças podem ser aplicadas sem erros antes de tentar aplicá-las de fato. Aqui, tentamos rodar o patch com –dry-run no arquivo bash-2.04-2.05.diff:

$ patch --dry-run -p1 -i ../bash-2.04-2.05.diff
patching file CHANGES
patching file COMPAT
patching file CWRU/POSIX.NOTES
...
patching file y.tab.h

Com a opção --dry-run nenhum arquivo é modificado. As mensagens mostram que o patch pode ser aplicado sem problemas. Você pode agora executar o comando patch sem --dry-run, estando certo de que todas as mudanças contidas no arquivo de patch fluirão normalmente.

Criando Patches

O próximo passo é criar seus próprios patches. Este é um conhecimento incidental que desenvolvedores pegam à medida em que ganham experiência, de modo que é assumido que todo mundo sabe fazer isso sem que ninguém precise dizer nada.

Patches são criados com o comando diff. Se você fizer uma cópia de um arquivo existente e editá-lo ligeiramente, você pode ver como o diff funciona. Por exemplo, eu fiz uma mudança simples em uma linha do arquivo mailcheck.c, que está na raiz do bash-2.05. Então, eu salvei a versão modificada como mymailcheck.c:

$ cd ~/src/bash-2.05
$ cp mailcheck.c mymailcheck.c
$ emacs mymailcheck.c
(to edit the new file)
$ diff mailcheck.c mymailcheck.c > mychange.diff
$ cat mychange.diff
402c402
< message = "You have new mail in $_";
---
> message = "New mail found in $_";

A saída resultante começa com 402c402... que quer dizer: vá para a linha 402 e pegue a linha original, aqui mostrada com <, e a substitua pela nova versão, que está mostrada aqui com >. Eu posso então enviar o arquivo mychange.diff para outras pessoas e elas poderão decidir se farão a mesma mudança ou não.

A saída simples do diff mostrada acima tem diversas limitações que a impedem de ser utilizada como um arquivo de patch na prática. O primeiro problema é que ele se refere a um número de linha específico no arquivo. Se outra pessoa estiver modificando seu código-fonte, ou aplicado outros patches, é bem possível que o número da linha tenha mudado, de modo que a aplicação do patch falhará.

Outro problema é que a saída do diff não menciona o nome do arquivo, dificultando o fornecimento de um patch que mude mais de um arquivo. De fato, tentar usar a saída simples do diff como um patch dará uma mensagem de erro dizendo que não foi possível encontrar um arquivo de patch na linha 1.

A forma correta de fazer um patch funcionar é criando um diff de contexto, que usa um formato de saída que inclui o nome de cada arquivo modificado e algumas linhas de contexto em torno das linhas modificadas. As linhas adicionais permitem que o comando patch localize a linha modificada mesmo que outra parte do arquivo tenha sido adicionada ou removida. Diffs de contexto são feitos adicionando-se o parâmetro -c ao comando diff:

$ cd ~/src/bash-2.05
$ diff -c mailcheck.c mymailcheck.c > mypatch.diff

Aqui está como um arquivo diff de contexto se parece:

$ cat mypatch.diff
*** mailcheck.c 2003-12-18 16:09:55.000000000 +0000
--- mymailcheck.c 2003-12-18 16:10:07.000000000 +0000
***************
*** 399,405 ****
/* If the mod time is later than the access time and the file
has grown, note the fact that this is *new* mail. */
if (use_user_notification == 0 && (atime < mtime) && file_is_bigger)
! message = "You have new mail in $_";
#undef atime
#undef mtime

--- 399,405 ----
/* If the mod time is later than the access time and the file
has grown, note the fact that this is *new* mail. */
if (use_user_notification == 0 && (atime < mtime) && file_is_bigger)
! message = "New mail found in $_";
#undef atime
#undef mtime

O patch feito como um diff de contexto é maior, mas justamente essas linhas adicionais é que dão ao comando patch uma forma de ele localizar e modificar linhas se a sua posição no arquivo for modificada. As linhas de contexto também tornam bem mais simples de entender o impacto das mudanças, para quem vai receber o patch. Estas duas razões combinadas implicam que patches devem sempre ser distribuídos em formato de contexto.

Além disso, no formato de contexto o nome do arquivo que estmos modificando está no patch (na primeira linha), de modo que o comando patch automaticamente saberá que arquivo atualizar.

$ patch -i mypatch.diff
patching file mailcheck.c

Estes exemplos têm tratado apenas arquivos isolados, mas a maioria dos patches precisará alterar múltiplos arquivos, de modo que precisamos estar aptos a criar um arquivo de patch contendo todas as diferenças entre dois diretórios. Isso é feito passando-se o parâmetro --recursive, ou -r, para o diff. Desta forma, se eu fosse fazer alguma mudança no Bash 2.05, começaria criando uma cópia do diretório, e fazemos as mudanças no novo diretório, de modo que eu tenha ainda uma cópia intacta do Bash 2.05, que possa utilizar para gerar meu arquivo de patch.

$ cd ~/src/
$ cp -a bash-2.05 bash-2.05-mychanges

(altere os arquivos necessários)

$ diff -cr bash-2.05 bash-2.05-mychanges > mychanges.diff

Agora terei um arquivo diff de contexto com todas as modificações que fiz no Bash 2.05, e eu posso disponibilizar o arquivo mychanges.diff para que outros possam utilizá-lo.

Há várias opções adicionais para o comando diff que são úteis quando preparando patches para múltiplos arquivos: isso inclui a habilidade de excluir certos diretórios ou arquivos, e o tratamento de arquivos que tenham sido adicionados ou removidos.

Antes de distribuir um patch para outras pessoas, você deve sempre testá-lo primeiro. Você pode usar o comando diff para ver se um patch funcionou da maneira esperada. Depois de aplicar mychanges.diff a uma cópia do seu diretório bash-2.05, você pode usar diff -r para comparar a versão recentemente remendada com a versão que você modificou.

Vamos então verificar se o mychanges.diff aplicado ao bash-2.05 lhe dá em definitivo a mesma árvore que o bash-2.05-mychanges. Para fazer isso, faça uma nova cópia do código-fonte e aplicando o patch a ela:

$ cp -a bash-2.05 bash-2.05-test
$ cd bash-2.05-test
$ patch -p1 -i mychanges.diff
patching file mailcheck.c
(and other modified files . . .)
$ cd ..
$ diff -cr bash-2.05-mychanges bash-2.05-test

Se tudo estiver bem com o comando patch, não haverá qualquer diferença e o comando diff final não mostrará nada. Se você não estiver interessado em detalhes sobre as diferenças, só se os diretórios são ou não diferentes, adicione a opção -q ou --brief.

Outras Informações

Você deve agora ter um conhecimento básico sobre os princípios de criação e aplicação de patches. Há muitas outras opções para os comandos diff e patch que fogem ao escopo deste artigo – isso inclui opções para ignorar espaços, tabulação e linhas vazias, mesclar arquivos com preprocessamento condicional, controle de “fuzz-factors” do patch e seleção de subconjuntos diferentes usando expressões regulares.

Há também várias ferramentas de linha de comando relacionadas, tais como cmp, sdiff, e diff3, que podem ser utilizadas para comparações de arquivos mais avançadas. O comando diff3 permite comparação de arquivos em 3-vias, comparando dois arquivo com um ancestral comum – isso é útil em situações onde duas pessoas têm modificados um único arquivo de maneira independente, e suas mudanças precisem ser reconciliadas.

Mais informações sobre estas opções e comandos podem ser encontradas no manual GNU "Comparing and Merging Files with GNU diff and patch" (ISBN 0-9541617-5-0). Cópias impressas estão disponíveis na Network Theory Ltd.

Este artigo está disponível sob a licença GNU Free Documentation License.