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.