Manter acessível um registo histórico das várias versões de um conjunto de ficheiros, ao longo do tempo.
O CVS é uma ferramenta de open source para controlo de versões que segue um modelo cliente-servidor. Existe um repositório centralizado dos dados (ficheiros) que estão sobre controlo de versões. Esse repositório está no servidor ao qual vários clientes se podem ligar para obter cópias locais. Essas cópias locais podem ser modificadas e essas modificações propagadas de volta para o repositório; ou seja, nunca há interacções directas entre clientes.
O repositório regista todas as alterações efectuadas em cada ficheiro e permite o acesso a qualquer versão, em qualquer momento.
Um repositório é meramente um directório que contém informação especial para controlar as versões dos ficheiros que aí forem colocados. Os ficheiros são armazenados no repositório, recorrendo à estrutura do sistema de ficheiros do servidor, ou seja, em sub-directórios. Cada directório (ou sub-directório) pode ser visto como um módulo. Um módulo é simplesmente uma colecção de ficheiros com alguma relação semântica entre si. Nunca se alteram directamente ficheiros no repositório. As alterações são sempre feitas em cópias locais que são depois enviadas de volta para o repositório, através de comandos do CVS.
Na figura anterior pode ver-se um repositório composto por três módulos. Cada cliente pode obter cópias locais de módulos ou ficheiros que estejam no repositório, em qualquer versão1. Por exemplo, o Cliente 1, tem uma cópia local:
As versões podem ter nomes (ver Etiquetas). O nome HEAD é um nome especial que representa sempre a versão mais recente. Ou seja, o Cliente 1, pediu ao repositório a versão mais recente (na altura do pedido) dos ficheiros contidos no "módulo1" e a versão "v1" dos ficheiros contidos no "módulo2".
O modelo de funcionamento do CVS permite que múltiplos clientes estejam ao mesmo tempo a efectuar alterações ao mesmo ficheiro. Este modelo denomina-se unreserved checkout model, ou seja a obtenção de uma cópia de um ficheiro por parte de um cliente não impede outro cliente de obter outra cópia do mesmo ficheiro e modificá-la concorrentemente. Assim sendo, as equipas de trabalho devem ter cuidado ao distribuir o trabalho, com vista a evitar potenciais conflitos.
Cada ficheiro tem associado um conjunto crescente de versões. De cada vez que um cliente envia modificações a um ficheiro para o repositório (commit), é criada uma nova versão desse ficheiro com as alterações que foram entretanto efectuadas. No exemplo da figura seguinte, o Ficheiro1.txt tem 4 versões. Quando foi inicialmente colocado no repositório ficou na versão 1.1. Em seguida um cliente obteve uma cópia do ficheiro, alterou-a e enviou as alterações para o repositório que as registou na versão 1.2, e assim sucessivamente. A versão actual do ficheiro (1.4), tem o nome lógico "HEAD".
Cada ficheiro tem, na realidade, uma árvore de versões associada. Tal acontece, porque é possível criar ramos (branches) de desenvolvimento paralelos. Assim torna-se possível, por exemplo, experimentar variações sem interferir no ramo principal de desenvolvimento, usufruindo na mesma do sistema de controlo de versões. Mais tarde, é possível efectuar a composição (merge) das alterações realizadas num ramo com qualquer outro, tipicamente o ramo principal. Aconselha-se cuidado na utilização de ramos! Se se utilizarem ramos deve-se tentar efectuar regularmente o merge com o ramo principal, a fim de evitar que o desenvolvimento paralelo se afaste muito do principal, podendo aumentar significativamente o risco de conflitos. Quanto mais conflitos mais difícil se torna a operação de merge.
Dica: Uma forma de tentar reduzir os conflitos consiste em efectuar uma clara divisão do trabalho entre os elementos da equipa e manter uma constante comunicação. Desta forma todos sabem o que se passa no plano de desenvolvimento e evita-se a sobreposição de trabalho. Isto é muito importante e deve ser sempre tido em conta, não somente quando se utilizam ramos!
Uma das principais vantagens dos sistemas de controlo de versões é poder aceder facilmente às versões passadas dos dados. É frequente no decorrer do desenvolvimento necessitarmos de aceder a versões anteriores, pelas mais variadas razões: porque apagámos sem querer alguma coisa importante, porque experimentámos modificações que afinal não queremos manter, etc.
Tal como já foi dito é possível pedir ao repositório de CVS qualquer ficheiro em qualquer versão. Mas isto não é suficientemente prático. Tipicamente, o que pretendemos é obter um conjunto de ficheiros num estado coerente, por exemplo, "ontem às 12h". Ora, como cada ficheiro tem a sua própria árvore de versões, "ontem às 12h", pode corresponder ao ficheiro1.txt na versão 1.3 e ao ficheiro2.txt na versão 1.2.2. Isto é complicado (praticamente impossível) lembrar. Para isso existem as etiquetas (tags). As etiquetas são nomes lógicos que podemos dar a um conjunto de ficheiros numa dada versão.
Imaginemos que ontem às 12h, chegámos a um estado importante do desenvolvimento que queremos marcar. Podemos fazê-lo, dando uma etiqueta que tenha significado, sendo possível mais tarde obter todos os ficheiros que tenham essa etiqueta atribuida numa determinada versão.
A figura seguinte exemplifica com duas etiquetas "RELEASE-0.4" e "RELEASE-0.5". No caso do exemplo anterior, estaríamos interessados na "RELEASE-0.4".
Nota: O CVS permite obter versões dos ficheiros de três formas distintas: por versão, por etiqueta e por data. Assim sendo "ontem às 12h", também se podia obter mesmo que não houvesse a etiqueta "RELEASE-0.4". Mas normalmente, não sabemos o momento exacto daquilo que queremos; só sabemos o nome lógico do que queremos e daí a utilidade das etiquetas.
Ao obter uma cópia local diferente da HEAD6 (seja por versão, etiqueta ou data), essa cópia fica com informação adicional associada. Essa informação é sticky ou seja permanece associada à cópia local até esta ser novamente actualizada para o HEAD. Isto tem implicações relativamente ao que se pode fazer com esta cópia local: se a informação sticky indicar que se obteve uma cópia local da versão mais recente de um ramo, então será possível efectuar o commit nesse ramo. Pelo contrário, se a informação sticky indicar que se obteve uma versão no passado e que essa versão já tem alterações futuras (nesse ramo), então não será possível efectuar o commit (ou seja, não é possível mudar o passado!). Ver informação sobre sticky tags no manual do CVS.
Sendo o CVS um sistema no qual os clientes e servidores podem estar geograficamente distribuídos, torna-se necessário uma forma global de identificar a localização do repositório. O identificador do repositório é um nome composto pelos seguintes elementos:
Em cada comando CVS dado pelo cliente, é necessário definir o repositório, através da opção -d, ou em alternativa definir uma variável de ambiente CVSROOT com o identificador do repositório.
Os principais métodos de acesso são o :local: e o :ext:. O método :local: utiliza-se quando o repositório está na mesma máquina que o cliente. Neste caso não necessita de ser indicado. O método de acesso :ext: utiliza-se para aceder a um servidor remoto, via uma aplicação externa que efectuará uma ligação de terminal remoto com o servidor. Para utilizar este método de acesso deve definir-se uma variável de ambiente (CVS_RSH) com o valor do programa a utilizar (tipicamente o ssh).
Corresponde ao nome de utilizador que será utilizado para efectuar a ligação remota a um servidor. Este valor não é necessário quando o método de acesso é local, ou quando o utilizador remoto tem o mesmo nome que o utilizador da máquina cliente.
Nome da máquina que contém o repositório.
É o directório no servidor onde está armazenado o repositório, identificado pelo caminho absoluto do directório.
Segue-se uma lista não exaustiva de operações frequentes com o CVS. Nos comandos seguintes admite-se que a localização do repositório está definida na variável de ambiente CVSROOT. Assim evita-se apresentar em todos os comandos a definição do repositório através da opção -d. Para cada comando é possível executar:
cvs --help <comando>para obter mais informações sobre o comando e suas opções
Permite criar um repositório, na localização definida. A partir deste momento, o directório do repositório fica pronto a guardar versões de ficheiros. Não se deve voltar a executar este comando, pois isso destruiria os dados que já estivessem no repositório.
cvs init
Após ter um repositório criado, a primeira coisa a fazer é colocar alguns ficheiros sobre o controlo de versões. Para tal basta, no cliente, criar um directório com tudo o que se quer colocar no sob controlo de versões e dar os seguintes comandos:
cd <directório a importar> cvs import <nome_módulo> <nome_produto> <etiqueta_inicial>
O comando import
envia todos os ficheiros do directório corrente e
sub-directórios (recursivamente) para o repositório. O <nome_módulo>
é
um nome à escolha que serve para identificar o módulo que vai conter os
ficheiros enviados (na prática será um directório dentro da raiz do
repositório). O <nome_produto>
serve apenas para dar semântica aos
ficheiros importados; pode ser qualquer nome. A <etiqueta_inicial>
é
uma etiqueta que será automaticamente atribuida à versão inicial destes
ficheiros.
Nunca se trabalha directamente com os ficheiros do repositório!
Nunca se trabalha com ficheiros que não tenham vindo do repositório! (Exceptuando claro, ficheiros novos que ainda não tenham estado no repositório)
Dito isto, para se poder trabalhar, é necessário obter primeiro uma cópia dos ficheiros que estão no repositório, ou seja, os ficheiros que foram anteriormente utilizados para importar dados para o repositório não servem, porque não estão "sob controlo de versões"3.
cvs checkout [-r <versão/etiqueta>] <módulo>...
Este comando permite opcionalmente indicar qual a versão ou etiqueta que se pretende obter dos módulos indicados. O cvs cria um directório no cliente e lá dentro coloca as cópias dos módulos/ficheiros pedidos.
Nota: Nos comandos cvs que suportam a indicação opcional da versão/etiqueta, quando nenhuma versão/etiqueta é indicada, o cvs assume "HEAD".
cvs update [-r <versão/etiqueta>] [<directório>|<ficheiro>]...
Permite actualizar uma cópia local com alterações que entretanto tenham sido colocadas no repositório, por exemplo, vindas de outra pessoas que tenham efectuado modificações aos ficheiros/módulos, dos quais este cliente tenha um cópia.
Se não se indicarem directórios nem ficheiros o cvs actualizará todos os ficheiros do directório corrente e sub-directórios recursivamente.
Nota: A diferença entre os comandos checkout e update é: só se usa checkout quando não se tem nenhuma cópia local. A partir daí só se usa update em vez de checkout.
Nota: Podem ocorrer conflitos se as alterações entretanto feitas por terceiros forem conflituosas com alterações presentes na cópia local.
Quando um cliente pretende tornar visíveis no repositório as suas alterações executa:
cvs commit [<directório>|<ficheiro>]...
Nesta altura o cliente tem a possibilidade de registar (log) uma mensagem explicativa das alterações que efectuou. Esta mensagem é uma importante forma de comunicação com os restantes membros da equipa.
A cópia local fica implicitamente actualizada, ou seja, um cvs commit
inclui implicitamente um cvs update
prévio.
cvs add <ficheiro>...
Informa o CVS de novos ficheiros que devem ser tomados em conta quando for
feito o commit. Por omissão o cvs ignora os novos ficheiros que não
tenham vindo do repositório, pelo que é necessário adicionar esses
ficheiros com o comando add
. Cuidado para não perder ficheiros por
esquecimento de executar add
. É frequente um cliente criar novos
ficheiros e depois de fazer commit apagar a sua cópia local pensando que
todas as alterações estão no servidor, mas esquecendo-se de adicionar os
novos ficheiros. A maioria dos ambientes gráficos que suporta a
utilização do CVS ajuda o utilizador, informando-o na altura do commit
que tem ficheiros não adicionados.
cvs remove [-f] <ficheiro>...
Informa o CVS que os ficheiros indicados devem ser apagados do repositório quando for efectuado o commit. Na verdade, o ficheiro não é completamente removido do repositório; o que acontece é simplesmente a criação de uma nova versão dos ficheiros na qual eles não existem. Isto permite que em qualquer altura se possam ir buscar os ficheiros apagados, bastando para isso pedir uma versão/etiqueta/data na qual os ficheiros existam. Esta funcionalidade é especialmente útil por permitir remover ficheiros com a confiança de que os podemos sempre recuperar se necessário.
o parâmetro opcional -f
permite indicar se o ficheiro deve ou não ser apagado
localmente logo na altura do comando cvs remove
.
O CVS não tem nenhum comando que permita renomear um ficheiro, porque o CVS não efectua controlo de versão entre ficheiros, ou seja, supondo que se pretende renomear um ficheiro em disco é necessário efectuar:
cvs remove <nome antigo> mv <nome antigo> <novo nome> cvs add <novo nome>
(Não esquecer o commit para efectivar as alterações no repositório)
Tal como já foi referido, o CVS permite agrupar ficheiros com um nome
lógico. O comando tag
permite dar esse nome lógico indicando quais os
ficheiros afectados.
cvs tag -c [-b] <etiqueta> [<directório>|<ficheiro>]...
A opção -c
é fortemente aconselhada, pois garante que os ficheiros a
etiquetar estão committed, evitando erros derivados de etiquetar no
repositório ficheiros com modificações locais que ainda não foram
enviadas para o repositório.
Etiquetar um conjunto de ficheiros com a opção -b
permite criar um ramo
(branch) alternativo ao tronco comum de desenvolvimento.
cvs add
assim que o ficheiro é criado. Para evitar
esquecimentos futuros...
.cvsignore
para configurar os ficheiros/directórios a ignorar.5
O IDE Eclipse suporta projectos sobre controlo de versões utilizando o CVS. No entanto, não suporta o modo de acesso :local:.
As operações disponíveis com do CVS estão acessíveis no menu de contexto do projecto no Eclipse (clique com o botão direito do rato em cima do nome do projecto) dentro da opção "Team". A figura seguinte mostra esse menu quando o projecto ainda não foi colocado sobre controlo de versões.
A opção Share Project permite fazer algo equivalente a um import
seguido de um checkout
.
Quando o projecto no Eclipse já está sobre controlo de versões o sub-menu tem o seguinte aspecto:
A primeira opção (Synchronize with Repository) é normalmente uma das mais usadas e permite uma visão das modificações locais e remotas dos ficheiros do projecto.
Nesta secção apresenta-se um exercício para que o leitor possa praticar alguns dos conceitos e comandos básicos do CVS. O exercício deve ser resolvido numa shell (linha de comandos) utilizando o cliente de CVS (comando cvs).
Teste.txt
e
escreva lá dentro algum conteúdo.
proj
) cujo único
ficheiro é para já o Teste.txt
.
Teste.txt
que usou para importar para o repositório.
proj
. Confirme que apareceu um novo
directório (proj
) com o ficheiro Teste.txt
lá dentro.
Teste.txt
e altere o seu conteúdo.
Teste.txt
. Desta vez não
envie as alterações para o repositório.
proj
) e mude para lá.
proj
e veja o conteúdo do
ficheiro Teste.txt
que aí apareceu.
Teste.txt
já tem as últimas alterações enviadas para o
repositório.
Teste.txt
com o nome treino
.
Teste.txt
.
Teste.txt
para a versão de nome
treino
. Confirme que o ficheiro mudou novamente para o texto
correspondente a essa versão.
Teste.txt
e tente
fazer commit. Por que é que não conseguiu?
FAQ do CVS (muito útil para resolver problemas comuns)
Notas:
1 Tendo obviamente em consideração o controlo de acesso. É possível que nem todos os utilizadores possam aceder a todos os ficheiros de um repositório se o sistema de ficheiros do servidor não o permitir (p.e. através das permissões dos ficheiros).
2 Excepto claro, ficheiros que tenham acabado de ser criados e portanto não tenham nunca estado no repositório.
3 É muito fácil identificar se os ficheiros locais estão ou não sob controlo de versões. Os ficheiros que estão sob controlo de versões têm "ao lado" directórios especiais com o nome "CVS". Estes directórios são utilizados pelo cliente para guardar informação específica do CVS. Os utilizadores não devem mexer nestes directórios, sob pena de estragarem o funcionamento do CVS.
4 Para poupar espaço, o CVS apenas guarda as diferenças linha-a-linha entre cada versão dos ficheiros. No caso de ficheiros binários não existe a noção de linha (sendo todo o ficheiro uma única linha), o que implica que qualquer byte modificado nesses ficheiros implica guardar a totalidade do ficheiro na nova versão.
5 Manual do CVS: Ignoring files via cvsignore. Exemplo de ficheiro .cvsignore
6 Exemplo: cvs update -r RELEASE-0.4 Ficheiro1.txt
.
Data: 2008/02/22 16:53:45