Git: Controlo de Versões para Pequenos e Grandes Projectos

Ignorar ficheiros e pastas com o .gitignore

Por padrão, o Git permite controlar as versões de qualquer tipo de ficheiro que existir dentro da pasta (ou sub-pastas) onde está o repositório, mas nem sempre queremos controlar as versões de todos os tipos de ficheiro.

Ficheiros temporários que são criados pelo sistema operativo ou pela ferramenta de desenvolvimento, não devem fazer parte do repositório, ou seja, nunca devem adicionados aos commits. No entanto, poderão existir na working folder enquanto trabalha, mas devem ser ignorados pelo Git quando adicionamos alterações a staging area e efectuamos commits.

Por exemplo, o Windows costuma criar ficheiros com o nome Thumbs.db em pastas que possuem imagens, onde armazena uma espécie de cache das miniaturas das imagens contidas na pasta. Esses ficheiros Thumbs.db jamais devem ser guardados no repositório, pois podem ser recriados pelo Windows conforme o conteúdo da pasta muda.

Ainda, num projecto .NET, o Visual Studio cria ficheiros do tipo NomeProjecto.suo, NomeProjecto.user, NomeProjecto.sln.cache, e outros tipos de ficheiros temporários que estão relacionados com o utilizador actual e podem ser recriados pelo Visual Studio a qualquer momento. Estes ficheiros também não devem ser armazenados no repositório Git, e portanto devem ser ignorados.

Além disso, é uma prática comum não guardar ficheiros executáveis, e outros ficheiros que podem ser gerados (compilados) a partir do código que está a ser controlado pelo repositório Git, ou seja, deve-se guardar apenas o código-fonte e as dependências necessárias para que seja possível compilar o código-fonte. Por exemplo, num projecto .NET, normalmente temos as pastas bin e obj que possuem o resultado da compilação de cada projecto do Visual Studio. Estas pastas também não devem ser armazenadas no repositório Git, pois deve ser possível recriá-las a qualquer momento, a partir do código-fonte do projecto.

Para poder ignorar determinados tipos de ficheiros ou pastas, criamos um ficheiro chamado .gitignore na pasta onde criamos o repositório. O Windows por padrão não permite criar ficheiros que não tenham nome e apenas uma extensão, como é o caso do .gitignore, por isso uma alternativa simples é criar o ficheiro a partir do Git Bash, executando o Bloco de Notas para criar o ficheiro:

$ notepad .gitignore

No conteúdo do ficheiro .gitignore, pode inserir comentários (linhas que começam com #), definir os tipos de ficheiros que pretende ignorar e as pastas que pretende ignorar por completo, independente do conteúdo. Neste exemplo:

# Ignorar ficheiros temporários do Windows Thumbs.db
# Ignorar pacotes de ficheiros
*.zip 
*.rar
# Ignorar ficheiros temporários do Visual Studio 
*.suo
*.user
*.userprefs
# Ignorar as pastas com ficheiros binários gerados via compilação
[Oo]bj/
[Bb]in/

A sintaxe é bastante intuitiva, e pode utilizar *.extensão dos ficheiros que pretende ignorar, e informar o nome das pastas com uma barra / no final para indicar que trata-se de uma pasta. O Git faz diferença entre letras maiúsculas e minúsculas, por isso pode utilizar expressões como [Oo]bj/ que permite indicar que tanto as pastas obj/ quanto Obj/ devem ser ignoradas.

Ao criar o ficheiro .gitgnore, os ficheiros e pastas definidos no conteúdo deste ficheiro serão ignorados pelo Git, e deixarão de aparecer, por exemplo, quando visualizar o estado do repositório com o comando git status, no entanto é importante adicionar o ficheiro .gitignore ao repositório para garantir que continuará a ignorar os ficheiros desejados nos próximos commits, e também para que todos os membros da equipa estejam a ignorar os mesmos tipos de ficheiros e pastas.

$ git add .gitignore
$ git commit -m "Adiciona o ficheiro .gitignore ao repositorio"

Consulta do Histórico de Commits

Conforme efectuamos commits, é muito comum necessitarmos consultar a história do projecto para, por exemplo, perceber quando foi introduzida uma determinada alteração. Para este efeito, o Git possui um comando chamado git log, que permite visualizar os commits que estão armazenados no repositório, incluindo informações como a data e hora, nome e e-mail do utilizador que efectuou cada commit, e também o identificador único de cada commit (ex: 5944d4c5c92c89766ac77de221b1b36b803ee37b), que podemos utilizar quando necessitamos efectuar operações específicas em determinados commits.

$ git log

Git: histórico (log)

 O comando git log pode receber diferentes parâmetros que permitem visualizar mais, ou menos informação sobre os commits, algumas opções de formatação, entre outros recursos.

Uma outra forma de consultar o histórico de commits é utilizar um utilitário instalado juntamente com o msysGit chamado gitk, que permite consultar os commits de forma gráfica.

$ gitk

Git: gitk

Desenvolvimento em Paralelo

Ao trabalharmos no desenvolvimento de software profissional, é muito comum termos um ambiente de desenvolvimento onde efectuamos testes de novas funcionalidades que estão a ser desenvolvidas, separado do ambiente de produção onde a aplicação está a ser executada pelos utilizadores finais. Em realidade, é também muito comum termos um ambiente intermédio de controlo de qualidade (também conhecido como ambiente de qualificação, de qa ou de testes), que geralmente possui as mesmas características do ambiente de produção, e possui uma versão da aplicação com funcionalidades que ainda precisam ser testadas antes de serem promovidas para o ambiente de produção.

Git: desenvolvimento paralelo

Desta forma, o ambiente de produção possui sempre a versão mais antiga do projecto, mas também a mais estável e que passou pelos testes de controlo de qualidade, enquanto o ambiente de qualificação (se houver) possui uma versão mais nova do projecto, mas que ainda necessita ser testado antes de evoluir para o ambiente de produção, e por fim o ambiente de desenvolvimento possui uma versão ainda mais nova do projecto, com as funcionalidades que estão a ser desenvolvidas e que após testes dos developers poderão ser enviadas para o ambiente de qualificação, para serem efectuados mais testes.

Uma vez que temos ambientes separados, podemos utilizar a ferramenta de controlo de versões para manter diferentes as versões dos nossos projectos em paralelo de forma a conseguirmos enviar uma nova versão para qualquer um dos ambientes o mais rápido possível e idealmente a qualquer momento.

Para este efeito, o Git e a grande maioria de sistemas de controlo de versões oferece um recurso chamado branch, que no Git é representado por um conjunto de commits é identificado por um nome escolhido pelo developer que efectua criação do branch.

Ao criar um novo repositório Git, automaticamente é criada um primeiro branch chamado master, que irá agregar todos os commits que fizer neste branch. Pode identificar a qualquer momento em qual branch encontra-se posicionado através do nome entre parênteses após o caminho da pasta:

Git: identificação do branch

Para criar um novo branch, pode utilizar o comando git branch, e informar o nome do branch a ser criado:

$ git branch desenvolvimento
$ git branch qualificacao

Estes comandos efectuam a criação de dois novos branches chamados desenvolvimento e qualificacao respectivamente, mas que estão a apontar para o mesmo commit do branch actual (master), que neste exemplo, é o commit que adiciona o ficheiro .gitignore.

O branch master, neste exemplo, está a ser usado como sendo o branch com a versão de produção, enquanto os outros branches representam as versões dos outros ambientes. Isto pode variar de acordo com a preferência pessoal da equipa. Em alguns casos, o branch master será utilizada como branch de desenvolvimento, por exemplo, e são criados outros branches para os outros ambientes.

Para mudar para um novo branch, deve utilizar o comando git checkout e informar o nome do branch para onde deseja ir:

$ git checkout desenvolvimento

A partir de agora todos os commits efectuados serão armazenados no branch desenvolvimento, de forma isolada da branch master criado inicialmente, e também de forma isolada do branch qualificacao.

Para este exemplo, pode criar um ficheiro chamado NovaFuncionalidade.txt e efectuar o commit:

$ echo " Conteudo" >> NovaFuncionalidade.txt
$ git add .
$ git commit -m "Adiciona o NovaFuncionalidade.txt"

E neste momento, o repositório possui quatro commits diferentes:

  • Commit 4 | Adiciona o NovaFuncionalidade.txt
  • Commit 3 | Adiciona o ficheiro .gitignore ao repositorio
  • Commit 2 | Adiciona funcionalidade Y
  • Commit 1 | Adiciona funcionalidade X

No entanto, o commit 4 está presente apenas no branch desenvolvimento, enquanto a branch master e qualificacao continuam a apontar para o commit 3, como pode visualizar através do utilitário gitk:

Git: visualizar branches no gitk

Desta forma, é possível continuar a efectuar commits neste branch sem comprometer as versões que estão em paralelo e que correspondem aos outros ambientes, e apenas quando for apropriado, poderá juntar os commits efectuados em um branch, com outro.

A utilização de branches no Git é tão simples e tão rápida, que muitos developers adoptam uma convenção conhecida como branch-per-feature, onde criam novas branches para cada nova funcionalidade que pretendem implementar num projecto, e depois decidem que funcionalidade/branch deve ser adicionada nos branches principais de cada ambiente.

A operação de juntar os commits de uma branch com os commits de outro branch é chamada de merge e para este efeito o Git possui o comando git merge que permite juntar o branch informado como parâmetro, no branch onde está posicionado.

Por exemplo, para juntar as alterações do branch desenvolvimento com o branch master, em primeiro lugar é preciso ir para o branch master (com o comando git checkout) e então executar o comando git merge e informar que deve ser efectuado o merge do branch desenvolvimento com o branch actual:

$ git checkout master
$ git merge desenvolvimento

Git: checkout e merge

Como pode reparar na imagem acima, o Git efectuou um fast-forward que é o tipo de merge mais simples que existe, onde apenas o apontador do branch actual move-se para apontar para o novo commit, que neste caso é mais novo.

Existem outros tipos de merge, e em alguns casos um merge pode causar conflitos, por exemplo, caso as mesmas linhas de um ficheiro tenham sido alteradas por commits diferentes, e pode necessitar de intervenção manual do developer, e que normalmente utiliza uma ferramenta para auxiliar a resolução de conflitos. Este é um assunto que merece um artigo próprio, que ficará para uma próxima edição desta revista.

Partilha de Alterações em Equipa

A partilha de commits entre os membros da equipa pode ser feita directamente entre os repositórios dos developers envolvidos, pode ser utilizado um repositório partilhado, ou outras formas de acordo com o fluxo de trabalho da equipa. Na próxima secção irá encontrar breve explicação sobre os workflows mais comuns para controlo de versões em sistemas distribuídos.

Os principais comandos para a utilização do Git em equipa são o git clone, git pull e o git push.

O comando git clone serve para criar uma cópia integral de um repositório Git. Este é o comando utilizado quando desejamos participar de um projecto, e para isso precisamos ter uma cópia do repositório em nosso computador. O git clone automaticamente guarda uma referência para o repositório original, de forma a facilitar obter actualizações desse repositório, bem como enviar as actualizações feitas localmente.

Já o comando git pull permite receber novos commits que tenham sido adicionados num repositório de origem. É tipicamente utilizado para receber as alterações enviadas por outros membros da equipa para um repositório partilhado, ou ainda para receber novos commits de um repositório específico de um membro da equipa.

E por fim, o comando git push, como o nome indica, faz exactamente o inverso do git pull, e serve para enviar as alterações efectuadas localmente no repositório, para um repositório de origem, tipicamente um repositório remoto partilhado com os membros da equipa.

Criação de um Repositorio Partilhado

Um repositório partilhado pode estar no mesmo computador, ou em um computador remoto que pode estar na mesma rede, em uma rede separada, ou ainda em um servidor que pode aceder via internet. A comunicação entre repositórios pode ser feita de diferentes formas, via rede (partilha de pastas), SSH, HTTP, HTTPS, entre outras formas. Para efeitos de exemplo, todos os repositórios mostrados abaixo são criados no mesmo computador, em pastas diferentes.

Como explicado no início desta secção, a criação de repositórios é feita através do comando git init. No entanto, para a criação de repositórios partilhados que poderão receber actualizações (push) de outros utilizadores, é necessário indicar que trata-se de um repositório partilhado através dos parâmetros --shared e --bare.

$ cd ..
$ mkdir RepositorioPartilhado
$ cd RepositorioPartilhado
$ git init --shared --bare

Após a execução das instruções acima, é criado um repositório chamado RepositorioPartilhado que irá servir como um repositório intermédio para dois membros da equipa, o Tiago e o Carlos, que terão cada um os seus próprios repositórios.

Criação de Clones de Repositórios

Para criar um clone de um repositório, como referido acima, utilizamos o comando git clone, informando o nome do repositório a ser clonado:

$ cd ..
$ git clone RepositorioPartilhado Tiago
$ git clone RepositorioPartilhado Carlos

As instruções acima permitiram criar dois clones do repositório RepositorioPartilhado, um para o Tiago ou outro para o Carlos, criados e pastas separada, e neste momento estão vazios (sem qualquer commit armazenado).

Para este exemplo, é criado um ficheiro no repositório do Tiago que será então armazenado em um commit. Em seguida este commit será enviado (push) para o repositório partilhado RepositorioPartilhado, e a partir daí o Carlos pode obter as actualizações (pull) do repositório RepositorioPartilhado e consequentemente irá obter o commit efectuado inicialmente no repositório do Tiago e que foi partilhado no repositório RepositorioPartilhado.

Passo 1 : Efectuar as alterações no repositório do Tiago

$ cd Tiago/
$ echo "Alteracao Tiago" >> NovaFuncionalidade. txt
$ git add .
$ git commit -m "Adiciona nova funcionalidade (Tiago)"

É importante reparar que o commit foi efectuado no branch master deste repositório.

Passo 2: Enviar as alterações para o repositório partilhado (push)

Como referido acima, para enviar as alterações efectuadas no repositório local para o repositório de origem, deve utilizar o comando git push, e para isto deve informar o nome da referência do repositório de origem, e o nome do branch que deve ser considerada para o envio.

Ao efectuar um clone de um repositório, o Git automaticamente cria uma referência para o repositório de origem com o nome origin. É possível alterar este nome se desejar, e também é possível criar outras referências para outros repositórios remotos.

Assim, para enviar as alterações para o repositório partilhado, basta informar origin como referência para o repositório partilhado, e master como nome do branch, uma vez que o commit que deve ser enviado está neste branch.

$ git push origin master

Passo 3: Receber as alterações enviadas para o repositório partilhado no repositório do “Carlos”

Para receber as alterações existentes no repositório partilhado, deve utilizar o comando git pull, e para isto deve informar o nome da referência do repositório de origem, e o nome do branch que deve ser considerada para o recebimento.

Dessa forma, assim como o comando git push basta informar origin como referência para o repositório partilhado, e master como nome do branch.

$ cd ..
$ cd Carlos/
$ git pull origin master

E a partir de agora, os três repositórios estão sincronizados e possuem os mesmos commits.

Assim, o fluxo de trabalho comum no dia-a-dia do Git em equipa é algo como:

  • início
    • Efectuar alterações/novos commits (git commit)
    • Juntar actualizações do repositório partilhado com o repositório local (git pull)
    • Enviar as actualizações do repositório local para o repositório partilhado (git push)
  • loop

Workflows Comuns para Controlo de Versões Distribuído

Subversion-style

Git: workflow subversion-styleEste é o workflow mais simples, e normalmente utilizado em equipas que estão a utilizar o Git pela primeira vez. Neste workflow, utiliza-se o Git como se fosse um sistema de controlo de versões centralizado, mas com as vantagens de um sistema distribuído, onde pode-se efectuar commits localmente, de forma desconectada, e enviar para o repositório partilhado apenas quando for apropriado. Todas as alterações são partilhadas num repositório partilhado e não há comunicação directa entre os membros da equipa.

Decentralized but Centralized

Git: workflow decentralized but centralizedEste é o workflow mais comum para pequenos e médios projectos em equipas com alguma experiência com o Git. Os membros da equipa acordam entre si que todas as alterações que devem ser consideradas para as futuras versões do projecto serão armazenadas em um repositório partilhado principal, conhecido por blessed repository (repositório abençoado).

Os membros da equipa podem então partilhar commits entre eles directamente enquanto trabalham em determinadas tarefas, e quando for apropriado, podem enviar os commits para o blessed repository.

Integration Manager

Git: workflow integration managerEste é um workflow mais sofisticado e indicado para projectos médios e grandes, onde cada membro da equipa possui dois repositórios, um público e outro privado (que podem estar no mesmo computador). Cada developer trabalha em seu repositório privado, e quando apropriado pode partilhar (push) as alterações que efectuou em seu repositório privado no repositório público.

Existe então uma pessoa da equipa que assume o papel de integration manager que é a pessoa responsável em obter (pull) as alterações do repositório público de cada developer, validar, e então enviar (push) para o blessed repository.

Dictator and Lieutenants

Git: workflow dictator and lieutenantsEste é um workflow ainda mais sofisticado que o anterior e indicado para projectos extremamente grandes e com muitas pessoas a participar no desenvolvimento. Cada developer possui um repositório público onde pode partilhar as alterações que posteriormente serão validadas por pessoas na equipa que assumem o papel de lieutenant (tenente) e normalmente são responsáveis por módulos específicos do projecto.

Após a validação das alterações pelos lieutenants, estas são enviadas para outra pessoa que assume o papel de dictator (ditador) e que efectua uma validação final, antes de enviar para o blessed repository.

Por curiosidade, este é o workflow utilizado actualmente para controlar as versões do kernel do Linux. Existem pessoas consideradas como sendo pessoas “de confiança” e que são responsáveis por diferentes módulos do kernel e validam as alterações enviadas pelas centenas de pessoas que contribuem para o projecto, e por fim são enviadas para o “ditador” que as valida e escolhe quais alterações farão parte do repositório principal e que eventualmente irão fazer parte de uma futura versão do sistema operativo.

Serviços de Alojamento de Repositórios Git na Internet

Existem dezenas de empresas que fornecem serviços de alojamento de repositórios Git na internet, permitindo desenvolver projectos (open-source ou não) com equipas distribuídas sem precisar criar e manter uma infra-estrutura própria.

O serviço mais popular e provavelmente o mais utilizado em todo o mundo é o GitHub (http://github.com) que oferece a possibilidade de criar repositórios públicos gratuitos com até 300 MB, para quem pretende desenvolver software open-source, e oferece também a possibilidade de criar repositórios privados para empresas que queiram ter repositórios privados partilhados e não possui infra-estrutura própria com preços que variam entre os 7 e 22 dólares americanos por mês no momento em que escrevo este artigo. Em Abril de 201 1 , o GitHub ultrapassava os 2 milhões de repositórios.

Outros exemplos serviços que oferecem serviços semelhantes são:

Exemplos de Grandes Projectos que Utilizam o Git

O Git é amplamente utilizado em projectos open-source em todo o mundo, em pequenos, médios e grandes projectos, e foi originalmente desenvolvido para controlar as versões do kernel do Linux e desde a sua primeira versão continua a ser utilizado para tal.

Alguns projectos populares que utilizam Git para controlo de versões, além do kernel Linux, são: Ruby on Rails, Node.js, jQuery, Modernizr, Scriptaculous, Android, CakePHP, Sinatra, VLC, entre muitos outros, e o próprio Git. Exacto! O controlo de versões do código-fonte do Git é feito através do próprio Git.

Uma lista mais detalhada de projectos que utilizam o Git está disponível no wiki do Git em https://git.wiki.kernel.org/index.php/GitProjects, e pode acompanhar os projectos open-source mais populares no GitHub em https://github.com/popular/watched.

Links para Referência

  1. Git ScmSite oficial do Git
    http://git-scm.com
  2. Posts sobre Git em meu blog
    http://caioproiete.net/pt/tag/git/
  3. Vídeo: Controlo de Versões Distribuído com Git
    http://vimeo.com/20652754
  4. Pro Git (e-book)
    http://progit.org
  5. Git Ready (tutorial / tips)
    http://www.gitready.com
  6. Git Magic (e-book)
    http://www-cs-students.stanford.edu/~blynn/gitmagic
  7. Git for Beginners
    http://stackoverflow.com/questions/315911/git-for-beginners-the-definitive-practical-guide
  8. Why Git is Better than X
    http://whygitisbetterthanx.com
  9. Git Is Your Friend not a Foe
    http://hades.name/blog/2010/01/17/git-your-friend-not-foe
  10. A successful Git branching model
    http://nvie.com/posts/a-successful-git-branching-model
  11. Use Git For What It Is Not Intended (UGFWIINI)
    http://thread.gmane.org/gmane.comp.version-control.git/110411

Publicado na edição 29 (PDF) da Revista PROGRAMAR.