Grandes empresas de desenvolvimento de software trabalham de forma paralela, onde os funcionários se dividem em pequenas equipes e cada uma delas foca em uma determina parte do projeto. Porém, como software desenvolvido é todo integrado, é necessário que as equipes conversem e os seus pedaços de código também.

A partir dessa necessidade o versionamento de um sistema se faz necessário para promover a integração como também cuidar das melhorias constantes, ou muitos outros motivos. É indiscutível manter as cópias do sistema por questões de segurança também. Há um grande do que chamamos de Sistemas de controle de versões, tanto locais como remotos. Dentre as opções encontramos o Git.

O Git é um dos programas mais utilizados para controlar as versões de sistemas em desenvolvimento. Ao longo desse artigo veremos os principais comandos que, com certeza, todo desenvolvedor deve saber para trabalhar em equipe com o versionamento do código.

Para começar vamos clonar um repositório que já está pronto, bastando usar o comando da Listagem 1. Assim, pegamos tudo que estava em um repositório remoto e colocamos no nosso repositório local. A partir deste ponto já é possível fazer alterações no projeto.


  prompt> git clone git://github.com/projetoCMS/meusite.git
  Initialized empty Git repository in /work/meusite/.git/
  remote: Counting objects: 12, done.
  remote: Compressing objects: 100% (8/8), done.
  remote: Total 12 (delta 2), reused 0 (delta 0)
  Receiving objects: 100% (12/12), done.
  Resolving deltas: 100% (2/2), done.
Listagem 1. Clonagem de repositório

Comando Add

Os desenvolvedores estão constantemente adicionando novos arquivos e realizando alterações no conteúdo dos seus repositórios. Utilizando o comando git add passando o nome do arquivo ou dos arquivos como parâmetro faz com que ele armazene-os no seu Stage, estando prontos para serem comitados.

As alterações no Stage são simplesmente alterações no working tree em que se pretende “avisar” ao repositório sobre essas alterações que foram realizadas. O Stage também é chamado de Index ou Staging Area (ou área temporária) no Git.

O Staging Area é apenas um lugar em que se quer configurar commits que serão realizados no repositório local.

De forma geral, tem-se o working tree ou working directory, que é a visão atual em que estão os arquivos do projeto, ostaging area ou index prepara os arquivos a serem comitados, e por fim, após realizar um commit tem-se os arquivos no repositório na qual serão versionados, gerenciados e supervisionados pelo Git.

Pode parecer que o desenvolvedor tenha que realizar trabalhos duplicados, pois é necessário fazer git add para colocar o arquivo no staging area e depois um git commit para colocá-los no repositório. No entanto, o staging area é um local em que se pode preparar os futuros commits a serem realizados, sendo algo importante e que deve ser utilizado.

O comando git add também possui outras opções para alterar alguns comportamentos. Entre as opções disponíveis pode-se utilizar o comando add para selecionar quais arquivos ou partes de arquivos serão armazenados no stage. Para isso utiliza-se a opção -i, que abre um shell interativo permitindo fazer essas diferentes seleções.

Para exemplificar vamos considerar o arquivo de exemplo da Listagem 2 chamado index.html, que está no working tree.

<html>
  <head>
           <title>Testando o Git</title>
  </head>
  <body>
           <h1>Página Inicial GIT</h1>
           <ul>
                     <li><a href="teste2.html">Teste com Link</a></li>
           </ul>
  </body>
  </html>
Listagem 2. Arquivo HTML com conteúdo inicial de uma página

Realize um comando git add e git commit para adicionar ao stage e comitar o arquivo, respectivamente.

Agora podemos realizar uma alteração no arquivo anterior adicionando um link para que o usuário obtenha maiores informações sobre a página. Esse link pode ser adicionado após a tag <h1>, conforme o exemplo a seguir:

<li><a href="informacoes.html">Informações</a></li>

Executando o comando git add -i tem-se o prompt a seguir sendo exibido:


  prompt> git add -i
       staged unstaged path
       1: unchanged +1/-1 index.html
        *** Commands ***
       1: status 2: update 3: revert 4: add untracked
       5: patch 6: diff 7: quit 8: help
  What now>

Pode-se verificar que o What now> está aguardando uma opção a ser digitada (conforme vemos no código a seguir):

  • Digitando 1 será gerado o status atual no staging;
  • Digitando 2 será exibida a mensagem a seguir:
  • 
      What now> 2
        staged unstaged path
        1: unchanged +1/-1 index.html
      Update>>

Esta opção gera uma lista de arquivos que podem ser armazenados no stage. Nesse caso, existe um arquivo para ser armazenado, assim basta digitar 1 para que ele mude para um asterisco (*) ao lado de seu nome, significando que ele vai ser armazenado no stage, conforme pode ser visto no exemplo a seguir:


  Update>> 1
           staged unstaged path
           * 1: unchanged +1/-1 index.html
  Update>>

Agora basta pressionar Enter para voltar ao menu principal:


  What now> 1
           staged unstaged path
           1: +1/-1 nothing index.html
           *** Commands ***
           1: status 2: update 3: revert 4: add untracked
           5: patch 6: diff 7: quit 8: help
  What now>

Se o status for verificado novamente, pode-se verificar que existe uma alteração no stage e nada listado no unstaged changes.

Outro comando que pode ser utilizado é o revert que faz reversão da funcionalidade, ou seja, retira do stage a alteração realizada. Segue na Listagem 3 um exemplo em que a última alteração será revertida.


  What now> 3
           staged unstaged path
           1: +1/-1 nothing index.html
           Revert>> 1
           staged unstaged path
           * 1: +1/-1 nothing index.html
           Revert>>
           reverted one path
           *** Commands ***
           1: status 2: update 3: revert 4: add untracked
           5: patch 6: diff 7: quit 8: help
  What now>
Listagem 3. Comando revert

Pode-se verificar que a alteração no arquivo index.html estava armazenada no stage, porém ainda não havia sido executado um commit que tiraria a alteração do stage e colocaria no repositório local.

Executando novamente o status pode-se verificar como está a situação atual:


  What now> 1
           staged unstaged path
           1: unchanged +1/-1 index.html
           *** Commands ***
           1: status 2: update 3: revert 4: add untracked
           5: patch 6: diff 7: quit 8: help

A quarta opção permite colocar no stage todos os arquivos que ainda não estão lá. Essa opção é interessante quando foram realizadas diversas modificações e, ao invés de um add para cada arquivo ou uma lista de arquivos, adiciona-se todos os arquivos em um único comando.

O patch permite que sejam adicionados arquivos através do modo com o mesmo nome, assim, todas as diferenças são exibidas. Ele opera através de um shell interativo disponibilizando várias opções, como podemos ver na Listagem 4.


  What now> 5
           staged unstaged path
           1: unchanged +1/-1 index.html
           Patch update>> 1
           staged unstaged path
           * 1: unchanged +1/-1 index.html
           Patch update>>
           diff --git a/index.html b/index.html
           index e812d0a..ca86894 100644
           --- a/index.html
           +++ b/index.html
           @@ -6,7 +6,7 @@
           <html>
           <head>
           <title>Testando o Git</title>
           </head>
           <body>
           <h1>Página Inicial GIT</h1>
           <ul>
           - <li><a href="teste2.html">Teste com Link</a></li>
           + <li><a href="informacoes.html">Informações</a></li>
           </ul>
           </body>
           </html>
   
           Stage this hunk [y/n/a/d/e/?]?
Listagem 4. Modo Patch

Agora existe uma opção que permite armazenar esse pedaço (ou hunk) de código ou negá-lo. Um hunk é uma alteração dentro de um arquivo. Cada área diferente em um arquivo é considerada um hunk.

Existem algumas opções a serem digitadas:

  • Digite y e as alterações são aceitas;
  • Digite n para pula as alterações;
  • Digite a ou d para adicionar ou nega todo o resto das alterações no arquivo;
  • Digite ? para exibir uma ajuda explicando o que cada uma das opções faz.

Se digitar a opção 7” o shell interativo é finalizado, como vemos no código a seguir:


  Stage this hunk [y/n/a/d/e/?]? n
  *** Commands ***
  1: status 2: update 3: revert 4: add untracked
  5: patch 6: diff 7: quit 8: help
  What now> 7
  Bye.

Agora que os arquivos já foram adicionados ao stage podemos comitá-los. Como fazer commits e as opções disponível para o commit serão verificadas na próxima seção.

Comando Commit

Commit é um processo simples que adiciona as alterações para o histórico do repositório e atribui um nome ao commit.

Um ponto importante é que o Git não versiona diretórios. Todo diretório deve possuir pelo menos um arquivo que será então versionado.

O git permite que os desenvolvedores criem arquivos que não sejam versionados, ou seja, eles serão ignorados pelo repositório. Para isto basta criar um arquivo com um ponto no inicio do nome como, por exemplo, ".nomearquivo". Todos arquivos que começam com um ponto são ignorados pelo sistema de arquivos. Neste caso, a alteração não é enviada para o repositório central.

O comando git commit pode ser utilizado de múltiplas formas para comitar as alterações para o repositório, porém todo commit necessita de uma mensagem para logar, já que esta o identificará. Para isso basta utilizar o comando git commit -m "mensagem de exemplo". A mensagem pode ser qualquer string válida. Também é possível definir múltiplos parágrafos utilizando múltiplas opções –m.

Além dessa, existem outras três formas de realizar um commit.

De forma geral, primeiramente o desenvolvedor executa o comando git add para adicionar as alterações ao stage para então poder comitá-las (bastando executar o comando commit). Faremos isso com os comandos a seguir:


  prompt> git add qualquer-arquivo
  prompt> git commit -m "alterações realizadas em qualquer-arquivo"

Outra forma é executar o commit com o parâmetro –a, assim, dizemos ao Git para que ele pegue a última versão do working tree e realize um commit ao repositório. Arquivos novos que não estão versionados não serão adicionados, apenas os que já foram versionados.

Assim, para utilizar o parâmetro -a basta executar o comando a seguir:

prompt> git commit -m " alterações realizadas em qualquer-arquivo" –a

Também é possível comitar um arquivo específico através do comando a seguir:

prompt> git commit -m " alterações realizadas em qualquer-arquivo" qualquer-arquivo

Este comando é útil quando se quer realizar commit apenas em alguns arquivos que foram alterados e não em todos.

Após realizar um commit o stage é esvaziado.

Verificando Alterações

Para saber o que foi alterado no working tree pode-se utilizar os comandos git status e git diff.

O comando git status verifica todas as alterações que ocorreram no repositório. A saída é gerada baseando-se no status dos commits e do working tree.

Segue na Listagem 5 um exemplo de como utilizá-lo.


  prompt> git status
  # On branch master
  # Changes to be committed:
  # (use "git reset HEAD <file>..." to unstage)
  #
  # modified: index.html
  #
Listagem 5. Comandogit status

Pode-se notar que existem alterações aguardando para serem comitadas conforme informa a linha “Changes to be committed”. Porém, vamos adicionar outra alteração no arquivo index.html conforme a seguir:

<li><a href="contato.html">Contato</a></li>

Após salvar as alterações executa-se outro git status para verificar agora como ficou o status atual:


  prompt> git status
  # On branch master
  # Changes to be committed:
  # (use "git reset HEAD <file>..." to unstage)
  #
  # modified: index.html
  #
  # Changed but not updated:
  # (use "git add <file>..." to update what will be committed)
  #
  # modified: index.html
  #

Pode-se notar que agora o arquivo index.html está listado duas vezes. A primeira modificação diz respeito à alteração no stage que ainda foi comitado. A segunda alteração refere-se à alteração que foi realizada agora no working tree e que ainda não foi adicionada no stage, por isso a mensagem Changed but not updated.

Agora é um bom momento de verificar as modificações realizadas. Para isso pode-se utilizar o comando diff. Este comendo permite que possamos verificar o que foi alterado no working tree, no stage e no repositório.

O comando da Listagem 6 mostra as alterações no working tree que não foram armazenadas no stage ou que não foram comitadas ainda.


  prompt> git diff
  diff --git a/index.html b/index.html
  index ca86894..5fdc539 100644
  --- a/index.html
  +++ b/index.html
  @@ -7,6 +7,7 @@
  <html>
  <head>
  <title>Testando o Git</title>
  </head>
  <body>
  <h1>Página Inicial GIT</h1>
  <ul>
  <li><a href="informacoes.html">Informações</a></li>
  + <li><a href="contato.html">Contato</a></li>
  </ul>
  </body>
  </html>
Listagem 6. Comando diff

Veja que tem-se o link que foi adicionado, o sinal + no inicio da linha mostra que neste ponto ocorreu uma adição e está faltando no stage. Já o link acima "informacoes" mostra que não há qualquer alteração neste ponto.

Executando o comando diff sem qualquer parâmetro compara-se as alterações no working tree com o staging area.

Para visualizar as diferenças entre o staging area e o repositório basta utilizar o parâmetro --cached na chamada conforme a Listagem 7.


  prompt> git diff --cached
  diff --git a/index.html b/index.html
  index e812d0a..ca86894 100644
  --- a/index.html
  +++ b/index.html
  @@ -6,7 +6,7 @@
  <html>
  <head>
  <title>Testando o Git</title>
  </head>
  <body>
  <h1>Página Inicial GIT</h1>
  <ul>
  - <li><a href="teste2.html">Teste com Link</a></li>
  + <li><a href="informacoes.html">Informações</a></li>
  </ul>
  </body>
  </html>
Listagem 7. Parâmetro --cached

Agora pode-se notar no código que o sinal de "-" indicando que algo foi removido. Mas lembre-se que o que não está no stage ainda não é mostrado.

Para verificar o que está no working tree e no stage com o que está no repositório pode-se utilizar o comando da Listagem 8.


  prompt> git diff HEAD
  diff --git a/index.html b/index.html
  index e812d0a..5fdc539 100644
  --- a/index.html
  +++ b/index.html
  @@ -6,7 +6,8 @@
  <html>
  <head>
  <title>Testando o Git</title>
  </head>
  <body>
  <h1>Página Inicial GIT</h1>
  <ul>
  - <li><a href="teste2.html">Teste com Link</a></li>
  + <li><a href="informacoes.html">Informações</a></li>
  + <li><a href="contato.html">Contato</a></li>
  </ul>
  </body>
  </html>
Listagem 8. Verificando o que está no stage

O HEAD é uma palavra chave que se refere ao commit mais recente no branch que estamos atualmente.

Para comitar tudo basta utilizar o comando a seguir:


  prompt> git commit -a -m "Realizando todos os commits"   -m "
  Adicionado o link sobre informações"   -m " Adicionado o link sobre contato"
  Created commit 6f1bf6f: Realizando todos os commits
  1 files changed, 2 insertions(+), 1 deletions(-)

Algumas vezes é necessário limpar alguma coisa, por isso, o git fornece alguns comandos para mover arquivos ou conteúdos, conforme mostra a Listagem 9.


  prompt> git mv index.html testeola.html
  prompt> git status
  # On branch master
  # Your branch is ahead of 'origin/master' by 1 commit.
  #
  # Changes to be committed:
  # (use "git reset HEAD <file>..." to unstage)
  #
  # renamed: index.html -> testeola.html
  #
Listagem 9. Limpando arquivos e conteúdos

O Git remove o arquivo, mas mantém o histórico. Para eliminar tudo isso é necessário chamar o git add e na sequência git rm para remover o arquivo index.html que está no repositório. Assim, o commit realizado:

prompt> git commit -m "testando o arquivo com um novo nome"
  Created commit 9a23464: testando o arquivo com um novo nome
  1 files changed, 0 insertions(+), 0 deletions(-)
  rename index.html => testeola.html (100%)

Branches

O Git pode ser usado com um único branch, o branch master, similar ao trunk do SVN, que é outro tipo de repositório. Com isso, o desenvolvedor tem o versionamento do código e histórico e pode colaborar com outros desenvolvedores, não se preocupando com deleções acidentais. O grande problema disso é que o desenvolvedor não estaria usando todo o potencial da ferramenta.

Para começar a exploração dos branches pode-se fazer o clone de um projeto já existente, conforme o código a seguir:


  prompt> git clone git://github.com/projetoCMS/meusite.git
  Initialized empty Git repository in /work/meusite/.git/

Tudo no Git é tratado como um brach. Mesmo que todo o trabalho feito pelo desenvolvedor esteja no branch master é possível alterar o nome para um mais apropriado. Para renomear este branch principal para outro nome basta fazer conforme a seguir:


  prompt> git branch -m master meubranchmaster
  prompt> git branch
  * meubranchmaster

Dessa forma, o branch master foi renomeado para meubranchmaster. O parâmetro -m no primeiro comando solicita ao Git a execução da operação de renomeação. Os outros dois parâmetros são: o nome do branch antigo seguido pelo nome do novo branch.

O segundo comando git branch sem nenhum parâmetro tem como saída o nome de todos os branches locais do repositório.

Como tudo no Git é considerado um branch, ele é dito como sendo "barato", diferente de outros sistemas em que os arquivos são copiados em um novo diretório. No Git, um branch faz o versionamento dos commits que são realizados, mantendo assim o rastreamento do último. Este possui um link para seus pais, por onde o Git pode buscar todas as alterações nesse branch.

A maior dúvida entre os desenvolvedores e projetistas na maioria das organizações é quando criar um novo branch e qual o momento para fazer isso?

Existem basicamente três situações quando se deve criar um novo branch:

  1. Alterações Experimentais: Quando for preciso tentar reescrever um algoritmo para torná-lo mais rápido ou tentar refatorar uma parte do código para algum padrão pode-se criar um novo branch. Assim, é possível trabalhar nessa parte separadamente sem afetar o restante que já está funcional e pronta para deploy;
  2. Novas funcionalidades: Sempre que é necessário trabalhar em uma nova funcionalidade pode-se criar um novo branch. Quando finalizar a implementação da funcionalidade basta fazer um merge com o branch de release da versão;
  3. Correção de Bugs: Quando surgirem bugs a serem corrigidos no código que ainda não foi lançado ou em uma versão que já foi taggeada e lançada cria-se um branch para realizar a correção do bug. Isso garante uma maior flexibilidade aos desenvolvedores para trabalharem no erro. Após a correção pode-se realizar um merge.

A criação de um branch é simples utilizando o comando git branch e adicionando o nome do branch:

prompt> git branch novobranch

Para verificar se o branch foi criado basta executar o seguinte comando:


  prompt> git branch
  * master
  novobranch

Pode-se verificar que existem dois branches: o master e novobranch. O asterisco antes do nome do branch indica que este é o branch atual que estamos trabalhando no working tree.

Agora que o branch foi criado é necessário baixar os arquivos do branch e realizar as alterações necessárias. Para isso deve-se realizar um check out no branch:


  prompt> git checkout novobranch
  Switched to branch “novobranch"

Executando o comando git branch novamente pode-se verificar que agora o branch atual foi alterado:


  prompt> git branch
  master
  * novobranch

O Git também permite que um branch seja criado e que imediatamente seja realizado um check out dele, tudo em apenas um único passo. Para isso será criado um novo branch usando git checkout -b, lembrando que este precisa ter um nome único no repositório:


  prompt> git checkout -b alternate master
  Switched to a new branch "alternate"

Repare que o terceiro parâmetro master solicita para o Git criar o branch a partir do master, ao invés do branch atual.

Agora é necessário também fazer os merges necessários. Para isso existem diferentes formas:

  1. Straight merges: Este tipo pega o histórico de um branch e mescla com o histórico de outro. Este tipo de merge é utilizado quando se realiza um pull, e para exemplificar este caso basta criar um novo arquivo e realizar um add e commit no repositório, como mostra a Listagem 10.
    prompt> git add informacoes.html
    prompt> git commit -m "Adicionado o esqueleto de uma página de informações"
    Created commit 217a88e: Adicionado o esqueleto de uma página de informações
    1 files changed, 15 insertions(+), 0 deletions(-)
    create mode 100644 informacoes.html
    Listagem 10. Tipo de merge Straight
    Ao realizar esse commit em um branch pode-se verificar que ele ainda não existe no branch master. Para isso, basta realizar um merge entre os dois. Primeiramente, é preciso trocar para o branch que receberá as alterações realizadas em um outro. Nesse caso, é preciso ir para o branch master:
    prompt> git checkout master
    Switched to branch "master"
    Agora basta usar o git merge com o nome do branch que será mesclado com o branch atual (master), como mostra a Listagem 11.
    prompt> git merge alternate
    Updating 9a23464..217a88e
    Fast forward
    informacoes.html | 15 +++++++++++++++
    1 files changed, 15 insertions(+), 0 deletions(-)
    create mode 100644 informacoes.html
    Listagem 11. Git Merge
    Dessa forma, as alterações do branch alternate foram mescladas com o branch master.
  2. Squashed commits: Este tipo de merge pega o histórico de um branch e comprime (ou squash) em um commit no topo de outro branch.
    Branches criados para adicionar apenas funcionalidades utilizam mais esse tipo de merge. Assim, criam-se as funcionalidades, ou talvez se corrija algum bug dentro de um branch para então realizar um commit squased para mesclar as alterações de volta no branch.
    Esses commits são squased, pois o Git pega todo o histórico de um branch e comprime em um commit em outro branch. Deve-se ter cuidado com os commits squashed, pois na maioria das vezes queremos todos os commits separados no histórico, porém, se as alterações de um branch representam uma alteração individual, então este branch é um grande candidato para o commit squashed. Assim, ao invés de realizar o commit de cada versão da funcionalidade apenas realiza-se o commit da versão final.
    Como exemplo, criaremos um branch a partir do master chamado contato e realizaremos o check out deste branch: Agora se adiciona o arquivo contato.html que possui apenas um e-mail na página HTML e comitamos no branch, como mostra a Listagem 12.
    prompt> git add contato.html
    prompt> git commit -m "Adicionado arquivo HTML contato com o e-mail"
    Created commit 5b4fc7b: Adicionado arquivo HTML contato com o e-mail
    1 files changed, 15 insertions(+), 0 deletions(-)
    create mode 100644 contato.html
    Listagem 12. Comitando no branch
    Alteramos o arquivo colocando um segundo e-mail e comitamos novamente:
    prompt> git commit -m "Adicionado o Segundo e-mail" -a
    Created commit 2f30ccd: Adicionado o Segundo e-mail
    1 files changed, 4 insertions(+), 0 deletions(-)
    Assim, existem dois commits no branch contato. Podemos comprimir estes em apenas um no branch master, mas para isso é necessário primeiramente ir para o branch master e realizar o merge passando --squash como parâmetro, como mostra a Listagem 13.
    prompt> git checkout master
    Switched to branch "master
    prompt> git merge --squash contato
    Updating 217a88e..2f30ccd
    Fast forward
    Squash commit -- not updating HEAD
    contato.html | 19 +++++++++++++++++++
    1 files changed, 19 insertions(+), 0 deletions(-)
    create mode 100644 contato.html
    Listagem 13. Comprimindo a Listagem 12 em um branch
    Agora os commits do branch contato foram aplicados no working tree e no stage, porém ainda não foram comitados. Para isso basta executar o comando git status para verificar, presente na Listagem 14.
    prompt> git status
    # On branch master
    # Your branch is ahead of 'origin/master' by 1 commit.
    #
    # Changes to be committed:
    # (use "git reset HEAD <file>..." to unstage)
    #
    # new file: contato.html
    #
    Listagem 14. Comando Git Status
    Agora então precisamos executar o comando a seguir para realizar o commit, como mostra o código a seguir:
    prompt> git commit -m "Adicionada a página contato" -m "possuindo o e-mail primário e secundário"
    Created commit 18f822e: Adicionada a página contato
    1 files changed, 19 insertions(+), 0 deletions(-)
    create mode 100644 contato.html
    Com esses passos o merge já foi realizado.
  3. Cherry-picking: Este merge é necessário quando é preciso realizar o merge de apenas um commit entre branches, e assim, não queremos realizar um commit de tudo.Para realizar esses commits individuais utiliza-se o cherry-pick. Exemplificando este caso primeiramente realizamos um check out no branch contato novamente com os comandos a seguir:
    prompt> git checkout contato
    Switched to branch "contato"
    Agora podemos adicionador outro contato no arquivo contato.html como, por exemplo, o link do Twitter, e comitamos o arquivo no branch com os comandos a seguir:
    
    prompt> git commit -m "Adicionado link do twitter" -a
    Created commit 321d76f: Adicionado link do twitter
    1 files changed, 4 insertions(+), 0 deletions(-)
    Devemos guardar o nome do commit: 321d76f para fazer o cherry-pick, já que esses nomes são únicos, portanto não tem problema de dar algum conflito.
    Agora podemos fazer um check out no branch que receberá as alterações, nesse caso o branch master, e por fim realizar o cherry-pick com os comandos da Listagem 15.
    
    prompt> git checkout master
    Switched to branch "master"
    prompt> git cherry-pick 321d76f
    Finished one cherry-pick.
    Created commit 294655e: Adicionado link do twitter
    1 files changed, 4 insertions(+), 0 deletions(-)
    Listagem 15. Comandos para o cherry-pick
    Para realizar o cherry-pick de múltiplos commits basta passar apenas o parâmetro -n junto.

Outro problema bastante comum que ocorrem nos merges são os conflitos, que ocorrem quando um mesmo arquivo é editado de diferentes formas em diferentes branches, assim, ao tentar realizar um merge o Git avisa que existe um conflito.

Portanto, o Git acusa um conflito quando ele não consegue realizar automaticamente o merge. Um exemplo de um conflito é usarmos um nome de variável diferente em cada branch nas mesmas partes de um arquivo. Para um exemplo de como funciona um conflito segue o comando a seguir em que se cria um branch:


  prompt> git checkout -b informacoes master
  Switched to branch "informacoes"

Neste momento pode-se criar um arquivo informacoes.html com os nomes de algumas linguagens de programação. Após isso basta executar os comandos a seguir:


  prompt> git add informacoes.html
  prompt> git commit -m "Lista com linguagens de programação."
  Created commit 01fe684: Lista com linguagens de programação.
  1 files changed, 7 insertions(+), 0 deletions(-)

Crie um segundo branch chamado informacoes2, mas sem alterar para este branch:

prompt> git branch informacoes2 informacoes

E antes de mudar para o branch informacoes2 é necessário preencher o arquivo informacoes.html com outra linguagem de programação. Após salvar a página basta realizar um commit:


  prompt> git commit -m "Adicionado Javascript para a lista" -a
  Created commit 9e114ac: Adicionado Javascript para a lista
  1 files changed, 1 insertions(+), 0 deletions(-)

Portanto, agora existem dois informacoes.html diferentes no branch informacoes e no branchinformacoes2.

Agora executa-se o comando a seguir para ir ao branch informacoes2:


  prompt> git checkout informacoes2
  Switched to branch " informacoes2"

O arquivo informacoes.html não possui a última linguagem de programação adicionada anteriormente, pois aquela alteração está no branchinformacoes. Então, para após adicionar, salve e comite as alterações conforme a seguir:


  prompt> git commit -m "Adicionado EMCAScript para a lista" -a
  Created commit b84ffdc: Adicionado EMCAScript para a lista
  1 files changed, 1 insertions(+), 0 deletions(-)

Mas ai existe um conflito! Agora voltamos ao branch informacoes e tentamos fazer um merge com informacoes2 com os comandos da Listagem 16.


  prompt> git checkout informacoes
  Switched to branch "informacoes"
  prompt> git merge informacoes2
  Auto-merged informacoes.html
  CONFLICT (content): Merge conflict in informacoes.html
  Automatic merge failed; fix conflicts and then commit the result. 
Listagem 16. Merge com informacoes2

Pode-se verificar que a linha CONFLICT indica que foi detectado um conflito no arquivo informacoes.html, conforme a Listagem 17.


  <ul>
  <li>Erlang</li>
  <li>Python</li>
  <li>Objective C</li>
  <<<<<<< HEAD:informacoes.html
  <li>Javascript</li>
  =======
  <li>EMCAScript</li>
  >>>>>>> informacoes2:informacoes.html
  </ul>
Listagem 17. Conflito identificado

Pode-se verificar que o Git encontrou dois nomes diferentes no último elemento <li>. A primeira linha é o commit realizado anteriormente e o segundo é a linha que foi adicionada no informacoes2.

O conflito começa com a linha "<<<<<<< HEAD:informacoes.html". O código conflitado com o outro branch é exibido após o separador "=======" e finaliza com a linha ">>>>>>> informacoes2:informacoes.html".

O que estas linhas dizem é que qualquer código precedido de <<<<<<< é o código do branch atual, e qualquer código após >>>>>>> é do outro branch. Além disso, o nome que está tentando realizar o merge vem antes do nome do arquivo. Neste caso, HEAD que é o nome do commit mais recente no branch atual que conflitou com informacoes2 que é o outro branch.

Para resolver o conflito deve-se realizar o procedimento manualmente, abrindo-o em um editor e realizando a alteração. Neste caso, pode-se deixar a linha que deu conflito igual à do outro branch e adiciona-se abaixo desta mais um <li> com o nome da outra linguagem de programação.

Após realizar os merges e criar a tag que lança a versão efetivamente pode-se apagar todos os branches, visto que eles já serviram aos seus propósitos. Assim, basta realizar o comando a seguir:


  prompt> git branch -d informacoes2
  Deleted branch informacoes2. 

Para isto funcionar, o branch que será deletado já deve ter sido mesclado com o branch atual que estamos usando atualmente. Por exemplo, se voltar ao branch master, em que não foi realizado nenhum merge, e se for realizada uma tentativa de exclui-lo não será possível como vemos a seguir:


  prompt> git checkout master
  Switched to branch "master"
  prompt> git branch -d informacoes
  error: The branch 'informacoes' is not an ancestor of your current HEAD.
  If you are sure you want to delete it, run 'git branch -D informacoes'. 

No entanto, se for necessário remover o branch sem realizar merge, basta forçar a deleção com o parâmetro -D ao invés de -d.

Outra situação que às vezes se faz necessária é renomear um branch, e para isto basta executar o comando da Listagem 18.


  prompt> git branch -m contato contatos
  prompt> git branch
  informacoes
  alternate
  contatos
  * master
  novo
Listagem 18. Renomeando o branch

Outro comando bastante útil para executar em qualquer branch é verificar o histórico dele. Qualquer arquivo adicionado ou alterações realizadas cria um novo commit que mantém um histórico no repositório.

O comando git log mostra todo histórico do repositório e o log é exibido em ordem cronológica reversa, como um blog. Para exibi-lo basta realizar o comando da Listagem 19.


  prompt> git log
  commit 0bb3dfb752fa3c890ffc781fd6bd5dc5d34cd3be
  Author: Higor Medeiros <contato@devmedia.com>
  Date: Sat Nov 4 11:06:47 2015 -0500
  Adicionado link para o twitter
  commit 18f822eb1044761f59aebaf7739261042ae10392
  Author: Higor Medeiros <contato@devmedia.com>
  Date: Sat Nov 4 10:34:51 2015 -0500
  Adicionada página de contato
  
Listagem 19. Comando log

O Git mostra algumas informações do commit como o nome, o autor, a data e uma mensagem utilizada ao realizá-lo.

Uma opção que pode ajudar é o -p, que mostra a diferença que a revisão criou. Também é possível visualizar apenas um número limitado de commits. Por exemplo, utilizado o -1 o log é limitado a apenas um commit, -2 limita a dois commits, e assim por diante.

Se for necessário apenas verificar o log de uma dada revisão basta passar a revisão como na Listagem 20.


  prompt> git log 7b1558c
  commit 7b1558c92a7f755d8343352d5051384d98f104e4
  Author: Higor Medeiros <contato@devmedia.com>
  Date: Sun Nov 21 14:20:21 2015 -0500
  Adicionada mensagem de teste ao arquivo HTML
Listagem 20. Log por revisão

Pode-se verificar que não é necessário passar o nome todo e sim os primeiros caracteres, como os quatro ou cinco primeiros caracteres, porém os sete primeiros são mais garantidos.

Existem algumas opções mais úteis como filtrar as informações. Por exemplo, para recuperar os commits das últimas cinco horas pode-se executar o comando da Listagem 21.


  prompt> git log --after='5 hours' 
  commit 0bb3dfb752fa3c890ffc781fd6bd5dc5d34cd3be
  Author: Higor Medeiros <contato@devmedia.com>
  Date: Sat Nov 4 11:06:47 2015 -0500
  Adicionado link para o twitter
Listagem 21. Recuperando commits

Também é possível pular as últimas cinco horas e visualizar apenas os mais antigos com o código da Listagem 22.


  prompt> git log --before="5 hours" -1
  commit 18f822eb1044761f59aebaf7739261042ae10392
  Author: Higor Medeiros <contato@devmedia.com>
  Date: Sat Nov 4 10:34:51 2015 -0500
  Adicionada página de contato. 
Listagem 22. Logs antigos

Outras opções que o Git também aceita são: --since="24 hours", --since="1 minute" e --before="2008-10.01".

Além disso, também é possível passar uma faixa de commits com o código da Listagem 23.


  prompt> git log 18f822e..0bb3dfb
  commit 0bb3dfb752fa3c890ffc781fd6bd5dc5d34cd3be
  Author: Higor Medeiros <contato@devmedia.com>
  Date: Sat Nov 4 11:06:47 2015 -0500
  Adicionado link para o twitter
Listagem 23. Passando faixa de commits

Também é possível utilizar o HEAD, que é o sinônimo de última versão do branch atual, como podemos ver na Listagem 24.


  prompt> git log 18f822e..HEAD
  commit 0bb3dfb752fa3c890ffc781fd6bd5dc5d34cd3be
  Author: Higor Medeiros <contato@devmedia.com>
  Date: Sat Nov 4 11:06:47 2015 -0500
  Adicionado link para o twitter
Listagem 24. Utilizando o HEAD

O comando diff também é bastante utilizado quando é preciso visualizar o histórico dentro do repositório, como mostra a Listagem 25.


  prompt> git diff 18f822e
  diff --git a/contato.html b/contato.html
  index 64135cb..63617c2 100644
  --- a/contato.html
  +++ b/contato.html
  @@ -13,6 +13,10 @@
  <p>
  <a href="mailto:contato@devmedia.com">E-mail</a>
  </p>
  +
  + <p>
  + <a href="http://twitter.com/devmedia">Twitter</a>
  + </p>
  </body>
  </html>
Listagem 25. Comando diff

O diff também permite visualizar algumas estatísticas sobre as alterações que tem sido realizadas, como mostra a Listagem 26.


  prompt> git diff --stat 1.0
  informacoes.html | 15 +++++++++++++++
  contato.html | 23 +++++++++++++++++++++++
  testeola.html | 13 +++++++++++++
  index.html | 9 ---------
  4 files changed, 51 insertions(+), 9 deletions(-)
Listagem 26. Utilizando diff

Por fim, pode-se também reverter um commit devido alguma alteração realizada equivocadamente ou por algum outro fator que invalide o commit, conforme mostra o exemplo da Listagem 27.


  prompt> git revert -n HEAD
  Finished one revert.
  prompt> git revert -n 540ecb7
  Removed copy.txt
  Finished one revert.
  prompt> git commit -m "revert 45eaf98 and 540ecb7"
  Created commit 2b3c1de: revert 45eaf98 and 540ecb7
  2 files changed, 0 insertions(+), 10 deletions(-)
  delete mode 100644 copy.txt
Listagem 27. Revertendo Commit

Veja que código reverteu o commit de nome 540ecb7 e o HEAD. E após a reversão o commit foi realizado, pois o Git mantém as alterações no stage, assim o desenvolvedor deve realizar um commit para efetivar as alterações.

Veja que não é difícil usar o Git para garantir o versionamento do seu código. Vimos pela sequência de códigos que erros cometidos durante o armazenamento podem ser revertidos. E qualquer alteração é registrada com os logs.

Espero que tenham gostado do artigo. Até a próxima.

Bibliografia

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

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

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