Os repositórios remotos são utilizados quando os desenvolvedores precisam compartilhar o seu trabalho com outros desenvolvedores do time. Não faremos uma introdução sobre estes nesse artigo, pois no link disponibilizado aqui já tratamos.

Agora precisamos entender alguns conceitos sobre seus protocolos. Posteriormente, será visto como criar uma cópia de um repositório remoto, compartilhar arquivos, branches, tags e todas as demais funcionalidades que os envolvem.

Protocolos

O Git se comunica com o repositório remoto através da rede. Esta pode ser uma LAN interna, uma VPN ou a própria internet.

Três protocolos são fornecidos pelo Git para que essa comunicação com o repositório remoto seja realizada: SSH, Git e HTTP/HTTPS. Com exceção desse último, os demais deverão estar instalados na máquina, mas veremos isso com mais detalhes nas próximas seções.

SSH

Acessar um repositório através do Secure Shell (SSH) é similar a acessá-lo diretamente do file system, com uma pequena exceção que será discutida posteriormente.

Antes do caminho do repositório adiciona-se o nome e username (se este for necessário) para acessá-lo. Por exemplo, na URL "git@github.com/meurepoexemplo/meusite.git" tem-se que “git” é o username, “github.com” é o server e “meurepoexemplo/meusite.git” é o caminho do repositório.

A única diferença entre a URL do SSH apresentada e a URL do file system é a adição do “git@github.com”. Isto solicita ao Git para que ele tente logar no servidor “github.com” utilizando o protocolo SSH com o usernamegit” e então clonar o repositório localizado no caminho "meurepoexemplo/meusite.git ".

O username "git" no exemplo anterior é opcional. Não é necessário especificá-lo quando é utilizado o mesmo username no computador local.

GIT

O Git também possui o seu próprio protocolo que foi desenvolvido para ter mais velocidade que os outros. Este é o protocolo mais rápido, mas pode ter problemas com firewalls, uma vez que o protocolo utiliza a porta 9418, que é uma porta sem tráfego de rede normalmente.

Um exemplo de URL do git é "git://github.com/meurepoexemplo/meusite.git" onde "git://" é o protocolo, "github.com" é o server e "meurepoexemplo/meusite.git" é o nome do repositório.

Neste exemplo, o caminho completo não é especificado. Quando você inicia um servidor Git informa-se qual o diretório para procurar os repositórios, apenas especificando o nome dos mesmos.

A grande diferença entre o protocolo Git e o SSH, além da encriptação, é que o Git é anônimo. Isto é bom quando se deseja fornecer um acesso público de apenas leitura (ou read-only) ao repositório. Por permitir acesso anônimo este acesso poderia ser perigoso caso o repositório fosse aberto para escrita, por isso tal restrição. Dessa forma, ninguém pode fazer alterações no mesmo quando acessado através do protocolo Git.

Assim sendo, sempre que se verifica um repositório usando "git://" pode-se afirmar que ele tem acesso apenas à leitura.

Normalmente o que as organizações fazem é permitir que os desenvolvedores utilizem o protocolo Git para fazer um pull, recebendo os arquivos do repositório. Utiliza-se o SSH os desenvolvedores que faze um push, enviando assim as alterações diretamente para o repositório.

HTTP/HTTPS

O protocolo HTTP é geralmente utilizado como último recurso, pois este apresenta a forma menos eficiente para fazer um pull das alterações e exige muito mais sobrecarga da rede. A diferença é que o HTTP é sempre permitido pelos firewalls, além de ser rápido e fácil de configurar.

Considerando o exemplo anterior com o caminho “meurepoexemplo/meusite.git” e o servidor “github.com”, poderíamos acessar o repositório hospedado no GibHub com a URL http://github.com/meurepoexemplo/meusite.git.

No entanto, vale ressaltar que o GitHub não aceita acesso HTTP, portanto, se tentássemos clonar um repositório com esta URL obteríamos um erro do Git.

Escolhendo opções de rede

Devido à quantidade disponível de protocolos (SSH, GIT, HTTP ou HTTPS) tem-se a impressão que a escolha de qual protocolo utilizar é algo complicado. No entanto, escolher o protocolo para acesso ao repositório é bastante simples e direto. Basicamente, se o desenvolvedor quiser velocidade a melhor escolha é o protocolo Git, caso a preferência seja por uma maior segurança o protocolo indicado é o SSH, mas se o desenvolvedor não quiser realizar reconfigurações no firewall o protocolo mais indicado é o HTTP ou HTTPS.

Em relação à autenticação o protocolo git é anônimo, permitindo assim que qualquer um tenha permissão de leitura no repositório. O SSH requer permissões para que o usuário possa manipulá-lo, e o HTTP/HTTPS requer um servidor WebDAV.

No entanto, pode-se utilizar mais que um protocolo, assim pode-se balancear velocidade e segurança num mesmo repositório.

Frequentemente as organizações optam por utilizar uma mistura entre SSH e o protocolo Git, em que o SSH é utilizado para autenticação de usuários que desejam realizar operações de escrita, e para leitura permite-se usuários anônimos através do protocolo Git.

Clonando um Repositório Remoto

Quando se faz necessário compartilhar o trabalho realizado com todos os outros desenvolvedores utiliza-se um repositório remoto para receber e disponibilizar os artefatos produzidos pelos desenvolvedores. A forma mais fácil de trabalhar com repositórios remotos é clonar um repositório existente. A operação de clone cria uma cópia local do repositório remoto.

Para os projetos que já estão em andamento, esta é a forma padrão, mas não é a única. Um repositório remoto pode ser configurado posteriormente, por exemplo, nos casos em que começamos a trabalhar em um projeto localmente e, em seguida, faz-se necessário compartilhá-lo.

A cópia local criada através de um clone trabalha como se tivéssemos criado um repositório utilizando o "git init". A única diferença é que recebemos o histórico do repositório remoto até o ponto que o clone foi criado.

O comando "git clone" é utilizado a qualquer momento que se deseja clonar um repositório para começar a trabalhar diretamente sobre ele. Na forma mais simples de clonar um repositório é necessário apenas um parâmetro que é o nome do repositório que está sendo clonado. A seguir temos um exemplo de como um repositório pode ser clonado:

prompt> git clone git://github.com/meurepoexemplo/meusite.git
  Initialized empty Git repository in /work/meusite/.git/
  remote: Counting objects: 37, done.
  remote: Compressing objects: 100% (31/31), done.
  remote: Total 37 (delta 10), reused 0 (delta 0)
  Receiving objects: 100% (37/37), 4.08 KiB, done.
  Resolving deltas: 100% (10/10), done.

Pode-se verificar que após executado o comando é realizado o download do repositório armazenado no servidor e configurado uma cópia local do repositório. Para visualizar os arquivos criados no repositório local basta executar os comandos a seguir:


  prompt> cd meusite
  prompt> ls
  informacoes.html contato.html teste.html 

Agora existe uma cópia total do repositório remoto configurada para versionar alterações locais e tudo que for buscado do repositório remoto.

Buscando Alterações no Repositório

Após clonar o repositório tem-se as últimas modificações, porém, com o passar do tempo os outros desenvolvedores realizam alterações e atualizam o repositório com essas alterações. Assim, é preciso fazer um fetch dessas alterações periodicamente após cloná-lo. Dessa forma, para buscar essas alterações do repositório remoto basta utilizar o comando "git fetch".

Para visualizar os branches locais basta executar o comando "git branch". Caso deseja-se visualizar os branches remotos executa-se o comando com o parâmetro "-r" conforme mostra o exemplo a seguir:


  prompt> git branch -r
  origin/HEAD
  origin/master

Pode-se fazer um check out desse branches assim como se faz com outro branch local, mas esses branches não deveriam ser alterados. Se for necessário realizar qualquer alteração nesses branches, deve-se criar um branch local a partir do primeiro, e então realizar as alterações.

Executar um "git fetch" atualiza os branches locais, mas este comando não faz o merge com as alterações do branch local. Também podemos utilizar o "git pull" se for necessário buscar as alterações de um repositório remoto e fazer o merge com o branch local ao mesmo tempo.

O "git pull" recebe dois parâmetros, o repositório remoto que se quer fazer um "pull" e o branch local que receberá as alterações (sem o prefixo origin/).

O prefixo “origin/” no nome do branch remoto serve para manter o branch remoto separado do nosso branch local. O “origin” é o nome do repositório remoto padrão em que se criou localmente através do clone.

Agora que já é possível buscar as alterações do repositório, na próxima seção será visto como enviar as alterações para o repositório remoto.

Enviando Alterações para o Repositório

Receber as alterações do repositório remoto é apenas uma parte que se deve saber para manter-se sincronizado com todo o time de desenvolvimento. Também devemos enviar as alterações para o repositório para compartilharmos essas alterações que foram realizadas localmente.

Para isso utiliza-se o "push" que envia as alterações para o repositório remoto. Este procedimento é semelhante ao “commit” do SVN e do CVS que enviam as alterações para repositório central.

É importante atentar para este comando do Git, pois ele faz algumas suposições quando se utiliza o "git push" sem quaisquer parâmetros. Primeiramente, o Git assume que estão sendo enviadas as alterações para o repositório “origin”. Segundo, ele também assume que está sendo enviado o branch atual que estamos localmente trabalhando para o repositório remoto, caso este branch também exista remotamente.

O Git envia apenas aquilo que foi marcado para check in, assim quaisquer alterações realizadas no working tree ou que esteja na área de stage não são enviadas. A seguir temos um exemplo de como utilizar o comando git push:


  prompt> git push
  Counting objects: 11, done.
  Compressing objects: 100% (7/7), done.
  Writing objects: 100% (9/9), 933 bytes, done.
  Total 9 (delta 0), reused 0 (delta 0)
  Unpacking objects: 100% (9/9), done.
  To /meurepoexemplo/meusite/.git
  5ef8232..d49d1e5 master -> master

Para verificar as alterações que foram enviadas ao repositório pode-se utilizar o parâmetro "--dry-run" no comando.

Também é possível especificar o repositório que se quer realizar um push. A sintaxe é dada por git push <repository> <refspec>, em que <repository> pode ser qualquer repositório válido, inclusive, pode ser uma URL. O <refspec> é uma tag, um branch, ou uma palavra especial como HEAD, assim, pode-se utilizá-lo para especificar quais branches deseja-se realizar um push e para onde se deseja enviá-los. Por exemplo, o comando "git push origin meubranch:master" faz um push das alterações de meubranch para o master remoto.

Uma forma de push que não é muito comum é a possibilidade de realizar um push diretamente em outro repositório privado. Um exemplo disso é quando se tem o repositório também em uma máquina virtual. Se for necessário fazer isso é bom ressaltar que esse push não altera o working tree, isso evita que alguém sobrescreva algo que já tenha sofrido alterações no working tree e ainda não tenha sido comitado. Assim, é necessário executar um "git checkout HEAD" para realizar um pull de todas as últimas alterações do repositório para o working tree. Também se tem a oportunidade de gerenciar qualquer conflito que venha surgir.

Adicionando Novos Repositórios Remotos

É possível realizar um push ou pull em qualquer repositório que se tenha permissão de leitura ou escrita. É possível que esses comandos tenham que ser executados diversas vezes mesmo durante um dia. No entanto, digitar o nome completo de um repositório pode ser complicado e cansativo.

O uso de aliases é muito comum no Git. Por exemplo, o repositório padrão é chamado de "origin", que é um alias para o nome completo do repositório.

Para ilustrar isso, no exemplo a seguir é realizado um pull para um repositório conforme a seguir:

prompt> git pull git://devmedia.com/dev-higor.git

Se um pull for realizado apenas uma vez não tem problema especificar todo o caminho, porém se isso for realizado mais de uma vez, adicionar um alias pode resolver o problema. Para adicionar um repositório remoto nomeado pode-se utilizar o "git remote add <nome> <repo-url>" conforme a seguir:

prompt> git remote add higor git://devmedia.com/dev-higor.git

Agora basta utilizar apenas o "higor" ao invés de precisar do nome completo do repositório, sempre que for necessário realizar um push ou pull para quaisquer alterações. Assim, se for necessário realizar um pull pode-se fazer conforme o exemplo a seguir:


  prompt> git pull higor HEAD
  warning: no common commits
  remote: Counting objects: 6318, done.
  remote: Compressing objects: 100% (2114/2114), done.
  remote: Total 6318 (delta 3899), reused 5680 (delta 3505)
  Receiving objects: 100% (6318/6318), 1.03 MiB, done.
  Resolving deltas: 100% (3899/3899), done.
  From git://devmedia.com/dev-higor.git
  * branch HEAD -> FETCH_HEAD

Utilizar essa forma é muito mais simples de lembrar. Pode-se utilizar qualquer nome para nomear, porém cada nome deve ser único. Assim, podemos ter apenas um "higor".

Pode-se utilizar um "git remote add" para adicionar um origin para o repositório se não existe nenhum. Isso é útil quando se deseja iniciar um repositório localmente com "git init" e, em seguida, é necessário enviá-lo para um repositório remoto mais tarde.

Este comando também pode ser invocado sem nenhum parâmetro, assim tem-se como resultado uma lista de todos os aliases criados. Outro comando útil é o "git remote show <nome>" que exibe algumas informações do repositório remoto.

Para remover o alias basta executar o comando "git remote rm".

Organizando o Repositório

Obter acesso ao histórico inteiro do projeto é o benefício chave de qualquer sistema de controle de versão. Com isso, é possível pesquisar versões antigas do software e pesquisar o histórico dos arquivos para descobriu como ele acabou ficando no estado atual que ele se encontra.

No entanto, toda esta informação disponível pode ser um tanto exagerada, sendo assim difícil de manter e de realizar um rastreamento posteriormente. Por isso é importante ter uma boa estratégia para a organização do repositório.

Na medida em que o repositório progride tem-se diversos marcos, em que novas versões são criadas e corrigidas, deploys são realizados, etc. Dessa forma, as Tags fornecem uma forma conveniente para indicar esses marcos, para que possamos voltar a eles mais tarde se necessário.

O uso mais comum das Tags é para marcar a entrega de uma versão do projeto. Isso permite que possamos voltar atrás no código que foi lançado em produção, caso se deseja consertar ou alterar alguma coisa.

Vale ressaltar que as Tags no Git são read-only, permitindo apenas a leitura, diferente das Tags no CVS e no SVN que permitem alterações. Assim, as Tags são diferentes do branches que permitem alterações. Isso garante que a versão taggeada não foi alterada em nenhum momento, estando do mesmo jeito quando a versão foi lançada em produção.

O comando para trabalhar com Tags no git é o "git tag". Assim como o "git branch", este comando pode ser chamado sem quaisquer parâmetros para que possa ser verificado as Tags que existem atualmente no repositório. A seguir temos um exemplo utilizando o comando e a saída gerada:


  prompt> git tag
  1.0

O resultado obtido foi a Tag com o nome “1.0”. Para criar uma Tag chamada "1.1" basta chamar o mesmo comando conforme acima e especificar o nome da Tag conforme o exemplo a seguir:

prompt> git tag 1.1

Pode-se verificar que o Git não retorna nenhuma mensagem de que a Tag foi criada com sucesso. Para confirmar a criação da Tag basta executar o comando a seguir:


  prompt> git tag
  1.0
  1.1

Como pode-se verificar a Tag foi corretamente criada.

Porém, se ocorrer algum problema na criação da Tag será retornado um erro. Por exemplo, ao tentar criar uma Tag usando um nome que não é válido, como um nome com espaços, teremos como resultado um erro conforme mostra o exemplo a seguir:


  prompt> git tag "version 1.1"
  fatal: 'version 1.1' is not a valid tag name. 

O Git cria uma Tag baseada no working tree atual, isso porque foi fornecido apenas o nome da Tag.

No entanto, pode-se criar uma Tag a partir de um outro branch a seguir:


  prompt> git tag contatos/1.1 contatos
  prompt> git tag
  1.0
  1.1
  contatos/1.1

No exemplo mostrado criou-se a Tag "contatos/1.1" com base nos arquivos do branch “contatos”.

Apesar de não ser possível alterar os arquivos dentro de uma Tag, é possível fazer um check out como é feito em um branch, conforme exemplo a seguir:

prompt> git checkout 1.0

Agora é possível criar um branch a partir desse check out, para isso basta utilizar o parâmetro "-b" com o comando check out conforme mostra o exemplo a seguir:

git checkout -b <nome_branch_novo>

Pode-se verificar que apesar de ter criado o branch, ainda não estamos nele, por isso ainda não é possível realizar alterações. Se executarmos o comando "git branch" para verificar os branches locais, pode-se verificar que ainda não estamos no branch conforme a seguir:


  prompt> git branch
  * (no branch)
  informacoes
  contatos
  master
  novo

O Git permite que possa ser criado um novo branch e realizar um check out com "git checkout -b" conforme a seguir:


  prompt> git checkout -b comBaseNo-1.0
  Switched to a new branch "comBaseNo-1.0"

Agora já podemos realizar as alterações necessárias.

O Git ainda oferece outra forma, em que também é possível criar um novo branch usando "git branch" ou "git checkout -b" e usar um nome de Tag como segundo parâmetro a seguir:


  prompt> git checkout -b outro-comBaseNo-1.0 1.0
  Switched to a new branch "outro-comBaseNo-1.0"

Assim, criou-se o branch "outro-comBaseNo-1.0" com base no Tag "1.0".

A criação de branches a partir das Tags são úteis para realizar correções de bugs ou fazer alterações no código lançado. Esses branches são chamados de "branches da release" e devem conter apenas alterações mínimas e focar principalmente nas correções, se existem bugs ou problemas na lógica do código, mas nunca devem conter novas funcionalidades.

Os branches das releases tornam possível a continuidade no desenvolvimento de novas funcionalidades no branch "master", enquanto outra parte da equipe foca na correção da versão lançada, sem as novas funcionalidades.

Uma boa prática utilizada nas organizações é utilizar como nome dos branches das releases o prefixo "RB_" mais o número da release. Assim, uma release com a versão 1.2 seria "RB_1.2". Outra prática comum é que este branch exista até que a nova versão seja totalmente testada e aprovada, assim, cria-se uma nova Tag contendo essas alterações e deleta-se o branch da release, visto que o seu propósito já foi atingido. Caso surjam outros erros nessa nova Tag, cria-se novamente um novo branch, corrige e testa-se e depois novamente uma Tag é criada e o branch é deletado.

A seguir temos um exemplo de como criar um branch de uma release (ou o branch de uma Tag) e realizar um check out para trabalhar no branch criado:


  prompt> git branch RB_1.0.1 1.0
  prompt> git checkout RB_1.0.1
  Switched to branch "RB_1.0.1"

Pode-se verificar que basta utilizar o nome da Tag como segundo parâmetro no comando "git branch". Assim, este comando cria um branch baseando-se na Tag referenciada.

Após realizar as alterações necessárias no branch basta criar uma nova Tag conforme a seguir:

prompt> git tag 1.0.1

Agora que o código foi corrigido e uma Tag foi criada basta deletar o branch. Vale ressaltar que não é possível deletar o branch que estamos, assim teríamos que voltar para o branch master e depois proceder com a deleção a seguir:


  prompt> git checkout master
  Switched to branch "master"
  prompt> git branch -D RB_1.0.1
  Deleted branch RB_1.0.1. 

Os branches e Tags ajudam a manter o repositório organizado. O único cuidado que se deve ter é em manter os nomes bem definidos, o padrão que foi indicado ajuda a contornar este problema.

Os nomes das Tags ou dos branches podem conter uma barra (/), mas não podem terminar com a barra. Isso permite organizar os branches como estruturas de diretórios. Também pode ser usado o ponto (.) no nome, exceto no inicio da porção de um nome como "releases/.1.0" ou ".releases/1.0". Alguns caracteres especiais também não são permitidos nos nomes das Tags ou dos branches como espaços, til (~), dois pontos (:), ponto de interrogação (?), asteriscos (*), e abertura de colchetes ([). Por fim, branches e Tags podem ser nomeados assim como qualquer arquivo válido ou diretório do file system.

Uma última observação quanto a organização dos repositórios é com relação aos múltiplos projetos. Múltiplos projetos são bastante comuns nas organizações de médio e grande porte. Mesmo se esses projetos forem diferentes partes de um produto, é preciso segregar todos eles facilitando o gerenciamento do projeto como um todo. O Git permite o gerenciamento de múltiplos projetos através de diferentes formas. Podemos armazenar todos os projetos em um único repositório ou ter um repositório individual para cada projeto. Cada abordagem tem seus prós e contras.

O primeiro método é mais direto, em que se cria apenas um repositório e armazena-se todos dentro deste repositório. Essa é a mesma abordagem utilizada pelo SVN e CVS. Assim sendo, basta clonar um repositório para ter acesso a tudo. Essa abordagem funciona bem para projetos que necessitem ter um histórico em comum, tal como um projeto que é composto de vários componentes que são liberados juntos. Portanto, se cada projeto ou componente é sempre lançado como parte de um projeto maior, compartilhar um mesmo histórico pode ser interessante. Isso garante que todo o histórico do repositório gire em torno de um projeto.

No entanto, se os projetos são liberados separadamente, provavelmente é mais interessante que cada um necessite do seu próprio histórico. É importante fazer essa separação, visto que as Tags e branches no Git se referem a todo repositório e não apenas a uma seção deste. Se fosse utilizado o método anterior teríamos muitos branches e Tags que cresceriam de forma exponencial. A criação de repositórios no Git é dita como lightweight (ou leve), assim a criação de repositórios no Git é algo simples e de baixo custo.

Caso seja necessário gerenciar mais de um repositório pode ser que precisaremos utilizar submódulos. Este assunto será tratado em outro artigo.

Bibliografia

[1] Travis Swicegood. Pragmatic Version Control Using Git. Bookshelf, 2008.

[2] Git Reference
https://git-scm.com/

[3] GitHub
https://github.com/