-
Notifications
You must be signed in to change notification settings - Fork 2
03 Hands On (Git 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.
- Aprender a criar um branch
- Alternar entre branches
- Fazer commits numa branch
- Revisitar o comando
$git log
com mais opções úteis para os branches - Fazer merge
- Resolver conflitos
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.
Usa o comando $git branch
para criar a branch, especificando um nome a gosto. No exemplo, é usado bugFix.
Por exemplo,
$git branch bugFix
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!
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.
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
.
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.
Está na hora de fazer o merge. Neste caso, quero que as alterações feitas em bugFix (origem) venham para o master (destino).
- Então a primeira coisa a fazer é mudar para o master com
$git checkout
- 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
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>
.