CVS: Concurrent Versions System

Índice

1 Introdução

1.1 Objectivo

Manter acessível um registo histórico das várias versões de um conjunto de ficheiros, ao longo do tempo.

1.2 Características do CVS

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.

2 Conceitos

2.1 Distribuição dos ficheiros

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:

2.1.1 Unreserved checkout model

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.

2.2 Versões

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!

 

2.3 Etiquetas (tags)

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.

3 Utilização

3.1 Identificação do repositório

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:

3.1.1 Método de acesso

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).

3.1.2 Nome do utilizador

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.

3.1.3 Endereço do servidor

Nome da máquina que contém o repositório.

3.1.4 Directório raiz do repositório

É o directório no servidor onde está armazenado o repositório, identificado pelo caminho absoluto do directório.

3.1.5 Exemplos de identificadores de repositórios

3.2 Operações mais frequentes

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

3.2.1 Criar um repositório

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

3.2.2 Importar um módulo

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.

3.2.3 Obter cópia local

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".

3.2.4 Actualizar cópia local a partir do repositório

 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.

3.2.5 Actualizar repositório a partir de 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.

3.2.6 Adicionar um ficheiro

 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.

3.2.7 Remover um ficheiro

 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.

3.2.8 Renomear um ficheiro

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)

3.2.9 Etiquetar uma release

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.

3.3 Algumas dicas de utilização

3.4 O CVS e o Eclipse

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.

4 Exercício

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).

  1. Crie localmente no seu computador um directório vazio, com um nome à sua escolha (mas sem espaços).
  2. Defina a variável de ambiente CVSROOT para usar o directório criado como repositório (deve indicar o caminho absoluto para o directório).
  3. Inicialize o repositório de CVS.
  4. Num outro directório vazio crie um ficheiro de texto Teste.txt e escreva lá dentro algum conteúdo.
  5. Crie um novo módulo no repositório (com o nome proj) cujo único ficheiro é para já o Teste.txt.
  6. Apague o ficheiro Teste.txt que usou para importar para o repositório.
  7. Obtenha uma cópia local do módulo proj. Confirme que apareceu um novo directório (proj) com o ficheiro Teste.txt lá dentro.
  8. Edite a cópia local do ficheiro Teste.txt e altere o seu conteúdo.
  9. Envie as alterações para o repositório.
  10. Altere novamente a cópia local do ficheiro Teste.txt. Desta vez não envie as alterações para o repositório.
  11. Crie um outro directório auxiliar (que não seja dentro da cópia local de proj) e mude para lá.
  12. Obtenha para aí uma cópia local do módulo proj e veja o conteúdo do ficheiro Teste.txt que aí apareceu.
  13. Mude para a outra cópia local e faça agora o commit.
  14. Mude novamente para a segunda cópia local e faça update. Confirme que o ficheiro Teste.txt já tem as últimas alterações enviadas para o repositório.
  15. Etiquete a versão actual do ficheiro Teste.txt com o nome treino.
  16. Efectue algumas alterações a esse ficheiro e faça commit. (Não se esqueça que estamos a trabalhar na segunda cópia local).
  17. Mude agora para a primeira cópia local que obteve. Faça um update e confirme que as actualizações apareceram no ficheiro Teste.txt.
  18. Agora efectue o update do ficheiro Teste.txt para a versão de nome treino. Confirme que o ficheiro mudou novamente para o texto correspondente a essa versão.
  19. Efectue por uma última vez alterações ao ficheiro Teste.txt e tente fazer commit. Por que é que não conseguiu?

5 Links úteis

Homepage do CVS

Manual do CVS

FAQ do CVS (muito útil para resolver problemas comuns)

Eclipse Team CVS Tutorial


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