Introducció

Normalment un projecte no el desenvolupa només una persona, sino que treballen vàries a la vegada.

Git és un sitema de control de versions descentralitzat que no necessita cap repositori comú on compartir el codi, però la forma habitual de treballar de molts equips és mitjançant un repositori centralitzat. Els més utilitzats són Github i Gitlab.

Nosaltres treballarem amb Gitlab. El motiu? Que és una solució oberta que pots hostetjat en els teus propis servidors.

Repositori remot

Ves a https://about.gitlab.com/ i registra't (si encara no estas registrat):

Omple el formulari de registre:

Omple el formulari de registre:

Projecte

El primer que has de fer es crear un nou projecte:

Escull Create blank project:

Omple les dades que et demanen i crea el projecte:

Com que has escollit l’opció de inicialitzar el repositori amb un fitxer README, aquest s’ha creat amb un commit inicial, i el podem clonar el repositori a la nostra màquina local.

Web IDE

Però abans de fer aixó, anem a editar el projecte directament desde una Web IDE:

La IDE es molt semblant a VS Code.

Crea un fitxer main.py:

Editem el fitxer:

Fes un commit dels nous canvis.

Per defecte el commit el fa a una nova branca. T’enrecordes que vas aprendre que era una branca en l'activitat Git?

Aquest cop li direm que no, que faci servir la branca main:

Important. A més de fer un commit, aquest commit s’escriu al repositori remot!

Clone

Es pot clonar un projecte fent servir ssh o https. Nosaltres farem servir https i necessitem la direcció url:

Ja pots clonar el projecte i verificar que podem executar el fitxer main.py:

$ git clone https://gitlab.com/ddemingo/daw.git
$ cd daw
$ python3 main.py
hola david!

El que farem ara es modificar el fitxer main.py amb la comanda nano:

x = 1
if x == 1:
    print("x is 1")

I fem un commit de la modificació:

$ git commit -a -m "fem servir un if"

Push

Per escriure el canvis realitzats en el nostre repositori al repositori remot de Gitlab hem de fer un push. Però com sap git a quina adreça l’ha d’enviar?

Podem veure tots els repositoris remots que tenim registrats amb la comanda git remote:

$ git remote

La comanda retorna origin, que és el nom que git dona per defecte al servidor desde el qual varem clonar el repositori, però origin no es cap adreça url …

Pots fer servir el flag -v per tal git mostri la URL a la que està associat el nom origin i que es fa servir quan es llegeix o s’escriu a aquest repositori remot :

$ git remote -v
origin  https://gitlab.com/ddemingo/daw.git (fetch)
origin  https://gitlab.com/ddemingo/daw.git (push)

Pots veure que quan fas un fetch o un push a origin s'està fent servir l’adreça https://gitlab.com/ddemingo/daw.git

Podem tenir configurat més d’un repositori remot i treballar amb tots ells a la vegada, però ho veurem més tard

Quan has modificat el teu projecte local fins arribar a un punt en que vols compartir els teus canvis, pots fer un “push upstream”. La comanda per fer això és molt sencilla: git push <remote> <branch>. Si vols fer un push a la branca main del teu servidor origin, pots executar aquesta comanda per fer un “push” de tots els “commits” que has fet al servidor:

$ git push origin main
Writing objects: 100% (3/3), 314 bytes | 104.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://gitlab.com/ddemingo/daw.git
   d041d97..914c179  main -> main

Quan executis aquesta comanda et demanarà el teu usuari i contrasenya de Gitlab. Només faltaria que tothom pogues modificar el teu repositori remot

Pots veure que el resultat d’aquesta comanda és que s’han escrit els canvis al servidor origin:

Quan clones un projecte d’un repositori remot, per defecte es configura que si no especifiques altre cosa, per defecte el “push” es faci al repositori remot origin a la branca main. Per tant, en el cas anterior es suficient amb la comanda git push:

git push

Sincronitzar

Ves a la Web IDE i refresca la pàgina web (pots fer servir la tecla F5) per veure els canvis que has escrit al repositori remot.

Modifica el fixer main.py:

Fes un commit a la branca main:

Torna al terminal i verifica que el teu repositori local està sincronitzat amb la comanda git status:

$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

Pots veure que segons aquesta comanda la branca main està sincronitzada amb origin/main

Però si mires el contingut del fitxer main.py veurás que no es així

El motiu és que main i origin/main apunten al mateix commit:

%%{init: { 'gitGraph': {'mainBranchName': 'origin/main'}} }%%
    gitGraph
       commit
       commit
       commit tag:"main"

Fetch

Git no permet sincronitzar codi sense que tu no aprovis els canvis que es faran a la teva còpia local, és una qüestió de seguretat i confiança. En aquest cas ets tú, desde un altre entorn que has fet els canvis, però en la majoria dels casos no és així si treballes amb altre gent i aquests amb altre gent.

El primer que has de fer és un git fetch:

git fetch origin
Unpacking objects: 100% (3/3), 304 bytes | 101.00 KiB/s, done.
From https://gitlab.com/ddemingo/daw
   914c179..c3e3ef6  main       -> origin/main

Pots veure que es llegeixen els canvis que s’han escrit al repositori remot.

Per veure els “commits” que s’han afegit al “upstream main”, pots executar la comanda git log fent servir origin/main com a filtre:

$ git log --oneline main..origin/main
c3e3ef6 (origin/main, origin/HEAD) if-else

Pots veure que l’únic canvi que s’ha fet és el commit “if-else”.

Però no pots veure aquests canvis perquè el punter main no ha avançat

%%{init: { 'gitGraph': {'mainBranchName': 'origin/main'}} }%%
    gitGraph
       commit
       commit
       commit tag:"main"
       commit
       commit

Executa aquesta comanda:

$ git log origin/main
commit c3e3ef64f04735da05b33cd9e0e853b4b046b53e (origin/main, origin/HEAD)
Author: David de Mingo <david@optersoft.com>
Date:   Mon Apr 10 15:17:02 2023 +0000

    if-else

commit 914c179cb86d7b362ee8f708765a7145b6c629c8 (HEAD -> main)
Author: David de Mingo <ddemingo@xtec.cat>
Date:   Mon Apr 10 14:36:10 2023 +0000

    fem servir un if

Pots veure que HEAD -> main apunta a un commit diferent que origin/main i origin/HEAD.

Pots canviar a origin/main amb la comanda git checkout:

$ git checkout origin/main
$ python3 main.py
x is not 1

El resultat és x is not 1.

Pots tornar a main amb la comanda git checkout:

$ git checkout main
$ python3 main.py
x is 1

El resultat torna a ser x is 1.

Merge

Si els canvis et semblan bé pots fer un merge:

$ git merge origin/main

Després del merge main i origin/main tornen a coincidir:

%%{init: { 'gitGraph': {'mainBranchName': 'origin/main'}} }%%
    gitGraph
       commit tag:"main"
       commit
       commit

I si ara fas un git log pots veure que tots els punters apunten al mateix commit:

$ git log
commit c3e3ef64f04735da05b33cd9e0e853b4b046b53e (HEAD -> main, origin/main, origin/HEAD)
Author: David de Mingo <david@optersoft.com>
Date:   Mon Apr 10 15:17:02 2023 +0000

    if-else

Pull

El que aviat apendrás es que la majoria de la gent vol anar ràpid i confia amb els altres perquè es va més ràpid. I que passa amb la seguretat? Doncs passa el que passa

Ves a la Web IDE, torna a modificar el fitxer main.py:

list = ["barcelona","girona","tarragona"]
for city in list:
    print(city)

Fes el commit corresponent (enrecorda’t de fer el commit a la branca main!)

Ara anirem ràpid sense verificar res amb la comanda git pull (és com fer un fetch i un merge a la vegada):

$ git pull

I pots verificar que el repositori local s’ha sincronitzar amb el repositori remot:

$ python3 main.py
barcelona
girona
tarragona

Posem-nos d’acord

El que aviat descubrirás és que si no ens posem d’acord les coses no són tan fàcil com sembla.

Ves a la Web IDE, torna a modificar el fitxer main.py:

map = { "Portugal": "Lisboa", "França": "París" }
print(map["Portugal"])

Fes el commit corresponent (enrecorda’t de fer el commit a la branca main!)

Modifica el fitxer main.py del repositori local:

tuple = ( "David", 51, "Barcelona")
print(tuple[0])

Fes el commit corresponent:

$ git commit -a -m "tuple"

Ara tenim un problema, saps quin és? Ho pots imaginar, i git és conscient del problema si fas un git pull:

$ git pull
From https://gitlab.com/ddemingo/daw
   29c2140..876bb66  main       -> origin/main
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge (the default strategy)
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only

El que has de fer en aquesta situació és crear una branca nova per conservar els teus canvis:

$ git branch conflict

A continuació has de fer un reset de la teva branca principal a l’estat en que està la branca origin/main:

git reset --hard origin/main

Si executes el fitxer main.py pots veure que s’està executant el codi que has escrit mitjançant la Web IDE:

$ python3 main.py
Lisboa

Resoldre el conflicte

Fem un merge de la branca conflict

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

El resultat de la comanda ens indica que tenim un conflicte en el fitxer main.py que git no pot resoldre de manera automàtica:

Edita el fitxer main.py:

<<<<<<< HEAD
map = { "Portugal": "Lisboa", "França": "París" }

print(map["Portugal"])
=======
tuple = ( "David", 51, "Barcelona")
print(tuple[0])
>>>>>>> conflict

Que git no sàpida resoldre el conflicte no vol dir que no sapiga exactament quin és el conflicte, i t’indica clarament en el fitxer main.py quin és el conflicte.

Resol el conflicte. Per exemple, pots deixar els dos segments de codi:

map = { "Portugal": "Lisboa", "França": "París" }
print(map["Portugal"])

tuple = ( "David", 51, "Barcelona")
print(tuple[0])

Un cop resolt el conflicte ja pots fer el commit i el push corresponent:

git commit -a -m "merged tuple in main.py"
git push

També pots borrar la branca conflict que ja no et fa falta.

$ git branch -d conflict

Auto-merging

Pràctica

Aquesta pràctica es fa en grups de dos alumnes.

Suposem que són la Núria i en Joan.

1.- La Núria ha de crear un repositori remot a la seva compte de Gitlab, i afegir al Joan com a membre del projecte.

A la pàgina del projecte fes clic a Project Information > Members:

Invita a l’alumne B amb el rol Maintainer:

A la pàgina members apareixarà l’alumne B.

2.- El Joan ha de clonar el projecte, crear el fitxer data.py, fer un commit i un push:

courses = ["Python", "Java"]
enrollments = []
students = ["Eva", "Marc"]

3.- La Núria ha de clonar el projecte, crear el fitxer admin.py, fer un commit i un push:

import data

def enroll(course, student):
  if student in data.students and course in data.courses:
      data.enrollments.append((course,student))
      return True
  return False

5.- El Joan ha de fer un fetch + merge, crear el fitxer main.py, fer un commit i un push:

import admin
import data

assert "Eva" == data.students[0]
assert True == admin.enroll("Python","Eva")

print("It's working!")

6.- La Nuria ha de fer un fetch + merge.

Executa el fitxer main.py:

$ python3 main.py

Veurás que es crea una carpeta __pycache__ que no s’ha d’incorporar al control de versions.

Has de crear un fitxer .gitignore amb aquest contingut:

/__pycache__

Fes un commit i un push.

7.- A continuació heu d’anar millorant el codi de manera interactiva: cada alumne en el seu ordinador i compartint les modificacions amb git push i git pull.

Per exemple:

  • Afegir més estudiants als cursos
  • Afegir més cursos
  • Afegir les dades de teachers
  • Afegir una funció is_enrolled(course,student) al fitxer admin.py
  • Que la funció enroll(course,student) verifiqui que l’alumne no estigui matricular al curs.

L’objectiu és que aprengueu a fer servir git per treballar en equip, i de pas, una mica més de python