Les branques són una de les característiques més importants de Git,
ja que permeten desenvolupar un projecte de manera col·laborativa i en paral·lel.
Moltes de les estratègies de treball amb Git es basen a fer els canvis en branques independents que,
una vegada acabades, s'integren en la branca principal. Aquesta branca s'anomena main
(o master en repositoris més antics), tal com s'explica en l'apartat
Inicialització d'un repositori.
Preparació del repositori d'exemple
En aquests apunts es treballa sobre un nou repositori local, que s'inicialitza amb les ordres següents:
#!/bin/bash# Elimina el repositori si existeixif[-d~/git_branques];thenrm-rf~/git_branques
fimkdir-p~/git_branques
cd~/git_branques
gitinit
gitbranch-mmain# (1)!echo"# Branques a Git">README.md
gitaddREADME.md
gitcommit-m"Commit inicial"gitlga
Una branca és una línia de desenvolupament independent.
En Git, una branca és simplement un punter a un commit, que avança a mesura que s'hi fan nous commits.
Figura 1. Estructura de branques inicial.
Exemple: Estructura de branques inicial
L'estructura de branques inicial del repositori d'aquests apunts és la que mostra
la Figura 1.
S'observa que hi ha un únic commit, on està situada l'única branca, anomenada main.
A més, HEAD apunta a la branca main. Això indica que és la branca activa
i que el Directori de treball es troba en aquest estat.
El HEAD es tracta amb més detall en l'apartat Canviar de branca.
L'ordre git branch permet consultar i manipular les branques d'un repositori.
Canviar de branca significa moure el punter HEAD a la branca desitjada.
A més, el contingut del Directori de treball passa a l'estat del commit al qual apunta la branca.
Hi ha dues ordres per a canviar de branca, cadascuna amb la seua sintaxi i opcions:
Originalment, s'utilitzava l'ordre git checkout per a canviar de branca.
No obstant això, com que aquesta ordre té moltes altres funcions, a partir de la versió 2.23 de Git
es va introduir l'ordre git switch per a evitar confusions1.
La Figura 2 mostra l'estat del repositori quan el HEAD apunta a la branca main
i la Figura 3, l'estat després de canviar a la branca menjar.
Exemple: Canviar a la branca menjar
L'estat del repositori abans de canviar a la branca menjar és el següent:
jpuigcerver@fp:~/git_branques(main)$catREADME.md
# Branques a Gitjpuigcerver@fp:~/git_branques(main)$gitbranch
beguda* main menjar netejajpuigcerver@fp:~/git_branques(main)$gitlga
*ec4c927-(0 seconds ago)Commit inicial-Joan Puigcerver(HEAD -> main, neteja, menjar, beguda)
S'observa que HEAD s'ha desplaçat a la branca menjar.
No obstant això, el contingut del repositori és el mateix, ja que les dues branques apunten al mateix commit.
Figura 3. Estructura de branques després de canviar a la branca menjar.
[-f | --force]: (opcional) força l'eliminació de la branca.
-D: abreviatura de --delete --force.
<nom>: nom de la branca que es vol eliminar.
Eliminar una branca pot provocar la pèrdua de commits.
Quan un commit perd totes les referències que permeten accedir-hi, es diu que és un commitorfe,
i el recol·lector de fem (garbage collector) de Git l'eliminarà.
Per evitar-ho, si l'eliminació deixa commits orfes, Git mostra un error i no elimina la branca,
tret que s'utilitze l'opció -D o --delete --force.
Exemple: Eliminació de la branca neteja
S'intenta eliminar la branca neteja amb l'opció -d.
Com que els seus canvis no s'han fusionat, eliminar la branca provocaria la pèrdua dels commits realitzats.
Per tant, per a esborrar-la cal utilitzar l'opció -D.
jpuigcerver@fp:~/git_branques(neteja)$gitcheckoutmain
Switched to branch 'main'jpuigcerver@fp:~/git_branques(main)$gitlga
*7540d67-(0 seconds ago)Beguda-Joan Puigcerver(beguda)| *a7ef672-(0 seconds ago)Menjar-Joan Puigcerver(menjar)|/ | *06d7d94-(0 seconds ago)Neteja-Joan Puigcerver(neteja)|/ *ec4c927-(0 seconds ago)Commit inicial-Joan Puigcerver(HEAD -> main)jpuigcerver@fp:~/git_branques(main)$gitbranch-dneteja
error: the branch 'neteja' is not fully mergedhint: If you are sure you want to delete it, run 'git branch -D neteja'hint: Disable this message with "git config advice.forceDeleteBranch false"jpuigcerver@fp:~/git_branques(main)$gitbranch-Dneteja
Deleted branch neteja (was 06d7d94).jpuigcerver@fp:~/git_branques(main)$gitlga
*7540d67-(0 seconds ago)Beguda-Joan Puigcerver(beguda)| *a7ef672-(0 seconds ago)Menjar-Joan Puigcerver(menjar)|/ *ec4c927-(0 seconds ago)Commit inicial-Joan Puigcerver(HEAD -> main)
S'observa que la branca neteja s'ha eliminat i, per tant, el commitProductes de neteja
s'ha convertit en un commit orfe que el recol·lector de fem de Git eliminarà.
Figura 7. Estructura de branques després d'eliminar la branca neteja.
La fusió directa (fast-forward) es produeix quan la branca actual (HEAD) no té cap commit nou
des que es va crear la branca que es vol fusionar. És a dir, la branca actual està darrere
de la branca que es vol fusionar i la història és lineal.
Figura 8. Estructura de branques abans de la fusió directa.
Aquesta fusió consisteix a avançar el punter de la branca actual (HEAD)
fins on es troba la branca que es vol fusionar.
Figura 9. Estructura de branques després de la fusió directa de la branca menjar en la branca main.
La fusió directa és la manera més senzilla i neta de fusionar branques, ja que no crea cap commit addicional i manté una història lineal i fàcil de seguir.
Per a assegurar que la fusió és directa, es pot utilitzar l'opció --ff-only.
Si la fusió no pot ser directa, Git mostra un error i no la fa.
També es pot configurar Git perquè només faça fusions directes:
gitconfig--globalmerge.ffonly
Exemple: Fusió directa de la branca menjar en la branca main
Partint de la situació de la Figura 8, on la branca menjar té un commit més
que la branca main, la fusió de menjar en main és una fusió directa.
No sempre és possible fer una fusió directa (fast-forward).
Pot ocórrer que les dues branques hagen divergit, és a dir, que cada branca tinga canvis
que no estan presents en l'altra.
Figura 10. Estructura de branques abans de la fusió de branques divergents.
En aquest cas, la fusió es fa mitjançant un commit de fusió (merge commit),
un commit que té dos pares, un per cada branca que es fusiona, i que incorpora els canvis de totes dues.
Figura 11. Estructura de branques després de la fusió de la branca divergent beguda en la branca main.
Com qualsevol altre commit, el commit de fusió necessita un missatge, que es pot indicar amb l'opció -m.
Si no se n'especifica cap, s'obri l'editor de text configurat per defecte
(vegeu Confirmar canvis (git commit)).
Aquest tipus de fusió no és tan neta com la fusió directa (fast-forward), ja que la història del projecte es torna més complexa i no lineal.
Per a forçar que la fusió es faça amb un commit de fusió, es pot utilitzar l'opció --no-ff.
També es pot configurar Git perquè sempre utilitze aquesta opció:
gitconfig--globalmerge.ffno
Exemple: Fusió de la branca divergent beguda en la branca main
Es parteix de la situació de la Figura 10, on la branca beguda
ha divergit de la branca main.
Durant la fusió de branques, pot ocórrer que les dues branques hagen modificat la mateixa part d'un fitxer.
En aquest cas, Git no pot resoldre el conflicte de manera automàtica i cal resoldre'l manualment.
Quan s'executa git merge, Git detecta els conflictes i els marca en el fitxer amb la notació següent:
<<<<<<< HEAD
Contingut de la branca actual
=======
Contingut de la branca a fusionar
>>>>>>> branca_a_fusionar
A més, el repositori passa a l'estat de fusió (MERGING), que indica que hi ha una fusió en curs.
Per a resoldre el conflicte, cal seguir aquests passos:
Editar el fitxer i resoldre manualment el conflicte.
Esborrar les marques de conflicte.
Marcar el fitxer com a resolt amb git add i confirmar els canvis amb git commit.
Per a cancel·lar el procés de fusió (MERGING), es pot utilitzar l'ordre git merge --abort.
Preparació de les branques divergents fruita i verdura
Es creen les branques fruita i verdura des de la branca main.
S'afigen dues fruites al fitxer menjar.txt en la branca fruita.
jpuigcerver@fp:~/git_branques(main)$gitcheckoutfruita
Switched to branch 'fruita'jpuigcerver@fp:~/git_branques(fruita)$echo"Pomes">>menjar.txt
jpuigcerver@fp:~/git_branques(fruita)$echo"Bresquilles">>menjar.txt
jpuigcerver@fp:~/git_branques(fruita)$gitstatus
On branch fruitaChanges not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory)modified: menjar.txtno changes added to commit (use "git add" and/or "git commit -a")jpuigcerver@fp:~/git_branques(fruita)$gitaddmenjar.txt
jpuigcerver@fp:~/git_branques(fruita)$gitcommit-m"Fruta"[fruita d32e3ba] Fruta 1 file changed, 2 insertions(+)jpuigcerver@fp:~/git_branques(fruita)$gitlga
*d32e3ba-(1 second ago)Fruta-Joan Puigcerver(HEAD -> fruita)*d71a079-(1 second ago)Merge branch 'beguda' into 'main'-Joan Puigcerver(verdura, main)|\ | *7540d67-(1 second ago)Beguda-Joan Puigcerver(beguda)* |a7ef672-(1 second ago)Menjar-Joan Puigcerver(menjar)|/ *ec4c927-(1 second ago)Commit inicial-Joan Puigcerver
S'afigen dues verdures al fitxer menjar.txt en la branca verdura.
jpuigcerver@fp:~/git_branques(fruita)$gitcheckoutverdura
Switched to branch 'verdura'jpuigcerver@fp:~/git_branques(verdura)$echo"Tomaques">>menjar.txt
jpuigcerver@fp:~/git_branques(verdura)$echo"Creïlles">>menjar.txt
jpuigcerver@fp:~/git_branques(verdura)$gitstatus
On branch verduraChanges not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory)modified: menjar.txtno changes added to commit (use "git add" and/or "git commit -a")jpuigcerver@fp:~/git_branques(verdura)$gitaddmenjar.txt
jpuigcerver@fp:~/git_branques(verdura)$gitcommit-m"Verdura"[verdura e4cfd4d] Verdura 1 file changed, 2 insertions(+)jpuigcerver@fp:~/git_branques(verdura)$gitlga
*e4cfd4d-(0 seconds ago)Verdura-Joan Puigcerver(HEAD -> verdura)| *d32e3ba-(1 second ago)Fruta-Joan Puigcerver(fruita)|/ *d71a079-(1 second ago)Merge branch 'beguda' into 'main'-Joan Puigcerver(main)|\ | *7540d67-(1 second ago)Beguda-Joan Puigcerver(beguda)* |a7ef672-(1 second ago)Menjar-Joan Puigcerver(menjar)|/ *ec4c927-(1 second ago)Commit inicial-Joan Puigcerver
Exemple: Fusió de les branques fruita i verdura amb conflictes
Les branques fruita i verdura han modificat la mateixa secció del fitxer menjar.txt.
Per tant, quan es fusionen, es produeix un conflicte.
jpuigcerver@fp:~/git_branques(verdura)$gitcheckoutmain
Switched to branch 'main'jpuigcerver@fp:~/git_branques(main)$gitlg
*d71a079-(1 second ago)Merge branch 'beguda' into 'main'-Joan Puigcerver(HEAD -> main)|\ | *7540d67-(1 second ago)Beguda-Joan Puigcerver(beguda)* |a7ef672-(1 second ago)Menjar-Joan Puigcerver(menjar)|/ *ec4c927-(1 second ago)Commit inicial-Joan Puigcerverjpuigcerver@fp:~/git_branques(main)$gitshowfruita
commit d32e3bac4fc8792f3c13805d983a926c72841fb5Author: Joan Puigcerver <j.puigcerveribanez@edu.gva.es>Date: Tue Oct 29 16:23:59 2024 +0100 Frutadiff --git a/menjar.txt b/menjar.txtindex c8d5456..312ebef 100644--- a/menjar.txt+++ b/menjar.txt@@ -1,2 +1,4 @@Pa
Macarrons
+Pomes+Bresquillesjpuigcerver@fp:~/git_branques(main)$gitshowverdura
commit e4cfd4d48cb94e0e87c573f14f6eed310bb12750Author: Joan Puigcerver <j.puigcerveribanez@edu.gva.es>Date: Tue Oct 29 16:24:00 2024 +0100 Verduradiff --git a/menjar.txt b/menjar.txtindex c8d5456..03fb9ab 100644--- a/menjar.txt+++ b/menjar.txt@@ -1,2 +1,4 @@Pa
Macarrons
+Tomaques+Creïlles
Primer, es fa una fusió directa de la branca fruita en la branca main,
que no provoca cap conflicte.
jpuigcerver@fp:~/git_branques(main)$gitmergefruita
Updating d71a079..d32e3baFast-forward menjar.txt | 2 ++ 1 file changed, 2 insertions(+)jpuigcerver@fp:~/git_branques(main)$gitlga
*e4cfd4d-(0 seconds ago)Verdura-Joan Puigcerver(verdura)| *d32e3ba-(1 second ago)Fruta-Joan Puigcerver(HEAD -> main, fruita)|/ *d71a079-(1 second ago)Merge branch 'beguda' into 'main'-Joan Puigcerver|\ | *7540d67-(1 second ago)Beguda-Joan Puigcerver(beguda)* |a7ef672-(1 second ago)Menjar-Joan Puigcerver(menjar)|/ *ec4c927-(1 second ago)Commit inicial-Joan Puigcerver
Després, es fusiona la branca verdura en la branca main, i es produeix un conflicte.
jpuigcerver@fp:~/git_branques(main)$gitmergeverdura
Auto-merging menjar.txtCONFLICT (content): Merge conflict in menjar.txtAutomatic merge failed; fix conflicts and then commit the result.jpuigcerver@fp:~/git_branques(main|MERGING)$gitstatus
On branch mainYou have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge)Unmerged paths: (use "git add <file>..." to mark resolution)both modified: menjar.txtno changes added to commit (use "git add" and/or "git commit -a")jpuigcerver@fp:~/git_branques(main|MERGING)$catmenjar.txt
PaMacarrons<<<<<<< HEADPomesBresquilles=======TomaquesCreïlles>>>>>>> verdura
Es resol el conflicte eliminant les marques de conflicte i mantenint els canvis de les dues branques.
Després, es marca el fitxer com a resolt amb git add i es confirmen els canvis amb git commit.
Pa
Macarrons
<<<<<<< HEAD
Pomes
Bresquilles
=======
Tomaques
Creïlles
>>>>>>> verdura
Pa
Macarrons
Pomes
Bresquilles
Tomaques
Creïlles
jpuigcerver@fp:~/git_branques(main|MERGING)$codemenjar.txt# (1)!jpuigcerver@fp:~/git_branques(main|MERGING)$gitaddmenjar.txt
jpuigcerver@fp:~/git_branques(main|MERGING)$gitstatus
On branch mainAll conflicts fixed but you are still merging. (use "git commit" to conclude merge)Changes to be committed:modified: menjar.txtjpuigcerver@fp:~/git_branques(main|MERGING)$gitcommit-m"Merge branch 'verdura' into 'main'"[main ce43962] Merge branch 'verdura' into 'main'jpuigcerver@fp:~/git_branques(main)$gitlga
*ce43962-(0 seconds ago)Merge branch 'verdura' into 'main'-Joan Puigcerver(HEAD -> main)|\ | *e4cfd4d-(0 seconds ago)Verdura-Joan Puigcerver(verdura)* |d32e3ba-(1 second ago)Fruta-Joan Puigcerver(fruita)|/ *d71a079-(1 second ago)Merge branch 'beguda' into 'main'-Joan Puigcerver|\ | *7540d67-(1 second ago)Beguda-Joan Puigcerver(beguda)* |a7ef672-(1 second ago)Menjar-Joan Puigcerver(menjar)|/ *ec4c927-(1 second ago)Commit inicial-Joan Puigcerver
S'edita el fitxer menjar.txt per a eliminar les marques de conflicte i mantenir els canvis
de les dues branques.
El canvi de base (rebase) és una altra manera d'integrar els canvis de dues branques divergents.
Consisteix a aplicar, en ordre cronològic, els canvis dels commits d'una branca sobre una altra branca.
Aquesta tècnica permet incorporar canvis entre branques divergents mantenint una història lineal.
Aquest procés es fa amb l'ordre git rebase, amb la sintaxi següent:
gitrebase<nova_base>[<branca>]
<nova_base>: nom de la branca que es vol utilitzar com a nova base.
[<branca>]: (opcional) nom de la branca a la qual es vol canviar la base.
Si no s'especifica, es canvia la base de la branca actual (HEAD).
Si s'especifica, l'operació fa automàticament un git checkout <branca> abans de començar.
Per exemple, per a canviar la base de la branca paella a la branca main,
es pot executar qualsevol de les ordres següents des de la branca paella:
gitrebasemain
gitrebasemainpaella
Figura 12. Història abans del canvi de base.
En primer lloc, el canvi de base identifica tots els commits de la branca que no estan presents
en la nova_base, és a dir, tots els posteriors a l'avantpassat comú.
Després, el HEAD es mou a la nova_base (main en aquest cas) i s'hi apliquen els commits
de la branca en ordre seqüencial.
Figura 13. Història després del canvi de base.
Preparació de les branques divergents desdejuni i paella
Es creen les branques desdejuni i paella des de la branca main.
jpuigcerver@fp:~/git_branques(main)$gitcheckoutmain
Already on 'main'jpuigcerver@fp:~/git_branques(main)$gitbranchdesdejuni
jpuigcerver@fp:~/git_branques(main)$gitbranchpaella
jpuigcerver@fp:~/git_branques(main)$gitlga-3# (1)!*ce43962-(0 seconds ago)Merge branch 'verdura' into 'main'-Joan Puigcerver(HEAD -> main, paella, desdejuni)|\ | *e4cfd4d-(0 seconds ago)Verdura-Joan Puigcerver(verdura)* |d32e3ba-(1 second ago)Fruta-Joan Puigcerver(fruita)|/
L'opció -3 mostra només els tres últims commits de la història del repositori.
S'afig un producte al fitxer beguda.txt en la branca desdejuni.
El canvi de base (rebase) també pot provocar conflictes si les dues branques han modificat
la mateixa part d'un fitxer. Git indica que hi ha conflictes i cal resoldre'ls manualment,
d'una manera semblant a la resolució de conflictes en la fusió de branques.
La diferència és que cal resoldre els conflictes de cada commit al qual s'està canviant la base,
seguint aquests passos:
Editar el fitxer per a resoldre manualment el conflicte i esborrar les marques de conflicte.
Marcar els fitxers com a resolts amb git add.
Continuar el procés de rebase amb l'ordre git rebase --continue.
Aquest procés es repeteix per a cada commit que tinga conflictes.
Per a cancel·lar el procés de canvi de base (REBASING), es pot utilitzar l'ordre git rebase --abort.
Preparació de les branques divergents postre i aperitiu
Es creen les branques postre i aperitiu des de la branca main.
La branca aperitiu ha divergit de la branca main. Per a mantindre una història lineal,
es canvia la base de la branca aperitiu a la branca main.
Com que les dues branques han modificat la mateixa secció del fitxer menjar.txt,
es produeixen conflictes.
jpuigcerver@fp:~/git_branques(main)$gitcheckoutaperitiu
Switched to branch 'aperitiu'jpuigcerver@fp:~/git_branques(aperitiu)$gitrebasemain
Auto-merging menjar.txtCONFLICT (content): Merge conflict in menjar.txtRebasing (1/1)error: could not apply f3c59a0... Menjar aperitiuhint: Resolve all conflicts manually, mark them as resolved withhint: "git add/rm <conflicted_files>", then run "git rebase --continue".hint: You can instead skip this commit: run "git rebase --skip".hint: To abort and get back to the state before "git rebase", run "git rebase --abort".hint: Disable this message with "git config advice.mergeConflict false"Could not apply f3c59a0... Menjar aperitiujpuigcerver@fp:~/git_branques(aperitiu|REBASE 1/1)$gitstatus
interactive rebase in progress; onto 0d71e2bLast command done (1 command done): pick f3c59a0 Menjar aperitiuNo commands remaining.You are currently rebasing branch 'aperitiu' on '0d71e2b'. (fix conflicts and then run "git rebase --continue") (use "git rebase --skip" to skip this patch) (use "git rebase --abort" to check out the original branch)Unmerged paths: (use "git restore --staged <file>..." to unstage) (use "git add <file>..." to mark resolution)both modified: menjar.txtno changes added to commit (use "git add" and/or "git commit -a")
Es resolen els conflictes eliminant les marques de conflicte i mantenint els canvis de les dues branques.
Després, es marquen els fitxers com a resolts amb git add i es continua el procés de rebase.
Pa
Macarrons
Pomes
Bresquilles
Tomaques
Creïlles
Arròs
Pollastre
<<<<<<< HEAD
Iogurt
=======
Olives
>>>>>>> f3c59a0 (Menjar aperitiu)
Pa
Macarrons
Pomes
Bresquilles
Tomaques
Creïlles
Arròs
Pollastre
Iogurt
Olives
jpuigcerver@fp:~/git_branques(aperitiu|REBASE 1/1)$codemenjar.txt# (1)!jpuigcerver@fp:~/git_branques(aperitiu|REBASE 1/1)$gitaddmenjar.txt
jpuigcerver@fp:~/git_branques(aperitiu|REBASE 1/1)$gitstatus
interactive rebase in progress; onto 0d71e2bLast command done (1 command done): pick f3c59a0 Menjar aperitiuNo commands remaining.You are currently rebasing branch 'aperitiu' on '0d71e2b'. (all conflicts fixed: run "git rebase --continue")Changes to be committed: (use "git restore --staged <file>..." to unstage)modified: menjar.txtjpuigcerver@fp:~/git_branques(aperitiu|REBASE 1/1)$gitrebase--continue
[detached HEAD 0a53b4b] Menjar aperitiu Author: Joan Puigcerver <j.puigcerveribanez@edu.gva.es> 1 file changed, 1 insertion(+)Successfully rebased and updated refs/heads/aperitiu.jpuigcerver@fp:~/git_branques(aperitiu)$gitlga-3
*0a53b4b-(0 seconds ago)Menjar aperitiu-Joan Puigcerver(HEAD -> aperitiu)*0d71e2b-(0 seconds ago)Menjar postre-Joan Puigcerver(postre, main)*8c43498-(0 seconds ago)Menjar paella-Joan Puigcerver(paella)
S'eliminen les marques de conflicte i es combinen els dos textos.
Després del canvi de base, ja és possible fusionar la branca aperitiu en la branca main
amb una fusió directa (fast-forward).