PC Laborator 2.1 (opțional)

De la WikiLabs

Obiective

  • definirea noțiunii de Version Control System;
  • utilizarea unui sistem de Version Control: Git.

Sisteme de control al versiunii

Gândiți-vă la următorul scenariu: lucrați la un proiect într-un limbaj de programare sau de descriere hardware; acest proiect este mare și nu poate fi terminat într-o zi, prin urmare vă faceți timp și lucrați cate o oră în fiecare zi. Apar următoarele situații:

  • peste două săptămâni vă dați seama că unele lucruri pe care le-ați adăugat în program nu sunt necesare, deci ați vrea să vă întoarceți la o versiune mai veche - este nevoie de un sistem care să țină o istorie a modificărilor și să permită oricând întoarcerea la o versiune anterioară;
  • proiectul e pe echipe, deci la el lucrează trei persoane; fiecare editează unul sau mai multe fișiere, fiind posibil ca doi membri ai echipei să editeze același fișier - e nevoie de un sistem care să poată să centralizeze modificările și să trateze situația în care același fișier e modificat de doi ingineri;
  • unul din membrii echipei găsește o problemă și dorește să vadă cine și ce a modificat în zona respectivă de cod și care a fost scopul modificării - este deci nevoie de un sistem care să permită documentarea fiecărei modificări pe perioada dezvoltării proiectului și vizualizarea acestor modificări;
  • după documentare, tester-ul dorește să poată nota problema, atribuind-o unui membru al echipei pentru rezolvare - este nevoie de un sistem care să permită înregistrarea și documentarea erorilor în scopul rezolvării;

Toate aceste cerințe sunt adresate de sistemele de control al versiunii, acestea pot:

  • ține evidența modificărilor și permite întoarcerea la o versiune mai veche;
  • permite ramificarea dezvoltării în mai multe direcții și unificarea ulterioară a acestor ramuri;
  • sincroniza modificările fiecărui utilizator cu un depozit central pentru a putea fi accesate de ceilalți membri ai proiectului;

Există mai multe sisteme de control al versiunii:

  • Pe model client - server:
    • Concurrent Versions System (CVS) - open Source
    • Subversion (SVN) - open source
    • Perforce – nu este open source, dar poate fi folosit gratis în proiecte open-source
  • Pe model distribuit:
    • Git - creat de Linus Torvalds pentru dezvoltarea kernel-ului de Linux - open source
    • BitKeeper – folosit pentru dezvoltarea kernel-ului de Linux între 2002 și aprilie 2005.

În continuare vom prezenta conceptele cele mai importante și funcționarea sistemului Git.

Git

Git este un sistem de control de versiune distribuit. Asta înseamnă că poate fi folosit de către un utilizator fără a fi conectat la un depozit central, copia locală de pe calculatorul utilizatorului ținând în baza ei de date toate informațiile legate de conținutul curent și de modificările anterioare.

 Odată instalat Git, fiecare utilizator trebuie să-și configureze adresa de e-mail și numele:
  git config --global user.name "Ion Vasile"
  git config --global user.email ion.vasile@gmail.com

Git - repository local

Fiecare proiect este stocat în Git într-un "depozit" numit repository.

Un repository se crează cu comanda git init.

Odată un repository creat, se pot adăuga apoi șterge sau modifica fișiere. Aceste modificări se adaugă în repository prin operații specifice inițiate de utilizator, numite commit.

Starea curentă a repository-ului se poate vedea în orice moment cu comanda git status.

Pregătirea unui nou commit - staged files

Un nou commit se pregătește selectând fișierele dorite și punându-le într-o stare numită "staged for commit" adică pregătite pentru commit. Fișierele care pot fi pregătite pentru commit sunt:

  • fișiere noi, care vor fi adăugate în repository - prin comanda git add fișier;
  • fișiere existente în repository, dar care au fost modificate - prin comanda git add fișier;
  • fișiere existente care se vor șterge din repository - prin comanda git rm fișier;
  • fișiere care există în repository, dar iși schimbă locația - prin comanda git mv fișier_sursă fișier_destinație;

Mai multe fișiere pot fi pregătite pentru un commit, apoi toate odată se actualizează într-un singur commit.

Un fișier pregătit pentru commit se poate șterge din lista de fișiere pregătite, păstrând modificările, cu comanda git reset fișier

Adăugarea de fișiere în repository - commit

Fiecare nou commit, care poate conține modificări pentru unul sau mai multe fișiere, se adaugă întotdeauna peste ultimul commit, și conține informații despre:

  • utilizatorul care făcut modificările;
  • data și ora când s-au realizat modificările respective;
  • mesajul adăugat de utilizator în momentul commit-ului;
  • un număr de identificare, generat din celelalte informații;
  • modificările efective.
Un commit nou se realizează cu comanda git commit -m "mesaj".

Ultimul commit se numește HEAD. Orice nou commit se poate pune doar peste HEAD-ul curent și va deveni noul HEAD.

Git commit stack.svg

Un fișier modificat dar care nu este pregătit pentru commit, poate fi readus în stadiul din HEAD prin comanda git checkout fișier

Revenire la versiuni anterioare - detached state

Lista commit-urilor existente se poate inspecta folosind comanda git log.

Dacă se dorește revenirea la un commit anterior, acest lucru se poate face în trei feluri, specificând ID-ul commit-ului la care se dorește revenirea:

  • revenire fără pierderea commit-urilor ulterioare; în acest caz, starea devine detached și se creează automat o ramură nouă de dezvoltare (branch) - prin comanda git checkout commit_id
  • revenire cu pierderea commit-urilor ulterioare dar fără pierderea modificărilor fișierelor; în acest caz, commit-ul la care se revine devine HEAD - prin comanda git reset commit_id
  • revenire cu pierderea commit-urilor ulterioare și a modificărilor fișierelor; și în acest caz, commit-ul la care se revine devine HEAD - - prin comanda git reset --hard commit_id

În detached state nu se șterge nimic din repository, iar commit-urile noi se adaugă branch-ului nou creat temporar. Pentru a reveni din detached state, trebuie revenit la ramura principală de dezvoltare.

Revenire fără pierdere de commit-uri - detached state
Revenire cu pierdere de commit-uri

Ramuri de dezvoltare - branch

Ramurile de dezvoltare (branches) sunt o componentă foarte importantă a sistemelor de control al versiunii care permit dezvoltarea simultană a mai multor feature-uri sau componente pentru același sistem, precum și posibilitatea de a avea în lucru mai multe versiuni ale aceleiași aplicații. Ideea de branch este aceea că dintr-un anumit punct al dezvoltării programului (adică de la un commit), stiva de commit-uri se poate ramifica și apoi direcțiile de dezvoltare vor diverge:

Schema bloc a unui repository cu două branch-uri

Branch-ul implicit care se creează inițial se numește master.

Pentru a vedea lista branch-urilor dintr-un repository (inclusiv care este cel curent), se folosește comanda git branch.
Pentru a crea un nou branch, se folosește comanda git branch nume_branch_nou.
Pentru a schimba branch-ul curent, se folosește comanda git checkout nume_branch.
Pentru a șterge un branch, se folosește comanda git branch -d nume_branch. 
În anumite situații, dacă branch-ul conține modificări care nu au fost portate pe branch-ul părinte, veți primi o eroare. În acest caz, se poate șterge forțat un branch cu comanda git branch -D nume_branch. Atenție, nu puteți șterge branch-ul curent.

Merges și rezolvarea conflictelor

Dacă un branch a fost utilizat pentru dezvoltarea unei componente a unui sistem, modificările din acest branch trebui aduse înapoi (merged) pe branch-ul părinte.

Merge între un branch oarecare și master.
Pentru a realiza o operație de merge se folosește comanda git merge branch_modificari -m "Mesaj pentru commit.".

Înainte de a face o operație de merge, branch-ul curent trebuie să fie acela unde se doresc aduse modificările. Pentru schema de mai sus, secvența corectă de comenzi ar fi:

git checkout master
git merge branch_pentru_scriere_in_fisier -m "Am facut merge la functionalitatea de scriere in fisier"

Dacă modificările de pe branch-ul componentei sunt complet disjuncte față de modificările de pe branch-ul sistemului, atunci operația de merge se poate face automat. Dacă în schimb aceleași fișiere au fost modificate pe aceleași linii, atunci fișierele respective se marchează ca fiind în conflict, operația de merge se oprește, iar utilizatorul trebuie să rezolve conflictele editând fișierele cu probleme, în care va găsi marcate variantele din cele două branch-uri. După ce fișierele sunt salvate cu modificările dorite, acestea trebuie pregătite pentru commit, iar apoi trebuie realizat commit-ul care va aduce modificările pe branch-ul curent.

Exemple

  • Prima oară după instalarea Git, trebuie configurat numele și adresa de e-mail a utilizatorului:
  student@pracsis01 ~ $ git config --global user.name "Ion Vasile"
  student@pracsis01 ~ $ git config --global user.email ion.vasile@gmail.com
  • Creați un director numit gitroot în directorul personal al userului student.
  • În directorul gitroot creați un alt director numit pc_lab.
  • În directorul ~/gitroot/pc_lab inițializați un repository Git:
  student@pracsis01 ~/gitroot/pc_lab $ git init
  Initialized empty Git repository in /home/student/gitroot/pc_lab/.git/
  • În directorul ~/gitroot/pc_lab creați un fișier text numit README.md care să conțină:
Acesta este primul meu repository Git
=====================================
  • Pregătiți fișierul pentru commit:
  student@pracsis01 ~/gitroot/pc_lab $ git add README.md
  student@pracsis01 ~/gitroot/pc_lab $ 
  • Vizualizați starea curentă a repository-ului:
  student@pracsis01 ~/gitroot/pc_lab $ git status
   On branch master
   
   Initial commit
   
   Changes to be committed:
     (use "git rm --cached <file>..." to unstage)
   
           new file:   README.md
  • Realizați primul commit:
  student@pracsis01 ~/gitroot/pc_lab $ git commit -m "Primul commit - fisierul README.md"
  [master (root-commit) 75f2cfb] Primul commit - fisierul README.md
   1 file changed, 0 insertions(+), 0 deletions(-)
   create mode 100644 README.md
  • Vizualizați din nou starea curentă a repository-ului.
  • Vizualizați lista ultimelor commit-uri.
  student@pracsis01 ~/gitroot/pc_lab $ git log
  ...
  • Creați in directorul ~/gitroot/pc_lab un director numit lab2. În acest director creați un fișier cod sursă C care să afișeze pe ecran textul "Hello Git!". Creați și un Makefile care să compileze sursa C și să genereze un executabil.
  • Pregătiți cele două fișiere pentru commit.
  • Realizați al doilea commit cu mesajul "Am adaugat fisierele sursa pentru laboratorul 2.".
  • Vizualizați lista ultimelor commit-uri.
  • Identificați ID-ul primului commit. Întoarceți-vă la starea de după primul commit (atenție, ID-ul de mai jos este doar un exemplu):
  student@pracsis01 ~/gitroot/pc_lab $ git checkout 75f2cfb
  • Vizualizați conținutul directorului curent.
  • Vizualizați starea curentă a repository-ului.
  • Vizualizați branch-ul curent.
  • Reveniți la HEAD pe branch-ul master.
  • Creați un branch nou numit lab2.
  • Fiind în continuare pe master, editați fișierul sursă C astfel încât să afișeze "Hello Master!".
  • Faceți commit la noua modificare.
  • Mutați-vă pe branch-ul lab2.
  • Vizualizați conținutul fișierului sursă C.
  • Editați fișierul sursă C astfel încât să afișeze "Hello new branch!".
  • Faceți commit la noua modificare.
  • Mutați-vă pe branch-ul master.
  • Vizualizați conținutul fișierului C.
  • Faceți merge branch-ului lab2.
  • Observați mesajul de eroare. Rezolvați conflictul și finalizați operația de merge.

Git Remote

Puterea platformei Git nu vine doar din faptul că se poate folosi offline de către un utilziator, pe propriul calculator, dar și pentru că oferă posibilitatea de a sincroniza repository-uri pentru același proiect. În fiecare repository se poate defini un număr de adrese URL pentru alte repository-uri, aceste adrese se numesc remote.

Pentru a vedea lista de adrese remote configurate pentru un repository, se folosește comanda  git remote -v.

Cu toate că Git nu are nici o restricție legată de modul de organizare a acestor repository-uri (adică oricare două se pot sincroniza între ele, făcând Git un sistem cu adevărat distribuit), modelul cel mai des întâlnit este acela de a avea un repository central, pe un server accesibil global, iar fiecare programator/ membru al echipei se sincronizează cu acest server central, care se numește popular origin.

Remote.svg

Pentru a adăuga un remote pentru un repository, se folosește comanda git remote add nume_remote url_remote, unde numele este în general (dar nu obligatoriu) origin.

În momentul în care un repository are configurat un remote, commit-urile de toate branch'-urile locale și din remote se pot sincroniza.

Pentru a aduce modificările de pe remote a branch-ului curent, se folosește comanda git pull nume_remote.
Pentru a actualiza fișierele din remote cu modificările locale de pe branch-ul curent, se folosește comanda git push nume_remote.
Atenție, dacă pe remote există commit-uri adăugate de alți utilizatori, pe care nu le aveți local, sunteți obligați să faceți pull înainte de push. Acest pull poate produce conflicte care se rezolvă identic cu cele apărute la operațiile de merge.

Adăugarea unui remote manual se face în general când proiectul a fost creat local și se dorește a fi publicat pe remote. În cazul în care proiectul (și implict repository-ul) există la o adresă, acesta poate fi copiat într-un repository local.

Pentru a copia un repository de la o adresă într-un repository local se folosește comanda git clone url_remote.

După clonare, repository-ul local este sincronziat cu origin și remote-ul este configurat cu adresa de unde a fost clonat.

Platforma Gitlab

Gitlab este o platformă online ce găzduiește reposity-uri de Git. Pe lângă rolul de remote pentru sincronizare, Gitlab mai oferă următoarele funcționalități:

  • modificarea tipului de acces la repository pentru fiecare utilizator (access control);
  • adăugarea de etape de dezvoltare și asocierea de bug-uri acestor etape (milestones);
  • posibilitatea de a înregistra bug-uri, precum și a comenta și nota evoluția lor evoluția lor (issues - bug tracking);
  • merge automat din interfața web între branch-urile aceluiași repository precum și între copii ale aceluiași repository (merge requests);
  • posibilitatea de a vizualiza modificările codului în cadrul unui merge request și a adăuga comentarii la orice linie de cod (code review);
  • posibilitatea de a crea un Wiki pentru fiecare proiect;
  • un sistem de integrare continuă (continuous integration) care permite adăugarea de scripturi care să compileze, testeze și implementeze aplicația la fiecare nou commit;
  • ... și altele.

Toate aceste servicii sunt oferite gratuit utilizatorilor, dar fără suport.

Exemple

  • Înregistrați-vă un cont pe platforma Gitlab.
  • Creați un proiect nou în platforma Gitlab, numit pc_lab.
  • Observați instrucțiunile pentru realizarea legăturii între repository-ul local și cel de pe Gitlab.
  • În repository-ul local de Git, adăugați, ca remote numit origin, repository-ul nou creat de pe Gitlab (ca URL folositi varianta https și înlocuiți adresa corectă în sintaxa de mai jos):
student@pracsis01 ~/gitroot/pc_lab $ git remote add origin https://gitlab.com/.../....git
  • Sincronizați modificările locale cu origin:
student@pracsis01 ~/gitroot/pc_lab $ git push origin
  • Reîncărcați pagina proiectului și observați modificările.
  • Faceți echipe de câte două pesoane. În setările proiectului din Gitlab, dați acces de dezvoltator (developer) celuilalt membru al echipei.
  • Reîncărcați lista proiectelor în Gitlab și observați că acum aveți acces la încă un proiect, pe lângă cel al vostru.
  • Navigați în directorul ~/gitroot și clonați repository-ul colegului/ colegei (pentru că ambele proiecte au același nume, la clonare trebuie specificat un alt nume de director în care să fie copiat noul repository, pentru a evita suprapunerea cu proiectul vostru):
student@pracsis01 ~/gitroot $ git clone https://gitlab.com/.../....git pc_lab_temp
  • Adăugați o modificare unuia din fișierele din pc_lab_temp sau adăugați un fișier nou.
  • Pregătiți fișierul și apoi realizați commit-ul.
  • Sincronizați repository-ul cu origin, branch-ul master:
student@pracsis01 ~/gitroot/pc_lab_temp $ git push origin master
  • Reveniți în repository-ul personal și sincronizați-l aducând modificările din origin.
  • Listați commit-urile din branch-ul curent.
  • Observați aceleași modificări și în interfața web a platformei Gitlab.