Diferență între revizuiri ale paginii „PC Laborator 2.1 (opțional)”

De la WikiLabs
Jump to navigationJump to search
 
(Nu s-au afișat 97 de versiuni intermediare efectuate de același utilizator)
Linia 1: Linia 1:
 
= Obiective =
 
= Obiective =
  
În urma parcurgerii acestui laborator studentul va fi capabil:
+
* definirea noțiunii de Version Control System;
* să utilizeze stream-urile standard ale unui proces în Linux și în programe C;
+
* utilizarea unui sistem de Version Control: [https://git-scm.com/ Git].
* să utilizeze funcționalitatea de bază a unui sistem de revision control - Git.
 
* să înțeleagă funcționalitatea preprocesorului și să folosească directive de preprocesare;
 
  
= [https://en.wikipedia.org/wiki/Standard_streams Stream-urile standard] ale unui proces =
+
= Sisteme de control al versiunii =
  
[[Fișier:standard_streams.png]]
+
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;
  
După cum se poate observa în schema de mai sus, un proces care rulează într-un sistem de operare dispune de trei stream-uri de intrare ieșire implicite:
+
Toate aceste cerințe sunt adresate de sistemele de [https://en.wikipedia.org/wiki/Version_contro control al versiunii], acestea pot:
* Stream-ul standard de intrare ('''stdin''') - este utilizat pentru a introduce date în proces și este implicit atașat tastaturii;
+
* ține evidența modificărilor și permite întoarcerea la o versiune mai veche;
* Stream-ul standard de ieșire ('''stdout''') - este utilizat pentru a extrage rezultate ale execuției procesului și este implicit atașat consolei;
+
* permite ramificarea dezvoltării în mai multe direcții și unificarea ulterioară a acestor ramuri;
* Stream-ul standard de eroare ('''stderr''') - este tradițional utilizat pentru a extrage informații legate de erorile apărute în execuția procesului și este implicit atașat consolei;
+
* sincroniza modificările fiecărui utilizator cu un depozit central pentru a putea fi accesate de ceilalți membri ai proiectului;
  
Se observă că ambele stream-uri de ieșire sunt implicit trimise către consolă, unde utilizatorul procesului le poate vedea. Motivul pentru care sunt două este acela că aceste stream-uri pot fi redirectate către alte procese sau către fișiere, astfel se poate face o separare simplă și rapidă a mesajelor de eroare, în scopul depanării programului.
+
Există [https://en.wikipedia.org/wiki/List_of_version_control_software 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.
  
Toate aceste stream-uri sunt formatate pe octet (byte). În cele mai multe din cazuri, acești bytes reprezintă caractere codate [https://en.wikipedia.org/wiki/ASCII ASCII].
+
În continuare vom prezenta conceptele cele mai importante și funcționarea sistemului Git.
  
== Scrierea pe '''stdout''' ==
+
= [https://www.atlassian.com/git/tutorials/ Git] =
  
Pentru a scrie pe stream-ul standard de ieșire se folosește generic funcția <code style="color: green">fprintf</code>:
+
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.
  
Funcția <code style="color: green">fprintf</code> este definită în header-ul <code style="color: blue">stdio.h</code> (standard input/output) și este implimentată în biblioteca standard a limbajului C. Pentru scrierea pe stream-ul standard de ieșire, se folosește în felul următor:
+
  Odată instalat Git, fiecare utilizator trebuie să-și configureze adresa de e-mail și numele:
 +
  <syntaxhighlight lang="bash">
 +
  git config --global user.name "Ion Vasile"
 +
  git config --global user.email ion.vasile@gmail.com
 +
  </syntaxhighlight>
  
<syntaxhighlight lang="C">
+
== Git - ''repository'' local ==
fprintf(stdout, "[format]", val1, val2, ...);
 
</syntaxhighlight>
 
 
 
 
 
<div class="sfat">'''<font color="blue">Observație:</font>''' Deoarece scrierea pe stream-ul standard de ieșire este atât de utilizată, s-a creat o funcție specială pentru aceasta, numită <code style="color: green">printf</code>:
 
<syntaxhighlight lang="C">
 
printf("[format]", val1, val2, ...);
 
</syntaxhighlight>
 
</div>
 
  
Formatul reprezintă modelul de date pe care îl dorim afișat în consolă.
+
Fiecare proiect este stocat în Git într-un "depozit" numit ''repository''.  
  
Specificatorul de format pentru <code style="color: green">fprintf</code>, respectiv <code style="color: green">printf</code> urmează următorul prototip:
+
Un repository se crează cu comanda <code style="color: green">git init</code>.
  
%[flags][width][.precision][length]specifier
+
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''.  
  
unde caracterul ''specifier'' de la sfârșit este componenta cea mai importantă deoarece definește ce caractere sunt extrase din stream, interpretarea lor, și tipul de date corect al argumentului corespunzător:
+
Starea curentă a repository-ului se poate vedea în orice moment cu comanda <code style="color: green">git status</code>.
  
{| class="wikitable"
+
=== Pregătirea unui nou commit - ''staged files'' ===
! ''specifier'' !! Afișare !! Exemplu
 
|-
 
|i, d || întreg decimal cu semn || -694
 
|-
 
|u || întreg decimal fără semn || 1659
 
|-
 
|o || întreg în baza 8 || 254
 
|-
 
|x || întreg în baza 16 (litere mici) || 1af43
 
|-
 
|X || întreg în baza 16 (litere mari) || 1AF43
 
|-
 
|f || număr în virgulă mobilă || 23.65
 
|-
 
|e || număr în virgulă mobilă, notație științifică (mantisa - exponent), litere mici || 3.9265e+2
 
|-
 
|E || număr în virgulă mobilă, notație științifică (mantisa - exponent), litere mari || 3.9265E+2
 
|-
 
|g || număr în virgulă mobilă, cea mai scurtă reprezentare (%f sau %e) || 23.65
 
|-
 
|G || număr în virgulă mobilă, cea mai scurtă reprezentare (%F sau %E) || 23.65
 
|-
 
|a || număr în virgulă mobilă în baza 16, litere mici || -0xc.90fep-2
 
|-
 
|A || număr în virgulă mobilă în baza 16, litere mari || -0xc.90FEP-2
 
|-
 
|c || caracter || a
 
|-
 
|s || șir de caracere || castravete
 
|-
 
|p || adresă în memorie (pointer) || 00000000b8000000
 
|-
 
|n || nu se printează nimic || numărul de caractere citit până în momentul curent este stocat în variabila specificată
 
|-
 
|% || % ||Un caracter % urmat de alt caracter % afișează un singur %.
 
|}
 
  
Specificatorul de format poate conține și sub-specificatori: ''flags'', ''width'', ''.precision'' și ''modifiers'' (în această ordine), care sunt opționale și respectă următoarele reguli:
+
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 <code style="color: green">git add ''fișier''</code>;
 +
* fișiere existente în repository, dar care au fost modificate - prin comanda <code style="color: green">git add ''fișier''</code>;
 +
* fișiere existente care se vor șterge din repository - prin comanda <code style="color: green">git rm ''fișier''</code>;
 +
* fișiere care există în repository, dar iși schimbă locația - prin comanda <code style="color: green">git mv ''fișier_sursă'' ''fișier_destinație''</code>;
  
{| class="wikitable"
+
Mai multe fișiere pot fi pregătite pentru un ''commit'', apoi toate odată se actualizează într-un singur ''commit''.
! ''flag'' !! Descriere
 
|-
 
| - || Aliniere la stânga cu dimensiunea dată de câmpul ''width'' (implicit aliniera este la dreapta). Vezi descrierea lui ''width''.
 
|-
 
| + || Forțează printarea unui semn (+ sau -), chiar și pentru valori pozitive (implicit se afișează semnul doar pentru numere negative).
 
|-
 
| (spațiu) || Dacă semnul nu se afișează, în locul lui se inserează un spațiu.
 
|-
 
| # || Folosit cu o, x, sau X, printează 0, 0x sau respectiv 0X în fața valorii, pentru numere diferite de 0. Folosit cu a, A, e, E, f, g sau G, forțează printarea punctului decimal, chiar dacă după el nu mai urmează alte cifre. Implicit, dacă după punct nu mai urmează alte cifre (numărul este întreg), punctul nu se mai afișează.
 
|-
 
| 0 || Când se specifică o dimensiune (''width''), numărul se completează la stânga cu 0 în loc de spațiu. Vezi descrierea lui ''width''.
 
|}
 
 
 
{| class="wikitable"
 
! ''width'' !! Descriere
 
|-
 
| (număr) || Numărul minim de caractere care trebuie scrise. Dacă lungimea numărului este mai mică decât această valoare, el este completat cu spații. Dacă lungimea este mai mare, numărul este afișat în întregime.
 
|-
 
| * || Valoarea ''width'' nu este specificată în string-ul ''format'', ci într-un argument suplimentar al funcției, care precede argumentul ce trebuie formatat.
 
|}
 
 
 
{| class="wikitable"
 
! ''.precision'' !! Descriere
 
|-
 
| (număr) || Pentru valori întregi (d, i, o, u, x, X), ''precision'' specifică numărul minim de cifre care trebuie afișate. Dacă numărul ce trebuie afișat este mai scurt decât această valoare, rezultatul este completat la stânga cu cifra 0. Dacă numărul este mai lung, el este afișat în întregime. O precizie 0 înseamnă că pentru valoarea 0 nu se va afișa nimic. Pentru specificatorii a, A, e, E, f și F, aceasta este numărul de cifre ce a fi afișat după punct (implicit valoarea aceasta este 6). Pentru specificatorii g și G, acesta este numărul maxim de cifre semnificative ce trebuie afișat. Pentru s, acesta este numărul maxim de caractere ce trebuie afișat. Implicit toate caracterele sunt afișate, până la întâlnirea unui terminator de string. Dacă punctul este scris fără o valoare pentru precizie, aceasta se consideră implicit 0.
 
|-
 
| * || Valoarea ''precision'' nu este specificată în string-ul ''format'', ci într-un argument suplimentar al funcției, care precede argumentul ce trebuie formatat.
 
|}
 
 
 
Specificatorul ''length'' modifică dimensunea tipului de date. Tabelul următor arată tipurile folosite pentru a interpreta argumentele corespunzătoare, cu și fără specificatorul ''length'' (dacă un alt tip este folosit, se realizează promovarea sau conversia tipului, dacă este posibil):
 
<table class="wikitable">
 
<tr><th></th><th colspan="7">specifiers</th></tr>
 
<tr><th><i>length</i></th><th><tt>d i</tt></th><th><tt>u o x X</tt></th><th><tt>f F e E g G a A</tt></th><th><tt>c</tt></th><th><tt>s</tt></th><th><tt>p</tt></th><th><tt>n</tt></th></tr>
 
<tr><td><i>(none)</i></td><td class="yes"><tt>int</tt></td><td class="yes"><tt>unsigned int</tt></td><td class="yes"><tt>double</tt></td><td class="yes"><tt>int</tt></td><td class="yes"><tt>char*</tt></td><td class="yes"><tt>void*</tt></td><td class="yes"><tt>int*</tt></td></tr>
 
<tr style="background-color: yellow;"><td><tt>hh</tt></td><td class="yes"><tt>signed char</tt></td><td class="yes"><tt>unsigned char</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>signed char*</tt></td></tr>
 
<tr><td><tt>h</tt></td><td class="yes"><tt>short int</tt></td><td class="yes"><tt>unsigned short int</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>short int*</tt></td></tr>
 
<tr><td><tt>l</tt></td><td class="yes"><tt>long int</tt></td><td class="yes"><tt>unsigned long int</tt></td><td class="no"></td><td class="yes"><tt>wint_t</tt></td><td class="yes"><tt>wchar_t*</tt></td><td class="no"></td><td class="yes"><tt>long int*</tt></td></tr>
 
<tr style="background-color: yellow;"><td><tt>ll</tt></td><td class="yes"><tt>long long int</tt></td><td class="yes"><tt>unsigned long long int</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>long long int*</tt></td></tr>
 
<tr style="background-color: yellow;"><td><tt>j</tt></td><td class="yes"><tt>intmax_t</tt></td><td class="yes"><tt>uintmax_t</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>intmax_t*</tt></td></tr>
 
<tr style="background-color: yellow;"><td><tt>z</tt></td><td class="yes"><tt>size_t</tt></td><td class="yes"><tt>size_t</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>size_t*</tt></td></tr>
 
<tr style="background-color: yellow;"><td><tt>t</tt></td><td class="yes"><tt>ptrdiff_t</tt></td><td class="yes"><tt>ptrdiff_t</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="yes"><tt>ptrdiff_t*</tt></td></tr>
 
<tr><td><tt>L</tt></td><td class="no"></td><td class="no"></td><td class="yes"><tt>long double</tt></td><td class="no"></td><td class="no"></td><td class="no"></td><td class="no"></td></tr>
 
</table>
 
 
 
<div class="sfat">'''<font color="blue">Observație:</font>''' Tabelele de mai sus pot fi înțelese abia după parcurgerea cursurilor și laboratoarelor legate de tipuri și dimensiuni de date. Ele vor fi folosite pe parcursul cursului drept referință și nu trebuie memorate.</div>
 
 
 
<div class="sfat">'''<font color="blue">Observație:</font>''' Rândurile marcate cu galben au fost introduse în standardul C99.</div>
 
 
 
== Scrierea pe '''stderr''' ==
 
 
 
Scrierea pe stream-ul standard de eroare se face identic cu scrierea pe '''stdout''', specificând numele streamului ca prim argument al funcției <code style="color: green">fprintf</code>:
 
 
 
<syntaxhighlight lang="C">
 
fprintf(stderr, "[format]", val1, val2, ...);
 
</syntaxhighlight>
 
  
 +
Un fișier pregătit pentru ''commit'' se poate șterge din lista de fișiere pregătite, păstrând modificările, cu comanda <code style="color: green">git reset ''fișier''</code>
  
<div class="sfat">'''<font color="blue">Observație:</font>''' Nu se poate folosi funcția <code style="color: green">printf</code> pentru scrierea pe stream-ul standard de eroare.</div>
+
=== Adăugarea de fișiere în repository - ''commit'' ===
  
== Citirea din '''stdin''' ==
+
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.
  
Pentru a citi de pe stream-ul standard de intrare se folosește generic funcția <code style="color: green">fscanf</code>:
+
Un ''commit'' nou se realizează cu comanda <code style="color: green">git commit -m "mesaj"</code>.
  
Funcția <code style="color: green">fscanf</code> este definită în header-ul <code style="color: blue">stdio.h</code> (standard input/output) și se folosește în felul următor:
+
Ultimul ''commit'' se numește HEAD. Orice nou commit se poate pune doar peste HEAD-ul curent și va deveni noul HEAD.  
  
<syntaxhighlight lang="C">
+
[[Fișier:git_commit_stack.svg]]
fscanf(stdin, "[format]", val1, val2, ...);
 
</syntaxhighlight>
 
  
 +
Un fișier modificat dar care nu este pregătit pentru ''commit'', poate fi readus în stadiul din HEAD prin comanda <code style="color: green">git checkout ''fișier''</code>
  
<div class="sfat">'''<font color="blue">Observație:</font>''' Deoarece citirea de pe stream-ul standard este atât de utilizată, s-a creat o funcție specială pentru aceasta, numită <code style="color: green">scanf</code>:
+
=== Revenire la versiuni anterioare - ''detached state'' ===
<syntaxhighlight lang="C">
 
scanf("[format]", val1, val2, ...);
 
</syntaxhighlight>
 
</div>
 
  
Formatul reprezintă modelul de date pe care ne așteptăm să-l primim de la tastatură. Acesta este un șir de caractere unde fiecare caracter este tratat în următorul fel:
+
Lista ''commit''-urilor existente se poate inspecta folosind comanda <code style="color: green">git log</code>.
* '''caracter alb''' - se va citi și ignora din stream orice caractere albe până la întâlnirea unui caracter care nu este alb (caracerele albe includ spații, linie nouă, caractere tab); un singur caracter alb în format validează oricâte caractere albe extrase din stream (inclusiv nici unul).
 
* '''caracter care nu este alb, exceptând specificatorul de format (%)''' - orice caracter care nu este alb (spațiu, linie nouă sau tab) sau care nu face parte dintr-un specificator de format (care începe cu %) face ca următorul caracter să fie citit din stream, comparat cu acest caracter din format, iar dacă sunt identice, ele sunt ignorate și se continuă cu următorul caracter; dacă nu sunt identice, <code style="color: green">fscanf</code> se va opri cu eroare;
 
* '''specificator de format''' - o secvență care începe cu caracterul "la sută" (%) indică un specificator de format, utilizat pentru a indica tipul și formatul unei date care trebuie citită și stocată într-una din variabilele specificate în apelul lui <code style="color: green">fscanf</code>.
 
  
Specificatorul de format pentru <code style="color: green">fscanf</code> urmează următorul prototip:
+
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 <code style="color: green">git checkout ''commit_id''</code>
 +
* 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 <code style="color: green">git reset ''commit_id''</code>
 +
* 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 <code style="color: green">git reset --hard ''commit_id''</code>
  
%[*][width][length]specifier
+
Î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.
  
unde caracterul ''specifier'' de la sfârșit este componenta cea mai importantă deoarece definește ce caractere sunt extrase din stream, interpretarea lor, și tipul de date corect al argumentului corespunzător:
+
[[Fișier:git_detached_state.svg|none|frame|Revenire fără pierdere de ''commit''-uri - '''detached state''']]
  
{| class="wikitable"
+
[[Fișier:git_reset.svg|none|frame|Revenire cu pierdere de ''commit''-uri]]
! ''specifier'' !! Descriere !! Caractere extrase din stream
 
|-
 
|i, u || întreg || Orice număr de cifre, opțional precedate de un semn (+ sau -). Baza zece este considerată implicit (0-9), dar un 0 ca prefix marchează baza ca fiind opt (0-7), iar "0x" ca prefix marchează baza șaisprezece (0-f).
 
|-
 
|d || întreg în baza 10 || Orice număr de cifre decimale (0-9), opțional precedate de un semn (+ sau -).
 
|-
 
|o || întreg în baza 8 || Orice număr de cifre octale (0-7), opțional precedate de un semn (+ sau -).
 
|-
 
|x || întreg în baza 16 || Orice număr de cifre hexazecimale (0-7, a-f, A-F), opțional precedate de "0x" sau "0X", și totul opțional precedat de un semn (+ sau -).
 
|-
 
|f, e, g, a (C99) || număr în virgulă mobilă || O serie de cifre decimale, conținând opțional un punct decimal, opțional precedat de un semn (+ sau -), opțional urmat de un caracter e sau E, și un întreg în baza 10. În standardul C99 se acceptă și numere în virgulă mobilă în baza hexazecimală, precedate de 0x sau 0X.
 
|-
 
|c || caracter || Următorul caracter. Dacă '''width''' este specificat, se vor citi exact '''width''' caractere și vor fi stocate în locații succesive în vectorul primit ca argument. Nu este adăugat terminator de șir la final.
 
|-
 
|s || șir de caractere ||Orice număr de caractere care nu sunt albe, oprindu-se la primul caracter alb înlâlnit. Este automat adăugat terminator de șir la final.
 
|-
 
|p || adresă în memorie ||O secvență de caractere reprezentând o adresă în memorie. Formatul folosit depinde de sistem și de implementarea bibliotecii, dar este identic cu cel folosit de formatul %p în <code style="color: green">fprintf</code>.
 
|-
 
|[characters] || set de caractere || Orice număr de caractere dintre cele specificate între parantezele drepte. O liniuță (-) care nu este pe prima poziție între paranteze poate produce comportamente nedefinite în unele implementări de bibliotecă standard.
 
|-
 
|[^characters] || set de caractere negat || Orice număr de caractere care nu sunt specificate între parantezele drepte.
 
|-
 
|n || număr caractere citite || Nu este consumat nimic din stream. Numărul de caractere citit până în momentul curent este stocat în variabila specificată.
 
|-
 
|% || % ||Un caracter % urmat de alt caracter % consumă un singur % din stream.
 
|}
 
În afară de n, cel puțin un caracter va fi consumat din stream de către oricare alt specificator. Altfel, operația eșuează și citirea se încheie în acel punct.
 
  
Specificatorul de format poate conține de asemenea și sub-specificatori: steluță (*), '''width''' și '''length''' (în această ordine), care sunt opționale și respectă următoarele specificații:
+
=== Ramuri de dezvoltare - ''branch'' ===
  
{| class="wikitable"
+
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:
! sub-specifcator !! Descriere
 
|-
 
| * || O steluță ca sub-specifiator indică faptul că data citită nu este stocată într-o variabilă ci este ignorată.
 
|-
 
| ''width'' || Specifică numărul maxim de caractere ce vor fi citite din stream de operația curentă.
 
|-
 
| ''length'' || Unul din hh, h, l, ll, j, z, t, L. Acest caracter modifică tipul de date așteptat ca argument. Comportamentul acestora este identic cu cel definit la capitolul [[#Scrierea pe stdout]]
 
|}
 
  
<div class="regula">'''<font color="red">Atenție:</font>''' Variabilele care nu sunt de tip vector sau pointer și sunt folosite în apelul funcției <code style="color: green">fscanf</code> pentru a stoca date din stream, trebuie obligatoriu precedate de operatorul &. Explicația pentru această regulă va fi prezentată în cursul legat de pointeri și adrese de memorie.</div>
+
[[Fișier:branches.svg|none|frame|Schema bloc a unui ''repository'' cu două ''branch''-uri]]
  
== Exemple ==
+
''Branch''-ul implicit care se creează inițial se numește '''master'''.
  
<ol>
+
Pentru a vedea lista ''branch''-urilor dintr-un ''repository'' (inclusiv care este cel curent), se folosește comanda <code style="color: green">git branch</code>.
<li>
 
Scrieți următorul program într-un fișier numit <code style="color: blue">exemplu_stdin.c</code>
 
<syntaxhighlight lang="C">
 
#include <stdio.h>
 
  
int main(){
+
Pentru a crea un nou ''branch'', se folosește comanda <code style="color: green">git branch ''nume_branch_nou''</code>.
    int integerValue;
 
    fprintf(stdout, "Introduceti o valoare numerica: ");
 
    fscanf(stdin, "%d", &integerValue);
 
    printf("Valoarea introdusa este %d\n", integerValue);
 
    return 0;
 
}
 
</syntaxhighlight>
 
</li>
 
</ol>
 
  
= [https://en.wikipedia.org/wiki/C_preprocessor Preprocesorul C] =
+
Pentru a schimba ''branch''-ul curent, se folosește comanda <code style="color: green">git checkout ''nume_branch''</code>.
  
Preprocesarea unui fișier cu cod C este prima etapă din lanțul de ''build'', și reprezintă, așa cum îi spune și numele, o etapă de dinainte de procesarea (compilarea) efectivă. Motivul pentru care se realizează această etapă inițială este faptul că prin preprocesarea codului se pot elimina secțiuni din program care ori nu sunt necesare, ori nu sunt compilabile pe un anumit procesor sau sistem de operare. Spre exemplu, pentru a folosi o interfață serială, în sistemul de operare Windows, și compilatorul Visual Studio, există un tip de date care se numește <code style="color: green">HANDLE</code>. Acest tip de date nu există în Linux, aici fiind înlocuit simplu cu <code style="color: green">int</code>. În acest caz, compilarea unui program care folosește <code style="color: green">HANDLE</code> pe Linux va eșua cu eroare. Aici intervin directivele de preprocesare, după cum vom vedea în continuare.
+
Pentru a șterge un ''branch'', se folosește comanda <code style="color: green">git branch -d ''nume_branch''</code>.  
 +
<div class="regula">Î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 <code style="color: green">git branch -D ''nume_branch''</code>. Atenție, nu puteți șterge ''branch''-ul curent.</div>
  
În acest laborator se va discuta despre:
+
=== ''Merges'' și rezolvarea conflictelor ===
* <code style="color: green">#include</code>
 
* <code style="color: green">#define</code>
 
* <code style="color: green">#undef</code>
 
* <code style="color: green">#ifdef</code>
 
* <code style="color: green">#ifndef</code>
 
* <code style="color: green">#else</code>
 
* <code style="color: green">#endif</code>
 
  
== Directiva <code style="color: green">#include</code> ==
+
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.
  
Probabil <code style="color: green">#include</code> este cea mai frecvent utilizată directivă de preprocesare, dar și cea mai ușor de înțeles. Efectiv, preprocesorul caută fișierul specificat între paranteze unghiulare sau ghilimele în lista de directoare dintr-o listă cunoscută și apoi înlocuiește directiva <code style="color: green">#include</code> cu conținutul fișierului respectiv.
+
[[Fișier:merge.svg|none|frame|''Merge'' între un ''branch'' oarecare și '''master'''.]]
  
=== Exemplu ===
+
Pentru a realiza o operație de ''merge'' se folosește comanda <code style="color: green">git merge ''branch_modificari'' -m "Mesaj pentru commit."</code>.
  
* Scrieți un fișier nou într-un editor de text care să conțină următorul cod:
+
Î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:
<syntaxhighlight lang="C">
+
<syntaxhighlight lang="bash">
int main(){
+
git checkout master
    return 0;
+
git merge branch_pentru_scriere_in_fisier -m "Am facut merge la functionalitatea de scriere in fisier"
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
* Salvați fișierul cu numele <code style="color: blue">test_preprocessor.c</code> în directorul <code style="color: blue">~/work/''prenume_nume''</code>
 
* Scrieți un fișier <code style="color: blue">Makefile</code> cu o singură rețetă care să producă un fișier numit <code style="color: blue">processed.c</code> din fișierul de mai sus, apelând compilatorul C (gcc) cu fanionul corespunzător pentru a face doar preprocesare (vezi [[PC Laborator 1#Generarea fișierului executabil|PC Laborator 1]]).
 
* Ce diferențe există între fișierul original și fișierul preprocesat?
 
* Scrieți un nou fișier numit <code style="color: blue">header_file.h</code>, care să conțină următorul text:
 
<syntaxhighlight lang="C">
 
// This is the start of the header file
 
int variable;
 
// This is the end of the header file
 
</syntaxhighlight>
 
* Modificați fișierul <code style="color: blue">test_preprocesor.c</code> prin înserția pe prima linie a unei directive '''include''':
 
<syntaxhighlight lang="C">
 
#include "header_file.h"
 
int main(){
 
    return 0;
 
}
 
</syntaxhighlight>
 
* Rulați din nou comanda <code style="color: green">make</code>. Observați mesajul de eroare: fișierul <code style="color: blue">header_file.h</code> nu este găsit de preprocesor, cu toate că este în directorul curent.
 
<div class="regula">'''<font color="red">Atenție:</font>''' Fișierele antet (header) sunt căutate doar în anumite căi predefinite în compilator (de exemplu <code style="color: blue">/usr/include</code>). Pentru a face preprocesorul să caute și în alte directoare, calea până la acestea trebuie specificată la compilare, folosind fanionul <code style="color: green">-I</code></div>
 
* Adăugați comenzii de compilare din <code style="color: blue">Makefile</code> următorul fanion: <code style="color: green">-I.</code> Asta va spune compilatorului (care mai departe va spune preprocesorului) să caută fișiere antet și în directorul curent (.)
 
* Rulați din nou comanda <code style="color: green">make</code>.
 
* Afișați conținutul celor două fișiere C. Ce diferențe observați?
 
  
== Directivele <code style="color: green">#define</code> și <code style="color: green">#undef</code> ==
+
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.
  
Directiva <code style="color: green">#define</code> este utilizată pentru a defini '''macro'''-uri de preprocesor. Acestea se folosesc în trei feluri distincte:
+
=== Exemple ===
* <code style="color: green">#define ''token'' ''value''</code> - este definit '''macro'''-ul ''token'' existența acestuia putând fi testată cu directivele <code style="color: green">#ifdef</code> și <code style="color: green">#ifndef</code>; dacă ''token'' este utilizat în program, după definirea lui, el va fi înlocuit cu ''value'' (efectiv, această operație este identică cu un "Search and Replace" dintr-un editor de text, unde ''token'' este înlocuit cu ''value''. 
 
<syntaxhighlight lang="C">
 
#define PI 3.1415
 
  
float a = 2 * PI;
+
* Prima oară după instalarea Git, trebuie configurat numele și adresa de e-mail a utilizatorului:
</syntaxhighlight>
+
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~ $</span> git config --global user.name "Ion Vasile"'''
* <code style="color: green">#define ''token''</code> - este definit '''macro'''-ul ''token'', existența acestuia putând fi testată cu directivele <code style="color: green">#ifdef</code> și <code style="color: green">#ifndef</code>; dacă ''token'' este utilizat în program, după definirea lui, va fi șters de peste tot unde apare (este de fapt cazul de mai sus unde ''value'' este de fapt un string de lungime zero).
+
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~ $</span> git config --global user.email ion.vasile@gmail.com'''
<syntaxhighlight lang="C">
 
#include <stdio.h>
 
#define DEBUG
 
  
int main(){
+
* Creați un director numit <code style="color: blue">gitroot</code> în directorul personal al userului '''student'''.
#ifdef DEBUG
+
* În directorul <code style="color: blue">gitroot</code> creați un alt director numit <code style="color: blue">pc_lab</code>.
     printf("Debug is ON!\n");
+
* În directorul <code style="color: blue">~/gitroot/pc_lab</code> inițializați un repository '''Git''':
#endif
+
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot/pc_lab $</span> git init'''
     return 0;
+
  Initialized empty Git repository in /home/student/gitroot/pc_lab/.git/
}
+
* În directorul <code style="color: blue">~/gitroot/pc_lab</code> creați un fișier text numit <code style="color: blue">README.md</code> care să conțină:
 +
Acesta este primul meu repository Git
 +
=====================================
 +
* Pregătiți fișierul pentru ''commit'':
 +
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot/pc_lab $</span> git add README.md'''
 +
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot/pc_lab $</span> '''
 +
* Vizualizați starea curentă a ''repository''-ului:
 +
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot/pc_lab $</span> 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'':
 +
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot/pc_lab $</span> 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.
 +
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot/pc_lab $</span> git log'''
 +
  ...
  
</syntaxhighlight>
+
* Creați in directorul <code style="color: blue">~/gitroot/pc_lab</code> un director numit <code style="color: blue">lab2</code>. Î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.
* <code style="color: green">#define ''token(arg1,arg2,..) expression''</code> - este definit '''macro'''-ul ''token'', existența acestuia putând fi testată cu directivele <code style="color: green">#ifdef</code> și <code style="color: green">#ifndef</code>; ''token'' este utilizat în program ca o funcție, iar el va fi înlocuit de ''expression'', în care ''arg1,arg2,...'' vor fi înlocuite cu valorile din program.
+
* Pregătiți cele două fișiere pentru ''commit''.
<syntaxhighlight lang="C">
+
* Realizați al doilea ''commit'' cu mesajul "Am adaugat fisierele sursa pentru laboratorul 2.".
#include <stdio.h>
+
* Vizualizați lista ultimelor ''commit''-uri.
#define MAX(a,b) (a < b ? b : a)
+
* 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):
 +
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot/pc_lab $</span> git checkout 75f2cfb'''
  
int main(){
+
* Vizualizați conținutul directorului curent.
    printf("Value is %d!\n", MAX(4,5)); // Replaced to: printf("Value is %d!\n", (4 < 5 ? 5 : 4));
+
* Vizualizați starea curentă a ''repository''-ului.
//  printf("Value is %d!\n", MAX(4 + 1, 5 + 1)); // Replaced to: printf("Value is %d!\n", (4 + 1 < 5 + 1 ? 5 + 1 : 4 + 1));
+
* Vizualizați ''branch''-ul curent.
    return 0;
+
* Reveniți la HEAD pe ''branch''-ul master.
}
+
* Creați un ''branch'' nou numit '''lab2'''.
</syntaxhighlight>
+
* 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 ==
  
<div class="regula">'''<font color="red">Atenție:</font>''' O greșeală frecventă în definirea macrourilor este adăugarea unui caracter ; la sfârșitul liniei. Acesta se va înlocui și el în momentul în care '''macro'''-ul se expandează în text:
+
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 <code style="color: green">remote</code>.
<syntaxhighlight lang="C">
 
#include <stdio.h>
 
#define PI 3.1415;
 
  
int main(){
+
Pentru a vedea lista de adrese ''remote'' configurate pentru un ''repository'', se folosește comanda  <code style="color: green">git remote -v</code>.
    float f = PI * 2; // Replaced to: float f = 3.1415; * 2;
 
    return 0;
 
}
 
</syntaxhighlight>
 
Se vede imediat că sintaxa este greșită și compilatorul va genera o eroare.
 
</div>
 
  
Pentru a anula definiția unui '''macro''', se folosește directiva <code style="color: green">#undef</code> astfel:
+
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'''.
<syntaxhighlight lang="C">
 
#undef PI
 
#undef DEBUG
 
#undef MAX
 
</syntaxhighlight>
 
  
=== Exemplu ===
+
[[Fișier:remote.svg]]
  
* Modificați fișierul <code style="color: blue">test_preprocessor.c</code> în felul următor:
+
Pentru a adăuga un ''remote'' pentru un ''repository'', se folosește comanda <code style="color: green">git remote add ''nume_remote'' ''url_remote''</code>, unde numele este în general (dar nu obligatoriu) '''origin'''.
<syntaxhighlight lang="C">
 
#define PI 3.1415
 
#define INC(x) (x + 1)
 
#define DEBUG
 
  
int main(){
+
În momentul în care un ''repository'' are configurat un ''remote'', ''commit''-urile de toate ''branch'-urile locale și din ''remote'' se pot sincroniza.
    printf("PI is %f\n", PI);
 
    printf("%d comes after 4\n", INC(4));
 
  
#ifdef DEBUG
+
Pentru a aduce modificările de pe ''remote'' a branch-ului curent, se folosește comanda <code style="color: green">git pull ''nume_remote''</code>.
    printf("This runs in debug mode\n");
+
Pentru a actualiza fișierele din ''remote'' cu modificările locale de pe ''branch''-ul curent, se folosește comanda <code style="color: green">git push ''nume_remote''</code>.
#else
 
    printf("This runs in release mode\n");
 
#endif
 
    return 0;
 
}
 
</syntaxhighlight>
 
* Rulați comanda <code style="color: green">make</code> în consolă.
 
* Afișați cele două fișiere .c. Observați diferențele.
 
  
== Directivele <code style="color: green">#ifndef</code> și <code style="color: green">#define</code> pe post de gardă pentru dublă incluziune ==
+
<div class="regula">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 <code style="color: green">pull</code> înainte de <code style="color: green">push</code>. Acest <code style="color: green">pull</code> poate produce conflicte care se rezolvă identic cu cele apărute la operațiile de ''merge''.</div>
  
Luând ca punct de plecare [[#Exemplu|primul exemplu]] de la directiva <code style="color: green">#include</code>, oare ce se întâmplă dacă header-ul <code style="color: blue">header_file.h</code> este inclus de două ori?
+
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.
<syntaxhighlight lang="C" line>
 
#include "header_file.h"
 
#include "header_file.h"
 
int main(){
 
    return 0;
 
}
 
</syntaxhighlight>
 
  
Răspunsul este simplu, preprocesorul va înlocui ambele linii cu conținutul fișierului, lucru care va face ca variabila '''variable''' să fie definită de două ori, lucru care sintactic greșit în C. Sigur, nimeni nu va include în mod voit un header de două ori, dar este posibil ca indirect lucrul acesta să se întâmple. Spre exemplu, dacă în fișierul <code style="color: blue">header_file.h</code> este inclus  <code style="color: blue">stdio.h</code>, iar în <code style="color: blue">test_preprocessor.c</code> sunt incluse și <code style="color: blue">header_file.h</code> și <code style="color: blue">stdio.h</code>. În această situație, <code style="color: blue">stdio.h</code> ajunge să fie inclus de două ori. Pentru a evita problemele apărute în această situație, se folosește garda de incluziune ([https://en.wikipedia.org/wiki/Include_guard include guard]). Aceasta se adaugă în fiecare fișier header:
+
Pentru a copia un ''repository'' de la o adresă într-un ''repository'' local se folosește comanda <code style="color: green">git clone ''url_remote''</code>.
  
<syntaxhighlight lang="C" line>
+
După clonare, ''repository''-ul local este sincronziat cu '''origin''' și ''remote''-ul este configurat cu adresa de unde a fost clonat.
#ifndef _HEADER_FILE_H_
 
#define _HEADER_FILE_H_
 
  
// This is the start of the header file
+
== Platforma Gitlab ==
int variable;
 
// This is the end of the header file
 
  
#endif
+
[http://gitlab.com 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:
</syntaxhighlight>
+
* 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.
  
Includerea acestui fișier de către preprocesor se realizează acum în următoarea secvență:
+
Toate aceste servicii sunt oferite gratuit utilizatorilor, dar fără suport.
# Prima directivă include din fișerul sursă C va copia conținutul fișierului de mai sus în locul unde este inclus (test_preprocesor.c, linia 1).
 
# Directiva ifndef verifică dacă '''macro'''-ul _HEADER_FILE_H_ ''nu'' este definit (header_file.h, linia 1).
 
# Deoarece nu este definit, tot textul dintre ifndef și endif se păstrează (header_file.h, liniile 2-7).
 
# Prima linie din text-ul păstrat este o directivă define care definește  '''macro'''-ul_HEADER_FILE_H (header_file.h, linia 2).
 
# A doua directivă include din fișerul sursă C va copia conținutul fișierului de mai sus în locul unde este inclus (test_preprocesor.c, linia 2).
 
# Directiva ifndef verifică dacă '''macro'''-ul _HEADER_FILE_H_ ''nu'' este definit (header_file.h, linia 1).
 
# Deoarece el acum ESTE definit în cadrul include-ului anterior, tot textul dintre ifndef și endif se elimină (header_file.h, liniile 2-7).
 
  
=== Exemplu ===
+
=== Exemple ===
  
* Modificați fișierul <code style="color: blue">test_preprocessor.c</code> pentru a include header-ul <code style="color: blue">header_file.h</code> de două ori, conform exemplului de mai sus.
+
* Înregistrați-vă un cont pe platforma Gitlab.
* Rulați comanda <code style="color: green">make</code> și confirmați dubla declarare a variabilei '''variable'''.
+
* Creați un proiect nou în platforma Gitlab, numit '''pc_lab'''.
* Adăugați gardă de incluziune fișierului header, conform exemplului de mai sus.
+
* Observați instrucțiunile pentru realizarea legăturii între ''repository''-ul local și cel de pe Gitlab.
* Rulați comanda <code style="color: green">make</code> și confirmați că variabile '''variable''' este declarată o singură dată.
+
* Î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):
 +
'''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot/pc_lab $</span> git remote add origin <nowiki>https://gitlab.com/.../....git</nowiki>'''
 +
* Sincronizați modificările locale cu '''origin''':
 +
'''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot/pc_lab $</span> 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 <code style="color: blue">~/gitroot</code> ș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):
 +
'''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot $</span> git clone <nowiki>https://gitlab.com/.../....git</nowiki> pc_lab_temp'''
 +
* Adăugați o modificare unuia din fișierele din <code style="color: blue">pc_lab_temp</code> 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''':
 +
'''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/gitroot/pc_lab_temp $</span> 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.

Versiunea curentă din 29 octombrie 2017 21:08

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.