[go: up one dir, main page]

Academia.eduAcademia.edu

TUTORIAL GIT

2016, Git Tutorial

Tutorial Git - instalare și mod de folosire Autor: Julen Pardo Traducere în română

TUTORIAL GIT Autor: Julen Pardo (traducere în limba română) Copyright (c) Exelixis Media P.C., 2016 Toate drepturile rezervate. Fără a limita drepturile de autor rezervate mai sus, nici o parte din această publicație nu poate fi reprodusă, stocată sau introdusă într-un sistem de recuperare sau transmisă, sub orice formă sau prin orice mijloace (electronice, mecanice, fotocopiere, înregistrare sau în alt mod), fără permisiunea proprietarului drepturilor de autor. Tutorial Git 2/18 Tutorial Git Cuprins Capitolul 1. Ce este controlul versiunii? Ce este Git? ............................................................... 4 Capitolul 2. Git vs SVN (DVCS vs CVCS) ................................................................................... 4 Capitolul 3. Descărcare și instalare Git .................................................................................... 5 3.1 Linux ...................................................................................................................................... 5 3.2 Windows ................................................................................................................................. 5 Capitolul 4. Utilizare Git ........................................................................................................... 5 4.1 Crearea unui depozit - repository .............................................................................................. 5 4.2 Crearea istoricului: commit ....................................................................................................... 5 4.3 Vizualizarea istoricului .............................................................................................................. 7 4.4 Linii de dezvoltare independente: branches (ramuri) ................................................................... 7 4.5 Combinarea istoricelor: îmbinarea ramurilor ............................................................................... 8 4.6 Îmbinări conflictuale ................................................................................................................ 9 4.6.1 Când se știe dinainte ce versiune va rămâne ...................................................................................................... 10 4.7 Verificarea diferențelor ........................................................................................................... 10 4.7.1 Interpretarea diferențelor ................................................................................................................................. 10 4.7.2 Diferențele dintre directorul de lucru și ultimul commit ........................................................................................ 11 4.7.3 Diferențele dintre punctele exacte din istoric ...................................................................................................... 11 4.8 Etichetarea punctelor importante ............................................................................................ 11 4.9 Anularea și ștergerea lucrurilor ............................................................................................... 11 4.9.1 Modificarea ultimului commit ............................................................................................................................ 11 4.9.2 Renunțarea la modificările la care nu s-a făcut commit ....................................................................................... 12 4.9.3 Ştergerea de comenzi commit........................................................................................................................... 12 4.9.4 Ștergerea etichetelor ....................................................................................................................................... 12 Capitolul 5. Strategii de ramificare ........................................................................................ 12 5.1 Ramuri de lungă durată - Long running branches ..................................................................... 12 5.2 O versiune, o ramură ............................................................................................................. 13 5.3 Indiferent de strategia de ramificare: o ramură pentru fiecare bug ............................................. 14 Capitolul 6. Depozite la distanță ............................................................................................ 14 6.1 Scrierea modificărilor la distanță - remote ................................................................................ 14 6.2 Clonarea unui depozit ............................................................................................................ 14 6.3 Actualizarea referințelor la distanță: preluare - fetch ................................................................. 15 6.4 Preluarea și îmbinarea simultană la distanță: tragerea - pull ...................................................... 15 6.5 Conflicte la actualizarea depozitului la distanță ......................................................................... 15 6.5.1 O modalitate greșită de a rezolva conflictele ...................................................................................................... 15 6.6 Ștergerea elementelor din depozitul la distanță ........................................................................ 16 6.6.1 Ştergerea commit ............................................................................................................................................ 16 6.6.2 Ștergerea ramurilor ......................................................................................................................................... 16 6.6.3 Ștergerea etichetelor ....................................................................................................................................... 16 Tutorial Git 3/18 Capitolul 7. Corecții - Patches ................................................................................................ 16 7.1 Crearea de patch-uri .............................................................................................................. 16 7.2 Aplicarea patch-urilor ............................................................................................................. 16 Capitolul 8. Comanda cherry-pick .......................................................................................... 16 Capitolul 9. Cârlige - Hooks ................................................................................................... 17 9.1 Cârlige client-side .................................................................................................................. 17 9.2 Cârlige server-side ................................................................................................................. 17 9.3 Cârligele nu sunt incluse în istoric............................................................................................ 17 Capitolul 10. O abordare a integrării continue ....................................................................... 17 Capitolul 11. Concluzie .......................................................................................................... 18 Capitolul 12. Resurse ............................................................................................................. 18 Prefață Git este, fără îndoială, cea mai populară versiune de sistem de control. În mod ironic, există alte versiuni de sisteme de control mai ușor de învățat și de utilizat, dar, în ciuda acestui fapt, Git este opțiunea preferată pentru dezvoltatori, iar acest lucru explică destul de clar puterea Git. Git a devenit instrumentul de facto utilizat pentru controlul versiunilor distribuite. Din acest motiv, am furnizat mai multe tutoriale aici, la Java Code Geeks, dintre care cele mai multe pot fi găsite aici: https://examples.javacodegeeks.com/category/softwaredevelopment/git/ Aici am vrut să creăm un ghid independent, de referință, pentru a oferi un cadru cu privire la modul de lucru cu Git și pentru a vă ajuta să începeți rapid propriile proiecte. Aici vom acoperi toate subiectele necesare: scopul utilizării Git în mod corespunzător, explicații despre ceea ce este și modul în care aceasta diferă de la alte instrumente, la utilizarea sa, acoperind, de asemenea, subiecte avansate și practici care pot presupune o valoare adăugată la procesul de control versiune. Succes! Despre autor Julen deține diploma de licență în inginerie informatică de la Mondragon Universitatea, în Spania. El contribuie la proiecte open source cu plugin-uri, și dezvoltă, de asemenea, propriile sale proiecte open-source. Julen încearcă în permanență să învețe și să adopte principii și practici de inginerie software pentru a construi software mai bun, mai sigur, lizibil și mai bine întreținut. Tutorial Git 4/18 Capitolul 1. Ce este controlul versiunii? Ce este Git? Controlul versiunii este gestionarea modificărilor făcute în cadrul unui sistem, care nu trebuie neapărat să fie software. Chiar dacă încă nu ați folosit Git sau instrumente similare, probabil că ați efectuat vreodată o versiune de control. O practică foarte utilizată și proastă în dezvoltarea de software este atunci când software-ul a ajuns la o situație stabilă, să fie salvată o copie locală a acestuia, identificată ca fiind stabilă, și apoi urmând modificările în altă copie. Acest lucru este ceva ce fiecare inginer de software a făcut înainte de a utiliza instrumente specifice de versiune de control, asa că nu este o problemă dacă ați făcut-o. De fapt, acest lucru este mult mai bun decât codul comentat, ca de exemplu: /* Prima versiune public void foo(int bar) { return bar + 1; } * / /* A doua versiune public void foo(int bar) { return bar - 1; } * / public void foo(int bar) { return bar * 2; } care ar trebui să fie declarat ilegal. Sistemele de control al versiunii (VCS) sunt concepute pentru a efectua o gestionare adecvată a modificărilor. Aceste instrumente oferă următoarele caracteristici: • Reversibilitate. • Concurenţă. • Adnotare. Reversibilitatea este principala capacitate a unui VCS, permițând revenirea la orice punct al istoricului la codul sursă, de exemplu, atunci când a fost introdusă o eroare și codul trebuie să revină la un punct stabil. Concurența permite ca mai multe persoane să facă modificări în cadrul aceluiași proiect, facilitând procesul de integrare a bucăților de cod dezvoltate de doi sau mai mulți dezvoltatori. Adnotarea este caracteristica care permite adăugarea de explicații suplimentare și gânduri despre modificările făcute,cum ar fi un CV al modificărilor făcute, motivul pentru care au fost făcute aceste modificări, o descriere generală a stabilității, etc Astfel, VCS rezolvă una dintre cele mai frecvente probleme de dezvoltare software: teama de a schimba software-ul. Sunteți probabil familiarizați cu celebra zicală „dacă ceva funcționează, nu-l schimba”. Este aproape o glumă, dar, de fapt, acționăm astfel de multe ori. Un VCS vă poate ajuta să scăpați de teama despre schimbarea codului. Există mai multe modele pentru sistemele de control al versiunii. Cel pe care l-am menționat, procesul manual, poate fi considerat ca un sistem local de control al versiunilor, deoarece modificările sunt salvate numai local. Git este un sistem distribuit de control al versiunilor (DVCS), de asemenea, cunoscut sub numele de descentralizat. Acest lucru înseamnă că fiecare dezvoltator are o copie completă a depozitului, care este găzduită în cloud. Vom vedea mai detaliat caracteristicile DVCS în capitolul următor. Capitolul 2. Git vs SVN (DVCS vs CVCS) Înainte ca să apară DVCS, cel mai popular VCS era, probabil, Apache Subversion (cunoscut, de asemenea, ca SVN). Acest VCS a fost centralizat (CVCS). Un VCS centralizat este un sistem conceput pentru a avea o singură copie completă a depozitului, găzduit pe server, în cazul în care dezvoltatorii salvează modificările pe care le-au făcut. Desigur, folosirea unui CVCS este mai bună decât un control de versiune locală, care este incompatibil cu munca în echipă. Dar având un sistem de control versiune care depinde complet de un server centralizat are o implicare evidentă: în cazul în care serverul, sau conexiunea la acesta cade, dezvoltatorii nu vor fi în măsură să salveze modificările. Sau chiar mai rău, în cazul în care depozitul central devine corupt, și nu există nici o copie de rezervă, istoria depozitului va fi pierdută. CVCS poate fi lent. Înregistrarea unei modificări în depozit înseamnă efectiv a face schimbarea în depozitul de la distanță și se bazează pe viteza de conectare la server. Revenind la Git și DVCS, fiecare dezvoltator are depozitul complet local. Deci, dezvoltatorii pot salva modificările ori de câte ori doresc. Dacă la un moment dat serverul care găzduiește depozitul cade, dezvoltatorii pot continua să lucreze fără nici o problemă. Iar modificările ar putea fi înregistrate în depozitul partajat mai târziu. O altă diferență cu CVCS, este că sistemele DVCS, în special Git, sunt mult mai rapide, deoarece modificările sunt efectuate la nivel local, iar accesul la disc este mai rapid decât accesul la rețea, cel puțin în condiții normale. Diferențele dintre cele două sisteme ar putea fi rezumate la următoarele: cu un CVCS sunteți forțat să aveți o dependență completă pe un server remote pentru a efectua controlul versiunii, iar cu un DVCS serverul la distanță este doar o opțiune pentru a partaja modificările. Tutorial Git 5/18 Capitolul 3. Descărcare și instalare Git 3.1 Linux După probabil ați ghicit, Git poate fi instalat în Linux executând următoarele comenzi: sudo apt-get update sudo apt-get install git 3.2 Windows În primul rând, trebuie descărcată ultima versiune stabilă de pe pagina oficială. Executați executabilul și faceți clic pe butonul "Next" până când ajungeți la pasul următor: Utilizarea Git numai din Git Bash Aceasta este cea mai sigură alegere ca PATH să nu fie modificată la toate. Se pot folosi numai instrumentele de linie de comandă Git de la Git Bash. Utilizarea Git din promptul de comandă Windows Această opțiune este considerată sigură, deoarece adaugă doar câteva învelișuri git minime la PATH pentru a evita aglomerarea mediului cu instrumente unix opționale. Se pot folosi Git atât din Git Bash, cât și din promptul de comandă Windows. Utilizarea instrumentelor Git și Unix opționale din promptul de comandă Windows Atât Git, cât și instrumentele Opționale Unix vor fi adăugate la PATH. Avertisment: Aceasta va suprascrie instrumentele Windows, cum ar fi „find” și „sort”. Utilizați această opțiune numai dacă înțelegeți implicațiile. Figura 3.1: Configurarea Git în Windows pentru a-l utiliza numai prin Git Bash Bifați prima opțiune. Următoarele opțiuni pot fi lăsate cele implicite. Urmează patru sau cinci butoane „Next” până la instalarea completă Git. Apoi, din meniul contextual al Windows (clic dreapta), apar două opțiuni noi: • "Git GUI here". • "Git Bash here". În acest ghid vom fi folosi bash. Toate comenzile afișate vor fi pentru executarea lor în acest bash. Capitolul 4. Utilizare Git În acest capitol, vom vedea cum se folosește Git pentru a începe cu versiunea noastră de control. 4.1 Crearea unui depozit - repository Pentru a începe utilizarea Git, mai întâi trebuie creat un depozit, cunoscut și sub numele de "repo". Pentru asta, în directorul în care vrem să avem acel depozit, se execute comanda: git init Avem un depozit Git! Rețineți că s-a creat un folder numit .git. Depozitul va fi directorul în care este plasat folderul .git. Acest folder este metadata depozitului, o bază de date încorporată. Este mai bine să nu atingeți nimic în interiorul acestuia dacă nu sunteți familiarizați cu Git. 4.2 Crearea istoricului: commit Git construiește istoria depozitului cu instrucțiunile commit. Un commit este un instantaneu complet al depozitului, care este salvat în baza de date. Fiecare stare a fișierelor care sunt realizate cu commit, pot fi recuperabile mai târziu, în orice moment. When doing a commit, we have to choose which files are going to be committed; not all the repository has to be committed necessarily. Acest proces se numește staging, unde fișierele sunt adăugate la index. Indexul Git este unde datele care urmează să fie salvate cu commit sunt stocate temporar, până când se face comiterea. Să vedem cum funcționează.. Vom crea un fișier și vom adăuga conținut la el, de exemplu: echo 'My first commit!' > README.txt Tutorial Git 6/18 Adăugând acest fișier, starea depozitului s-a modificat, deoarece în directorul de lucru a fost creat un fișier nou. Putem verifica starea depozitului cu opțiunea de stare: git status Care, în acest caz, ar genera următoarele: On branch master Initial commit Untracked files: (use "git add <file>..." to include in what will be committed) README.txt nothing added to commit but untracked files present (use "git add" to track) Git spune că "aveți un fișier nou în directorul depozitului, dar acest fișier nu este încă selectat pentru a fi… comitted". Dacă dorim să includem commit pentru acest fișier, amintiți-vă că acesta trebuie adăugat la index. Acest lucru se face cu comanda add, după cum sugerează Git în rezultatul de stare : git add README.txt Din nou, starea depozitului are modificări: On branch master Initial commit Changes to be committed: (use "git rm —cached <file>..." to unstage) new file: README.txt Acum, putem face comiterea! git commit Acum, va fi afișat editorul de text implicit, unde trebuie să tastați mesajul de confirmare, apoi salvați. Dacă lăsăm mesajul gol, commit va fi abandonat. În plus, putem utiliza versiunea prescurtată cu marcatorul -m, specificând mesajul de comitere în linie: git commit -m 'Commit message for first commit!' Putem adăuga toate fișierele din directorul curent, recursiv, la index, cu: git add . Rețineți că următoarele: echo 'Second commit!' > README.txt git add README.txt echo 'Or is it the third?' > README.txt git commit -m 'Another commit' ar comite fișierul cu conținutul 'Second commit!', pentru că a fost cel adăugat la index, și apoi am schimbat fișierul de director de lucru, nu cel adăugat la zona de așteptare (staging). Pentru a comite cea mai recentă modificare, va trebui să adăugăm din nou fișierul la index, fiind primul fișier adăugat suprascris. Git identifică fiecare commit în mod unic folosind funcția hash SHA1, pe baza conținutului fișierelor comise. Deci, fiecare comitere este identificată cu un șir hexazecimal de 40 de caractere, cum ar fi următoarele, de exemplu: de5aeb426b3773ee3f1f25a85f471750d127edfe Luați în considerare faptul că mesajul de comitere, data comiterii sau oricare altă variabilă, diferită de conținutul (și dimensiunea) fișierelor comise, nu sunt incluse în calculul hash. Deci, pentru primele două comenzi commit, istoricul ar fi următorul: Figura 4.1: Istoricul depozitului (repo), cu două comenzi commit Git scurtează suma de control a fiecărui commit la 7 caractere (ori de câte ori este posibil), pentru a le face mai lizibile. Fiecare commit se referă la comanda commit de la care a fost creată, acest lucru fiind numit „strămoș”. Atenție la elementul HEAD. Acesta este unul dintre cele mai importante elemente din Git. HEAD este elementul care se referă / indică punctul curent din istoricul depozitului. Conținutul directorului de lucru va fi cel care aparține instantaneului indicat de HEAD. Acest HEAD va fi descris în detaliu mai târziu. 4.2.1 Sfaturi pentru crearea de mesaje commit bune Conținutul mesajului commit este mai important decât poate părea la prima vedere. Git permite adăugarea de explicații pentru orice schimbare făcută, fără a modifica codul sursă, și ar trebui să profităm întotdeauna de acest lucru. Pentru formatarea mesajului, există o regulă nescrisă cunoscută sub numele de regula 50/72, ceea ce este foarte simplă: • O primă linie cu un rezumat de maxim 50 de caractere. • Explicații ulterioare pe linii de cel mult 72 de caractere. Acest lucru se bazează pe modul în care Git formatează ieșirea atunci când revizuim istoricul. Dar, cel mai important este conținutul mesajului în sine. Primul lucru care ne vine în minte este să scriem modificările care au fost făcute, ceea ce nu este rău deloc. Dar obiectul comis în sine este o descriere a modificărilor care au fost făcute în codul sursă. Pentru a face mesajele de comitere utile, trebuie să includeți întotdeauna motivul pentru care au fost făcute schimbările. Tutorial Git 7/18 4.3 Vizualizarea istoricului Desigur, Git este capabil să afișeze istoria depozitului. Pentru aceasta, este utilizată comanda log (jurnal): git log Dacă o încercați, veți vedea că modul de afișare nu este foarte frumos. Comanda log are mulți parametri disponibili pentru a afișa formate grafice. Iată o sugestie pentru utilizarea acestei comenzi pentru acest tutorial, în acest caz graficele sunt afișate pentru fiecare scenariu: git log —all —graph —decorate —oneline Dacă doriți, puteți omite parametrul –oneline pentru a afișa informațiile complete ale fiecărui comit. 4.4 Linii de dezvoltare independente: branches (ramuri) Ramificarea este, probabil, cea mai puternică caracteristică a Git. O ramură reprezintă o cale de dezvoltare independentă. Ramurile coexistă în același depozit, dar fiecare are propria istorie. În secțiunea anterioară, am lucrat cu o ramură, ramura implicită a lui Git, care este numită coordonator. Având în vedere acest lucru, modul corect de exprimare a istoriei ar fi următorul, având în vedere ramurile. Figura 4.2: Istoricul depozitului, afișând indicatorul de ramură. Crearea unei ramuri cu Git este simplă: git branch <branch-name> De exemplu,: git branch second-branch Și asta e tot. De fapt, ce face Git atunci când creează o ramură? Se creează doar un indicator cu acel nume de ramură care indică spre commit în cazul în care ramura a fost creată: Figura 4.3: Istoricul depozitului cu o nouă ramură Aceasta este una dintre cele mai notabile caracteristici ale Git: viteza de creare a ramurii, aproape instantanee, indiferent de dimensiunea depozitului. Pentru a începe să lucreze în această ramură, trebuie să-l verificăm cu checkout: git checkout second-branch Acum, commit va exista doar în a doua ramură. De ce? Deoarece HEAD acum este îndreptat spre a doua ramură, astfel încât, istoricul creată de acum va avea o cale independentă față de maestru. Putem vedea că face o pereche de commit-uri situate pe a doua ramură: echo 'The changes made in this branch...' >> README.txt git add README.txt git commit -m 'Start changes in second-branch' echo ' ... Only exist in this branch' >> README.txt git add README.txt git commit -m 'End changes in second-branch' Dacă verificăm conținutul fișierului pe care îl modificăm, vom vedea următoarele: Second commit! The changes made in this branch... ... Only exist in this branch Tutorial Git 8/18 Dar, ce se întâmplă dacă ne întoarcem la maestru? git checkout master Conținutul fișierului va fi: Second commit! Acest lucru se datorează faptului că, după crearea istoricului cu a doua ramură, am plasat HEAD care indică spre master: Figura 4.4: Istoric independent pentru a doua ramură 4.5 Combinarea istoricelor: îmbinarea ramurilor În subsecțiunea anterioară, am văzut cum putem crea căi diferite pentru istoricul depozitului nostru. Acum, vom vedea cum pot fi combinate, process care în Git se numește merging. Să presupunem că, după modificările făcute pe a doua ramură, revenim la maestru. Pentru aceasta, trebuie să plasăm HEAD în ramura de destinație (master) și să specificăm ramura care urmează să fie fuzionată cu această ramură de destinație (a doua ramură), cu comanda merge: git checkout master git merge second-branch Și Git va afișa următorul rezultat: Updating f043d98..0705117 Fast-forward README.txt | 2 ++ 1 file changed, 2 insertions(+) Acum, istoricul celei de-a doua ramuri a fost îmbinat cu masterul, deci, toate modificările făcute în această primă ramură au fost aplicate celei de-a doua. În acest caz, întregul istoric al celei de-a doua ramuri face acum parte din istoria maestrului, având un grafic ca următorul: Figura 4.5: Istoric după îmbinarea celei de-a doua ramuri cu masterul După cum se poate vedea, nu a fost salvată nicio urmă pentru cea de a doua ramură, când, probabil, ne așteptam la un copac frumos. Acest lucru se datorează faptului că Git a unit ramura folosind modul fast-forward. De ce a făcut Git acest lucru? Pentru că maestrul și a doua ramură au un strămoș comun, f043d98. Când fuzionăm ramuri, se recomandă să nu utilizați modul fast-forward. Acest lucru se realizează folosind parametrul –no-ff în timpul îmbinării: Tutorial Git 9/18 git merge —no-ff second-branch Ce se petrece cu adevărat? Ei bine, se creează doar un intermediar, al treilea commit, între HEAD și „de la” ramura cu ultimul commit. După salvarea mesajului de commit (desigur, este editabil), ramura va fi îmbinată, având următorul istoric: Figura 4.6: Istoric după îmbinarea celei de-a doua ramuri cu master, utilizând modul fără fast-forward, care este mult mai expresiv, deoarece istoricul reflectă starea de fapt. Modul fără derulare rapidă (no fast-forward) trebuie utilizat întotdeauna. O îmbinare a unei ramuri presupune sfârșitul vieții acesteia. Deci, ar trebui să fie ștearsă: git branch -d second-branch Desigur, în viitor, puteți crea din nou o a doua ramură cu numele branch. 4.6 Îmbinări conflictuale În secțiunea anterioară am văzut o îmbinare "automată", adică, Git a fost capabil să fuzioneze ambele istorii. De ce? Din cauza strămoșului comun menționat anterior. Adică, ramura se întoarce la punctul de la care a început. Dar, atunci când ramura naște o altă ramură, apar probleme. Pentru a înțelege acest lucru, să construim un nou istoric, care va avea următorul grafic: Figura 4.7: Continuând istoricul pentru master, după crearea celei de-a doua ramuri cu următoarele comenzi: echo 'one' >> file.txt git add file.txt git commit -m 'first' echo 'two' >> file.txt git add file.txt git commit -m 'second' git checkout -b second-branch Tutorial Git 10/18 echo 'three (from second-branch)' >> file.txt git add file.txt git commit -m 'third from second branch' git checkout master echo 'three' >> file.txt git add file.txt git commit -m 'third' Ce se va întâmpla dacă încercăm să unim a doua ramură la master? git checkout master git merge second-branch Git nu va fi capabil să o facă: CONFLICT (content): Merge conflict in file.txt Automatic merge failed; fix conflicts and then commit the result. Git nu știe să o facă, deoarece modificările făcute în a doua ramură nu sunt direct aplicabile la master, deoarece s-a modificat de la această primă ramură începută. Ceea ce a făcut Git este să indice în ce părți există aceste incompatibilități. Rețineți că nu am folosit parametrul -no-ff, din moment ce știm dinainte că fast-forward nu va fi posibil. Dacă verificăm starea, vom vedea următoarele: On branch master You have unmerged paths. (fix conflicts and run "git commit") Unmerged paths: (use "git add <file>..." to mark resolution) both modified: file.txt Se afișează fișierele conflictuale. Dacă deschidem fișierul, vom vedea că Git a adăugat câteva linii ciudate.: one two <<<<<<< HEAD three three (from second-branch) >>>>>>> second-branch Git a indicat care sunt modificările incompatibile. Și de unde știe? Modificările incompatibile sunt cele care au fost introduse „to - către”ramura de unire (master) de la crearea "de la - from" ramura de unire (second- branch). Acum, trebuie să decidem cum să combinăm schimbările. Pe de o parte, sunt afișate modificările aduse actualului HEAD (între <<<<<<< HEAD și =======), și, pe de altă parte, ramura pe care încercăm să o fuzionăm (între ======= și >>>>>>> second-branch). Pentru a rezolva conflictul, există trei opțiuni: • Folosirea versiunii HEAD. • Folosirea versiunii second-branch. • O combinație a celor două versiuni. Indiferent de opțiune, fișierul ar trebui să se termine fără oricare dintre metacaracterele pe care Git le-a adăugat pentru a identifica conflictele. Odată ce conflictele au fost rezolvate, trebuie să adăugăm fișierul la index și să continuăm cu îmbinarea, cu comanda commit: git add file.txt git commit Odată salvată comiterea, îmbinarea se va face, având în git creat un al treilea commit pentru această îmbinare, ca și atunci când am folosit --no-ff în secțiunea anterioară. 4.6.1 Când se știe dinainte ce versiune va rămâne Se poate întâmpla să știm dinainte ce versiune vrem să alegem în caz de conflicte. În aceste cazuri, îi putem spune lui Git ce versiune să folosească, pentru a o folosi direct. Pentru a face acest lucru, trebuie să trecem opțiunea -X pentru a fuziona, indicând ce versiune utilizează: git merge -X <ours|theirs> <branch-name> Deci, pentru utilizarea versiunii HEAD, ar trebui să folosim opțiunea ours; în schimb, pentru utilizarea versiunii care nu este HEAD, se va folosi opțiunea theirs. Adică: git merge -X ours second-branch ar lăsa fișierul cum este afișat: one two three Și, următoarea comandă: git merge -X theirs second-branch are ca rezultat: one two three (from second-branch) 4.7 Verificarea diferențelor Git permite verificarea diferențelor dintre punctele distincte din istoric. Acest lucru se face cu opțiunea diff. 4.7.1 Interpretarea diferențelor Înainte de a vedea la ce diferențe ne putem uita, în primul rând trebuie să înțelegem cum sunt arătate diferențele. Să vedem un exemplu de afișare a unei diferențe între același fișier: Tutorial Git 11/18 diff —git a/README.txt b/README.txt index 31325b6..55e8d58 100644 a/README.txt +++ b/README.txt @@ -1,2 +1,2 @@ -This is -the original file +This file +has been modified Aici, a este o versiune anterioară a fișierului, și b versiunea curentă. A treia și a patra linie identifică fiecare literă cu un simbol - sau +. Expresia @@-1,2 +1,2 @@ este numită „hunk header” (bucată de header). Aceasta identifică bucățile de cod care s-au modificat efectiv, fără a afișa părțile comune pentru ambele versiuni. Formatul este următorul: @@ <previous><from-line>,<number-of-lines> <current>,<from-line><number-of-lines> In this case: • "previous": identificat cu -, corespunzător lui a. • "from-line": numărul liniei de unde încep modificările. • "number-of-lines": numărul de linii afișate. • "current": identificat cu +, corespunzător lui b. În cele din urmă, sunt afișate care linii sunt scăzute și care sunt adăugate. În acest caz, două linii au fost scăzute din linie (cele precedate cu -), iar alte două au fost adăugate (precedate cu +). 4.7.2 Diferențele dintre directorul de lucru și ultimul commit O utilizare comună este verificarea diferențelor dintre directorul de lucru și ultimul commit. Pentru aceasta, este suficient să se execute: git diff lucru care va afișa diferența pentru fiecare fișier. Putem specifica, de asemenea, anumite fișiere: git diff <file1> <file2> 4.7.3 Diferențele dintre punctele exacte din istoric Putem căuta diferențe cu: • SHA1 id • Numele ramurilor • HEAD • Etichete - Tags sau combinații ale acestora. Sintaxa este următoarea: git diff <original>..<modified> De exemplu, următoarea comandă arată modificările care au fost aplicate la ramura dev, comparativ cu eticheta v1.0: git diff v1.0..dev 4.8 Etichetarea punctelor importante Etichetarea este una dintre cele mai frumoase caracteristici ale Git, deoarece permite marcarea punctelor importante din istoria depozitului, într-un mod foarte ușor. De obicei, etichetele sunt utilizate pentru a marca versiunile, nu numai pentru lansări stabile, ci și pentru versiuni subdezvoltate sau incomplete, ar fi: • Alpha • Beta • Release candidate (rc) Crearea unei etichete este atât de simplă, încât trebuie doar să situăm elementul HEAD în punctul pe care dorim să-l etichetăm și să specificăm numele etichetei cu opțiunea de etichetă: git tag -a <tag-name> De exemplu: git tag -a v0.1-beta1 Apoi, se va cere să se tasteze un mesaj pentru etichetă. De obicei, sunt specificate modificările efectuate de la ultima etichetă. Ca atunci când se face commit, putem specifica mesajul tag-ul în linie, cu parametrul -m: git tag -a v0.1 -m 'v0.1 stable release, changes from...' Atenție, numele etichetelor nu se pot repeta într-un depozit. 4.9 Anularea și ștergerea lucrurilor Git permite, de asemenea, anularea și modificarea unele lucruri din istoric. În această secțiune vom vedea ce poate face și cum. 4.9.1 Modificarea ultimului commit Este un lucru destul de obișnuit să se modifice ultima comandă commit, de exemplu, atunci când trebuie să fie adăugată doar o linie de cod; sau numai pentru a modifica mesajul de actualizare, fără a modifica nici un fișier. Pentru aceasta, Git are parametrul --amend la comanda commit: git commit --amend Acest lucru este la fel ca și commit, dar, în locul unei comenzi noi commit, va fi suprascrisă ultima comandă commit din acea Tutorial Git 12/18 ramură. 4.9.2 Renunțarea la modificările la care nu s-a făcut commit Acest lucru este valabil pentru situațiile când, după commit, se continuă cu dezvoltarea, credem că am luat o cale incorectă, și vrem să resetăm modificările, revenind la ultima stare de commit. În acest caz, comanda utilizată este checkout, ca și pentru mutarea între ramuri. Dar, atunci când se specifică un fișier, acesta va reveni la starea de la ultima comandă commit. De exemplu: echo 'one' > test.txt git add test.txt git commit -m 'commit one' echo 'two' > test.txt git checkout test.txt # Continutul lui test.txt este acum 'one'. 4.9.3 Ştergerea de comenzi commit De obicei, vrem să ștergem comenzi commit atunci când nu vrem să lăsăm nicio înregistrare a unui commit greșit, sau doar pentru eliminarea modificărilor inutile. Acest lucru se realizează mutarea ramurii sau a indiciilor HEAD. Mutarea indiciilor la comenzile commit anterioare face ca commit-urile rămase înainte să fie „pierdut”, nelegate de la lista legată. Pentru a le muta, se utilizează comanda de resetare. Există două moduri de a face o resetare: fără a atinge directorul de lucru (soft reset, parametrul --soft), sau și resetarea directorului (hard reset, parametrul --hard). Adică, dacă faceți o resetare soft, comenzile commit vor fi eliminate, dar modificările salvate în acele comenzi commit vor rămâne; și o resetare hard, care nu va lăsa schimbarea făcută în commit. Dacă nu este specificat niciun parametru, resetarea va fi de tip soft. Să începem să resetăm lucrurile. Următoarea comandă ar elimina ultimul commit, adică commit în cazul în care HEAD indică spre: git reset --hard HEAD~ Caracterul ~ este pentru a indica un strămoș. Utilizat o dată, indică părintele imediat; de două ori, indică bunicul; și așa mai departe. Dar, în loc să se tasteze de n ori, putem specifica cei n strămoși care vor fi eliminați: git reset --hard HEAD~3 care ar elimina ultimele 3 comenzi commit. Este posibil să fi observat că acest lucru poate provoca conflicte cu acele comenzi commit care au mai mult de un strămoș, de exemplu, rezultatul unei îmbinări fără fast-forward. Ei bine, aceasta nu cauzează nici o problemă: părintele urmat folosind HEAD~ este întotdeauna primul. Dar există o modalitate de a decide care dintre părinții comuni urmează ^, după care se scrie numărul de părinți. Deci, comanda: git reset --hard HEAD~2^2 ar elimina cele două comenzi commit anterioare, dar luând calea celui de-al doilea strămoș. Chiar dacă este posibil să se specifice calea strămoșului care va fi urmată, se recomandă să utilizați întotdeauna sintaxa pentru primul strămoș (numai ~) deoarece este mai ușor, chiar dacă ar fi necesare mai multe comenzi (din moment ce ar trebui să folosiți checkout pentru diferite ramuri pentru a actualiza poziția HEAD). 4.9.4 Ștergerea etichetelor Ștergerea etichetelor este simplă: git tag -d <tag-name> Capitolul 5. Strategii de ramificare Ajunși la acest punct, este posibil să apară întrebarea: „Bine, ramurile sunt utile, dar când ar trebui să le creez și când să le unesc?” Când dorim să efectuăm un control al versiunii, trebuie să știm ce strategie vom urma. Utilizarea Git fără a avea o politică clară de ramificare este un nonsens complet. Fluxul de lucru de ramificat de urmărit depinde, în principal, de modul în care dorim să menținem codul. În această secțiune, vom vedea cele două strategii principale de ramificare. 5.1 Ramuri de lungă durată - Long running branches Această strategie este utilizată atunci când dorim pentru a menține o singură versiune a software-ului la un moment dat. Acest lucru se folosește atunci când oferim ultima versiune a software-ului, în loc de a avea mai multe versiuni (care pot fi încă disponibile, dar fiind considerate vechi sau neîntreținute). Cheia acestei strategii este de a avea o ramură numai pentru versiuni stabile, în cazul în care versiunile sunt etichetate, pentru care ramura implicită utilizată este master; și având alte ramuri pentru dezvoltare, în cazul în care caracteristicile sunt dezvoltate, testate, și integrate. În această strategie, ramura master este ramura de producție, astfel încât aici ar trebui să fie numai versiunile testate, integrate în mod corespunzător, definitive. Sau, pot exista, de asemenea, versiuni în curs de dezvoltare sau partial testate, dar acestea trebuie să fie etichetate în mod corespunzător. Graficul următor arată un exemplu de istoric al unui depozit care urmează această strategie: Tutorial Git 13/18 Figura 5.1: Exemplu de istoric de depozit utilizând strategia de ramură de lungă durată Simplu și clar. Starea de producție este modificată doar pentru acele schimbări care au fost integrate cu ramura de dezvoltare, unde nu se întâmplă nimic dacă se rupe ceva. Modificările care trebuie făcute pentru fiecare caracteristică sunt perfect izolate, favorizând împărțirea sarcinilor între coechipieri. 5.2 O versiune, o ramură Acest flux de lucru este gândit pentru crearea de software care va fi disponibil și menținut pentru mai multe versiuni. Cu alte cuvinte, o versiune nu „suprascrie” versiunea anterioară, ci „suprascrie” doar versiunea de pe ramura pe care a fost făcută. Pentru a realiza acest lucru, fiecare versiune menținută trebuie să aibă versiunea sa principală, dar cu o cale comună de dezvoltare pentru toate acestea. Acest lucru se face având o ramură pentru fiecare versiune (de obicei numit PROJECT_NAME_XX_STABLE sau similar) pentru versiunea stabilă, „master” de fiecare versiune; și având o ramură principală (și sub-ramurile sale) în care se face dezvoltarea, pentru care se poate utiliza ramura principală implicită. Când fiecare caracteristică este dezvoltată și testată, ramura principală poate fi îmbinată cu oricare versiune stabilă dorită. Această strategie de ramificare se bazează pe termen lung, dar, în acest caz, având multe ramuri „master” în loc de una singură. Se ia în considerare faptul că fiecare caracteristică ar trebui să fie testată cu fiecare versiune a proiectului la care dorim să aplicăm această caracteristică. Atunci când se abordează această strategie se ia în considerare utilizarea integrării continue. Să vedem un exemplu de grafic al unui istoric pentru care această strategie a fost aplicată. Figura 5.2: Exemplu de istoric de depozit utilizând o versiune, o strategie de ramură După putem vedea, aici, ramura master este folosită ca ramură principală de dezvoltare. În versiunea v3.0, am simulat un bug pentru featurel. Acesta nu trebuie să fie neapărat un bug a apărut la momentul integrării, acesta poate fi un bug care a fost detectat mai târziu. În orice caz, suntem siguri că bug-ul de feature1 există doar pentru versiunea v3.0. În aceste cazuri, nu ar trebui să reparăm aceste bug-uri în ramura principală, deoarece nu este ceva care afectează toate versiunile. Ar trebui să creăm o ramură din versiunea afectată, să reparăm eroarea și să fuzionăm cu ramura specifică. Și, dacă în viitor aceeași eroare Tutorial Git 14/18 persistă pentru versiunile noi, putem lua în considerare opțiunea de a o îmbina pentru a o stăpâni. Principalul avantaj al acestei strategii este că caracteristicile comune urmează o cale comună, iar modificările specifice pot fi perfect izolate pentru versiunile afectate. 5.3 Indiferent de strategia de ramificare: o ramură pentru fiecare bug Indiferent de strategia de ramificare folosită, se recomandă crearea unei ramuri independentă pentru fiecare eroare, la fel cum a fost raportată în tracker-ul de erori (pentru că utilizați un sistem de urmărire a erorilor, nu?). De fapt, am văzut această practică în exemplul anterior, în integrarea caracteristică în ramura pentru versiunea v3.0. Având o ramură independentă pentru fiecare bug permite ca problemele să fie perfect localizate și identificate, iar modificările de reparare implică izolarea lor de alte ramuri. Identificarea acestei ramuri cu numărul de identificare generat de bug tracker pentru bug-ul dat, denumirea ramurii, de exemplu, ca versiunea-X, permite urmărirea modificărilor făcute pentru remedierea bug-ului, a comentariilor făcute în biletul corespunzător de tracker bug, care este foarte util, deoarece puteți explica soluții posibile pentru bug-uri, atașa imagini etc. Capitolul 6. Depozite la distanță Git, după am văzut în introducere, este un VCS distribuit. Acest lucru înseamnă că, în afară de depozitul local, putem avea o copie a depozitului găzduit într-un server la distanță, care, pe lângă faptul că face public codul sursă al proiectului, este folosit pentru dezvoltarea în colaborare. Cea mai populară platformă pentru găzduirea depozitelor Git este GitHub. Însă, GitHub nu oferă depozite private în planul său gratuit. Dacă aveți nevoie de o platformă de găzduire cu depozite private nelimitate, puteți utiliza Bitbucket. Și, dacă sunteți în căutarea pentru găzduirea depozitelor pe propriul server, opțiunea disponibilă este GitLab. Pentru această secțiune, va trebui să folosim una dintre opțiunile menționate mai sus. 6.1 Scrierea modificărilor la distanță - remote Primul lucru pe care trebuie să-l facem în găzduirea la distanță este să creăm un depozit, pentru care se va crea un URL cu următorul format: https://hosting-service.com/username/repository-name Odată ce avem un depozit de la distanță, trebuie să-l legăm cu depozitele noastre locale. Acest lucru se face la distanță cu comanda remote add <remotename> <repo-url>: git remote add origin https://hosting-service.com/username/repository-name unde origin este numit în mod implicit de Git, similar cu ramura master, dar nu neapărat. Acum, în depozitul nostru local, depozitul de la distanță este identificat ca origine. Putem începe acum să „trimitem” informații la depozit, care se face cu opțiunea push: git push [remote-name] [branch-name | Să vedem câteva exemple: --all] [--tags] git push origin --all # Actualizează depozitul la distanță cu toate ramurile locale git push origin master dev # Actualizează ramurile principale și dev la distanță git push origin –-tags # Trimite etichete către depozitele la distanță Acesta este un exemplu de afișare rezultat a unei actualizări cu success a ramurii master: Counting objects: 3, done. Writing objects: 100% (3/3), 235 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To https://hosting-service.com/username/repository-name.git * [new branch] master -> master Ce are Git intern cu acest lucru? Ei bine, acum a fost creat un director .git / refs / la distanță, și, în interiorul acestuia, un alt director, origin (pentru că asta e numele pe care le-am dat la distanță). Aici, Git creează un fișier pentru fiecare ramură care iese din depozitul la distanță, cu o referință la acesta. Această referință este doar ID-ul SHA1 al ultimei comenzi commit a ramurii date din depozitul la distanță. Acest lucru este utilizat de Git pentru a ști dacă în repo la distanță sunt orice modificări care pot fi aplicate la depozitul local. Vom acoperi acest lucru în detaliu mai târziu în următoarele secțiuni. Notă: un depozit poate avea oricâte depozite la distanță. De exemplu, am putea avea același depozit în GitHub și în Bitbucket: git remote add github https://github.com/username/repository-name git remote add bitbucket https://bitbucket.org/username/repository-name 6.2 Clonarea unui depozit Clonarea unui depozit se face de fapt o singură dată, când vom începe să lucrăm cu un depozit de la distanță. Clonarea depozitelor la distanță este simplă, trebuie doar să folosim opțiunea de clonare, specificând URL-ul depozitului: git clone https://hosting-service.com/username/repository-name care ar crea un director local cu depozit, cu referire la depozitul la distanță care a fost clonat. În mod implicit, la clonarea unui depozit, se creează numai ramura implicită (de obicei, master). Crearea de late ramuri la nivel local se face cu checkout. Ramurile la distanță pot fi afișate cu opțiunea de ramură cu parametrul -r: git branch -r Acestea vor fi afișate în formatul <remote-name>/<branch-name>. Pentru a crea ramura locală, este suficient să se facă un Tutorial Git 15/18 checkout la <branch-name>. 6.3 Actualizarea referințelor la distanță: preluare - fetch Preluarea depozitului la distanță înseamnă actualizarea de referință a unei ramuri locale, pentru a o pune chiar și cu ramura de la distanță. Să luăm în considerare un scenariu de colaborare în cazul în care doi dezvoltatori folosesc push pentru a face modificări la același depozit. La un moment dat, unul dintre dezvoltatori vrea să actualizeze referința sa la master, la ultimul push a celuilalt dezvoltator. Conținutul fișierului .git/refs/remotes/origin/master din depozitul lui Alice arată, înainte de orice actualizare, astfel: 5bfc81ce5f7a0b26b493be0c99f1966a1896c972 Al lui Bob va fi actualizat, deoarece a fost el ultimul care a actualizat depozitul la distanță: 37db4f82e346665f6048cc9e4b7cd48c83c6ebcb Alice vrea să aibă în depozitul ei local schimbările făcute de Bob. Pentru asta, ea trebuie să preia ramura master: git fetch origin master (Alternativ, ea poate prelua fiecare ramură cu parametrul --all): git fetch —all Rezultatul preluării ar putea să arate astfel: remote: Counting objects: 3, done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From https://hosting-service.com/alice/repository-name * branch master -> FETCH_HEAD 5bfc81c..37db4f8 master -> origin/master Și, acum, conținutul fișierului lui Alice .git/refs/remotes/origin/master va fi la fel cu cel de la distanță: 37db4f82e346665f6048cc9e4b7cd48c83c6ebcb Astfel, Alice a actualizat referința la ramura master la distanță, dar modificările nu au fost aplicate în depozit. Comanda fetch nu aplică modificări direct depozitului local. Este prima dintre cele două etape care trebuie să fie făcute. Celălalt pas este de a îmbina ramura la distanță, care tocmai a fost actualizată; cu ramura locală. Aceasta este doar o îmbinare ca oricare alta, dar aici trebuie indicat numele de la distanță: git merge origin/master Odată ce îmbinarea este terminată, Alice va avea cea mai recentă versiune de ramură master. Notă: în această îmbinare, nu am aplicat opțiunea no-fast forward. În acest caz, nu ar avea prea multe sens să se aplice, din moment ce unim intrinsec aceeași ramură, dar care este situată în altă parte. 6.4 Preluarea și îmbinarea simultană la distanță: tragerea - pull Git are opțiunea de a aplica modificări la distanță simultan, adică preluarea și îmbinarea ramurii cu o singură comandă. Această opțiune este pull. Având în vedere exact același scenariu văzut mai sus, am putea executa doar: git pull origin master Cum s-ar face cu fetch, urmată de merge. Folosește modul în care te simți mai confortabil. Nu există o cale mai bună; de fapt, fac exact același lucru, dar într-un mod diferit. 6.5 Conflicte la actualizarea depozitului la distanță Să revenim la scenariul prezentat mai sus. Ce s-ar întâmpla dacă, fără actualizarea depozitului ei local, Alice ar crea un commit și lar încărca cu push în depozitul de la distanță? Ei bine, ea ar primi un mesaj urât de la Git: ! [rejected] master -> master (fetch first) error: failed to push some refs to 'https://hosting-service.com/alice/repository-name.git' hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push —help' for details. Comanda push este respinsă pentru că Alice nu a continuat istoricul depozitului dintr-un punct care a fost deja înregistrat în depozitul la distanță. Deci, înainte de a scrie modificări în depozitul de la distanță, ea trebuie mai întâi să preia și să unească cu fetch și merge sau cu pull, depozitul la distanță cu modificările din el. 6.5.1 O modalitate greșită de a rezolva conflictele Există încă o altă cale, care nu poate fi considerată exact ca rezolvare. Aceasta forțează trimiterea push cu parametrul --force. Forțând un push suprascrie ramura depozit de la distanță (sau întregul depozit), cu versiunea trimisă. Deci, în scenariul de mai sus, Tutorial Git 16/18 în cazul în care Alice forțează trimiterea push din depozitul ei, dispare trimiterea 37db4f8 cu commit. Acest lucru l-ar lăsa pe Bob să lucreze în ceva similar cu un „univers parallel” care nu poate coexista cu realitatea actuală a proiectului., deoarece activitatea sa se bazează pe o stare care nu mai există în proiect. În concluzie, nu forțați push atunci când lucrați cu alți dezvoltatori, cel puțin pentru a evita o ceartă intensă cu ei. La o căutare pe Internet cu „git force push” în secțiunea imagini, veți vedea unele meme care explica perfect grafic cum este considerată această comandă. 6.6 Ștergerea elementelor din depozitul la distanță Ca și în cazul push --force, lucrurile din depozitele de la distanță trebuie șterse cu precauție extremă. Înainte de a șterge ceva, fiecare colaborator ar trebui să fie informat despre asta. De fapt, atunci când se trimit commit, ramuri, etc, acestea se trimit la o destinație ref, dar nu trebuie specificată destinația în mod explicit. Sintaxa explicită este următoarea: git push origin <sursa>:<destinatia> Deci, modul de ștergere a lucrurilor la distanță este actualizarea refs la stările anterioare, sau împingând (push) "nimic". Să vedem se face în fiecare caz. 6.6.1 Ştergerea commit Acest lucru este la fel ca ștergerea de commit locale, de exemplu, pentru a șterge ultimele două commit-uri: git push origin HEAD~2:master —force Dacă folosim HEAD pentru a ne referi la commit pentru a le elimina, trebuie să ne asigurăm că sunt situate pe aceeași ramură ca în depozitul la distanță. Rețineți că și aceste push trebuie să fie forțate. 6.6.2 Ștergerea ramurilor Acest lucru este simplu, se trimite cu push „nimic”, așa cum am scris înainte. Următoarea comandă elimină ramura dev din depozitul la distanță: git push origin :dev Ramurile la distanță trebuie îndepărtate atunci când ramura locală este îndepărtată. 6.6.3 Ștergerea etichetelor La fel ca și în cazul ramurilor, trebuie să împins (push) „nimic”, de exemplu: git push origin —tags :v1.0 Capitolul 7. Corecții - Patches Uneori un software poate fi actualizat cu ceva numit patch. Un patch este doar un fișier care descrie modificările care trebuie efectuate într-un program, indicând ce linii de cod trebuie eliminate și care trebuie adăugate. Cu Git, acest lucru se poate face cu un diff salvat într-un fișier. Atenție, un patch este o actualizare, dar o actualizare nu trebuie să fie neapărat un patch. Patch-urile sunt gândite pentru remedieri rapide, sau, de asemenea, caracteristici critice care trebuie să fie fixe sau puse în aplicare chiar acum, nu pentru a fi actualizarea comună și implementarea strategiei. 7.1 Crearea de patch-uri Un patch pentru Git este doar de ieșire a unui diff. Deci, ar trebui să redirecționăm acea ieșire către un fișier.: git diff <expression> > <patch-name>.patch # Extinderea nu este importantă. De exemplu: git diff master..issue-1 > issue-1-fix.patch Notă: un patch nu poate fi modificat. Dacă un fișier de corecție suferă orice modificare, Git îl va marca ca fiind corupt și nu îl va aplica. 7.2 Aplicarea patch-urilor Patch-urile sunt aplicate cu comanda apply. Este la fel de simplu ca specificarea căii către fișierul cale: git apply <patch-file> Dacă patch-ul merge bine, nu va fi afișat nici un mesaj (dacă nu ați utilizat parametrul --verbose). Dacă patch-ul nu este aplicabil, Git va arăta care sunt fișierele care cauzează probleme. Este destul de obișnuit să apară erori doar din cauza diferențelor de spații albe. Aceste erori pot fi ignorate cu parametrii --ignorespace-change și cu --ignore-whitespace. Capitolul 8. Comanda cherry-pick Pot exista unele scenarii în cazul în care suntem interesați de portarea pe o ramură doar a unui anumit set de modificări făcute pe o Tutorial Git 17/18 altă ramură, în loc de a le fuziona. Pentru a face acest lucru, Git permite commit-uri cherry-pick de pe ramuri, cu comanda cherry-pick. Mecanismul este același cu îmbinarea (merge), dar, în acest caz, se specifică id-ul commit SHA1, de exemplu: git cherry-pick -x aba6c1b # Several commits can be cherry picked. Un cherry-pick creează un nou commit, cu același mesaj ca și cel original. Opțiunea -x este pentru adăugarea la acel mesaj commit o linie care indică faptul că este un cherry-pick, de la care a fost ales commit: (cherry picked from commit aba6c1bf9a0a7d6d9ccceeab2b5dfc64f6c115c2) Cherry-pick nu ar trebui să fie o practică recurentă în fluxul de lucru, deoarece nu lasă urme în istoric, doar o linie care indică de unde a fost ales. Capitolul 9. Cârlige - Hooks Cârligele Git sunt scripturi particularizate care sunt declanșate atunci când apar evenimente importante, de exemplu, o comandă commit sau merge. Să presupunem că doriți să notificați o persoană cu un e-mail atunci când ramura de producție este actualizată sau când rulați o suită de testare; dar într-un mod automat. Cârligele pot face asta. Scripturile se află în directorul .git/hooks. În mod implicit, sunt furnizate unele cârlige eșantion. Fiecare cârlig trebuie să aibă un nume concret, pe care îl vom vedea mai târziu, fără extensie; și trebuie să fie marcate ca executabile. Puteți utiliza alte limbaje de programare, cum ar fi php sau Python, în afară de codul shell. Există două tipuri de cârlige: client-side și server-side. 9.1 Cârlige client-side Cîrligele client-side sunt cele folosite atunci când depozitul local suferă modificări. Acestea sunt cele mai frecvente: • pre-commit: invocate de comanda git commit, înainte ca să fie salvat commit-ul. Un commit poate fi anulat cu acest cârlig care iese cu o stare non-zero. • post-commit: invocate de commit, dar de data aceasta, atunci când commit a fost salvat. În acest moment, commit nu mai poate fi abandonat. • post-merge: la fel ca la post-commit, dar fiind declanșat de git merge. • pre-push: declanșat prin git push, înainte de transferul la distanță a unui obiect. Următorul script prezintă un exemplu aplicabil atât pentru cârligele pre-commit, cât și pentru cele post-commit, în scopul notificării: #!/bin/sh branch=$(git rev-parse —abbrev-ref HEAD) if [ "$branch" = "master" ]; then echo "Notifying release to everyone..." # Send the notification... fi Pentru a le utiliza pentru ambele cazuri, va trebui să creați ambele cârlige. 9.2 Cârlige server-side Aceste cârlige se află în serverul unde este găzduit depozitul la distanță. Evident, dacă utilizați servicii precum GitHub pentru găzduirea depozitelor, nu vi se va permite să executați cod arbitrar. În cazul GitHub, aveți servicii terțe disponibile, dar nu vă conectați niciodată la propriul cod. Pentru a utiliza executarea codului, va trebui să aveți propriul server pentru găzduire. Cele mai utilizate cârlige sunt: • pre-receive: acest lucru este declanșat după ce cineva face un push și înainte ca referințele să fie actualizate la distanță. Deci, cu acest cârlig, puteți nega orice push. • update: aproape exact ca mai sus, dar este executat pentru fiecare referință push. Adică, în cazul în care s-a folosit push pentru 5 referințe, acest cârlig va fi executat de 5 ori, spre deosebire de pre-receive, care este executat pentru push ca un întreg. • post-receive: acesta este executat odată ce push a avut succes, astfel încât să poată fi utilizată în scopuri de notificare. 9.3 Cârligele nu sunt incluse în istoric Cârligele există doar în depozitul local. Dacă efectuați o comandă push într-un depozit care are cârlige personalizate, acestea nu vor fi trimise la distanță. Deci, în cazul în care fiecare dezvoltator ar trebui să împartă aceleași cârlige, acestea ar trebui să fie incluse în directorul de lucru, și să fie instalate manual. Capitolul 10. O abordare a integrării continue Continuous Integration (CI) este o practică de inginerie software, care este strâns legată de versiunea de control, și merită menționată, cel puțin pentru a o cunoaște conceptual. Tutorial Git 18/18 CI constă în realizarea mai multor integrări ale codului (cel puțin, o dată pe zi), într-un mod complet automatizat, cu scopul de a găsi erori în fazele incipiente ale dezvoltării. Conceptul este aproape același cu cel al cârligelor, dar într-o manieră mult mai scalabilă și mai simplu de întreținut. Iată câteva dintre acțiunile care sunt efectuate în fiecare integrare: • Test execution - Executarea testului: unitate, acceptare, integrare, regresie, și multe altele. • Quality Assurance – Asigurarea calității, cu software-ul de verificare a măsurătorilor: complexitatea ciclomatică, acoperirea codului, și multe altele • Verificarea conformității standard de codificare a software-ului. Integrarea continuă permite efectuarea acestor acțiuni, și mai mult, în mod automat, fără a fi nevoie să fie executat manual, doar cu un push sau un commit. Interesant, nu-i așa?? Probabil, cele mai utilizate opțiuni CI sunt următoarele: • Travis CI: bazat pe cloud, care permite declanșarea integrărilor cu push, fără a fi nevoie de propriul server. • Jenkins: auto-găzduit, pentru care este necesar un server, dar care este complet configurabil. Compatibil cu multe instrumente de compilare, cum ar fi AntAnt, Gradle, Maven, Phing, Grunt... Capitolul 11. Concluzie Sistemele de control al versiunilor ajută la îndeplinirea unuia dintre visele fiecărui dezvoltator: după ce a identificat fiecare punct din întregul istorice al unui proiect, fiind capabil să se întoarcă în orice punct în orice moment. VCS pe care l-am văzut în acest ghid este Git, preferat de comunitatea dezvoltatorilor de software. După citirea acestui ghid, dacă folosiți prima dată cu Git, probabil vă veți întreba cum de ați putut trăi fără Git până astăzi. Capitolul 12. Resurse Cea mai bună resursă posibilă este Pro Git (ediția a 2-a), de Scott Chacon și Ben Straub, care acoperă aproape fiecare subiect despre Git. O puteți citi gratuit pe pagina oficială Git.