Entendendo Git: Branches — Parte 2

Júlio Guedes
7 min readMay 4, 2019

--

Na primeira parte do tutorial, entendemos os passos iniciais para o uso do git: adicionar arquivos localmente, criar commits e enviar as modificações a um host remoto (GitHub, GitLab, BitBucket, etc). Embora sejam passos iniciais e que são os fundamentos de uso do Git, ainda não cobrem os passos necessários para desenvolvimento em equipe. Nesse artigo, abordaremos as Branches, que são parte essencial quando não estamos desenvolvendo por nossa conta.

A vida de um repositório no Git pode ser imaginada como uma linha temporal composta de commits. Mas, como podemos ver em clássicos da cultura pop, as linhas temporais podem mudar com algum evento, que costuma ser uma viagem no tempo. No nosso caso, é a criação de uma nova branch: totalmente intencional e planejado.

Colocando um pouco de lado a cultura pop, vamos supor o seguinte cenário: com o que aprendemos no post anterior, criamos um repositório no GitHub que contém o código para uma aplicação que serve para organizar uma planilha.

Na nossa aplicação original desejávamos que, a partir da planilha original onde temos X colunas, seja gerada uma planilha com uma análise das colunas da planilha (média, mediana, etc). Mas agora queremos incluir outras estatísticas, como desvio padrão, variância, etc.

Como nosso programa já funciona, não queremos arriscar fazer mudanças que podem causar erros antes de testar o programa apropriadamente. Mas também não queremos deixar de enviar as mudanças que introduzimos ao git, de modo que possamos acessar de outro lugar e continuar o desenvolvimento. Para isso, criaremos uma branch, isto é, um ramo na nossa linha temporal:

Nova Branch

Na imagem ao lado, podemos ver a criação de uma nova branch chamada feature a partir da nossa branch master, e a partir de um commit específico da master. Podemos então entender que, independente dos commits dados na master após a criação da nova branch, ela não irá ser alterada por essas mudanças. Agora, para que alteremos a nossa branch, teremos que enviar commits diretamente para ela, e em alguns momentos, a atualizaremos com a branch master.

Já que agora temos algum conhecimento teórico sobre como criar uma branch, vamos aos comandos para fazê-lo. Inicialmente, é sempre importante verificarmos em que branch estamos no momento:

$ git status
On branch master
nothing to commit, working tree clean

Podemos ver que estamos na branch master, assim como vimos na imagem. Ainda assim, existe outra forma de verificar a branch em que estamos, através do comando git branch , onde o * indica a branch atual:

$ git branch
bug-fix
* master
nova-feature

Para, a partir do último commit dado na master, criarmos uma nova branch, podemos usar o comando:

$ git checkout -b feature
Switched to a new branch 'feature'

O git então nos informa que agora estamos numa nova branch chamada feature, e especificamos que queríamos uma nova branch usando o parâmetro -b . Se quisermos apenas mudar de branch para uma branch que já existe, omitimos o parâmetro. Por exemplo, agora que estamos na branch feature , vamos retornar à branch master :

$ git checkout master
Switched to branch 'master'

Perfeito! Agora podemos trabalhar em nossas branches de forma independente uma da outra! Para adicionar os arquivos e modificações, podemos simplesmente fazer da mesma forma que fizemos na primeira parte de nosso tutorial. Entretanto, é necessário tomar um cuidado extra ao criar commits e dar pushes, pois nossos commits são criados para a branch que estamos, então é sempre bom verificarmos com um git status . Além disso, nosso push é dado para uma branch (git push origin master ), e é necessário lembrar novamente a branch para qual se quer fazer push.

Adicionando as funcionalidades à branch principal

Merge da feature ao código principal

Podemos considerar então que a mudança na nossa aplicação foi concluída na branch feature e agora ela já está testada, pronta para ir ao código principal da aplicação. Para enviar o código para nossa branch master, precisamos fazer um merge. Assim como o nome em inglês já diz, precisamos unir o código da branch de origem (feature) com a branch de destino (master).

Supondo que na minha branch feature, eu criei um novo arquivo, dei commit e push para feature, e agora quero adicionar essa mudança em master. Para isso, devemos mudar para a branch destino e usar o comando de merge:

$ git checkout master # voltaremos à master
Switched to branch 'master'
$ git merge origin/feature # e faremos merge com a feature
Updating a063886..19151ff
Fast-forward
novoArquivo.txt | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 novoArquivo.txt

Caso não tenha nenhum conflito entre a branch de origem e a de destino, o merge é feito automaticamente pelo git, como foi possível ver no output do merge acima. Para confirmar o merge, é necessário dar um push após o comando de merge.

Lidando com conflitos

Logo acima, disse que se não houvesse nenhum conflito entre a branch de origem e de destino, seria possível o merge automático. Mas… O que é um conflito?

Bem, um conflito acontece quando tanto você (na sua branch) quanto outros outros desenvolvedores alteraram o mesmo arquivo, e é um pouco mais preocupante se vocês alteraram as mesmas linhas do arquivo. Para evitar erros, o Git pede que você, que conhece as funcionalidades e o que o código deveria ou não fazer, ajuste o arquivo para mesclar as alterações que você e sua equipe fizeram, mantendo tanto a funcionalidade que você fez quanto a que sua equipe fez.

Vejamos na prática: vamos supor que agora existe mais uma pessoa trabalhando com você para desenvolver essa aplicação, e que após você criar uma branch (nova-feature), ela criou outra (bug-fix), fez as modificações que devia fazer e enviou as alterações dela para a branch master, fazendo um merge.

Agora você já terminou suas alterações e também deseja enviar suas alterações para master, mas sua branch não é mais compatível para um merge automático pos seu amigo enviou a correção do bug para a master antes… O que fazer?

Conflitos

A visão atual das branches e do que aconteceu se encontra na imagem. Para resolver o conflito, inicialmente é preciso trazer as “novidades” que aconteceram no código principal para a sua branch, isto é, ela precisa ser atualizada com o conteúdo da master. Supondo que, nas duas branches, o único arquivo que mudamos em comum foi o changelog.md, vejamos:

# Considerando que estamos inicialmente na nossa branch:
$ git fetch # atualizaremos o nosso repositório local com o que aconteceu no host
$ git checkout master # iremos à master para atualizá-la localmente
# Como saída do git checkout, como nossa branch master local ainda está atualizada, geralmente vemos uma mensagem como "your branch master is 8 commits behind", por exemplo
$ git pull origin master # trazendo efetivamente os commits que aconteceram na master
$ git checkout nova-feature # voltaremos à nossa branch
$ git merge origin/master # e finalmente o merge!
Auto-merging changelog.md
CONFLICT (content): Merge conflict in changelog.md
Automatic merge failed; fix conflicts and then commit the result.

Agora, quando abrirmos o arquivo que o git nos indicou que houve conflito, poderemos ver as linhas do conflito. O arquivo estará assim:

# Changelog
<<<<<<< HEAD
- Adicionando nova feature no changelog
=======
- Corrigido bug exemplo
>>>>>>> origin/master

O trecho entre <<<<<<< HEAD e ======= indica o código que nós adicionamos na nossa branch, enquanto o trecho entre ======= e >>>>>>> origin/master indica o código que veio da master. Como nesse caso queremos que tanto a linha que nós adicionamos quanto a linha da master estejam no arquivo, apenas apagaremos os caracteres adicionados no merge, e nosso arquivo ficará assim:

# Changelog
- Adicionando nova feature no changelog
- Corrigido bug exemplo

Reordenar as linhas ou não fica a seu critério. Agora, podemos simplesmente seguir com o git add , git commit e git push , e seu código estará apto a ser merjado automaticamente com a master.

Uma feature e dica interessante:

A depender do editor de texto (IDE) que utilizamos para programar, existem algumas diferenças entre a experiência para o desenvolvedor. Há algum tempo venho utilizando o VS Code, e quando se trata de resolver conflitos nos arquivos, existe uma feature super interessante: ele dá um highlight na parte que você adicionou e na parte que veio da outra branch, dispondo também de alguns botões pra automatizar o processo de apagar os caracteres adicionados pelo comando de merge:

Resolução de conflitos no VS Code

Novamente, espero que todos tenham gostado 😃 e qualquer feedback é bem vindo!

No próximo post iniciarei a falar de alguns assuntos mais focados ao desenvolvimento Open Source: Pull Requests, Issues, etc. Nos vemos em breve! :D

PS: Se não quiser esperar o próximo post, mas quiser ajuda pra contribuir, se liga nos projetos da OpenDevUFCG, e se junte a nós no nosso Discord.

PPS: Se quiser ler mais sobre desenvolvimento OpenSource e gosta de música, acho que vai gostar dos posts do CallUsWhatYouWant, e também dá pra contribuir no GitHub.

--

--

Júlio Guedes

Web Developer @ Software Practices Laboratory — Undergraduate Student in Computer Science @ Federal University of Campina Grande, Paraíba, Brazil