Subversion: Controlo total sobre o Software

Revisões

O conceito de Revisões

Um svn commit introduz as alterações a qualquer número de ficheiros e directorias numa só transacção atómica (para além de indicar no output que ficheiros ou directorias foram modificados). Na cópia de trabalho pessoal de cada utilizador, podem-se alterar os conteúdos dos ficheiros, criar, apagar, renomear e copiar ficheiros e directorias, e só por fim fazer um commit a todo o conjunto de alterações englobando assim todas as alterações como se se tratasse de um só bloco de alterações.

No repositório, cada commit é tratado como uma operação atómica: ou o commit acontece de uma só vez a todos os ficheiros ou não acontece a nenhum. O Subversion tenta manter sempre esta atomicidade para que chrashes, problemas de rede ou outro tipo de situações não possam prejudicar o software desenvolvido.

De cada vez que o repositório aceita um commit, é criado um novo estado da árvore do sistema de ficheiros, chamado de revisão. A cada revisão é atribuído um número natural (1, 2, 3, …, n) único e maior uma unidade do que o número de revisão anterior (incrementa sempre 1 unidade). A revisão inicial de um repositório acabado de criar tem o número de revisão 0, e consiste apenas numa directoria-raíz vazia.

A figura seguinte ilustra uma forma simples de vermos um repositório. Imagine-se um array de números de revisão, começando pelo 0, e aumentando da esquerda para a direita. Cada número de revisão tem uma árvore de ficheiros associada a essa revisão, e cada número de revisão é assim uma maneira de vermos como está um repositório após um commit, como se fosse uma fotografia dos ficheiros do repositório.

Subversion: árvore de ficheiros em diferentes revisõesNúmeros de Revisão Globais

Os números de revisão do Subversion aplicam-se a árvores inteiras de ficheiros, e não a ficheiros individuais. Cada número de revisão escolhe uma árvore inteira de ficheiros, que é um estado particular do repositório após algumas alterações terem sido aplicadas com o comando commit. Outra forma de se pensar é que a revisão N representa o estado dos ficheiros do repositório após o N-ésimo commit. Quando se fala, por exemplo, na revisão 7 de um ficheiro ficheiro.c está-se a referir ao ficheiro.c como este aparece na revisão 7. Note-se que em geral, as revisões N e M de um ficheiro podem não diferir: a revisão é feita globalmente à árvore do sistema de ficheiros e não aos ficheiros individuais; por isso, se um utilizador alterar alguns ficheiros e fizer um commit, os ficheiros não alterados passam também a ter um número de revisão superior apesar de não terem sido alterados por esse utilizador. O CVS usa a revisão por ficheiros individuais ao contrário do Subversion e é normal haver alguma dificuldade em usar o Subversion para utilizadores que estão habituados a usar o CVS.

É importante notar que cópias de trabalho nem sempre correspondem a uma única revisão do repositório; estas podem conter ficheiros de diferentes revisões. Por exemplo, suponha que se faz checkout de uma cópia de trabalho de um repositório cuja revisão mais recente é a 4:

ProjX/Makefile:4
       ficheiroA.c:4
       configure:4
       ...

Neste momento, esta cópia de trabalho corresponde exactamente à revisão 4 do repositório. No entanto, suponha que se faz uma alteração ao ficheiroA.c, e que se faz commit a essa alteração. Assumindo que não foram feitos mais commits, vai ser criada a revisão 5 do repositório, e a cópia de trabalho vai parecer-se com isto:

ProjX/Makefile:4
       ficheiroA.c:5
       configure:4
       ...

Suponha que, a partir deste momento, a Alexandra faz commit a uma alteração no ficheiro ficheiroA.c, criando assim a revisão 6. Se se fizer svn update para colocar a cópia de trabalho actualizada, então teremos:

ProjX/Makefile:6
       ficheiroA.c:6
       configure:6
       ...

A alteração que a Alexandra fez no ficheiroA.c vai aparecer na cópia de trabalho, e essa mudança vai também estar reflectida nos outros ficheiros que não foram alterados. Neste exemplo, o texto da Makefile é o mesmo para as revisões 4, 5 e 6, mas no entanto, o Subversion marca na cópia de trabalho a revisão 6 para a Makefile indicando assim que a revisão deste ficheiro ainda é a mais actual. Por isso, após se fazer um update à cópia de trabalho, isso corresponderá exactamente a uma única revisão no repositório.

Ter revisões diferentes e misturadas num repositório é normal

De cada vez que um utilizador faz svn commit, a sua cópia de trabalho acaba por ficar com uma mistura de revisões. As alterações a que se fez um commit são marcadas com um número de revisão superior a tudo o resto. Após vários commits (sem updates pelo meio) a cópia de trabalho desse utilizador tem uma mistura de revisões diferentes. Mesmo que só haja um utilizador a alterar vários ficheiros, ele vai notar nesta característica do SVN. Para se ver a mistura de revisões existentes numa cópia de trabalho pode-se usar o comando svn status --verbose.

Frequentemente, novos utilizadores não estão a par de que a sua cópia de trabalho tem diferentes revisões. Isto pode ser confuso porque muitos clientes SVN são sensíveis à revisão de trabalho de um ficheiro que estejam a examinar. Por exemplo, o comando svn log é usado para mostrar um histórico das alterações a um ficheiro ou directoria, mas se o número de revisão desse ficheiro ou directoria for bastante antigo (possivelmente porque há já muito tempo que o utilizador não faz um svn update), então o histórico da versão mais antiga é que será mostrado ao utilizador.

Revisões misturadas são úteis

Se um projecto é suficientemente complexo, por vezes é útil fazer um retrocesso a algumas porções da cópia de trabalho para uma revisão mais antiga. Às vezes é útil testar uma versão mais antiga de um sub-módulo dentro de uma sub-directoria, ou talvez seja necessário saber quando um bug foi criado num ficheiro específico. Este é o aspecto de máquina do tempo de um sistema de controlo de versões – a característica que permite a um utilizador mover qualquer porção da cópia de trabalho para uma revisão mais antiga ou mais recente.

Limitações das revisões misturadas

No entanto, o uso de revisões diferentes e misturadas numa cópia de trabalho tem limitações à sua flexibilidade.

Em primeiro lugar, não se pode fazer commit quando se apaga um ficheiro ou uma directoria que não esteja com a revisão mais actual. Se uma versão mais recente do ficheiro existe no repositório, a tentativa de o apagar vai ser rejeitada, para prevenir que se apaguem acidentalmente alterações que o utilizador ainda não teve conhecimento.

Por fim, não se pode fazer commit numa alteração às propriedades de uma directoria (metadata) sem que esta esteja totalmente actualizada com a última revisão. A revisão de uma directoria de trabalho define um conjunto de entradas e de propriedades, e portanto, fazer commit a uma alteração às propriedades de uma directoria fora-do-prazo pode destruir propriedades que o utilizador ainda não saiba que existem.

Revisões: Números, Palavras-chave e Datas

Antes de se apresentar os exemplos de uso do SVN, convém reter mais algumas noções sobre os números de revisão, nomeadamente como se identificam as revisões num repositório.

As revisões são especificadas usando o switch --revision (ou -r) seguido do número de revisão que se pretende (svn --r NUM-REV), ou especificando um intervalo separando duas revisões por dois pontos (svn -r NUM-REV1:NUM-REV2). Posteriormente, o SVN identifica estas revisões pelo número, pela data ou por uma palavra-chave.

Números de Revisão

Quando se cria um repositório SVN, este começa a sua vida útil com a revisão zero e cada commit sucessivo aumenta o número de revisão em uma unidade. Após o commit ter terminado, o cliente de SVN informa o novo número de revisão:

$ svn commit --message "Corrigi o bug XPTO!!"
Sending        ficheiroA.c
Transmitting file data .
Committed revision 12.

Se a qualquer momento no futuro se quiser referenciar esta revisão, pode-se referenciá-la como a revisão 12.

O cliente de SVN percebe um número de palavras-chave relacionadas com as revisões. Estas palavras-chave podem ser usadas em vez dos números de revisão com o switch --revision, e são depois resolvidas de novo para números específicos de revisão pelo SVN, sendo elas as seguintes:

  • HEAD: a última (mais recente) revisão no repositório;
  • BASE: o número de revisão de um item na cópia de trabalho. Se o item foi modificado localmente, a versão BASE refere-se ao item sem essas modificação locais;
  • COMMITTED: a mais recente revisão antes, ou igual a BASE, de um item ter sido alterado;
  • PREV: a revisão imediatamente antes da última revisão na qual um item foi alterado (tecnicamente é igual a COMMITTED-1).

Nota: as palavras-chave PREV, BASE e COMMITTED pode ser usadas para referenciar caminhos (paths) locais, mas não URLs.

Eis alguns exemplos do uso de palavras-chave de revisão:

$ svn diff --revision PREV:COMMITTED ficheiroA.c

Mostra a última alteração feita ao ficheiroA.c (último commit).

$ svn log --revision HEAD

Mostra o log do último commit feito ao repositório.

$ svn diff --revision HEAD

Compara o ficheiro de trabalho (com eventuais alterações locais) com a última versão no repositório.

$ svn diff --revision BASE:HEAD ficheiroA.c

Compara a versão do ficheiroA.c (sem alterações locais feitas pelo utilizador) com a última versão no repositório.

$ svn log --revision BASE:HEAD

Mostra todos os logs dos commits feitos desde a última vez que o utilizador actualizou a cópia de trabalho local.

$ svn update --revision PREV ficheiroA.c

Retrocede a última alteração feita ao ficheiroA.c (a revisão de trabalho do ficheiroA.c é decrementada).

Estas palavras-chave permitem realizar várias operações importantes e rapidamente sem estar a ver especificamente quais os números de revisão que interessam da cópia de trabalho ou ainda qual é o último número de revisão da cópia de trabalho local.

Datas de Revisão

Quando se especifica um número de revisão ou uma palavra-chave de revisão, pode-se também especificar uma data dentro de { }. Pode-se ainda aceder a um intervalo de alterações no repositório usando em conjunto datas e números de revisão.

Eis alguns exemplos dos formatos de datas que o Subversion aceita. Não esquecer de usar { } à volta das datas.

$ svn checkout --revision {2006-10-25}
$ svn checkout --revision {15:30}
$ svn checkout --revision {15:30:00.200000}
$ svn checkout --revision {"2006-10-25 15:30"}
$ svn checkout --revision {"2006-10-25 15:30 +0230"}
$ svn checkout --revision {2006-10-25T15:30}
$ svn checkout --revision {2006-10-25T15:30Z}
$ svn checkout --revision {2006-10-25T15:30-04:00}
$ svn checkout --revision {20061025T1530}
$ svn checkout --revision {20061025T1530Z}
$ svn checkout --revision {20061025T1530-0500}

Quando se especifica uma data de revisão, o Subversion encontra a versão mais recente do repositório naquela data:

$ svn log --revision {2006-10-25}
-----------------------------------------------------------------------
r12 | ira | 2006-10-25 15:30:00 -0600 (Wed, 25 Oct 2006) | 14 lines
...

Se se especificar uma única data sem incluir uma hora do dia (por exemplo, 2006-10-25), pode pensar-se que o Subversion deverá dar a última revisão que aconteceu no dia 25 de Outubro. Em vez disso, obtém-se uma revisão de dia 24 ou ainda anterior a este dia. Recorde-se que o Subversion encontra a revisão mais recente do repositório até à data que se introduziu. Quando se deu a data de 2006-10-25, o Subversion assumiu a hora de 00:00:00, por isso ao pesquisar pela revisão mais recente, não devolveu nada no dia 25. Se se quiser incluir o dia 25 na pesquisa, pode-se especificar o dia 25 com o tempo ({2006-10-25 23:59}), ou apenas ({2006-10-26}).

Pode-se também usar um intervalo de datas. O Subversion encontra todas as revisões entre as duas datas inclusivé:

$ svn log --revision {2006-10-14}:{2006-10-25}

Podemos também misturar datas e números de revisão, como mencionado em cima:

$ svn log --revision {2006-10-14}:1453

Nota: o timestamp de uma revisão é guardado como uma propriedade da revisão – é uma propriedade sem versão, que pode ser modificada. Uma vez que podem ser alterados os timestamps (representando assim um momento cronológico que não é correcto), ou até mesmo removidos, o Subversion não conseguirá converter correctamente datas para números de revisão se estas propriedades forem modificadas.

Estado das cópias de trabalho

Para cada ficheiro numa directoria de trabalho, o Subversion grava duas informações na pasta administrativa, .svn/:

  • a revisão em que se está a trabalhar num determinado ficheiro (a chamada revisão de trabalho desse ficheiro), e
  • um timestamp que guarda a data da última actualização feita ao repositório para esse ficheiro local.

Dadas estas duas informações, e ao comunicar com o repositório, o Subversion pode indicar em que estado um ficheiro local se encontra (de um total de 4 estados possíveis):

  • Sem modificações, e actualizado: o ficheiro não tem modificações na directoria de trabalho, e não houve commits feitos ao repositório desde a revisão de trabalho desse ficheiro. Um svn commit a este ficheiro não vai alterar nada no seu conteúdo e um svn update também não vai alterar nada à revisão do ficheiro.
  • Com modificações locais, e actualizado: o ficheiro foi alterado na directoria de trabalho, mas não foi feito nenhum commit ao repositório desde a sua revisão anterior. Há alterações locais às quais não foi feito nenhum commit, logo um svn commit vai colocar as alterações no repositório de SVN, e um svn update não vai alterar nada ao conteúdo desse ficheiro.
  • Sem modificações locais, e desactualizado: o ficheiro não teve alterações na directoria de trabalho, mas foi modificado por outro utilizador no repositório. O ficheiro deverá eventualmente ser actualizado, para o colocar com a última revisão de acordo com a revisão no repositório. Um svn commit ao ficheiro não fará nada, mas um svn update vai colocar esse ficheiro com as últimas modificações feitas na cópia de trabalho.
  • Com modificações locais, e desactualizado: O ficheiro foi alterado pelo utilizador na cópia de trabalho local, e no repositório por outro utilizador. Um svn commit do ficheiro vai falhar com o erro de desactualizado. O ficheiro deverá ser actualizado primeiro; um svn update vai tentar fundir ou unificar as alterações públicas do repositório com as alterações locais. Se o Subversion não conseguir fazer essa fusão de uma forma automática, então cabe ao utilizador resolver manualmente este conflito.

O comando svn status mostra o estado de um item numa cópia de trabalho e é bastante útil para ver o estado dos ficheiros numa directoria de trabalho local.

Cópias de trabalho com revisões diferentes

O Subversion tenta sempre manter uma certa flexibilidade, tanto quanto possível, para permitir que se possa trabalhar numa cópia de trabalho com ficheiros e directorias com diferentes números de revisão. Infelizmente, isto pode ser um pouco confuso, mas facilmente se percebe a utilidade de ter números de revisão diferentes numa cópia de trabalho.

Uma das regras fundamentais do Subversion é que um push ao repositório não causa um pull, e vice-versa. Ou seja, quando se submetem alterações ao repositório (push) isso não significa que se vá receber as alterações feitas pelos outros utilizadores (pull). E se um utilizador estiver a fazer modificações que ainda não tenham sido passadas ao repositório com um commit, fazendo svn update vai unificar as últimas alterações do repositório com as alterações em curso deste utilizador, sem que o utilizador seja obrigado a fazer commit às alterações que estava a fazer.

O efeito principal desta regra é que uma cópia de trabalho tem que ter um trabalho extra em tomar nota de todas as revisões diferentes que há na cópia de trabalho, e de ser flexível com as diferentes revisões para permitir que existam localmente. O que também complica neste processo é o facto de as próprias directorias terem também versões.

Por exemplo, suponha que se tem uma cópia inteira de trabalho com a revisão 10. Edita-se o ficheiro xpto.html e faz-se em seguida um svn commit, que cria a revisão 16 no repositório. Após o commit ter sido bem sucedido, muitos utilizadores pensarão que a cópia de trabalho local está toda com a revisão 16, o que não acontece! Qualquer número de alterações pode ter sido feito ao repositório entre as revisões 10 e 16. O cliente de SVN não sabe de nenhumas dessas alterações ao repositório, uma vez que o utilizador local ainda não fez nenhum svn update, e um svn commit não traz para a cópia de trabalho as alterações feitas aos outros ficheiros entretanto. Por outro lado, se um svn commit fizesse um download automático das novas alterações que estavam no repositório, então isso iria colocar a cópia de trabalho local na revisão 16 – mas aí estaríamos a quebrar a regra de que um push e um pull são tarefas separadas. Portanto, a única acção segura que o cliente de SVN tem que fazer é de colocar o ficheiro xpto.html com a revisão 16. O resto da cópia de trabalho fica com a revisão 10. Só depois de se fazer um svn update é que as restantes alterações vão ser descarregadas, e toda a cópia de trabalho passa a ficar marcada com a revisão 16.