Workflow Git
- Conceptes
- Configuració inicial
- Flux de treball
- Escenari base
- Escenari amb conflicte
- Estratègies de branques
- Bones pràctiques
- Git i CI/CD
- Regles d’or del treball en equip
- FAQ
- Com crear un repositori a partir d’una carpeta existent?
- Quina diferència hi ha entre
git pulligit fetch+git merge? - Com s’associa una branca local amb una branca remota?
- He fet commit d’un arxiu que no hauria d’haver afegit. Com ho desfaig?
- Com puc veure els canvis que he fet abans de fer commit?
- He modificat arxius però vull descartar tots els canvis i tornar a l’últim commit
- Com puc desfer o corregir l’últim commit (si no he fet push)?
- Com puc tornar el repositori a l’estat que hi ha remotament?
- Referències
Aquesta entrada descriu un flux de treball amb git per a equips de desenvolupament, des de la línia de comanda. El focus principal és en equips petits (2-3 persones) amb un enfocament trunk-based (treballant directament sobre la branca principal), però també es presenten alternatives com les feature branches per a equips més grans o projectes amb més complexitat.
Conceptes
En local
git és una eina que permet treballar amb repositoris de codi locals i remots.
Els canvis sobre els arxius d’un repositori s’agrupen en commits. Un commit és l’acte d’emmagatzemar un conjunt de canvis al repositori.
En l’àmbit local, tenim tres espais:
- El working directory és el lloc on tens el teu codi. A l’arrel del teu working directory tindràs sempre una carpeta anomenada .git on es guarden els altres dos espais.
- El staging area és una capsa on pots ficar i treure arxius. Un commit estarà format per tots els arxius ficats en aquesta àrea, i s’identifica amb un hash o resum. Quan es fa el commit, es buida.
- El repositori és el lloc on s’emmagatzemen els commits d’arxius provenents del stagging area. Podem revisar i recuperar qualsevol arxiu de qualsevol commit del passat. El commit actual d’un repositori es diu HEAD.
Un repositori pot tenir branques (branches). Les branques permeten divergir de la línia principal de desenvolupament i fer feina sense afectar-la. En els exemples d’aquest document treballarem directament sobre la branca principal per simplificar, però més endavant es presenten les estratègies de branques disponibles. Cal saber que master és el nom de la branca que git crea per defecte quan es crea un repositori.
En remot
Opcionalment, podem tenir repositoris remots, i comunicar-nos per pujar o baixar coses. Un repositori remot és com un de local, però no té working directory. Se’n diu “bare”.
Ens interessa tenir-ne de remots per poder tenir un lloc on compartim el codi amb la resta de membres del grup. El flux de treball serà treballar en local i compartir en remot la feina, un cop la tenim enllestida.
A un repositori local podem emparellar un de remot. El nom que git dona al principal repositori remot és origin. Un cop els hem emparellat, el codi NO se sincronitza automàticament. Tenim disponibles una sèrie d’operacions:
- fetch: guarda en local els canvis remots (sense integrar-los).
- merge: barreja els canvis remots que tenim en local amb els locals.
- pull: és el mateix que fer un fetch i després un merge.
- push: puja tots els canvis locals al repositori remot.
Configuració inicial
Eina git
Instal·la la teva eina git de línia de comanda al teu sistema operatiu.
Intenta executar-la:
git --version
Crear el repositori
Primer, has de crear un repositori buit a github o a gitlab.
Quan l’hagis creat, pots obtenir un URL del tipus:
https://github.com/usuari/repositori.git o bé https://gitlab.com/usuari/repositori.git.
Configuració
Les següents tres comandes són interessants per treballar: les dues primeres, calen per indicar el teu usuari i correu que es guarda a l’activitat del repositori. El tercer serveix per guardar les credencials el primer cop que s’introdueixen. Compte: es guarden en text pla a $HOME/.git-credentials.
git config --global user.email “elteu@correu.com”
git config --global user.name “elteunom”
git config --global credential.helper store
El flag –global indica que els canvis apliquen a tots els repositoris. Si no s’indica, només aplica al repositori en què ens trobem.
També es pot configurar credential.helper per utilitzar una cache (900 segons per defecte):
git config --global credential.helper cache
Si es volen ignorar els canvis fets al mode dels arxius, es pot fer:
git config --global core.filemode false
La comanda per esborrar una entrada és:
git config --global --unset <key>
Clonar el repositori
A partir d’ara es parla de github, però les comandes són exactament les mateixes canviant l’URL pel de gitlab.
Clonarem el repositori buit que hem creat a github:
1$ git clone https://github.com/usuari/repositori.git
Això crea una carpeta “repositori” amb el working directory i la carpeta .git a dins.
Per mostrar l’estat:
1$ git status
On branch master
No commits yet
nothing to commit (create/copy files and use “git add” to track)
També pots mirar l’aparellament amb el repositori remot:
1$ git remote -v
origin https://github.com/usuari/repositori.git (fetch)
origin https://github.com/usuari/repositori.git (push)
Pots veure les branques locals i remotes així:
1$ git branch
* master
1$ git branch -r
origin/HEAD -> origin/master
origin/master
Flux de treball
Aquest és el flux de treball de referència que es detalla en els escenaris següents. Serveix com a guia ràpida de la sessió de treball habitual amb git: sincronitzar, resoldre conflictes si n’hi ha, treballar en local i pujar els canvis.
- Obtenir canvis remots, en dos passos:
- Obtenir-los amb git fetch
- Barrejar-los amb git merge
- Si el merge genera conflicte:
- Editar arxius conflictius
- Fer git add de les solucions
- Fer git commit
- Fer canvis en local:
- Modificar els arxius del working directory
- Afegir-los al staging area (git add)
- Fer commit (git commit)
- Pujar canvis locals:
- Fer git push
Escenari base
Reproduirem l’escenari base, amb dos usuaris i un repositori remot compartit. Els dos usuaris fan canvis en local i els sincronitzen amb el repositori remot.
Afegir contingut
Per afegir contingut, cal preparar el commit. Primer, crea o copia al working directory tot el contingut que vulguis.
Imaginem que afegim un arxiu així:
echo "Hola, món!" > arxiu.txt
Si mostres l’estat:
1$ git status
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
arxiu.txt
nothing added to commit but untracked files present (use "git add" to track)
Els missatges expliquen que tenim un arxiu fora del control del repositori (untracked). Per afegir-lo:
1$ git add arxiu.txt
1$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: arxiu.txt
L’estat mostra que l’arxiu és al staging area (Changes to be committed). Ara ja podem crear el commit:
1$ git commit -m "primer commit"
[master (root-commit) 17466a8] primer commit
1 file changed, 1 insertion(+)
create mode 100644 arxiu.txt
1$ git status
On branch master
Your branch is based on 'origin/master', but the upstream is gone.
(use "git branch --unset-upstream" to fixup)
nothing to commit, working tree clean
També pots mirar el log, el lloc on es guarden els canvis del repositori:
1$ git log
commit 17466a86c10203150c8502e3aaedb8066c9d9b67 (HEAD -> master)
Author: elteunom <elteu@correu.com>
Date: Sun Apr 26 19:39:54 2020 +0200
primer commit
També hi ha un format en una línia d’aquesta comanda:
1$ git log --graph --oneline
* 17466a8 (HEAD -> master) primer commit
El log mostra el commit “17466a8” amb el seu missatge. HEAD -> master indica que és el commit actual de la branca master.
Pujar contingut
Cal fer un push:
1$ git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 216 bytes | 216.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
* [new branch] master -> master
Nou estat:
1$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
Ara l’estat confirma que estem sincronitzats amb origin/master (el repositori remot). Si mirem el log:
1$ git log --graph --oneline
* 17466a8 (HEAD -> master, origin/master) primer commit
Ara apareix origin/master al costat del commit, confirmant que tant el repositori local com el remot apunten al mateix commit.
Treballar amb un segon usuari
Simularem que tenim un segon usuari amb un altre repositori. Per simplificar, els dos usuaris poden compartir credencials de github. Alternativament (recomanable), crea tants usuaris com calgui, i fes que siguin col.laboradors del projecte. Això es pot fer tant a github com a gitlab:
- github: cal afegir un col·laborador des del projecte > Settings > Manage access > Invite a collaborator.
- gitlab: cal afegir un membre des del projecte > Settings > Members > Invite member. Selecciona “mantainer” com a perfil.
Creem un segon workspace directory. Per distingir els dos, tindrem dos prompts diferents: 1$ i 2$.
2$ git clone https://github.com/usuari/repositori.git
Cloning into 'repositori'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
Ara, modificarem l’arxiu i mirem l’estat:
2$ echo "Com ba tot?" >> arxiu.txt
2$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: arxiu.txt
no changes added to commit (use "git add" and/or "git commit -a")
Ens diu que hi ha un arxiu modificat (modified), però no està al staging area.
Ens hem equivocat! Volíem escriure “Com va tot?”. Podríem editar l’arxiu un altre cop i esborrar la nova línia, però aprofitarem per recuperar l’arxiu abans de fer la modificació. Com que no hem fet el commit, es pot recuperar així:
2$ git reset --hard
HEAD is now at 17466a8 primer commit
2$ echo "Com va tot?" >> arxiu.txt
Ara, afegim l’arxiu al staging area i fem el commit:
2$ git add arxiu.txt
2$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: arxiu.txt
2$ git commit -m "afegim pregunta"
[master b475802] afegim pregunta
1 file changed, 1 insertion(+)
2$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
Ara, afegim els canvis al repositori remot:
2$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 263 bytes | 263.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
17466a8..b475802 master -> master
2$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
Ja tenim tots els canvis en remot. Mirem el log:
2$ git log --graph --oneline
* b475802 (HEAD -> master, origin/master, origin/HEAD) afegim pregunta
* 17466a8 primer commit
Com es veu, l’últim commit (b475802: “afegim pregunta”) es mostra com l’actual.
Rebre els canvis al primer usuari
Ara retornem al primer usuari (1$). Si mirem l’estat i el log:
1$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
1$ git log --graph --oneline
* 17466a8 (HEAD -> master, origin/master) primer commit
Com es pot veure, l’estat diu que està actualitzat amb origin/master (repositori remot), i al log no hi ha el nou commit que s’ha pujat al repositori remot (“afegim pregunta”).
Per poder veure’l, cal baixar-se els canvis del remot. Això es pot fer amb un fetch:
1$ git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/usuari/repositori
17466a8..b475802 master -> origin/master
Si es mira l’estat i el log:
1$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
1$ git log --graph --oneline
* 17466a8 (HEAD -> master) primer commit
El log no ha canviat, perquè fetch no integra els canvis al repositori, però l’estat sí: ara ens diu que estem per darrere d’origin/master, i que hauríem de fer un git pull. Com que un pull és un fetch + merge, farem només el merge.
El merge intentarà barrejar automàticament el contingut remot recuperat i el que tenim al working directory.
1$ git merge
Updating 17466a8..b475802
Fast-forward
arxiu.txt | 1 +
1 file changed, 1 insertion(+)
El merge ha funcionat: ha afegit una nova línia. Com es veu, aquesta operació és immediata: no necessita anar al repositori remot. L’arxiu s’ha actualitzat, i l’estat i el log estan igualats amb els de l’usuari 2:
1$ cat arxiu.txt
Hola, món!
Com va tot?
1$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
1$ git log --graph --oneline
* b475802 (HEAD -> master, origin/master) afegim pregunta
* 17466a8 primer commit
Etiquetes
Les etiquetes (o tags) és una forma senzilla d’identificar un cert commit o estat dins del repositori. Es poden posar locals i pujar-les en remot. A github, quan es pugen en remot, s’associen a una release que permet descarregar un arxiu empaquetat. A gitlab també es pot fer, però la secció es diu “tags”.
Per afegir un tag al commit actual i mostrar-lo:
1$ git tag v1.0
1$ git tag
v1.0
Per pujar una etiqueta:
1$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
* [new tag] v1.0 -> v1.0
Si volem veure les etiquetes des de l’altre repositori:
2$ git fetch
From https://github.com/usuari/repositori
* [new tag] v1.0 -> v1.0
2$ git tag
v1.0
Tags anotats
L’exemple anterior crea un tag lleuger (lightweight). Git també permet crear tags anotats, que inclouen un missatge descriptiu, l’autor i la data. Són més informatius i recomanables per a versions o fites del projecte:
1$ git tag -a v1.0 -m "Primera versió estable amb funcionalitat bàsica"
1$ git push origin v1.0
Un bon missatge de tag descriu el contingut del desplegament. Evita repetir el nom del tag com a missatge:
# Poc informatiu
git tag -a v1.0 -m "v1.0"
# Informatiu: descriu el contingut
git tag -a v1.0 -m "Primera versió: API REST amb endpoints /users i /products"
Convencions de noms per a tags
| Convenció | Exemple | Ús recomanat |
|---|---|---|
| Semantic versioning | v1.0.0, v1.1.0, v1.1.1 | Projectes amb API pública o versionat formal |
| Sprint-based | sprint-1, sprint-2 | Projectes acadèmics o amb sprints definits |
| Data-based | 2026-03-15 | Menys recomanat (no descriu el contingut) |
El semantic versioning (vMAJOR.MINOR.PATCH) és l’estàndard professional: incrementa MAJOR per canvis incompatibles, MINOR per noves funcionalitats, i PATCH per correccions.
Checkout: viatjar en el temps
El checkout ens permet recuperar qualsevol working directory per a un commit. És la veritable raó de ser dels repositoris: poder viatjar en el temps.
Per exemple, podem recuperar un arxiu concret d’un commit. De l’últim ( — significa que no indiquem el commit), o d’un concret:
1$ git checkout -- arxiu.txt
1$ git checkout 17466a8 arxiu.txt
Podem recuperar tot un commit, per exemple, el “primer commit”:
1$ git checkout 17466a8
Note: checking out '17466a8'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at 17466a8 primer commit
1$ git status
HEAD detached at 17466a8
nothing to commit, working tree clean
1$ cat arxiu.txt
Hola, món!
Com es veu, tornem al contingut de l’arxiu abans del segon commit.
El problema d’aquesta comanda és que estem en estat “detached HEAD”: el HEAD apunta directament a un commit en lloc d’apuntar a una branca. Podem mirar i experimentar, però qualsevol commit que fem no pertanyerà a cap branca i es podria perdre.
Sempre podem retornar al master:
1$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
I si volem recuperar cert commit al master, per exemple, el “primer commit”:
1$ git reset --hard 17466a8
HEAD is now at 17466a8 primer commit
També podem fer referència a una etiqueta:
1$ git reset --hard v1.0
Si volem que el reset quedi al repositori remot:
1$ git push
To https://github.com/usuari/repositori.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'https://github.com/usuari/repositori.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Això falla perquè no es pot fer un push d’un commit antic: git sempre comprova que siguin commits més nous. Podem ometre aquesta comprovació amb el paràmetre –force:
1$ git push --force
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
+ b475802...17466a8 master -> master (forced update)
Després de fer això, si anem a l’altre repositori i fem fetch:
2$ git fetch
From https://github.com/usuari/repositori
+ b475802...17466a8 master -> origin/master (forced update)
2$ git log --graph --oneline
* b475802 (HEAD -> master, tag: v1.0) afegim pregunta
* 17466a8 (origin/master) primer commit
2$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: arxiu.txt
no changes added to commit (use "git add" and/or "git commit -a")
Veiem que el repositori remot està al primer commit, però el local està al segon: ens diu “your branch is ahead”.
Això es pot resoldre canviant al commit remot en local:
2$ git reset --hard origin/master
2$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
2$ git log --graph --oneline
* 17466a8 (HEAD -> master, origin/master, origin/HEAD) primer commit
Comparar canvis amb diff
git diff permet comparar dos commits locals o remots. Per exemple, per comparar el HEAD amb el tag v1.0:
$ git diff v1.0
diff --git a/arxiu.txt b/arxiu.txt
index 0027e65..b4b62f7 100644
--- a/arxiu.txt
+++ b/arxiu.txt
@@ -1,2 +1,2 @@
Hola, món!
-Com va tot?
+segona línia 13
En aquest cas, ens diu que el canvi del tag v1.0 al HEAD és que s’ha esborrat una línia (“Com va tot?”) i s’ha afegit una altra (“segona línia 13”).
Si volem comparar dos commits, afegirem dos paràmetres. Per exemple, si hem fet prèviament un git fetch, podem utilitzar aquesta comanda per veure si el master local i el remot estan sincronitzats:
git diff master origin/master
Esborrar arxius del repositori
Per esborrar un arxiu o una carpeta del repositori:
git rm arxiu.txt
git rm --cached arxiu1.txt
git rm -r carpeta
L’opció --cached esborra l’arxiu del repositori però el manté al working directory. L’opció -r permet esborrar carpetes de forma recursiva. Després, cal fer git commit per confirmar l’eliminació.
Gitignore
Alguns tipus de fitxers no haurien de ser part del repositori de codi, i es poden indicar afegint un patró a l’arxiu .gitignore que hi ha a les carpetes. En general, seria millor no afegir certs tipus d’arxius:
- cachés de dependències, com els continguts de
/node_moduleso/packages - codi compilat, com
.o,.pyc, i.class - carpetes de sortida de compilació, com
/bin,/out, o/target - arxius generats en temps d’execució com
.log,.lock, o.tmp - arxius amagats del sistema, com
.DS_StoreoThumbs.db - arxius de configuració personal dels IDE, com
.idea/workspace.xml
Escenari amb conflicte
Què passa quan dos desenvolupadors modifiquen la mateixa línia d’un arxiu? Git no pot decidir quina versió és correcta, i genera un conflicte que cal resoldre manualment. Vegem-ho pas a pas.
Simulem la situació: els dos usuaris afegeixen una segona línia diferent a l’arxiu. El primer fa push sense problemes, però el segon trobarà l’error.
El primer fa:
1$ echo "segona línia 1" >> arxiu.txt
1$ git add arxiu.txt
1$ git commit -m "segona 1"
[master 8bf099d] segona 1
1 file changed, 1 insertion(+)
1$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 262 bytes | 262.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
17466a8..8bf099d master -> master
1$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
I el segon:
2$ echo "segona línia 2" >> arxiu.txt
2$ git add arxiu.txt
2$ git commit -m "segona 2"
[master eacb48e] segona 2
1 file changed, 1 insertion(+)
2$ git push
To https://github.com/usuari/repositori.git
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'https://github.com/usuari/repositori.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Com es veu, es diu que cal fer primer pull, ja que no pots fer push si no has integrat els canvis remots al teu repositori.
Provem de fer-ho. Primer el fetch:
2$ git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/usuari/repositori
17466a8..8bf099d master -> origin/master
2$ git status
On branch master
Your branch and 'origin/master' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
nothing to commit, working tree clean
Ens demana el pull, farem el merge (ja hem fet el fetch).
Important: fes sempre el merge amb el working directory net (sense canvis pendents de commit). Si tens canvis sense cometre, primer fes commit o guarda’ls amb
git stash.
2$ git merge
Auto-merging arxiu.txt
CONFLICT (content): Merge conflict in arxiu.txt
Automatic merge failed; fix conflicts and then commit the result.
$ git status
On branch master
Your branch and 'origin/master' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
You 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: arxiu.txt
no changes added to commit (use "git add" and/or "git commit -a")
Ja tenim el conflicte a arxiu.txt. Això es tradueix en el fet que git modifica l’arxiu del conflicte per a reflectir les dues versions, afegint tres delimitadors:
- <<<<<<< HEAD
- La versió local
- =======
- La versió remota
- >>>>>>> nom_de_la_branca
En el nostre cas:
2$ cat arxiu.txt
Hola, món!
<<<<<<< HEAD
segona línia 2
=======
segona línia 1
>>>>>>> refs/remotes/origin/master
En aquest punt, ens podríem fer enrere (no ho farem) fins a l’estat anterior del merge fent git merge --abort.
Ens diu que teníem “segona línia 2” (HEAD) i que al remot tenim “segona línia 1” (refs/remotes/origin/master). Per resoldre el conflicte manualment, hem d’editar aquest arxiu i decidir què fem, esborrant les línies delimitadores (<,=,>) i tot el que no ens interessi.
En el nostre cas, decidim que ni una línia ni l’altra: “segona línia 12”. Editem l’arxiu:
2$ cat arxiu.txt
Hola, món!
segona línia 12
Després d’editar-lo, cal fer git add per marcar el conflicte com a resolt i ja podem fer commit i push:
2$ git add arxiu.txt
2$ git commit -m "resolt!"
[master 6fada39] resolt!
2$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
2$ git push
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 523 bytes | 523.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To https://github.com/usuari/repositori.git
8bf099d..6fada39 master -> master
2$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
2$ git log --graph --oneline
* 6fada39 (HEAD -> master, origin/master) resolt!
|\
| * 8bf099d segona 1
* | eacb48e segona 2
|/
* 17466a8 primer commit
Es poden veure els dos commits en paral·lel, i com finalment hi ha un commit (6fada39) que resol el problema.
Ara tornem al repositori 1:
1$ git fetch
remote: Enumerating objects: 10, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), done.
From https://github.com/usuari/repositori
8bf099d..6fada39 master -> origin/master
1$ git status
On branch master
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean
1$ git log --graph --oneline
* 8bf099d (HEAD -> master) segona 1
* 17466a8 primer commit
1$ git merge
Updating 8bf099d..6fada39
Fast-forward
arxiu.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
1$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
1$ git log --graph --oneline
* 6fada39 (HEAD -> master, origin/master, origin/HEAD) resolt!
|\
| * 8bf099d segona 1
* | eacb48e segona 2
|/
* 17466a8 primer commit
I ja tenim els dos repositoris sincronitzats després del conflicte.
Estratègies de branques
El flux descrit en aquest document és trunk-based: tothom treballa directament a master sense crear branques. Això és l’enfocament més simple i adequat per a grups de 2 persones amb bona comunicació. Però existeixen altres estratègies que convé conèixer.
Trunk-based (sense branques)
Cada membre fa commits i pushes directament a master. La integració és contínua i el feedback és immediat.
# Al principi de la sessió: sincronitzar
git pull origin master
# Treballar, fer commits
git add arxius_modificats
git commit -m "descripció del canvi"
git push origin master
Feature branches (branques per tasca)
Cada unitat de treball viu en una branca de curta durada (p. ex., feature/nova-funcio). La branca es fusiona a master quan la tasca és completa i provada.
# Crear una branca per la tasca
git checkout -b feature/nova-funcio
# Treballar i fer commits
git add arxius_modificats
git commit -m "descripció del canvi"
# Quan la tasca és llesta, fusionar a master
git checkout master
git pull origin master
git merge feature/nova-funcio
git push origin master
# Neteja
git branch -d feature/nova-funcio
Comparació
| Trunk-based | Feature branches | |
|---|---|---|
| Complexitat git | Baixa | Mitjana |
| Risc de conflictes | Baix (si sincronitzen sovint) | Mig (si branques viuen > 3 dies) |
| Aïllament del treball | Cap: un error afecta tothom | Alt: errors queden a la branca |
| Recomanat per a 2 persones | Si | Opcional |
| Recomanat per a 3+ persones | Possible | Si |
Les branques de curta durada (2-3 dies) funcionen bé. Les de llarga durada (> 1 setmana) divergeixen molt de master i els merges es compliquen. La regla pràctica: una tasca, una branca, fusiona ràpid.
Flux d’aprovació (merge controlat)
Quan es treballa amb feature branches, el merge a la branca principal es pot fer de dues maneres: directament pel desenvolupador, o bé a través d’un flux d’aprovació on algú revisa els canvis abans d’integrar-los. Aquesta segona opció és la base de les pull requests o merge requests que ofereixen moltes plataformes, però la idea de fons és purament organitzativa i es pot implementar amb qualsevol servidor git.
La idea
El principi és senzill: separar qui proposa canvis de qui els accepta. Un desenvolupador treballa a la seva branca i, quan la tasca és llesta, demana a un responsable que revisi i integri els canvis a la branca principal. El responsable pot:
- Revisar el diff dels canvis proposats.
- Demanar correccions si cal.
- Acceptar i fer el merge quan tot és correcte.
Això afegeix una capa de revisió que millora la qualitat del codi i evita que canvis no revisats arribin a la branca principal.
El flux amb git pur
Aquest flux no requereix cap eina especial, només git i una convenció d’equip:
1. El desenvolupador treballa a la seva branca i la puja al repositori remot:
git checkout -b feature/nova-funcio
# ... treballa, fa commits ...
git push -u origin feature/nova-funcio
El -u associa la branca local amb la remota (estableix l’upstream), de manera que les properes vegades n’hi ha prou amb git push.
2. Avisa al responsable (per correu, xat, o qualsevol canal) que la branca està llesta per revisar.
3. El responsable revisa els canvis des del seu repositori local:
git fetch origin
git log --oneline master..origin/feature/nova-funcio # quins commits hi ha
git diff master..origin/feature/nova-funcio # què canvien
4. Si cal demanar correccions, ho comunica al desenvolupador. El desenvolupador fa els canvis a la mateixa branca, commit i git push (ja no cal especificar el remot ni la branca gràcies al -u inicial). El responsable torna a revisar.
5. Quan els canvis són acceptats, el responsable fa el merge:
git checkout master
git pull origin master
git merge origin/feature/nova-funcio
git push origin master
# Neteja de la branca remota
git push origin --delete feature/nova-funcio
6. El desenvolupador sincronitza i neteja en local:
git checkout master
git pull origin master
git branch -d feature/nova-funcio
Permisos sobre branques
Per reforçar aquest flux, es poden configurar permisos d’escriptura sobre les branques del repositori remot. Per exemple, restringir l’escriptura a master a un sol usuari o a un grup reduït. D’aquesta manera, els desenvolupadors poden pujar les seves branques de feature però no poden fer push directament a master: només el responsable pot fer-ho després de revisar.
Això converteix una convenció organitzativa en una restricció tècnica. La majoria de servidors git (inclosos els auto-allotjats) permeten configurar aquests permisos.
Quan val la pena
Per a equips petits (2-3 persones) amb bona comunicació, un flux d’aprovació formal pot ser excessiu. Però a mesura que l’equip creix o el projecte requereix més rigor, afegir una capa de revisió abans del merge és una de les pràctiques que més millora la qualitat del codi sense complicar excessivament el flux de treball.
Bones pràctiques
Missatges de commit
Un bon missatge de commit permet al teu company entendre el canvi sense llegir el diff. Dues convencions habituals:
Imperatiu simple (mínim de fricció):
git commit -m "add user validation"
git commit -m "fix null check in preprocessing"
git commit -m "remove unused imports"
Regla: comença amb un verb en imperatiu. Descriu què fa el commit, no el que has fet tu. Menys de 72 caràcters.
Conventional Commits (més estructurat):
git commit -m "feat: add predict endpoint"
git commit -m "fix: handle null age field"
git commit -m "test: add unit tests for validator"
git commit -m "docs: update README with examples"
git commit -m "chore: update dependencies"
Prefixes: feat (nova funcionalitat), fix (correcció), test (tests), docs (documentació), chore (manteniment), refactor (refactorització sense canvi de comportament).
| Imperatiu simple | Conventional Commits | |
|---|---|---|
| Fricció d’escriptura | Baixa | Mitjana (cal recordar el prefix) |
| Llegibilitat del log | Bona | Molt bona |
| Compatible amb eines de changelog | No | Si |
Commits atòmics
Un commit atòmic conté un sol canvi lògic. El codi ha de funcionar després de cada commit. Això contrasta amb fer un sol commit gran al final del dia amb tots els canvis barrejats.
Per què importa? Quan alguna cosa falla, els commits atòmics fan evident quin canvi ha causat el problema:
# Commit gran: on és el problema?
$ git log --oneline -1
a3f9b12 "add model, preprocessing, API and tests"
# Commits atòmics: el problema és al segon commit
$ git log --oneline -4
a3f9b12 "add predict endpoint"
def5678 "add logistic regression model" ← falla aquí
c72a3f8 "add preprocessing pipeline"
5e8d1a0 "add FastAPI skeleton"
Regla pràctica: fes commit de cada unitat lògica de treball per separat. Si el codi no està llest, utilitza git stash per guardar-lo temporalment sense fer commit.
Git i CI/CD
En molts projectes, el repositori git no és només un lloc on guardar codi: és el punt de connexió amb sistemes d’integració contínua (CI) i desplegament continu (CD). Entendre aquesta relació ajuda a treballar amb més cura.
Com funciona la connexió
Les plataformes com github i gitlab permeten configurar pipelines que reaccionen automàticament a events git:
- Push a una branca (p. ex.,
master): pot disparar l’execució de tests, anàlisi de codi, o compilació. Si els tests fallen, l’equip rep una notificació. - Push d’un tag: pot disparar un desplegament a producció o a un entorn de proves.
- Creació d’una pull/merge request: pot executar els tests sobre la branca abans que el merge sigui acceptat.
Això significa que cada git push té conseqüències més enllà del repositori: pot posar en marxa processos automàtics que afecten tot l’equip o fins i tot els usuaris finals.
Implicacions pràctiques
- Un push descuidat pot bloquejar el CI per a tot l’equip. Si fas push de codi que no compila o que trenca els tests, el pipeline fallarà i ningú podrà validar els seus canvis fins que es corregeixi.
- Els tags són decisions de desplegament. Un tag no és només una etiqueta: en molts projectes, crear un tag actualitza el producte en producció. Cal crear-los de forma deliberada.
- Testar en local abans de fer push. Executar els tests localment abans de pujar canvis evita cicles innecessaris de CI i estalvia temps a tot l’equip.
- L’ordre importa. Si el sistema CI reacciona a tags, cal assegurar-se que el commit ja és a la branca principal abans de crear el tag. L’ordre habitual és: primer
git pushdel commit, desprésgit pushdel tag.
git push origin master ← CI valida el codi
git tag -a v1.0 -m "versió 1"
git push origin v1.0 ← CI desplega a producció
Configuració del CI/CD
La majoria de plataformes git (github, gitlab, gitea, etc.) inclouen eines de CI/CD integrades. La configuració es fa típicament mitjançant arxius YAML al propi repositori, on es defineixen quins passos s’executen (tests, compilació, desplegament) i en resposta a quins events git (push, tag, merge request).
Regles d’or del treball en equip
git pull(ofetch+merge) primer, sempre – Sincronitza abans de treballar per minimitzar conflictes.- Commits atòmics amb missatges llegibles – Un commit, una idea; el teu company ha d’entendre el canvi sense llegir el diff.
- No fer push de codi trencat – Si saps que el codi no funciona, no el pugis. Trenca el flux de treball de tot l’equip.
- Comunicar-se amb l’equip – Avisa abans de fer canvis grans o resets. Un missatge ràpid evita molts conflictes.
FAQ
Com crear un repositori a partir d’una carpeta existent?
Si ja tens una carpeta amb codi i vols pujar-la a un repositori remot buit (creat prèviament a github o gitlab):
cd la-meva-carpeta
git init
git symbolic-ref HEAD refs/heads/main
git remote add origin https://gitlab.com/usuari/repositori.git
# afegir .gitignore abans del primer commit
git add .
git commit -m "commit inicial"
git push -u origin main
Alternativament, pots clonar primer el repositori buit i copiar-hi el contingut:
git clone https://gitlab.com/usuari/repositori.git
cd repositori
git switch -c main
# copiar arxius i afegir .gitignore
git add .
git commit -m "commit inicial"
git push -u origin main
Quina diferència hi ha entre git pull i git fetch + git merge?
git pull és equivalent a fer git fetch seguit de git merge. La diferència pràctica és que amb fetch + merge pots inspeccionar els canvis remots abans d’integrar-los (amb git log o git diff), mentre que pull ho fa tot de cop.
# Amb fetch + merge (més control)
git fetch
git log --oneline master..origin/master # veure què ha canviat
git merge
# Amb pull (més ràpid)
git pull origin master
Com s’associa una branca local amb una branca remota?
Quan fas git push o git pull sense especificar el remot ni la branca, git necessita saber a quina branca remota correspon la teva branca local. Aquesta associació s’anomena upstream o tracking branch.
Hi ha diverses maneres d’establir-la:
# Opció 1: al fer push per primera vegada, amb -u (o --set-upstream-to)
git push -u origin main
# Opció 2: explícitament, sense fer push
git branch --set-upstream-to=origin/main main
Un cop establerta l’associació, pots fer servir les comandes curtes:
git push # equivalent a git push origin main
git pull # equivalent a git pull origin main
Quan clones un repositori, git configura automàticament el tracking de la branca principal. Per això git pull funciona directament en un repositori clonat sense haver de fer -u primer.
Per veure quines branques locals tenen upstream configurat:
git branch -vv
La sortida mostra, entre claudàtors, la branca remota associada:
* main a1b2c3d [origin/main] últim missatge de commit
feature d4e5f6a [origin/feature] un altre missatge
He fet commit d’un arxiu que no hauria d’haver afegit. Com ho desfaig?
Si encara no has fet push, pots treure l’arxiu de l’últim commit sense perdre els canvis locals:
git reset HEAD~1 --soft # desfà el commit, manté els canvis al staging
git reset HEAD arxiu-erroni.txt # treu l'arxiu del staging
git commit -m "el commit correcte"
Si ja has fet push però vols treure l’arxiu del repositori sense esborrar-lo del disc:
git rm --cached arxiu-erroni.txt
# afegir-lo al .gitignore si cal
git commit -m "remove arxiu-erroni del repositori"
git push
Com puc veure els canvis que he fet abans de fer commit?
git diff # canvis al working directory (no afegits al staging)
git diff --staged # canvis ja afegits al staging area
git diff HEAD # tots els canvis respecte l'últim commit
He modificat arxius però vull descartar tots els canvis i tornar a l’últim commit
git reset --hard HEAD
Compte: això esborra tots els canvis no comesos. Si vols guardar-los temporalment per recuperar-los més tard:
git stash # guarda els canvis temporalment
# ... fas altres coses ...
git stash pop # recupera els canvis guardats
Com puc desfer o corregir l’últim commit (si no he fet push)?
Mentre no hagis fet push, l’últim commit es pot desfer o modificar sense afectar ningú.
Desfer el commit completament, mantenint els canvis al working directory:
git reset --soft HEAD~1 # els canvis queden al staging, llestos per tornar a fer commit
git reset HEAD~1 # els canvis queden al working directory, fora del staging
Desfer el commit i descartar els canvis (irreversible):
git reset --hard HEAD~1
Corregir l’últim commit (afegir arxius oblidats o canviar el missatge):
# Canviar només el missatge
git commit --amend -m "nou missatge corregit"
# Afegir arxius que faltaven al commit
git add arxiu-oblidat.txt
git commit --amend --no-edit # manté el missatge original
--amend reescriu l’últim commit. Si ja has fet push, no és recomanable fer-ho perquè reescriu l’historial i pot causar problemes als companys.
Com puc tornar el repositori a l’estat que hi ha remotament?
Si vols descartar tots els canvis locals (commits, staging i working directory) i sincronitzar-te exactament amb el repositori remot:
git fetch origin
git reset --hard origin/master
Això mou el HEAD local al mateix commit que origin/master, descartant qualsevol commit local que no s’hagi pujat i qualsevol canvi pendent. Si tens arxius nous que no estan al repositori (untracked), no s’esborren. Per eliminar-los també:
git clean -fd # esborra arxius i carpetes untracked
Compte: ambdues operacions són irreversibles. Si no estàs segur de voler perdre els canvis, fes primer una còpia o un git stash.