Git – Sistema de controle de versões distribuído

Logotipo do GitSe você trabalha com desenvolvimento e não registra as alterações feitas no seu projeto, você está praticamente perdido. O que fazer se uma alteração for enviada para produção e der algum erro? Tira tudo do ar? Restaura um backup? Da mesma forma, como descobrir quem fez essa alteração - no caso de você trabalhar em uma equipe de 5, 10, 20 pessoas? - e ainda, como separar o trabalho de cada um de tal forma que seja possível criar vários recursos novos para um mesmo projeto, paralelamente, juntando todas as alterações no final, sem necessitar do uso de pendrives para copiar arquivos para lá e para cá? Bom, é com o objetivo de facilitar todas essas tarefas que hoje eu vou falar um pouco (e apresentar um breve tutorial) sobre o Git, um poderoso sistema de controle de versões distribuído.

O Git foi criado por desenvolvedores do Kernel do Linux e tem como objetivo versionar os arquivos. Por versionar, quero dizer que cada arquivo devidamente salvo em um repositório git é registrado como alterações baseadas desde sua primeira versão, ou seja, quando o arquivo em questão é registrado pela primeira vez no repositório. Para o git, cada commit (conjunto de alterações) é visto como snapshots, ou seja, cópias do estado no qual o repositório estava no momento em que o commit foi feito. Ou seja, em um repositório git, você consegue voltar ao estado de um commit anterior a qualquer momento, podendo por exemplo, restaurar o estado após "apagar" aquele arquivo importante ou ver ou entender melhor como o sistema funcionava anteriormente, por exemplo (para o caso de existir alguma tabela mais antiga no banco de dados que, por algum motivo, não foi modificada também).

Mas, como que o Git sabe a ordem dos commits? Simples, para o Git, cada commit tem os chamados "parents", que são os commits "anteriores" à ele. Note que estou usando o plural de "anterior", pois no Git há situações no qual um único commit pode ter mais de um "parent" e há uma situação no qual um determinado commit também pode não ter "parent" (é o caso, por exemplo, do primeiro commit feito em um repositório).

Uma situação comum no qual um determinado commit tem mais de um "parent" é quando ele é um "merge commit", mas para entender o que é um "merge commit" precisamos entender primeiro algumas situações no qual ele pode aparecer, que é o nosso foco para esta introdução ao Git aqui no post (para podermos, então, partir para o tutorial).

Primeiro, preciso deixar claro que o Git não diz respeito apenas a um "registrador de versões". Isso porque, como eu expliquei lá no primeiro parágrafo, você pode querer trabalhar em um determinado recurso enquanto outro colega seu quer trabalhar em outro recurso, e aí você se depara com dois problemas básicos:

  • Cada recurso tende a exigir mais de um único commit (para acompanhamento do progresso durante o desenvolvimento do recurso);
  • Todas as alterações de cada recurso precisam ser "unidas" no final para poder fazer parte, enfim, do projeto;

Para resolver esses dois problemas, o Git trabalha com o conceito de "branches". Branches são "bifurcações" da árvore de commits do Git, ou seja, a partir de um determinado commit, branches permitem criar uma outra "linha" de commits, isolados da árvore principal, mas tendo como ponto de principio o commit base escolhido para o inicio da branch. Dessa forma, você pode ter várias branches e em cada uma trabalhar em um recurso ou correção de bug do seu código, por exemplo.

Ao final do desenvolvimento do recurso, você consegue "mergear" todas as alterações que você fez de volta na "linha" principal de commits do seu projeto (normalmente chamada de branch "master"), dessa forma, o recurso é integrado de maneira eficiente ao código do seu projeto, pois o git automaticamente aplica apenas as diferenças entre as duas branches que você quer juntar.

Mas, o que acontece se as mesmas linhas tiverem sido modificadas nos dois branches? Simples, acontece um conflito. Mas normalmente não é um problema que é muuuito complicado de resolver e trataremos melhor em um post futuro aqui no blog.

Voltando ao assunto, é quando você "junta" duas branches que há a possibilidade de aparecer esse "merge commit" mencionado logo acima. Digo que há a possibilidade pois, como o Git é muito poderoso, há mais de uma forma de fazer juntar duas branches, mas novamente isso é tópico para outro post.

Agora que já sabemos o básico do básico do básico dos conceitos do Git (acreditem quando eu digo que ele é muito poderoso), falta apenas esclarecer mais um ponto antes de irmos para o nosso tutorial: Como funciona o repositório Git enquanto algo distribuído.

Pois bem, para usar o Git como algo distribuído, precisamos sincronizar mudanças, e isso é feito a partir de duas operações básicas: "pull" (puxar, que faz o download das mudanças a partir de um repositório remoto) e "push" (empurrar, que faz o envio das mudanças do repositório local para um repositório remoto). Note que aqui, estou usando o termo "repositório remoto", isso porque no Git qualquer repositório pode ser usado para sincronização. E é daí que vem o fato de "distribuído" ser parte das características do Git: Qualquer repositório pode ser usado como "remoto", e não há um servidor central controlando tudo, portanto, se você assim preferir, você pode usar o Git no melhor estilo P2P (embora isso seja um bocadinho complexo).

Agora que você sabe um bocado sobre os conceitos do Git, vamos aprender um pouco a mexer nele? Bora!

Tutorial Básico de Git

Nesse tutorial, vamos fazer operações básicas relacionadas ao Git. como initializar um repositório, adicionar arquivos, comittar as mudanças, depois modificar os arquivos, adicionar os arquivos modificados, comittar tudo de novo, e por fim algumas noções básicas relacionadas a branch e merges. Segue o tutorial:

1) Para usar a ferramenta, precisamos ter o...git instalado. Para isso, há duas opções:

1.1) Caso você queira instalar o Git no seu computador, baixe e o instale gratuitamente para seu sistema-operacional. E crie uma pasta “projetos” em algum lugar pois vamos usá-la ao longo do tutorial.

1.2) Caso você não queira instalar o Git em seu computador, pode utilizar o Vagrant em conjunto com o Vagrantfile que eu criei para criar uma máquina virtual já com o git instalado. Para prosseguir com o tutorial, nesse caso, é necessário entrar na máquina virtual utilizando o comando “vagrant ssh“, como descrito no tutorial sobre o Vagrant.

2) Agora, entre na pasta projetos, crie uma pasta chamada "tutorial-git". Entre nela e rode o comando "git init". Segue a gravação:

3) Agora, experimente criar um arquivo chamado "test.txt", com uma única linha contendo  "Hello World". Em seguida, execute o comando "git add test.txt" seguido de "git commit -m 'First commit'".

Como esse é o seu primeiro commit e o Git não sabe nada sobre você, é necessário adicionar algumas informações (o Git emitirá um erro sempre que você tentar comittar sem essas informações devidamente registradas), como nome e e-mail, para que o Git saiba quem está fazendo o commit e poder registrar essa informação no repositório. Para ajustar isso, execute o comando "git config --global user.name '[seu nome]'" para configurar o seu nome (substitua [seu nome] pelo seu nome nesse comando) e "git config --global user.email '[seu e-mail]'" para configurar o seu e-mail (substitua [seu e-mail] pelo seu e-mail nesse comando).

Após executar esses dois comandos, execute novamente o comando "git commit -m 'First commit'". Agora sim, seu primeiro commit estará registrado! Veja na gravação abaixo o procedimento:

Note que o Git registrará, junto ao commit, o arquivo recém criado. Se você quiser ver como está o log da branch atual, execute o comando "git log". Segue a gravação:

4) Em seguida, modifique o arquivo, adicionando uma nova linha com o conteúdo "How are you?". Novamente, execute o comando "git add test.txt", mas agora seguido do comando "git commit -m 'Second commit'" (note que agora o Git não pede mais seu e-mail e nome), e com isso a modificação feita no arquivo será salva pelo git. Veja a gravação:

Agora, se você rodar o "git log" novamente, verá que há um novo registro na lista, correspondente ao segundo commit. Como você pode ver abaixo:

5) Experimente, agora que o commit está registrado, rodar o comando "git checkout master^" (sim, com o acento circunflexo no final), agora, abra novamente o arquivo test.txt no editor. Você verá que o conteúdo original do primeiro commit foi restaurado.  Segue a gravação:

Note que, na gravação acima, também foi mostrado a diferença entre o segundo commit e o primeiro commit usando o comando "git diff master". Usando esse comando, uma tela mostrando as diferenças será mostrada, como mostrada na gravação acima. Para sair dessa tela, basta usar a tecla "q".

Após fazer esses testes, use "git checkout master" para restaurar a visualização para o segundo commit, como feito na gravação acima.

6) Agora, vamos experimentar criar uma branch. Para fazer isso, execute o comando "git branch nova-branch" e após isso execute o comando "git checkout nova-branch". Para constatar a mudança de branches, execute o comando "git status", que mostra um status geral do repositório. Veja a gravação:

Nessa nova branch, chamada nova-branch, você ainda está com uma cópia dos commits da branch original (master), e ambas as branchs são iguais, pois você ainda não fez nenhum commit na nova branch. Experimente alterar o arquivo, adicionando uma nova linha com o conteúdo "I'm fine" e execute os comandos "git add test.txt" e "git commit -m 'Third commit'". Agora, rode o comando "git log" para ver os commits registrados na branch, como na gravação abaixo:

Agora, vamos experimentar executar o comando "git checkout master" para voltar a branch master e ver como estão as coisas por lá. Estando na branch master, execute novamente o comando "git log", e constate que o commit "Third commit" não está presente no log (pois afinal esse commit está presente apenas na branch nova-branch), como mostrado na gravação abaixo:

7) Ainda na branch master, experimente criar um arquivo chamado "test2.txt" com o conteúdo "Hello World 2", adicioná-lo ao Git executando "git add test2.txt" e commitar a mudança executando "git commit -m 'Fourth commit'". Como mostrado abaixo:

Se você executar o comando "git log" agora, verá que estão cadastrados na branch os commits "First commit", "Second commit" e...."Fourth commit". Como mostrado abaixo:

8) Agora, vamos "juntar" o "Third Commit" - que adiciona mais uma alteração no arquivo test.txt e que existe apenas na branch nova-branch - com a branch master, aplicando assim a alteração correspondente feita pelo test.txt e mantendo o arquivo test2.txt inalterado.

Para isso, precisamos fazer o "merge" das duas branches. Ainda na branch master, execute o comando "git merge nova-branch" (nesse procedimento, o git vai abrir o merge commit em um editor de texto para você poder alterá-lo, apenas salve o arquivo e feche o editor de texto para continuar) e logo em seguida execute o comando "git log" (note que, agora, você precisa apertar a tecla "q" para sair da visualização). Caso também queira, você pode abrir os arquivos test.txt e test2.txt em um editor de texto, para que possa conferir as mudanças feitas aos arquivos. Veja abaixo:

Um aspecto interessante para você notar é que o arquivo test2.txt, criado pelo commit "Fourth Commit", foi mantido inalterado pelo merge, pois nenhum commit existente na branch nova-branch altera o arquvo. Além disso, se você observar bem a ordem dos commits no comando "git log", verá que o "Third Commit" aparece antes do "Fourth Commit", pois o Git sabe a ordem em que os commits devem ser aplicados.

9) Para terminar o tutorial, execute o comando "git log --graph". Ele vai mostrar como o Git entende a ordem dos commits e inclusive uma visualização bem intuitiva feita em ASCII com a branch nova-branch. Veja:

Note que o commit "Third commit" aparece acima do "Fourth commit". Não há um motivo claro para isso ocorrer que não visualização, pois, como você pode ver, o "Third commit" está numa linha separada em relação ao do "Fouth commit", que começa a partir do "Second commit" e termina com o merge commit (aquele com a mensagem "Merge branch 'nova-branch'"). Confuso? Então presete atenção no "gráfico" gerado pelo comando "git log --graph", disponível logo à esquerda dos commits por si só. =)

Resumo dos comandos praticados

Se você fez o tutorial acima, deve ter ficado com pelo menos um pouco de dúvida sobre os comandos executados. Por isso, resolvi criar um índice dos comandos usados no tutorial acima e uma breve descrição de cada um:

  • git init - Inicializa um repositório git, criando uma pasta oculta chamada ".git" com os arquivos usados internamente pelo Git;
  • git add [arquivo] - Adiciona as modificações no arquivo (ou pasta) [arquivo] à "staged area" do Git, no qual os arquivos que vão ser comittados ficam registrados temporariamente. Note que se você fizer posterior modificações no arquivo, você terá que executar o comando novamente para adicionar as novas modificações à "staged area" do Git;
  • git commit -m "[mensagem]" - Registra um commit com as modificações registradas na "staged area" e com a mensagem "[mensagem]", armazenando também informações como nome e e-mail do autor do commit a partir das configurações salvas anteriormente na base de configurações do Git;
  • git config --global user.name "[seu nome]" - Salva o nome "[seu nome]" na base de configurações global do Git;
  • git config --global user.email "[seu e-mail]" - Salva o e-mail "[seu e-mail]" na base de configurações global do Git;
  • git log - Mostra os commits registrados na branch atual;
  • git checkout [alvo] - Faz o Git mudar o ponteiro HEAD (ou seja, o que se refere ao estado atual do repositório, que você vê) para [alvo], que pode ser o nome de uma branch, o hash de um commit, ou pode ser algumas referências especiais, como:
    • master^ - Muda para o penúltimo commit da branch master;
    • [branch] - Muda para a branch [branch];
  • git diff [alvo] - Faz o git mostrar as diferenças entre o commit do ponteiro HEAD (que você está vendo no momento) e [alvo], que pode ser uma branch, o hash de um commit, ou pode ser algumas referências especiais (como no caso do git checkout);
  • git branch [branch] - Cria uma nova branch com o nome [branch], copiando todos os commits da branch no qual você está até o commit referido pelo ponteiro HEAD;
  • git merge [branch] - "Junta" a branch atual no qual você está com a branch de nome [branch], criando um merge commit ao final do processo;
  • git log --graph - Mostra os commits registrados na branch atual, mostrando ao lado esquerdo a conexão entre um commit e outro;

Gostou do tutorial? Pois saiba que aqui foi abordado apenas uma parcela muito pequena dos recursos do Git - e ainda de maneira parcial -, pois eu queria apenas apresentar uma breve introdução ao que o git é capaz de fazer. Em breve, farei mais posts sobre o Git e seus "aliados" aqui no blog (e você está convidado para acompanhar o blog nesta empreitada), pois acredito que ele é sim muito importante para todos os desenvolvedores e ver que ainda tem pessoas que após alguns anos sequer o conhecem é bem...complicado.

Ainda assim, caso você já queira ir adiantando estudos, você tem duas opções:

Bom, por hoje é isso. Espero que tenham curtido e tenham gostado do Git, que é uma ferramenta incrível para trabalho em equipe e que facilita muito no desenvolvimento de um projeto. Segue os links relacionados ao Git:

Gostou do conteúdo desse post? Apoie o blog contribuindo a partir de R$1/mês através da nossa página no Apoia.se! Isso me ajudará a manter o blog no ar, além de trazer mais coisas legais para vocês! Obrigado desde já! 😀