Tutorial de git. Concepte de baza

Git-ul este un sistem care urmareste si centralizeaza* modificarile aduse la codul sursa a unui proiect.

*De fapt este un sistem descentralizat de versionare

TL;DR

  • git pastreaza commit-uri adica “poze” ale spatiului de lucru. (snapshots, checkpoints)

  • spatiul de lucru (working tree) este foarte volatil si trebuie tratat ca o ciorna. Lucrurile importante trebuie stocate in git.

  • git “urmareste” modificarile din working tree si stocheaza si istoricul lor in branch-uri.

  • un depozit de commit-uri, legate printr-un istoric se numeste un repository

  • Fiecare user, pentru fiecare proiect, are un astfel de repository local. Proiectul are unul[zero] sau mai multe repo-uri “in cloud”, numite remotes

  • Comenzi/concepte:
    • git clone URL aduce si initializeaza un repo local pentru un proiect aflat la URL

    • git checkout “muta” working tree pe un alt branch. Adica, modifica working tree astfel incat sa corespunda cu istoricul din branch-ul specificat

    • git commit creaza o “poza”, git push trimite commit-urile din repo-ul local in cel remote

    • git pull aduce commit-urile din repo-ul remote in cel local si le si aplica pe working tree

    • git merge uneste istoricul a doua branch-uri

    • toate cele de mai sus sunt atat comenzi CLI de git cat si comenzi ce le puteti da din interfetele grafice.

  • git-ul urmareste modificarile pe fisiere text, la nivel de linie. Binarele le considera o singura entitate.

  • Jargonul romenglez zgaraie la inceput dar trece.

Ce este git? La ce e bun?

Git-ul este un un sistem de versionare a codului sursa. Adica un sistem care ia textul scris de oameni (cod sau alt text) si il pastreaza intr-un loc mai sigur decat masinile proprii. Ajuta enorm la a agrega modificarile mai multor programatori si la lucrul in echipa.

Au existat mai multe sisteme, la ora actuala GIT-ul e cam dominant.

Foarte important. Git-ul lucreaza bine doar cu fisiere text, la nivel de linie. Fisierele binare pot trai in git, dar nu pot beneficia de toata puterea sistemului de versionare. Si pentru fisiere text, se pastreaza in git doar cele scrise de mana. Daca exista fisiere autogenerate (ex html-ul generat pe baza acestui text) acestea NU se vor adauga la git.

Executabilele sau alte artefacte generate de procesul de build, NU au ce cauta in git. La limita, intra setarile de IDE (desi, daca nu se schimba de-a lungul proiectului, exemplu PI/VisualC++, se tin afara din git).

Ca regula generala, daca un anumit artefact tine de proiect, se versioneaza. Daca tine de masina locala, NU se versioneaza. Se versioneaza insa instructiuni de setare/instalare a mediului. Evident, instructiunile pot fi un tutorial sau cod executabil. artefact == un fisier (ex .sln sau .cpp) sau o bucata de cod (ex caile spre biblioteci, url-ul bazei de date, folder cu date, etc)

Pentru lucrul in comun cu documente “binare” create manual recomand Google drive (Docs, Sheets, Slides etc)

Imagini (ex creez niste imagini pentru un document) sau alte artefacte binare create de mana, se pot adauga in git. Dar nu exista suport de “merge”.

Cateva exemple si use case-uri

  1. Lucrez la un proiect si vreau sa imi salvez fiecare etapa. Mai ales ca as vrea sa ma razgandesc si sa ma uit inapoi la ce am facut mai demult.

Solutia “clasica” ar fi sa salvez tot proiectul intr-un alt folder si sa ii pun o data calendaristica in fata. Eventual si un cuvant de descriere. Dezavantaje:

  • Greu e cand am mai multe de zis despre etapa respectiva, sau, cand vreau sa compar varianta curenta cu ceea ce exista in urma cu 4-5 versiuni.

  • Am si binare generate automat care ocupa aiurea spatiu. Imi pot face un script “de curatare”, dar trebuie sa nu uit sa il rulez.

Git-ul poate urmari anumite fisiere si salva modificarile curente. La pachetul de modificari se adauga o descriere textuala. Se zice ca facem un commit: git commit.

  1. Lucrez/rulez cod pe mai multe calculatoare (desktop-ul, pe laptop, pe workstation-ul cu GPU de la centrul de calcul).

Da, se pot copia fisiere pe stick/ftp dar git-ul rezolva fara dureri copierea codului pe masini. Trebuie un pic de grija in a separa dependintele locale (ex specificatiile cailor sau bibliotecilor) in codul sursa.

  1. Vreau sa iau/dau proiectul la altii (studenti, alti colaboratori, etc).

Pot trimite pe mail, pe “stick”, sa il descarce de pe ftp, no problemo.

In git, “descarcarea” unei copii locale pentru prima data se face cu operatia de clone: git clone

  1. Vreau sa fac modificari si sa le trimit la ceilalti.

Ce se intampla daca am gasit un bug? Sau am implementat ceva nou si potential interesant pentru toti? Mai trimit odata totul la toti userii? Ce face userul? Descarca si da cu atentie “overwrite” doar la fisierele la care nu a lucrat? Ce se intampla daca modificarea e chiar in fisierul pe care lucreaza userul? Ce face un user daca a terminat task-ul curent si trebuie sa dea feature-ul la ceilalti? Ce fac eu, ca user, daca nu mai stiu exact pe care versiune lucrez? Pe care sa o descarc din cele 1000 zip-uri primite? Ce fac eu ca si sef de echipa, cand vreau sa integrez munca cuiva in proiectul mare? Va puteti imagina cum arata inbox-ul la un user? Dar la conducatorul de proiect?

Cam pentru toate situatiile de mai sus a fost facut git-ul. Daca se poate automatiza o operatie, git-ul o face. Daca nu, sare in sus si asteapta cuminte input de la om.

Munca la un feature, stiind de unde am pornit si fara sa poata sa isi bage altii “nasul” fara acordul meu: git branch. Integrat modificari de la colegi, cand vreau eu, nu cand trimit ei mail, git merge. Tot merge (merge request) este folosit si cand vreau sa pun munca mea impreuna cu munca facuta de membrii echipei.

  1. Vreau ca din cand in cand, sa fac un build mare al proiectului si sa trimit executabilele la client.

Pot sa compilez local, sa pun manual executabilele/binarele pe serverul de productie (sau pe alte calculatoare) si sa testez de mana daca totul este ok. Dar oare am chef sa o fac des? Daca imi ia 1 zi toata tarasenia? Poate nu o sa o fac la fiecare feature nou.

Cireasa de pe tort: cu git pot face astfel incat proiectul sa fie verificat automat inainte de a accepta ca modificarea sa fie “merge”-uita in trunchiul principal al proiectului. Si binarele generate tot automat, sa fie “trimise” la client. Dar despre testare si CI/CD in alt episod.

Daca v-ati convins, si vreti sa incercati git (sau vi se cere obligatoriu la clasa) continuati sa cititi.

Concepte de baza

Elementele si descrierile de aici sunt modul in care eu personal vad empiric git-ul. NU e singura explicatie si probabil nu e ceea ce a intentionat autorul (Linus Torvalds).

Working copy si repository

Pornim de la practica curenta. Avem pe disc un folder unde lucram la ceva. Sunt 2-3 fisiere, poate un readme, fisiere de configurare ale IDE-ului (codeblocks sau visual studio), fisiere obiect, executabile, etc.

Daca vreau sa fac o modificare, editez textul si salvez. Nimic spectaculos. Cand vreau sa salvez o “poza” a modificarilor curente, copy paste intr-un folder de “backup”.

Din punctul de vedere a perisabilitatii si volatilitatii datelor, folderul curent de lucru este cel mai volatil. Orice modificare o fac mai intai aici. In folderul de backup, nu prea se modifica nimic. Doar se adauga lucruri. Cand am terminat o etapa, o salvez in backup. Cand fac o “prostie” si crapa ceva masiv la proiect, ma duc si iau ultima copie din backup ca sa corectez.

In git, folderul de lucru are un nume specific, working tree, working folder. Este considerat cel mai putin stabil si multe comenzi git il modifica.

Aici trebuie un “shift” in gandire. Daca inainte, directorul de lucru era considerat “sfant” acum trebuie considerat ca o ciorna. Dupa mine, aceasta este cea mai importanta si mai grea modificare de viziune pe care trebuie sa o facem cand trecem pe git. Restu conceptelor decurg de aici.

Arhitectura proiectului si a codului trebuie facuta astfel incat modificarile primite de la altii, prin intermediul git-ului sa nu creeze frictziune in lucru.

Inca o data, spatiul curent de lucru, working tree este doar o ciorna.

Sa zicem ca am facut o modificare (am adaugat un feature) si vreau sa salvez “poza” ciornei, cu intentia de a o pastra si/sau a o trimite la altii. Pe moment ignoram istoricul modificarilor.

Cand vrem sa capturam si sa impachetam modificarile curente, dam comanda git commit. Acum git-ul se pune sa vada ce s-a modificat de la ultima poza, ne sugereaza ce sa includem in commit si ne lasa sa specificam si un text mic, descriptiv.

Cand facem o astfel de poza, facem un commit. Poza respectiva este pusa de git intr-un loc special, numit repository. Acesta de obicei traieste in directorul de lucru. Acolo exista un folder “ascuns” cu numele de .git

Ziceam mai sus ca doar fisierele create manual se stocheaza in git. Exista un fisier de configurare numit .gitignore unde putem enumera ce foldere si/sau fisiere sa NU fie urmarite de git. De exemplu folderul build/. Odata trecut in .gitignore git-ul va ignora orice modificare facuta acolo. Restul fisierelor, sunt “urmarite”. Git-ul ne arata lista fisierelor modificate de fiecare data cand vrem sa facem un commit nou.

Bun, avem poza, avem commit-ul, ce facem cu el? Evident, il punem undeva pe un server/cloud, sa fie stocat/partajat cu altii.

Repo-ul de pe server/cloud este numit de git un remote. Ce se afla acolo? O colectie de poze, adica un alt repository. Deja avem doua repo-uri (jargon pt repository). Unul local, si unul remote. Evident, trebuie sincronizate.

Cu comenzi de gen git push trimitem pozele noi, de pe repo-ul local pe cel remote si cu git pull aducem pozele altora in repo-ul local. Acum git pull mai si aplica pozele descarcate la ciorna noastra. (Exista git fetch care aduce “pozele” fara a le aplica la codul din working tree)

Pe masina locala sunt directorul de lucru si repo-ul local, iar “in cloud” e repo-ul remote. Comenzile principale care muta date intre zone sunt pe sagetile groase. git commit pentru a face o “poza” la modificarile curente, git push pentru a trimite modificarile la server si git pull pentru a aduce noile “poze” de pe server si a le aplica pe codul din zona de lucru. git fetch ne permite sa examinam mai intai ce s-a modificat pe server.

In figura de mai jos se vad aceste concepte.

_images/git_repos.png

Sub fiecare zona, este un exemplu. In stanga sub working tree, e structura de directori asa cum o arata IDE-ul in care lucrez acum (PyCharm). In mijloc, am listat continutul directorului .git. Nimic human readable acolo. Pe “cloud” am pus cum arata repository-ul remote. Pentru acest proiect folosesc GitLab, gazduit de UTCN. (gitlab.utcluj.ro). Acesta are o interfata web care permite setari de securitate, vizualizari rapide, partajare cu drepturi de acces a repository-ului. Dar despre ce permite gitlab, citi documentatia online.

Inca ceva important. Volatilitatea scade de la stanga spre dreapta. Daca working copy este unica pentru fiecare user, si diferita pentru fiecare branch (istoric), repo-ul din cloud este UNIC pentru tot proiectul. Repo-ul local poate fi cateodata diferit pentru fiecare user, in sensul ca e un subset din repo-ul mare.

[Mai exista o zona, intre working tree si repo-ul local, numita staging. Este importanta, poate fi o sursa de erori, daca nu se lucreaza corect cu ea, dar, majoritatea IDE-urilor o “ascund” si lucreaza automat si corect cu zona de staging. Ceea ce este in staging va alcatui un commit. Regula de baza: In staging adaugam fisiere doar exact inainte de a face un commit nou.]

History si commits

Ultimul concept vital pentru a intelege git-ul e cel de istorie. Fiecare commit (poza) are un stramos, si colectia de commit-uri e organizata intr-o ierarhie. NU e chiar arbore, pentru ca ramurile (numite branch-uri) se mai si unesc (graf orientat, aciclic).

Am vazut ca fiecare set de modificari sunt grupate intr-un commit. Acesta este facut ca o diferenta intre commit-ul anterior si starea curenta a working tree-ului. Fiecare astfel de commit are un ID (revision number) unic.

Daca working tree-ul e la starea A si avem modificarile de la A->B, git-ul poate, la cerere, sa aduca starea working tree-ului nostru de la starea A la starea B. Asta este esenta operatiei de git merge.

Istoria poate arata simplu, ceva de genul A->B->C->D->E unde E e starea curenta dupa ce s-a facut ultimul commit (adica, ceea ce e in poza din repository e si in working tree).

Pana acum totul e simplu. Daca ar fi sa aplicam ce am facut mai sus, pe scenariul cu lucrul pe mai multe statii (codez pe laptop si desktop, apoi rulez pe un workstation puternic), treburile ar fi asa:

Incep ziua cu toate repository-urile sincronizate (fac git pull la inceputul zilei):

gitlab:  A->B->C
work:    A->B->C
laptop:  A->B->C
desktop: A->B->C

Lucrez pe laptop si fac commitul D.:

laptop:  A->B->C->D

Fac git push

gitlab:  A->B->C->D
work:    A->B->C
laptop:  A->B->C->D
desktop: A->B->C

Intru pe workstation si fac git pull:

gitlab:  A->B->C->D
work:    A->B->C->D
laptop:  A->B->C->D
desktop: A->B->C

Dau drumul sa ruleze pe workstation, in timp ce eu pot lucra mai departe pe laptop.

Pana aici totul simplu si clar. git pull imi aduce modificarile de pe gitlab, pe workstation.

Calea aceasta de commit-uri, cu relatia de “stramos” dintre ele se numeste un branch. Exista un branch principal, numit de obicei master. Acolo sunt modificarile cele mai “stabile”, si de pe master se face de obicei build-ul care se trimite la client sau se pune in productie.

Hai sa experimentam si cu alt scenariu. Doi oameni lucreaza pe acelasi proiect (Ana si Maria).

Treburile sunt un pic mai putin intuitive aici, si am sa exemplific doar un singur mod de lucru. Daca la firma vi se va cere alt mod de lucru, conformati-va. (ex: 3 branch-uri principale: master, staging, dev; mesaje de commit cu format fix; un singur commit pe branch; gitflow; etc. etc. etc). Daca nu folositi nici un sistem de versioning, plecati.

Sa luam un exemplu. Avem doi useri, care lucreaza la 2 feature-uri.

Ideea de baza e ca fiecare user+feature sa fie un branch. Userul Ana incepe sa lucreze la feature-ul convolutie Face un branch. In momentul cand termina de lucrat, se pune munca pe master. Asa ca, la un moment dat vor fi n branch-uri in proiect (chiar daca NU toate pe aceeasi masina):

  • master

  • ana/feature/convolutie

  • maria/feature/opening

[Observati ca se urmareste un tipic in denumirile branch-urilor. Initiale slash feature slash 1-2 cuvinte reprezentative. Initialele cumva se asigura ca nu exista conflicte intre denumiri, feature arata ca se lucreaza la o functionalitate noua. E foarte util cand vrei sa vezi dintr-o privire ce s-a facut la proiect.]

Cand se incepe lucrul la un nou branch:

  • se incepe de la “ultimul” master (adica copia locala sa fie 1:1 cu ce e pe server/gitlab)’

  • se creaza un branch nou

  • se incepe lucrul, facand commit-uri la fiecare pas mai important.

Cand se termina lucrul pe un branch (preferabil cat mai repede, nu mai mult de cateva zile) se fac operatiile:

  • se “aduc” de pe master commit-urile care au aparut intre timp

  • se verifica daca modificarile noi nu interactioneaza negativ cu ce s-a lucrat pe branch-ul curent (merge conflicts, teste)

  • se corecteaza punctul anterior, chiar daca trebuie facut un alt commit

  • pt siguranta se mai verifica odata masterul

  • se face un merge request in care modificarile din branch-ul curent se incearca a se introduce in master.

  • De obicei altcineva “accepta” acel merge request dupa niste verificari (code review si/sau rulare automata de teste)

  • dupa merge, branch-ul este de obicei sters automat de git.

Dupa ce s-a terminat munca la cele 2 feature-uri, pe master ar trebui sa existe intreg istoricul si toata munca facuta pe cele doua masini, de cei doi useri.

Mai jos, un exemplu relativ clasic de istoric:

_images/git_histo.png

Istoricul e de jos in sus, (cel mai vechi commit e jos) pentru ca, in general, IDE-urile asa afiseaza istoricul. Cele mai recente activitati sunt in partea de sus.

Pana la commit-ul C, totul e simplu. Branch-ul master, 3 commit-uri, nimic spectaculos.

Ana si Maria incep lucrul concomitent. Fiecare isi creaza un branch nou cu denumiri specifice. Primele commit-uri sunt D in branch-ul Anei si E in branch-ul Mariei. Ana mai face G si apoi vrea sa isi puna munca “pe master”. Primul pas e de aducere a modificarilor de pe master. Ultima modificare pe master este C, din C s-a facut commit-ul D, git-ul isi da seama de acest lucru asa ca nu are ce modificari sa aduca.

Acum Ana face merge in master. Git-ul efectiv va copia toate “pozele” D si G in master, probabil generand un nou commit H. (poveste mai lunga aici dar o ignoram deocamdata). Ana face un merge request la branch-ul ei, in master. Dupa ce merge request-ul a fost aprobat, commit-urile D G si H apar pe master iar branch-ul ana/feature/convolutie este sters (ori automat ori manual).

Pasii de mai sus se pot intampla local (in repo-ul local). Recomandarea e ca pasul final, de merge in master sa se realizeze in gitlab.

Intre timp, Maria face commit-ul F, la ea in branch, independent de ceea ce se intampla in repo-ul local al Anei sau pe server. Dar vine momentul cand si Maria trebuie sa isi puna munca pe master.

Executa operatia de aducere de pe master a modificarilor (git pull) si se trezeste cu commit-urile D G si H. Verifica daca modificarile noi au interactionat negativ cu codul scris, si, cand totul e ok, creaza commit-ul I. Acest commit pune in acord ceea ce s-a lucrat in D si G cu ceea ce s-a lucrat in F si E. Dupa ce se face commit-ul I, Maria face un merge request pentru ca modificarile ei sa fie puse pe master. La succes, pe master apare si commit-ul J.

E IMPORTANT DE RETINUT CA IDE-URILE ARATA DEFALCAT COMMIT-URILE PE BRANCH-URI, DESI, FIZIC, ELE AJUNG SA FACA PARTE DIN MASTER.

E de retinut ca, daca ai copia locala sincronizata cu commit-ul J de pe server, ai toate bucatile de cod din D, E, F, G.

Putem “muta” working tree de pe un branch pe altul. Daca de exemplu Ana trebuie sa examineze ce face Maria, inainte ca munca Anei sa fie pusa pe master, Ana poate face checkout la branch-ul Mariei (daca acesta este deja pe server). Git va modifca working tree-ul astfel incat sa fie 1:1 cu ce commit are Maria. Evident, Ana nu are voie sa aiba modificari locale care sa nu fi fost puse intr-un commit pe branch-ul ei (ana/feature/convolutie) inainte sa faca “saltul”.

Aceasta abilitate de a “sari” de pe un branch pe altul este foare foarte utila cand faci depanare sau cand vrei sa intelegi modificari mai complicate, in cod. Acesta este inca un motiv pentru care working tree-ul este o ciorna ce se modifica foarte usor.

Deasemenea, sunt optiuni in git care sa permita transmiterea de modificari, peste OS-uri. De exemplu pe linux avem un standard de newline iar pe windows, altul. Afaik pe git se stocheaza tot timpul in format linux, dar, se poate configura ca atunci cand OS-ul gazda este Windows, sa se faca “traducerea” automata din si in CRLF.

Simplu? NU!

Acum vine ultima parte “spinoasa” din git. Si de fapt motivul central al existentei sistemelor de versionare. Cum se face merge-ul?

git merge si merge conflicts

Exista cateva reguli stricte atunci cand se face merge dintr-un branch in altul. Le enumar pe cele mai importante.

Sa presupumen ca suntem pe branch-ul Mariei si vrem sa aducam modificarile din master
  • Pentru fisierele care NU au fost modificate de Maria in maria/featue/opening:
    • Daca exista fisiere noi in branch, acestea se adauga pur si simplu

    • Daca exista modificari la fisiere existente, se aplica modificarile (ex in D, se adauga cod nou intr-un fisier in care Maria nu a lucrat)

    • Daca exista stergeri, fisierele se vor sterge

  • Daca exista un fisier modificat atat de Maria (ex in commitul F) cat si pe master (eg de commitul G): Avem merge conflict. Nu e foarte complicat de rezolvat, trebuie doar atentie.

Inca o data: Logica de “merge” a git-ului e foarte puternica. Dar cateodata chiar nu are cum sa isi dea seama ce vrea userul.

Cel mai simplu de a arata cum se rezolva conflictul e de a crea unul si de a arata screenshot-uri din IDE.

Textul de aici il pun pe master. Aceasta este ultima linie din fisier.

Acum, adaug aceasta linie, pe branch-ul master, si fac push.

Am creat un nou branch, numit cv/demo/conflict si am adaugat linia curenta de text. Apoi fac un git push.

Acum fac un screenshot la istoric-ul din IDE si adaug poza.

_images/git_merge_01.png

Asa arata istoricul in acest moment. Linia verde este un branch de demo, iar linia galbena, master-ul. Se observa cum commit-ul 25ab face parte doar din master, deocamdata.

Presupunand ca mi-am terminat treaba, vreau sa fac merge in master. Conform listei de mai sus, trebuie sa fac:
  • ma asigur ca nu am modificari in working tree care nu sunt commit-uite (Da, aceste linii nu sunt puse intr-un commit)

  • iau de pe master modificarile (Vor aparea conflicte!)

  • rezolv conflictele

  • “compilez” totul si ma asigur ca nu sunt probleme

  • un nou commit daca trebuie

  • fac un merge request in gitlab

  • il aprob.

Bun, si acum sa vedem pe rand cum arata rezolvarea unui conflict.

Am pus in branch-ul de demo tot ce am modificat, dupa care am dat merge la branch-ul master in branch-ul curent (cv/demo/conflict)

Imediat, git-ul si implicit IDE-ul m-a avertizat ca nu poate face operatia automat:

_images/git_merge_02_conflict1.png

Imi arata branch-ul curent (Yours) si cel cu care exista conflictul (master) precum si lista de fisiere cu probleme. Daca stiu sigur ce si cum, pot da direct “Accept Yours” sau “Accept Theirs”. Dar acum nu putem face asta pentru ca avem modificari utile si in branch-ul curent si in master. Asa ca dam “Merge…”

Apare fereastra de merge:

_images/git_merge_02_conflict2.png

Wow! Cat text! Dar focus-ul trebuie sa fie pe pane-ul din mijloc, acolo va fi rezultatul. Observam niste sagetute. Acelea ne ajuta sa mutam rapid modificarile din lateral spre centru.

Cum anume se face si in ce ordine, e dat de logica programului. In cazul nostru vreau sa fac merge la linia 302 din master apoi la blocul ce incepe tot la linia 302 din branch-ul curent. Dau click in ordine, pe sagetute.

Rezultatul e mai jos:

_images/git_merge_02_conflict3.png

Dau accept, si merge-ul se finalizeaza cu succes!

_images/git_merge_03_result.png

Se observa cu verde branch-ul curent si cu mov, cum a “deviat” master-ul. Deasemenea se vad commit-urile care le-am mai facut pentru a ma asigura ca nu am modificari “necommituite” in working tree.

Bun. Facem un push, ca modificarile sa fie duse si pe server.

Acum mergem pe gitlab si cream un merge request. Adica o cerere ca modificarile noastre sa fie acceptate pe master. In gitlab, pot sa compar un branch non master si sa creez un merge request:

_images/merge_request_01.png

Aici imi si spune daca se poate face merge-ul fara conflicte, sau nu. Conflictele se rezolva LOCAL, nu pe gitlab. De aceea e bine sa se aduca modificarile de pe master, inainte de a face merge request inspre master!

Bun, am dat pe “Create merge request”:

_images/merge_request_02.png

Se observa ca butonul verde “Merge” e activ.

Intr-un proiect cu mai multi useri, flow-ul ar fi ceva de genul:
  • Se verifica ca branch-ul curent sa poata fi merge-uit fara conflicte

  • Se creaza merge request

  • Se trece un coleg la “code review”

  • Colegul face code review

  • Ideal, CI-ul verifica testele

  • Dupa ce se obtine +1 la code review si teste, se poate executa merge-ul.

De obicei branch-ul de master e “protejat”, doar anumite persoane pot aproba un merge aici.

Bun, presupunem ca pasii sunt facuti, se aproba merge-ul. Intrand acum in gitlab, la Graphs, se observa structura:

_images/merge_request_03.png

Cu rosu, branch-ul master (se vede selectat sus). Cu albastru deschis, evolutia branch-ului (sters) cv/demo/conflict. Se observa cum s-a facut merge din master in branch, apoi cum s-a facut merge din branch in master.

Revenind in IDE, facem checkout la branch-ul master si dam git pull. Rezultatul, mai jos:

_images/merge_request_04.png

Cam aceeasi poveste ca si pe server, dar, colorata si aranjata un pic diferit.

Acum, copia locala este 1:1 cu ceea ce se afla pe server si putem continua munca. Din nou, cand sunt 2 useri sau mai mult care lucreaz activ, recomandarea este git pull -> branch nou -> codat -> luat modificari de pe master -> merge request.

Conflicte dese la merge-uri indica un cod ce poate fi structurat mai bine, Daca se respecta principii SOLID si DRY, in general astfel de conflicte se rezolva acceptand modifcarile din stanga si din dreapta. Rar se va intampla ca doi oameni sa lucreze in acelasi timp in aceeasi functie.

Din cauza conflictelor, binarele generate automat se tin DEPARTE de git. De unde si utilitatea lui .gitignore.

Setarile ce tin de configurarea locala (exemplu proiectul IDE-ului) sau un fisier de configurare a cailor, vor sta local.

Daca aveti un proiect mai mare, cu CI/CD si DevOps, s-ar putea sa trebuiasca sa cititi din environment-ul OS-ului variabile de sistem care contin aceste configurari locale.

Pentru proiecte quick-and-dirty (eg proiecte de semestru) se poate accepta un if care sa seteze astfel de cai, in functie de o variabila determinata local (eg in functie de hostname() sau ceva variabila din OS)

Butonul nuclear (local)

Daca nu muncesti nu gresesti asa ca se mai poate sari cate un pas din modul de lucru si de asta git-ul se poate bloca intr-un merge esuat, sau un pull esuat, cate si mai cate se pot intampla. De obicei toate se rezolva cu un pic de atentie.

De oboseala sau de neatentie, te poti ingropa intr-o situatie foarte neplacuta. Cat timp “buba” e locala, treburile se pot rezolva cu acest “buton”.

Optiunea nucleara e de a “salva” cumva modificarile facute de la ultimul merge/push apoi stergem totul local, si o luam de la inceput.

Asa ca:

Luati modificarile scrise de voi, din fisiere, si faceti copy paste undeva IN AFARA working tree-ului. STERGETI tot working tree-ul, fizic! (De unde si mentalitatea de a trata copia locala ca o ciorna). Clonati cu git clone repository-ul. Intrati pe branch-ul in care ati lucrat, sau faceti altul nou.

Cu atentie, faceti copy/paste inapoi la modificari.

Evident, sunt tool-uri pentru a evita asta. Le gasiti la capitolul git reset. Nu le recomand pentru incepatori.

Butonul nuclear (remote) - nu prea este

Daca ati gresit ceva masiv si modificarile au fost impinse pe server, well, treburile sunt mai complicate.

In principal se poate “corecta” working tree-ul printr-un dans de commit-uri si .gitignore.

Din pacate modificarile raman in istoric iar noi, muritorii de rand, nu putem modifica istoricul. Dezavantajul daca raman acolo e ca ocupa spatiu. Daca faceti commit la tot openCV-ul sau cuda, fiecare user va avea cate un repo local de ~10 Giga. NU E DELOC DRAGUTZ!

Atentie si la commit-uriea de parole, chei de acces, setari, configurari sau alte treburi sensibile si secrete! Raman vizibile pentru toti! Exista script-uri care scaneaza github in cautarea de astfel de “secrete”.

Sumar

  • Schimbare de mentalitate, directorul proiectului trebuie tratat ca o ciorna care se poate modifca oricand

  • Serverul remote (remote repository) este cel care detine “adevarul”

  • Git-ul urmareste modificarie din fisiere si pastreaza un istoric a lor.

  • Majoritatea comenzilor de git “plimba” aceste modificari de la useri pe server si inapoi la useri

  • Merge-ul este operatia de baza prin care aceste modificari se disemineaza.

  • Cu putina atentie la arhitectura codului si la instrumentele de git, conflictele de merge ar trebui sa fie usor de rezolvat.

  • Butonul nuclear, stergi totul local si iei o copie proaspata.