# Colaborando com Git Git funciona localmente. Commits, branches, logs estão gravados no repositório local. Para colaborar com outros projetos, precisamos publicar e ter acesso a repositórios públicos. É possível publicar através de vários protocolos. Além de um diretório local, uma das formas mais simples é utilizar SSH, já que independe de outra configuração e permite escrita. HTTP é uma boa opção somente leitura para repositórios públicos, já que não exige credenciais para acesso. A outra opção é o protocolo próprio do Git, através do git daemon. ## Clonando Repositórios Para criar um novo repositório a partir de um repositório publicado, usamos o comando git clone. ~/$ git clone /home/user2/project Cloning into 'project'... done. ~/$ cd project ~/project$ ls -a . .. .git README ~/project$ Quando um repositório é clonado, temos uma cópia local das referências deste repositório. O repositório é registrado com um nome, sendo origin o nome padrão, e as referências a seus branches são prefixadas com este nome e uma barra. Para visualizar estes branches remotos, utilizamos a opção -r do comando git branch. ~/project$ git branch -r origin/HEAD -> origin/master origin/master ~/project$ Note que o HEAD do repositório remoto aponta para o branch remoto chamado master. O HEAD remoto é utilizado para criar um novo branch local quando o repositório é clonado. ~/project$ git branch * master ~/project$ ## Remotes Com Git, é possível colaborar com mais de um repositório remoto. A lista de repositórios rastreados, com seus nomes e URLs fica no arquivo de configuração do repositório, ou seja, $REPODIR/.git/config. Para manipular esta lista, o comando git remote pode ser útil. É possível listar, adicionar, remover, renomear repositórios remotos. Ao clonar um repositório, o remoto origin já é criado. Este é o nome padrão para comandos que aceitam um remoto como parâmetro, mas permitem que seja omitido, assumindo o remoto origin. ~/project$ git remote origin ~/project$ Para adicionar um novo remoto, usamos o subcomando add, passando um nome e uma URL como parâmetros. ~/project$ git remote add git://alice.example.com/project.git/ ~/project$ git remote show alice origin ~/project$ git remote show -n alice * remote alice Fetch URL: git://alice.example.com/project.git/ Push URL: git://alice.example.com/project.git/ HEAD branch: (not queried) Local ref configured for 'git push' (status not queried): (matching) pushes to (matching) ~/project$ Ao adicionar um novo repositório remoto, apenas acrescentamos este repositório ao nosso arquivo de configuração. Não fazemos nenhum download dos seus branches e commits remotos. A opção -n do subcomando show evita buscar dados remotamente, que utilizaria a rede. Note que temos duas URLs, uma para fetch e outra para push, que veremos logo a seguir o que significam. Também temos o branch para qual o HEAD do remoto aponta. Neste caso, como o repositório não foi acessado, não temos esta informação. E a configuração para o push, que veremos logo a seguir, fará atualização de branches que se casem. Para remover um repositório remoto, utilizamos o subcomando rm, e pare renomear, o subcomando rename. ~/project$ git remote rename alice aliceS ~/project$ git remote show aliceS origin ~/project$ git remote rm aliceS ~/project$ ## Fazendo atualizações Após adicionar um remoto, queremos fazer o download dos branches e commits remotos. Um dos comandos que pode ser utilizado é git fetch. ~/project$ git fetch alice From git://alice.example.com/project.git/ * [new branch] master -> alice/master * [new branch] shell -> alice/shell ~/project$ git branch -r alice/master alice/shell origin/HEAD -> origin/master origin/devel origin/master ~/project$ Note que ao realizar o fetch, diferente do clone, não é criada uma referência ao HEAD do repositório remoto. Tampouco, é criado um branch local, ou feito um checkout. As referências, no entanto, são escritas localmente, de tal forma que é possível investigar os commits dos branches remotos sem novo acesso à rede. Como o acesso à rede não é feito a cada comando, novas atualizações precisam ser feitas quando o repositório remoto é atualizado e deseja-se investigar novos branches ou novos commits. Além de git fetch, pode ser utilizado git remote update. ~/project$ git remote update Fetching origin Fetching alice From git://alice.example.com/project.git/ * [new branch] devel -> alice/devel ~/project$ Veja que um novo branch foi atualizado. ## Branches remotos Agora que temos uma lista de branches remotos, podemos manipulá-los como branches locais, usando git log, git diff, entre outros comandos. Podemos fazer um checkout de um branch remoto, criando um branch local. ~/project$ git checkout -b shell alice/shell Branch shell set up to track remote branch shell from alice. Switched to a new branch 'shell' ~/project$ git branch -vv master 033d4b8 [origin/master] Acrescenta lista de arquivos a serem ignorados. * shell a5cb5bb [alice/shell] Implementação de hello em shell script. ~/project$ A opção -vv de git branch mostra o commit ID, o sumário do commit, e o branch remoto que é rastreado pelo branch local. O rastreamento de um branch local é importante para comandos como git push e git pull, que veremos logo mais. ## Atualizando repositórios remotos Após clonar ou adicionar um repositório remoto com uma URL que permite escrita, poderemos atualizá-lo através do comando git push. O comportamento de git push sofreu alterações ao longo das versões de Git. Veremos alguns destes comportamentos padrões, e como utilizar parâmetros explícitos para evitar surpresas. ~/project$ git add hello.c ~/project$ git commit -m "Utiliza world sem capitalização" [master 18c7b69] Utiliza world sem capitalização 1 file changed, 1 insertion(+), 1 deletion(-) ~/project$ git push origin master:master Counting objects: 3, done. Delta compression using up to 4 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 321 bytes | 0 bytes/s, done. Total 3 (delta 2), reused 0 (delta 0) To /home/user2/project 033d4b8..18c7b69 master -> master ~/project$ Adicionamos um novo commit ao nosso histórico, e fazemos uma atualização no repositório remoto origin. O primeiro parâmetro é o repositório remoto que queremos atualizar. O segundo parâmetro, chamado refspec, especifica o branch local e o branch remoto que serão sincronizados. No exemplo acima, o branch local master foi utilizado para atualizar o branch remoto master. Objetos são escritos no repositório remoto, e a referência atualizada. Note que o branch de origem deve conter todos os commits no branch de destino a ser atualizado. Caso contrário, o push não avançará o branch remoto, o que chamamos de fast-forward. Veremos mais à frente as condições para termos uma situação de fast-forward. Tanto o remoto quanto o refspec podem ser omitidos. O padrão em versões anteriores à versão 2.0.0 do Git era fazer o push de todos os branches com nomes iguais ao repositório remoto especificado explicitamente ou implicitamente. Se o remoto não é especificado, é assumido o remoto que o branch corrente rastreia. No exemplo sobre Branches remotos acima, o branch local shell rastreia o branch remoto alice/shell. Caso este branch seja o branch corrente, git push utilizaria por padrão o repositório remoto alice. O refspec matching, como é chamado o padrão utilizado em versões anteriores à Git 2.0.0, atualizaria todos os branches no remoto alice que tivessem um branch local com o mesmo nome. Ou seja, se alice tivesse branches master, devel e shell, e estes mesmos branches existissem localmente, todos seriam atualizados. Ainda que o branch local master rastreasse o branch remoto origin/master, alice/master seria atualizado neste caso específico. Se o branch corrente fosse master, que rastreasse origin/master, o repositório remoto origin seria atualizado no lugar do repositório remoto alice. O novo padrão, a partir da versão 2.0.0, se chama simple. Ele atualiza apenas o branch atual, apenas se o branch remoto rastreado tiver o mesmo nome. As outras opções, sendo todas elas configuráveis globalmente ou por repositório, através da opção push.default, são upstream, current e nothing. A opção nothing exige que o refspec esteja explícito na linha de comando. A opção current atualiza no remoto especificado ou ímplicito o branch com o mesmo nome que o branch local corrente. E a opção upstream atualiza o branch remoto rastreado pelo branch corrente. ## Repositórios bare É importante mencionar que ao realizar push em um repositório com um diretório de trabalho, o diretório de trabalho e o índice não são atualizados, mesmo que o branch corrente seja o branch atualizado. Isto pode causar problemas, caso o diretório de trabalho seja atualizado. Portanto, o padrão em versões desde 1.7.0 é impedir que tal branch seja atualizado, a não ser que a opção de configuração receive.denyCurrentBranch utilize os valores warn, false ou ignore. Anteriormente à versão 1.7.0, o diretório de trabalho era atualizado quando um push era feito. Isso poderia ser perigoso, pois o diretório de trabalho poderia conter alterações que não foram gravadas, causando a perda de dados. Mas existe um tipo de repositório que não possui diretório de trabalho, e pode evitar problemas como esses. Este tipo de repositório é conhecido como repositório bare. Ele pode ser criado com a opção --bare de git init ou de git clone. Geralmente, por convenção, tais repositórios têm a URL terminada em .git. $ git clone --bare project/ project.git/ Cloning into bare repository 'project.git'... done. $ ## Publicando um repositório Vimos como trabalhar com um repositório remoto, obtendo seus commits para trablharmos localmente. No entanto, o caminho reverso é necessário para publicarmos os commits locais para que outros possam trabalhar com eles. Há várias formas de fazê-lo. Veremos aqui algumas delas, utilizando um host próprio, que seja acessível ao público alvo, através de SSH, HTTP ou git daemon. ### SSH Publicar através de SSH é simples de ser feito, mas tem algumas implicações. Entre elas é o acesso exigido a um shell no host para os usuários, dificultando acesso anônimo e criando um novo problema de segurança a ser resolvido. A outra implicação é que o acesso por múltiplos usuários a um mesmo repositório exige um cuidado especial para evitar problemas de permissão quando um usuário publica novos commits e outros objetos. Há algumas soluções que resolvem alguns desses problemas, criando um shell e um usuário especial para uso dedicado a repositórios Git. Veremos, no entanto, apenas a solução mais comum, que não exige outro software instalado no host, além de Git. Uma excelente forma de utilizar a publicação via SSH é disponibilizá-la de forma somente leitura através de outros métodos, e ter um único usuário que possa atualizá-lo, o que resolve vários dos problemas já mencionados. Há duas formas simples para publicar um repositório via SSH. Uma delas é criar um bare clone do repositório e fazer a cópia para o host remoto, através do método de preferência. Uma opção é scp. $ git clone --bare project/ Cloning into bare repository 'project.git'... done. $ scp -rq project.git/ git.example.com:/srv/git/alice/ $ A outra opção é criar um repositório bare no host remoto, e fazer o primeiro push. alice@git.example.com $ mkdir project.git alice@git.example.com $ cd project.git alice@git.example.com project.git$ git init --bare Initialized empty Git repository in /srv/git/alice/project.git/ alice@git.example.com project.git$ ~/project$ git remote add origin git.example.com:/srv/git/alice/project.git/ ~/project$ git push origin master:master Counting objects: 3, done. Writing objects: 100% (3/3), 229 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To git.example.com:/srv/git/alice/project.git/ * [new branch] master -> master ~/project$