sábado, 5 de março de 2011

Como Remover um Commit Errado no SVN? Ou: Como Desfazer um Commit no Subversion?

Este artigo é dedicado a todos aqueles que já enviaram um commit equivocado para o SVN e quebraram ou estragaram alguma coisa no sistema onde trabalham. Quem nunca fez isso antes, que atire a primeira pedra!!
Bom, mas vamos lá.... A reposta para a pergunta acima é bem simples: não há como. Meu Deus, então agora estou perdido?? Calma, não se preocupe!! Existe uma técnica que lhe permite reverter o seu repositório para um estado anterior, se for realmente preciso. Acompanhe nosso artigo.


  • Por Que não há uma Forma Direta de Desfazer Commits??

Se desejar, você pode ir imediatamente para a solução do problema, lá embaixo. Entretanto, eu resolvi incluir esta seção aqui para lhe ajudar a pensar mais sobre como funciona o SVN e quais as suas premissas, aumentando seu conhecimento sobre o mesmo.
O SVN armazena seus dados em um banco de dados interno (ou em BDB - Berkeley Data Base, ou em FSFS). Evidentemente, seria possível fazer operações comuns a bancos de dados, como delete ou roll back. Contudo, o Subversion não permite qualquer tipo de remoção de entradas (inserts) de dados que foram efetivadas, sob risco de comprometer a segurança do repositório. Observe o risco a que os desenvolvedores estariam sujeitos e a falta de confiança que se geraria sobre os repositórios se isso fosse possível, considerando cenários como:

  1. Um desenvolvedor ou administrador de sistemas é demitido. Num acesso de raiva e considerando-se injustiçado, ele remove grande parte dos commits dos repositórios da empresa, deixando-os em estados muito primitivos e perdendo grande parte dos códigos gerados durante anos.
  2. Um desenvolvedor mal intencionado pratica sabotagem industrial: elimina ou deturpa parte do código do sistema antes do mesmo ser compilado e o envia para produção. Após a implantação do sistema danificado, o desenvolvedor remove o trecho defeituoso que criou, eliminando a única prova concreta de sua ação.
  3. Imagine que ninguém será demitido e não há qualquer pessoa mal intencionada. Ao contrário: todos trabalham felizes e freneticamente. Contudo, a equipe tem o hábito de, vez por outra, remover um commit mal feito. A uma certa altura, descobre-se um erro grave no sistema que está há mais de 1 mês em produção. A correção parece simples e poderia ser feita em cerca de um dia. Contudo, a quantidade de commits removidos impede que se consiga restaurar o código exato da versão que está em produção. Resultado: o erro vai persistir lá até que a versão seguinte seja totalmente finalizada e implantada...

Por motivos como estes, a remoção direta de commits não é possível. No entanto, existe a solução paliativa, um pouco mais agressiva, porém que funciona e é razoavelmente fácil e segura. Acompanhe-a na próxima seção!!


  • Removendo um Commit Indesejado

Se é realmente necessário remover um ou mais commits do SVN, e espero que tenham sido os últimos, então a única solução é fazem um dump do repositório filtrado pelo número de revisão, eliminá-lo, recriá-lo e restaurar o dump com um load, de forma a carregar no repositório recém-criado todas as revisões até a última antes das indesejadas. Todas estas ações devem ser feitas como root!! Vamos fazer isso passo a passo...

1. Fazendo o dump:
Considere que seu repositórios se chame "repo" e você está no diretório onde ele se encontra. Primeiramente, pare todos os acessos ao repositório, para congelá-lo (ou seja, pare o Apache, se você usa o WebDAV, ou impeça o login via SSH de usuários comuns, se você usa o protocolo svn+ssh://). Descubra o número da última revisão boa (desejada), a partir da qual todas serão eliminadas (suponhamos: revisão 200), para que façamos o dump de tudo até ela. Agora, considerando esta revisão, faça o dump:

svnadmin dump repo --revision 0:200 > repo.dump

É criado o arquivo repo.dump, que contém todas as revisões deste o tenro início de seu repositório até a revisão informada, de número 200.

2. Remova o repositório e crie outro idêntico:
Se você quiser, por segurança, pode mover o repositório para outro nome ao invés de apagá-lo, de forma a manter uma cópia de segurança do mesmo, caso alguma operação falhe. Você pode também fazer um backup do diretório completo do repositório ou armazená-lo em um arquivo .tar.gz, para economizar espaço, caso o repositório seja muito grande.

rm -rf repo
svnadmin create --fs-type bdb repo

Isto recria o repositório com mesmo nome, só que agora completamente vazio.

3. Agora sim, subindo tudo!!
Rode o comando, considerando o arquivo de dump gerado e o repositório novo recém-criado. Observe que, evidentemente, convém utilizar o mesmo nome do repositório antigo, para que outras configurações de seu sistema permaneçam válidas:

svnadmin load repo < repo.dump

Se tudo deu certo até aqui, você terá seu repositório recuperado até a revisão que você informou no dump, de forma que as posteriores foram ignoradas e serão perdidas.

4. Para finalizar, não esqueça deste procedimento!! Atribua as mesmas permissões de arquivos, dono e grupo originais às pastas do repositório, especialmente ao diretório "db" e seus arquivos. Se você usa o WebDAV, por exemplo, terá de tornar os arquivos pertencentes ao usuário "www-data" (ou o nome que tem seu usuário do Apache), e não "root", como é inicialmente criado. Não esqueça de verificar se os diretórios estão marcados como executáveis, se for o caso, para permitir a criação de novos arquivos de log no diretório "db".

5. Teste tudo bem detidamente, para evitar surpresas. Em tudo funcionando, finalmente, faça a faxina: elimine ou faça backup do repositório antigo e dos arquivos de dump que ainda estão no disco.

Bom, pessoal, testei realmente todos estes comandos, embora eu mesmo já tenha me visto em situações de utilizar este mecanismo para eliminar commits errados. Torço que tudo dê certo com vocês!!

Mais detalhes sobre administração de repositórios, consultem o manual do SVN (SVN Red Book), Capítulo 5. Veja também nosso artigo completo sobre Como Fazer Backup de Repositórios SVN. E, claro, COMENTEM!!!

17 comentários:

Anônimo disse...

Parabéns pelo post.
Me foi muito útil hoje ;-)

Claudemir

O Pajé disse...

Olá Claudemir,

Fico feliz de ter podido ajudar!! Estamos aqui para isso!!

Volte sempre ao pajeonline!!

Abraços,
Pajé

Anônimo disse...

Olá Pajé!

Obrigado por compartilhar este conhecimento. Foi vital para desfazer besteiras e deixar o repositório de alguns projetos impecável.

Um abraço!

O Pajé disse...

Olá Amigo,

Muito obrigado pela mensagem!! Eu mesmo já usei diversas vezes este procedimento, no intuito de manter o repositório sempre em ordem.
Torço que este blog continue sempre ajudando a todos!!

Anônimo disse...

Cara.. na boa.. estuda antes de escrever isso.. refazer repositório?
procure pelo menos a função "revert" e entenda ela antes de falar abobrinha!!

O Pajé disse...

Amigo anônimo, por que a arrogância? Aqui no Pajé Online não cultivamos agressividade, apenas trocas de conhecimentos. Se tem a contribuir, seja bem-vindo; caso contrário, nos poupe de seus comentários.

Alguns clientes de SVN têm a chamada função revert. Ela não tem relação com o que estamos falando no artigo. Revert, nos clientes, significa abandonar as modificações locais para se conformar com o último estado do repositório. É usado quando vc faz muitas modificações e nada deu certo, então vc abandona o caminho que seguiu e quer voltar ao início, revertendo o seu código naquele que supostamente está saudável, no repositório.

O Pajé disse...

Para aqueles que ficaram em dúvida, um link confiável, do svnbook:

http://svnbook.red-bean.com/en/1.7/svn.ref.svn.c.revert.html

Observe a descrição:

"svn revert — Undo all local edits."

Percebem?? "Undo all local edits" = desfaz todas as alterações LOCAIS.
Esta função não volta o repositório para estados anteriores, mas sim retorna a cópia CLIENTE do usuário ao estado em que está o repositório, vindo, conseqüentemente, a abandonar as alterações locais.

Usem este recurso apenas se vcs tomaram definitivamente um caminho que se mostrou inútil, pois, depois de revertido, o arquivo, diretório ou arquivos afetados não poderão ser recuperados.

Abraços a todos!!

Pajé

Unknown disse...

Eu apaguei um arquivo do repositório e coloquei outro com o mesmo nome, o histórico do deletado sumiu, tem como recuperar? owcpirez@gmail.com

O Pajé disse...

Tem como recuperar sim. Você deve identificar qual é o número de revisão com que o novo arquivo inicia e fazer um checkout do diretório onde ele está, porém com a revisão anterior a este número.

Exemplo: vc tem um diretório "web" que contém o arquivo "index.php". Suponha que vc apagou este arquivo e depois subiu outro com o mesmo nome. Este novo, com o mesmo nome, tem revisão número:

web
index.php - 2055

O histórico dele mostra:

index.php - 2055
index.php - 2050
index.php - 2048

Pronto!! O primeiro commit dele foi em 2048. Agora, faça um novo checkout do diretório web, porém pedindo que, ao invés de trazer o HEAD, traga a revisão 2046 (supondo que a 2047 seja a que apaga o arquivo). Isto pode trazer seu arquivo de volta.
Outra opção é vc ver o histórico do diretório "web" e ler as mensagens, identificando exatamente qual a revisão que apaga o arquivo, para vc fazer o checkout da anterior.

IMPORTANTE: sempre insira comentários nos commits, para que fique mais fácil identificar o ponto desejado em pesquisas como essa!!

Espero ter ajudado!!

Pajé

Allysson disse...

Pagé, fiz um comentário anterior de forma, conforme citado por você, de forma arrogante. Reconheço o erro e primeiramente gostaria de pedir desculpas por tal fato.. acredito que o stress do dia-a-dia e o fato de ver muita "abobrinha" solta na internet fizeram eu trocar as palavras.

Feito isso, gostaria de acrescentar que existe sim como "retirar" uma revisão do repositório com a função "revert changes from this revision". Segue documentação de tal função:

http://tortoisesvn.net/docs/release/TortoiseSVN_en/tsvn-howto-rollback.html

espero ter colaborado e reforço o pedido de desculpas.

O Pajé disse...

Opa Allysson,

Pedidos de desculpas aceitos!! Fique tranquilo, compreende as tensões pelas quais atravessamos em nossa área!! Por isso mesmo invisto tanto no Pajé em minhas horas vagas: para ajudar as pessoas com tutoriais e dicas práticas e rápidas nos momentos em que mais se precisa. Porque nem sempre podemos parar para ler os manuais.

Visitei o link que vc enviou e creio que vc está correto: o seu cliente de SVN, o TortoiseSVN, oferece esta funcionalidade de recuperar uma versão antiga, ou seja, desfazer um ou mais commits. Neste caso, vc trouxe uma ótima contribuição, da qual eu desconhecia!! Muito obrigado!!

Note, contudo, que isto não é um comportamento padrão do SVN. Os clientes que conheço (conheço vários, embora não todos) não têm essa funcionalidade.
Imagino que consigo explicar o que ocorre... O Tortoise, pela proximidade de sua equipe com os desenvolvedores do próprio SVN, tornou-se de fato um excelente cliente e inclui muitos scripts internos que agilizam várias tarefas desejáveis, como a implementação do Compare Editor para documentos binários do Office e LibreOffice, que não é padrão do SVN, mas o Tortoise faz sem sustos. Imagino que esta funcionalidade esteja no rol destes casos: eles implementaram um script complexo que, conhecendo bem as entranhas do SVN, consegue acessar o banco de dados diretamente e dar roll back, sem ser intermediado pelos comandos padrões, possibilitando, assim, esta operação. Parabéns para a equipe do Tornoise!!

Para aqueles que desenvolvem clientes para o SVN: o Tortoise é código aberto e, portanto, caso seja de interesse, podemos baixá-lo para compreender o caminho pelo qual ele conseguiu implementar esta funcionalidade. Quanto ao Compare Editor, existem na internet muitos scripts que podem ser facilmente embarcados para acionar os comparadores do Office e LibreOffice e criar um Compare Editor binário para seus formatos.

Pajé

João Luís B. Almeida disse...

Olá Pajé. Utilizei os comandos que o Alysson mencionou, mas as revisões não foram excluídas. Esse comando reverte uma revisão, mas não a exclui. Eu preciso excluir algumas revisões indesejadas que estão com arquivos de configurações que guardam senhas de usuários. O meu projeto é Open Source e a senha está visível para todos. Gostaria de remover esses arquivos das revisões. O meu projeto está no Google Code e uso o TortoiseSVN. Existe uma forma simples de fazer isso?

O Pajé disse...

Olá João Luís,

Desculpe a demora em responder... pelo que entendi de sua descrição, o problema é este: vc tem um repositório SVN no Google Code e fez commit, por engano, de arquivos com dados sensíveis (senhas de usuários). Agora, quer remover estes arquivos do histórico de revisões (e não apenas do HEAD).

Bom, a resposta inicialmente é bem simples: não há como fazer isso, já que o SVN se propõe justamente a fazer todo o esforço para que nunca se perca nenhum dado, ou seja, para que isto nunca aconteça.

Como o projeto está no Google Code, vc não deve ter como recriar o repositório, conforme ensino no artigo. Neste caso, o melhor a fazer é entrar em contato com o suporte da Google e, se não for possível, remover o projeto do Google Code, fazendo antes o dump. Depois disso, crie o projeto de novo, revertendo o Dump que vc tem, mas excluindo os commits errados. Vai dar um certo trabalho, mas pode ser que resolva.

Anônimo disse...

amigo imagine q eu tenho um repositorio, o mesmo tem varios usuarios, um ADM e o restante membro como eu faco para q os membros n possam deletar arquivos somente incluir ou editar, apesar de simples n encontro a resposta, grato!

Anônimo disse...

amigo como eu faco para q um usuario n tenha acesso para deletar arquivos qdo for dar o seu commit, no caso o msm so tem permissao para acescentar e editar mas n deletar
apesar de simples n acho a resposta

Willian Santos disse...

Pajé

Fui realizar um CLEAN UP do SVN e selecionei a opção (delete unversioned files and folders)assim apagou todas as pastas da minha area de trabalho, tem como reverter isso?

Email:widsantos@gmail.com

O Pajé disse...

Olá Willian,

Se os arquivos e pastas que você apagou não estavam versionados (unversioned), então eles nunca foram enviados ao seu repositório do SVN, havendo o tempo todo permanecido na sua máquina, em cópia única. Significa que, uma vez que eles foram apagados, a recuperação só poderá ser feita através de mecanismos de recuperação de arquivos apagados em seu computador, e isto depende do seu sistema operacional e do sistema de arquivos que você usa. Em outras palavras, se o seu cliente de SVN identifica um determinado arquivo ou pasta como "unversioned", então ele nunca foi versionado e nunca esteve presente no repositório. Por isso, nunca foi feito um commit dele, e não é possível extraí-lo de qualquer revisão do SVN, pois nunca esteve lá. Assim, seu problema não é de uso do SVN, mas sim de como recuperar um determinado conjunto de dados que foram acidentalmente apagados de seu computador.

Pajé.