Iniciando no Git

Livro Pro GitGit é uma ferramenta de controle de versão distribuída (Distributed Version Control System ou DVCS) criada por Linus Torvalds (sim, o mesmo criador do Linux), tem uma arquitetura diferenciada que cria cópias completas do repositório em cada máquina de trabalho, ao contrário da maioria das outras ferramentas que criam um único repositóriocentralizado. Dessa forma o Git ganha uma velocidade incrível, visto que quase toda operação é local. Daí, de tempos em tempos, basta sincronizar seu repositório local com o centralizado e todos verão suas alterações.

Outro diferencial da arquitetura do Git é que ele guarda versões completas dos arquivos de cada commit ao invés de apenas as alterações de um commit para outro, como a maioria das ferramentas de controle de versões o fazem, o que o permite ter uma flexibilidade (e poder) maiores e faz com que ele se porte como um mini-sistema de arquivos.

Abaixo estão algumas dicas para iniciar no Git e um guia de referência que lista os principais comandos utilizados com uma breve descrição.

Iniciando…

Minhas dicas para iniciar na ferramenta Git são os cursos on-line do site codeschool.com. O curso “Try Git” é grátis, e já ajuda a entender os mecanismos do Git. Mas quem puder, faça os cursos avançados, pois te darão um conhecimento legal em um tempo reduzido.

Se quiser um conhecimento um pouco mais aprofundado, entendendo mais detalhes dos mecanismos do Git, o livro grátis Pro Git é bem completo e de fácil leitura.

Guia de referência

Áreas do Git

O Git possui algumas divisões de áreas de trabalho. A versão (snapshot) dos arquivos que estamos trabalhando fica no diretório de trabalho. Quando queremos fazer um commit, precisamos adicionar os arquivos a serem comitados numa área conhecida como staging (preparação) e, sem seguida, efetivar a operação (commit).

Operações locais

Figura 1: Operações locais

Criação de repositórios

Há basicamente duas formas de conseguir um repositório no Git. Um deles é criando um novo a partir de um diretório já existente e o outro é “clonando” um repositório remoto (visto mais a frente):

$ git init

Com esse comando é criado um repositório na pasta atual e são inseridos alguns arquivos de metadados do repositório numa pasta .git criada automaticamente. Para remover um repositório, basta remover este diretório .git.

Configuração

Antes de começar a utilizar repositórios no Git, é interessante configurar algumas informações, como o nome do usuário e e-mail, que constarão nos detalhes de cada commit:

$ git config user.name "Vanessa Schissato"
$ git config user.email "abobrinha@gmail.com"

Se quiser que as configurações sejam globais, ou seja, valham para todos os repositórios:

$ git config --global user.name "Vanessa Schissato"

Ciclo de vida dos arquivos

Criar

Cada arquivo no repositório pode ter o status monitorado (tracked) ou não-monitorado (untracked). Arquivos monitorados são os que estavam presentes no último commit. Portanto, um arquivo novo criado no diretório fica com o status não-monitorado. Quando queremos realizar um commit, precisamos antes marcar os arquivos que serão comitados. Isto é feito adicionando-os numa área conhecida como staging. Após todos os arquivos necessários marcados para o commit, basta efetivar a operação. Segue o fluxo de um arquivo desde seu nascimento até seu commit:

Ciclo de vida de um arquivo

Figura 2: Ciclo de vida de um arquivo

As operações necessárias para criar e comitar um arquivo são:

$ touch arq1 # Cria um novo arquivo 'arq1' vazio (não-monitorado) no sistema de arquivo
$ git add arq1 # Prepara o arquivo 'arq1' para ser adicionado no próximo commit (staged)
$ git commit -m "Primeiro commit" # Cria um novo commit com todas as alterações na área de staging e torna os arquivos monitorados

Alguns atalhos são possíveis, como adicionar todos os arquivos na área de staging sem precisar discriminá-los um a um:

$ git add --all # Adiciona todos os arquivos modificados na área de staging

Outro atalho importante é o de adicionar e comitar num único comando. O detalhe importante aqui é que ele só adiciona na área de staging e, consequentemente, comita, arquivos que já estejam monitorados:

$ git commit -a -m "Primeiro commit" # Adiciona todos os arquivos monitorados que tenham sofrido modificações na área de staging e os comita

Uma coisa importante a se notar é que quando um arquivo é adicionado na área de staging, ele é armazenado tal qual no momento. Se alguma nova alteração for feita no mesmo arquivo, ela não será refletida na versão staged automaticamente. Se for necessário que as novas alterações sejam adicionadas no próximo commit, deve ser feito novamente o git add.

Por fim, um commit é apenas um ponteiro para um snapshot do conteúdo adicionado na área de staging na hora do commit:

Um comando essencial no uso do Git é o de verificação de status dos arquivos, que discrimina qual a situação dos arquivos do repositório:

$ git status

É possível adicionar arquivos que serão ignorados do sistema de versionamento. Para isso devem ser adicionadas entradas no arquivo .gitignore.

Alterar

Os arquivos sofrem alterações com o passar do tempo. Quando o conteúdo de um arquivo é modificado e queremos comitar estas alterações, basta seguir o fluxo staging -> commit.

$ git add arq1
$ git commit -m "Segundo commit"

Remover

Vimos como controlar o versionamento de arquivos de algumas maneiras, mas, e quando queremos remover esse arquivo do versionamento? Bom, a opção mais intuitiva:

$ rm [NOME_ARQUIVO] # Comando do SO para remover o arquivo
$ git add [NOME_ARQUIVO] # Adiciona modificações do arquivo no staging (nesse caso a sua remoção)

Mas, há um atalho para fazer as duas operações simultaneamente:

$ git rm [NOME_ARQUIVO]

Mover/Renomear

Bom, vimos comandos para o nascimento, vida e morte de um arquivo no controle de versionamento. Mas e se o arquivo tem seu próprio caminho alterado (alterada pasta e/ou nome do arquivo)? A idéia mais intuitita seria fazer uma remoção do arquivo antigo e uma adição do novo arquivo no Git:

$ mv arq1 arqNovo # Comando do SO para mover o arquivo
$ git rm arq1 # Comando para adicionar essa remoção no staging
$ git add arqNovo # Comando para adicionar o novo arquivo no staging

Funciona, mas existe um atalho mais prático que engloba essas 3 operações:

$ git mv arq1 arq1Femea

Revertendo alterações

Bom, na vida temos que aprender a conviver com nossos erros, mas o Git é muito mais poderoso, e nos permite fazer uma espécie de volta ao tempo e reverter quase todas as operações. Por exemplo, quer descartar as alterações locais (ainda não no staging e nem comitadas)?

$ git checkout -- [NOME_ARQUIVO] # Descarta alterações no arquivo e mantém a versão do último commit

Adicionou o arquivo no staging erroneamente (marcou para o próximo commit)?

$ git reset HEAD [NOME_ARQUIVO] # Remove o arquivo do staging e mantém a versão atual no diretório de trabalho

Chegou a commitar algo que não deveria? É possível também reverter o commit:

$ git reset HEAD^ # Reverte commit, mas mantém a versão do arquivo comitado no diretório de trabalho

Foi além e precisa reverter mais de um commit?

$ git reset HEAD^^ # Reverte últimos dois commits
$ git reset HEAD^^^ # Reverte últimos três commits

Ou seja, se eu quiser reverter os últimos 100 commits vou precisar seguir essa lógica aí do “um elefante incomoda muita gente”, um circunflexo pra cada commit? Não, podemos usar o atalho:

$ git reset HEAD~100

Só tem um problema…quando eu utilizo estes comandos, meus arquivos ficam com a versão do último commit no diretório de trabalho. Mas se for necessário inclusive descartar as alterações destes commits:

$ git reset --hard HEAD^ # Reverte o último commit, descartando as alterações que o commit introduziu

Esse argumento “–hard” diz para que todas as alterações sejam descartadas. O contrário seria usar o “–soft“, mas como ele é o default, resolvi omití-lo.

Mas, e no caso em que eu fiz um commit, mas esqueci de adicionar algum arquivo nele? Bom, a idéia intuitiva seria reverter o commit mantendo os arquivos e refazer:

$ git reset --soft HEAD^
$ git add [NOME_ARQUIVO_ESQUECIDO]
$ git commit -m "Recomitando"

Há um atalho para fazer esse remendo no commit:

$ git add [NOME_ARQUIVO_ESQUECIDO]
$ git commit --amend -m "Mensagem commit"

Histórico

Como em qualquer ferramenta de controle de versão, a análise do histórico pode ser uma importante ferramenta, seja para acompanhar o desenvolvimento do sistema, coletar métricas de commits, etc. O comando básico para verificar os commits efetuados é:

$ git log [-LIMITE_COMMITS_EXIBIDOS] [--stat] [-p]

Alguns argumentos mais utilizados são para limitar o número de commits exibidos, o comando “–stat” que traz algumas estatísticas do commit (como linhas adicionadas, linhas removidas, etc), além do argumento”p” que indica que deve ser exibido o diff do commit em relação ao anterior.

Diff

Para verificar as modificações nos arquivos é possível utilizar o comando diff:

$ git diff

Este comando acima exibe a comparação apenas dos arquivos que estão no seu diretório de trabalho com relação ao que está na área de staging. Se a necessidade é comparar os arquivos da área de staging com os do último commit:

$ git diff --staged

Além disso, pode ser necessário um maior detalhamento das informações alteradas, como, por exemplo, detalhamento em nível de palavra, e não de linha:

$ git diff --word-diff

Autorização

O Git por si só não realiza nenhum controle de acesso (autorização). Se for necessário, há algumas ferramentas no mercado, como Github (host), BitBucket (host) e Gitosis, Gitorious.

Repositórios remotos

Quando temos um repositório local e queremos torná-lo remoto:

$ git remote add [NOME_REMOTO] http://github.com/Teste/repo-git.git # Adiciona um repositório remoto do Github como 'origin', por exemplo.
$ git push [NOME_REMOTO] [NOME_BRANCH] # Sincroniza as alterações locais (branch master, por exemplo) com o repositório remoto dado por 'origin', por exemplo

Pode ser necessário fazer o passo contrário, clonar um repositório remoto já existente. Por exemplo, vamos supor que desejamos criar um repositório que aponte para um repositório remoto no Github:

$ git clone http://github.com/Teste/repo-git.git

Este comando acima já baixa o código, cria o repositório (init), adiciona uma entrada ‘origin‘ na lista de repositórios remotos (remote add), além de já ir para o branch master (checkout).

Para listar, adicionar ou remover ‘remotes’, ou seja, entradas de repositórios remotos:

$ git remote add origin http://github.com/Teste/repo-git.git $ Adiciona uma entrada para o repositório do Github como 'origin'

$ git remote rm origin # Remove entrada do repositório remoto 'origin'

$ git remote -v # Lista os repositórios remotos que o repositório local conhece

Branches (trabalhando numa feature)

O Git incentiva o uso de branches, já que são de rápida criação e relativamente simples de mergear em relação as outras ferramentas de controle de versão. Um branch é apenas um ponteiro para um commit específico. Para indicar qual o branch atual de trabalho, o Git guarda um ponteiro especial conhecido como HEAD.

Branch

Figura 3: Branch

Estes branches locais são também conhecidos como branches tópicos. Para criar e trabalhar numa branch:

$ git branch [NOME_BRANCH] # Cria a branch, mas sem alterar os arquivos do diretório de trabalho
$ git checkout [NOME_BRANCH] # Vai para o diretório de trabalho da branch

Há um atalho para fazer as duas operações acima (criar um branch e já apontar pra ela):

$ git checkout -b [NOME_BRANCH]

Se quiser ver todos os branches locais criados:

$ git branch [-v]

Se quiser listar apenas os branches que você já fez merge no branch atual, ou os que vocês ainda não fez o merge:

$ git branch --merged # Lista apenas os branches já mergeados com o branch atual
$ git branch --no-merged # Lista apenas os branches não mergeados com o branch atual

Para remover um branch local:

$ git branch -d [NOME_BRANCH]

Quando um branch ainda não está mergeado, para que se possa removê-lo, é necessário usar a seguinte sintaxe:

$ git branch -D [NOME_BRANCH]

Merge

Para realizar um merge entre dois branches, por exemplo, de um branch para o master:

$ git checkout master # Vai para o branch master
$ git merge [NOME_BRANCH] # Faz o merge das alterações no branch para o master

Cuidado! Arquivos ainda não monitorados ficam visíveis em todos os branches.

Ao tentar mudar de branch quando existem arquivos monitorados modificados, é necessário antes comitar as alterações ou descartá-las.

Ao fazer um merge, podem ocorrer duas situações distintas, um dos casos possíveis é quando o branch ‘pai’ no qual se quer realizar o merge não teve commits posteriores à criação do branch filho (ancestral direto). Nesse caso, para fazer o merge, basta avançar o ponteiro do branch ‘pai’ até o commit para o qual aponta o branch filho, num processo conhecido como Fast Forward.

Merge com Fast Forward

Figura 4: Merge com Fast Forward

Outro caso possível ao fazer um merge é que ambos os branches (pai e filho) tenham evoluído em paralelo, ocasionando uma bifurcação no histórico de commits. Nesse caso, o Git faz um processo de merge um pouco mais complexo, encontrando automaticamente um ancestral comum e fazendo o merge das duas branches gerando um novo commit automático de merge.

Merge recursivo

Figura 5: Merge com recursão

As vezes, ao realizar um merge, pode ocorrer um conflito devido a alterações diferentes na mesma parte do mesmo arquivo. Neste caso, os arquivos são listados como unmerged e devem ser tratados manualmente os conflitos e comitada a resolução:

$ git commit -a -m "Merge manual" # Adicionar correção no stage e commitar para resolver o conflito

Branches remotos

É muito importante entender o conceito de branches remotos, que são referências ao estado dos seus branches no seu repositório remoto. São representados localmente como branches locais que você não pode mover, eles se movem automaticamente a cada vez que é feita uma sincronização (push ou pull). Ou seja, são ponteiros para o estado do branch remoto no momento da última sincronização.

Branches remotos

Figura 6: Branches remotos

Depois de entendido que os branches remotos são copiados no repositório local como branches [REMOTE_NAME]/[BRANCH_NAME], todas as operações comuns de branches se aplicam, como merge, etc.

A boa prática diz que se for ser trabalhado mais de um dia num branch, este deve ser tornado remoto. Para fazer um branch local se tornar remoto:

$ git push origin master # Adiciona o branch master remotamente no repositório representado por origin

ou

$ git push origin master:master_heroku # Caso o nome do branch remoto seja difernete (like Heroku)

Para atualizar o repositório local (mas sem alterar os arquivos do diretório de trabalho):

$ git fetch origin # Atualiza o repositório local ('origin', por exemplo), mas sem mergear com os dados do diretório de trabalho

fetch

Figura 7: Operação fetch

Para fazer o merge dos commits remotos com o do branch local:

$ git merge origin/master # Faz o merge do branch master do repositório local origin 'origin/master' com o branch 'master' local

Há um atalho para fazer a atualização do repositório local e já fazer o merge com o branch local atual:

$ git pull

A operação contrária (enviar as alterações locais para o repositório remoto), pode ser feita com o comando:

$ git push origin master

Importante! Não fazer alterações nos commits depois de realizadas sincronizações com o repositório remoto, por exemplo: reset, commit –ammed.

Para obter um branch remoto:

$ git pull # Atualiza os branches

Para listar os branches remotos:

$ git branch -r # Lista branches remotos atualizados no repositório local

Para ver mais informações sobre um remote específico:

$ git remote show origin # Traz mais informações do remote origin, por exemplo

Para remover um branch remoto:

$ git push origin :master

E, para remover as referências locais de branches removidos remotamente:

$ git remote prune origin

Ao clonar um repositório remoto, é criado automaticamente um remote origin e um branch local master seguidor do branch origin/master (tracking branches). Por isso, ao fazer git pull e git push sem argumentos é subentendido git pull origin master.

Para criar um branch local seguindo (tracking) um branch remoto:

$ git checkout --track origin/outro_branch

Rebase

Além do comando merge, existe outra alternativa para integrar mudanças de um branch em outro, que é o comando rebase:

$ git checkout experiment
$ git rebase master

Estes comandos acima fazem com que as alterações feitas no branch experiment a partir do ponto do ancestral comum sejam adicionadas no branch master como se fossem commits feitos no próprio master, o que torna o histórico um pouco mais linear e limpo, embora funcionalmente nada mude.

É possível realizar o rebase sem a necessidade de checkoutar para o branch tópico:

$ git rebase master experiment

Rebase

Figura 8: Operação rebase

É possível fazer algumas coisas mais avançadas com rebase, como no exemplo abaixo, onde existe um branch derivado de outro branch, e queremos apenas mandar para o master o branch neto. Nesse caso, pode-se usar o comando:

$ git rebase --onto master server client

Operação rebase com parâmetro --onto

Figura 9: Operação rebase com parâmetro –onto

Cuidado! Ao fazer rebase de commits que já se tornaram remotos, pode haver problemas com outras pessoas que se basearam nos commits que deixaram de existir.

Tags

Assim como em outros sistemas de controle de versão, tags são snapshots do código, referentes a um determinado commit, em geral representando uma release version. Para criar tags:

$ git tag -a v0.1 -m "Adiciona versão 0.1" # Cria tag local 'anotada', ou seja, que é uma cópia completa dos arquivos (recomendado)

$ git tag v0.1 # Cria uma tag leve, ou seja, apenas um ponteiro para um commit

$ git tag -s v0.1 # Cria uma tag assinada

$ git tag -a v0.1 -m "Adiciona versão 0.1" [HASH_COMMIT] # Cria uma tag a partir do commit passado

Para tornar as tags remotas:

$ git push --tags # Adiciona todas as tags ao repositório remoto

ou

$ git push origin [NOME_TAG] # Adiciona apenas a tag referenciada ao repositório remoto

Para ver as tags existentes:

$ git tag

Para filtrar as tags existentes:

$ git tag -l 'v1.1*'

Da mesma forma que em branches, para visualizar o código de uma tag, basta dar o checkout para a mesma:

$ git checkout [NOME_TAG]

Mas, cuidado! Uma tag é apenas um ponteiro para um determinado commit. Ao fazer o checkout para uma tag, na prática se está fazendo o checkout para as versões de arquivo do commit específico, o que significa que você não estará apontando seu diretório de trabalho para nenhum branch, algo conhecido como detached.

Referências

Livro Grátis “Pro Git” – Scott Chacon
Cursos “Git” – codeschool.com

 

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>