Skip to content

03 Hands On (Git branches)

Fábio Gaspar edited this page Nov 14, 2018 · 3 revisions

Branches

Tal como apresentado no Workshop, existem diferentes cenários de merge, e para cada um o git vai tentar ser inteligente e resolver problemas por nós. Contudo, há casos ambiguos que o git não resolve, dando origem aos conflitos.

Neste hands on vamos tentar simular um caso de merge que dê conflito.

  1. Aprender a criar um branch
  2. Alternar entre branches
  3. Fazer commits numa branch
  4. Revisitar o comando $git log com mais opções úteis para os branches
  5. Fazer merge
  6. Resolver conflitos

Step by step

Considerando que já tem um repositório criado com alguns ficheiros, vamos criar um novo branch.

Para efeitos de ilustração, o meu repositório consiste de um ficheiro f1 vazio.

Criar um branch

Usa o comando $git branch para criar a branch, especificando um nome a gosto. No exemplo, é usado bugFix.

Por exemplo,

$git branch bugFix

Modificar o ficheiro no master

A nova branch foi criada, mas continuamos na branch anterior, neste caso, master.

Altera um dos ficheiros no master e faça commit. O objetivo é posteriormente alterar o mesmo ficheiro, mas na branch que foi criada (bugFix no exemplo).

No meu repositório, alterei o ficheiro f1 e fiz commit.

💡 Por defeito, quando se faz uma nova branch, este referencia o mesmo commit que o branch atual. No entanto, por vezes pretendemos criar um branch a partir de um commit particular. Para tal, usamos o git log para saber o SHA1 desse commit e usamos a seguinte variante do comando branch.

$git branch <branch name> <commit sha1>

💡 Agora que conheces o nome simbólico HEAD, fica a saber que podes usá-lo com uma syntaxe particular para referenciar commits. Basicamente existem dois operadores, ^ e ~, que a partir do HEAD/commit atual podes referenciar um pai desse commit. Uma imagem vale mais que mil palavras!

img

Basicamente, o ~, funciona em termos de níveis de hierarquia. Por exemplo, os commits D, E e F estão todos no mesmo nível de hierarquia relativamente ao commit A. Para ser mais preciso, estão a dois níveis de distância do commit A. Se usarmos puramente A~2, referenciamos o D. Se usarmos A~3, já estamos a referenciar o G.

Já o operador ^, significa o pai de um commit. Ou seja, se um commit tiver vários commit pai, devido à divergência das branches, então selecionamos o pai que queremos através do ^. Como ilustrado na imagem, o D tem dois commits pai, o G e H. Se fizermos D^1 ou simplesmente D^ (equivalentes), referenciamos G, se usarmos D^2, referenciamos o H.

Então, o commit F que tem dois caminhos em relação ao A, pode ser referenciado, passando por B, com A~^3 ou A^^3. Passando por C, podemos usar A^2^ ou A^2~.

A questão é como saber qual o primeiro, segundo, terceiro, etc "commits pai" de um certo commit. Visualmente estamos a interpretar da esquerda para a direita. Mas quando usamos o git, devemos ir buscar essa informação ao comando git log.

💡 Uma forma interessante de memorizar estes operadores pode ser através da sua forma visual. O ~ tem uma forma mais difusa, ou então representa "aproximadamente" em certos contextos. De facto, navegar por níveis na árvore de commits é algo vago. Já o ^ é algo preciso, referencia um commit em especifico.

Alternar entre branches

Para mudar a branch ativa, usa-se o comando $git checkout <branch name>. Como eu dei o nome bugFix à minha branch, no meu terminal executo:

$git checkout bugFix

💡 É possivel num só comando criar uma branch e de imediato mudar para essa nova branch.

$git checkout –b <branch name>
$git checkout –b <branch name> <commit sha1>
$git checkout –b <branch name> HEAD~n

💡 Para listares todas as branches do repositório local, usa $git branch.

Modificar um ou mais ficheiros comuns à duas branches

Lembra-te que o objetivo é criar um conflito no merge. Para tal, precisamos de criar um caso ambiguo em que o mesmo ficheiro foi alterado nas duas branches.

Antes de avançar, vamos usar o git log para ver o aspeto atual. O comando usado abaixo é algo extenso, mas basicamente estou a pedir que:

  • liste todas as branches -all
  • que faça um grafico --graph
  • que mostre informação sucinta --oneline
  • que mostre as alterações me cada commit -p.
$git log --graph --all --oneline --decorate -p

* 4f1592c (master) Added first paragraph
| diff --git a/f1 b/f1
| index e69de29..9550b9f 100644
| --- a/f1
| +++ b/f1
| @@ -0,0 +1 @@
| +Hello darkness, my old friend!
* c5b35e4 (HEAD -> bugFix) Init commit
  diff --git a/f1 b/f1
  new file mode 100644
  index 0000000..e69de29
  diff --git a/f2 b/f2
  new file mode 100644
  index 0000000..e69de29

Se fizesse merge neste momento, o git faria um simples Fast Forward, porque existe um "caminho" direto de bugFix para master.

Agora sim, alterei o ficheiro f1 e fiz commit na branch bugFix. Correndo novamente o git log, com todas as opções ...

* b4b9d42 (HEAD -> bugFix) a conflict is comming
| diff --git a/f1 b/f1
| index e69de29..6415fdc 100644
| --- a/f1
| +++ b/f1
| @@ -0,0 +1,2 @@
| +Join IEEE!
| +:)
| * 4f1592c (master) Added first paragraph
|/
|   diff --git a/f1 b/f1
|   index e69de29..9550b9f 100644
|   --- a/f1
|   +++ b/f1
|   @@ -0,0 +1 @@
|   +Hello darkness, my old friend!
* c5b35e4 Init commit
  diff --git a/f1 b/f1
  new file mode 100644
  index 0000000..e69de29
  diff --git a/f2 b/f2
  new file mode 100644
  index 0000000..e69de29

Visualmente já se vê um ramo, uma divergência entre os dois branches. Mas mais importante que isso, é que o mesmo ficheiro foi alterado em ambos. E também importa realçar que alterei o mesmo bloco (linha) nos dois ficheiros.

💡 A questão dos blocos é importante. Não basta que o mesmo ficheiro seja alterado em dois branches para gerar conflito. É preciso que o mesmo bloco do ficheiro seja modificado, e então neste caso o git não sabe mesmo o que fazer e gera o conflito. Se tiver um ficheiro com vários paragrafos e numa branch alterar o primeiro e noutra alterar o último, o git é esperto o suficiente para fazer o merge e manter o primeiro paragrafo de uma das branches e o último da outra. O nível mais básico de bloco para o Git é uma linha.

Merge

Está na hora de fazer o merge. Neste caso, quero que as alterações feitas em bugFix (origem) venham para o master (destino).

  1. Então a primeira coisa a fazer é mudar para o master com $git checkout
  2. Usar o comando $git merge <origin branch name>

... e sem surpresa, vamos ter um conflito! O git informa todos os ficheiros onde houve um conflito e fica à espera que resolvemos manualmente o problema.

Auto-merging f1
CONFLICT (content): Merge conflict in f1
Automatic merge failed; fix conflicts and then commit the result.

Importa realçar que para cada ficheiro com conflito, o git coloca delimitadores, que é uma forma de indicar de onde é que veio um certo bloco de texto. No meu caso, o meu ficheiro f1 ficou com este aspeto:

>>>>>>> master
Hello darkness, my old friend!
=======
Join IEEE!
:)
<<<<<<< bugFix

Resolver conflitos

O git status volta a ser útil e dá umas dicas.

On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:   f1

no changes added to commit (use "git add" and/or "git commit -a")

O passo seguinte é então alterar cada um dos ficheiros manualmente. Quando este processo terminar, faz-se $git add seguido de $git commit, e assim se conclui o processo de merging com conflitos.

Resolvido o problema, eis o gráfico:

*   ed84ac1 marged branches
|\
| * b4b9d42 a conflict is comming
| | diff --git a/f1 b/f1
| | index e69de29..6415fdc 100644
| | --- a/f1
| | +++ b/f1
| | @@ -0,0 +1,2 @@
| | +Join IEEE!
| | +:)
* | 4f1592c Added first paragraph
|/
|   diff --git a/f1 b/f1
|   index e69de29..9550b9f 100644
|   --- a/f1
|   +++ b/f1
|   @@ -0,0 +1 @@
|   +Hello darkness, my old friend!
* c5b35e4 Init commit
  diff --git a/f1 b/f1
  new file mode 100644
  index 0000000..e69de29
  diff --git a/f2 b/f2
  new file mode 100644
  index 0000000..e69de29

💡 Para apagar uma branch, quando esta já não for necessária, usa-se $git branch -d <branch name>.