Jei kada nors dirbai su kodu bent kiek rimčiau, tikriausiai jau susidūrei su Git. Ir jei nesi susidūręs — tai tik laiko klausimas. Šiandien sunku įsivaizduoti bent kiek save gerbiantį programuotoją, kuris nemoka bent minimalaus Git rinkinio. Bet čia yra vienas įdomus paradoksas: daugelis žmonių Git naudoja jau metų metus, o supranta gal 15 procentų to, ką jis iš tikrųjų sugeba. Naudoja tris ar keturias komandas kaip ritualą, nesuprasdami kas vyksta po apačia, ir kai kas nors sugenda — panika, googlinimas, kartais net repo trynimas ir klonavimas iš naujo (taip, žinau žmonių, kurie taip daro reguliariai).
Šitame straipsnyje noriu praeiti per komandas, kurios tikrai verčia įsigilinti, jei nori jaustis Git valdovu, o ne jo įkaitu. Ne viskas čia bus paviršutiniška — pabandysiu paaiškinti ir kodėl kai kurios komandos veikia taip, kaip veikia.
Pradžia be baimės: init, clone ir status
Skamba banaliai, bet git status yra ta komanda, kurią turėtum rašyti maždaug kas 30 sekundžių dirbdamas su repozitorija. Rimtai. Žmonės, kurie nesinaudoja status reguliariai, dažniausiai ir susikuria sau problemų — pamiršta, kokioje šakoje yra, nepastebi, kad failas liko nepridėtas commit’ui, arba nesupranta kodėl push’as neveikia.
git init sukuria naują repozitoriją lokaliai. Paprasta, bet svarbu žinoti, kad tai sukuria .git aplanką, kuriame ir slypi visa Git magija — visos versijos, visa istorija, visi objektai. Jei kada nors ištrini šitą aplanką — viskas, istorijos nebėra, liks tik dabartinė failų būsena.
git clone [url] — tai komanda, kurią naudoji kai nori parsisiųsti jau egzistuojantį repo iš GitHub, GitLab ar kur kitur. Svarbu suprasti: clone atsineša ne tik dabartinę versiją, bet visą istoriją, visas šakas (branches), visus tag’us. Tai reiškia, kad jei projektas turi 5 metų istoriją su tūkstančiais commit’ų, tu parsisiunti viską. Kartais tai gali užtrukti, ypač su dideliais repo — tada gelbsti --depth parametras, kuris leidžia atsisiųsti tik paskutinius kelis commit’us, be visos istorijos.
Add, commit, push — šventoji trejybė
Čia prasideda tikrasis darbas. git add perkelia pakeitimus į vadinamąją staging area — tarpinę zoną prieš commit’ą. Daug kas naudoja git add ., kas prideda absoliučiai viską, kas pasikeitė. Tai patogu, bet ir pavojinga — labai lengva netyčia pridėti failus, kurių tikrai nereikėtų (slaptažodžius, .env failus, laikinus build failus). Todėl geras įprotis prieš add naudoti git status ir pasižiūrėti, kas iš tikrųjų keičiasi.
git commit -m "žinutė" — čia užfiksuoji pakeitimus lokaliai. Ir štai kur žmonės dažnai klysta: rašo commit žinutes tipo „fix” arba „update” arba, dievai saugok, „asdasd”. Po metų, kai reikės suprasti kodėl kažkas pakeista, tokia žinutė bus visiškai bevertė. Gera commit žinutė turi paaiškinti KODĖL, ne tik KĄ. „Pataisytas login bug’as” yra prastesnė žinutė nei „Pataisytas login bug’as, kuris atsirado dėl netinkamo token’o validavimo edge case’e kai vartotojas turi tuščią sesiją”.
git push siunčia lokalius commit’us į nuotolinį repo (dažniausiai GitHub ar GitLab). Čia svarbu suprasti sąvoką „remote” — tai nuoroda į nuotolinį serverį, kur laikomas jūsų kodas. Standartinis remote pavadinimas yra „origin”, bet gali būti ir kitų, jei dirbate su fork’ais ar keliais serveriais.
Šakos: čia prasideda tikroji galia
Jei Git naudoji tik main šakoje ir niekada nekuriate branch’ų — jūs iš esmės naudojate 10% to, ką Git gali pasiūlyti. Šakos (branches) yra tai, kas leidžia komandoms dirbti lygiagrečiai, nesumaišant vieni kitų darbo.
git branch [pavadinimas] sukuria naują šaką. git checkout [pavadinimas] arba naujesnis git switch [pavadinimas] perjungia į ją. Beje, naujesnė switch komanda buvo sukurta specialiai todėl, kad checkout darė per daug dalykų vienu metu — ir šakas keitė, ir failus atstatinėjo — todėl kilo painiava. Jei naudoji naujesnę Git versiją, rekomenduočiau įprasti prie switch ir restore, jos aiškesnės.
Labai patogus trikas: git checkout -b nauja-saka sukuria ir iškart perjungia į naują šaką vienu ėjimu. Panašiai git switch -c nauja-saka.
Kai darbas baigtas, šakas reikia sujungti — tam skirtas git merge. Bet čia dažnai atsiranda konfliktai, kai tas pats failas buvo keičiamas skirtingai skirtingose šakose. Konfliktų nereikia bijoti — Git tiesiog parodo abi versijas failo viduje su specialiais žymekliais (<<<<<<<, =======, >>>>>>>), ir tavo darbas — nuspręsti, kuri versija (arba abi sujungtos) turėtų likti.
Rebase vs merge — amžinas debatas
Štai tema, dėl kurios komandose kyla beveik religinių ginčų. git rebase daro tą patį, ką ir merge iš esmės — sujungia pakeitimus iš skirtingų šakų. Bet daro tai kitaip: vietoj to, kad sukurtų naują „merge commit”, jis perrašo istoriją taip, tarsi jūsų pakeitimai būtų padaryti ant naujausios main šakos versijos.
Rezultatas — švaresnė, linijinė istorija. Nėra tų šakotų grafikų su daugybe merge commit’ų, kurie kartais atrodo kaip metro schema. Bet yra ir kaina: rebase perrašo commit’ų hash’us. Jei jau pushinai savo šaką ir kas nors kitas iš jos traukia (pull), o tu po to darai rebase ir vėl pushini — prasideda chaosas, nes istorijos nebesutampa.
Todėl auksinė taisyklė: nedaryk rebase ant šakų, kurios jau yra bendros su kitais žmonėmis, nebent tikrai žinai ką darai. Savo asmeninėje feature šakoje prieš merge’inant į main — rebase yra puikus įrankis susitvarkyti istoriją prieš ją parodant kitiems.
Kai reikia grįžti atgal: reset, revert ir reflog
Anksčiau ar vėliau, kažką sugadinsi. Tai neišvengiama, ir tai visiškai normalu. Svarbu žinoti, kaip iš to išsikapstyti.
git reset leidžia grįžti atgal į ankstesnę būseną. Yra trys pagrindiniai variantai: --soft (grąžina commit’us, bet palieka pakeitimus staging area), --mixed (numatytasis, grąžina commit’us ir pakeitimus iš staging, bet failai lieka pakeisti) ir --hard (viską ištrina, grąžina viską į tą būseną, kokia buvo tame commit’e — su šituo reikia būti itin atsargiam, nes pakeitimai tiesiog dingsta be pėdsakų).
git revert veikia kitaip ir dažnai yra saugesnis pasirinkimas viešose šakose. Vietoj to, kad ištrintų commit’ą, jis sukuria naują commit’ą, kuris atšaukia ankstesnio pakeitimus. Istorija lieka pilna ir sąžininga — matosi, kad kažkas buvo padaryta ir po to atšaukta, o ne tiesiog dingsta tarsi niekada nebuvo.
O čia dabar mano mėgstamiausia dalis — git reflog. Tai komanda, kuri gali išgelbėti gyvybę (na, bent jau projektą). Reflog rodo visą HEAD judėjimo istoriją — visus checkout’us, reset’us, commit’us, net tuos, kurie tarsi „dingo” po hard reset’o. Jei kada nors netyčia padarei git reset --hard ir prarandi darbą, kurio dar nespėjai push’inti — reflog dažnai leis rasti tą „prarastą” commit’ą ir jį atstatyti. Tai kaip Git slaptas saugumo tinklas, apie kurį daugelis net nežino.
Stash — kai reikia greitai pasislėpti
Situacija: dirbi prie kažko, staiga reikia skubiai perjungti šaką ir pataisyti kritinę klaidą kitur, bet tavo dabartiniai pakeitimai dar nebaigti ir jų nenori commit’inti. Čia atsiranda git stash.
Ši komanda paima visus nesubaigtus pakeitimus ir „paslepia” juos į tarsi laikinąjį stalčių, grąžindama darbo katalogą į švarią būseną. Gali laisvai keisti šakas, daryti ką reikia, o vėliau git stash pop grąžina viską atgal ten, kur buvai sustojęs.
Galima turėti kelis stash’us vienu metu — git stash list parodo visus, o git stash apply stash@{2} leidžia pritaikyti konkretų iš sąrašo. Vienintelis dalykas, dėl kurio žmonės dažnai suklysta — stash neišsaugo naujai sukurtų (untracked) failų pagal nutylėjimą. Jei reikia įtraukti ir juos, naudok git stash -u.
Log ir diff — detektyvo įrankiai
Kartais reikia suprasti, kas ir kada keitė kodą, ir kodėl. git log parodo commit’ų istoriją, bet numatytasis vaizdas gali būti gan sunkiai skaitomas ilgoje istorijoje. Rekomenduoju paeksperimentuoti su git log --oneline --graph --all — tai suspaudžia kiekvieną commit į vieną eilutę ir vizualiai parodo šakojimąsi grafiku. Kai pirmą kartą pamatai savo projekto istoriją tokiu būdu — daug kas atsiveria akyse.
git diff parodo skirtumus tarp failų versijų. Be argumentų jis rodo nesubaigtus (unstaged) pakeitimus. Su --staged arba --cached — parodo tai, kas jau paruošta commit’ui. O git diff commit1 commit2 leidžia palyginti bet kuriuos du commit’us, net jei jie visiškai skirtinguose taškuose istorijoje.
Dar viena nepelnytai užmiršta komanda — git blame [failas]. Ji parodo, kas ir kada pakeitė kiekvieną failo eilutę. Skamba kaip kaltinimo ieškojimo įrankis (na, pavadinimas tikrai apie tai užsimena), bet realybėje tai neįkainojama, kai reikia suprasti, kodėl kažkoks keistas kodo gabalas apskritai atsirado — dažnai commit žinutė ar susijęs ticket’as paaiškina logiką, kuri iš pirmo žvilgsnio atrodo beprasmiška.
Vietoj išvadų — kelios gyvybiškai svarbios pamokos iš praktikos
Jei iš viso šito straipsnio reikėtų išsinešti tik kelis dalykus, čia jie:
Pirma — commit dažnai ir mažais gabalėliais. Didelis vienas commit su 40 pakeistų failų ir žinute „big update” yra košmaras bet kam, kas vėliau bandys suprasti istoriją ar grąžinti konkretų pakeitimą. Maži, loginiai commit’ai su aiškiomis žinutėmis — tai investicija į ateitį, kuri atsipirks kai mažiausiai to tikėsiesi.
Antra — prieš darant bet ką destruktyvaus (reset --hard, rebase, force push), pagalvok dukart. O jei jau padarei ir kažkas nutiko ne taip — nepanikuok, git reflog greičiausiai turi atsakymą. Git retai kada tikrai negrįžtamai ką nors sunaikina, nebent tyčia paleidi garbage collection’ą ar ištrini .git aplanką.
Trečia — .gitignore failas nėra prabanga, tai būtinybė. Sukurk jį pačioje projekto pradžioje ir iškart įtrauk build aplankus, node_modules, .env failus, IDE konfigūracijas. Tai išgelbės tave nuo situacijos, kai netyčia pushini API raktus į viešą repo (tai vyksta dažniau, nei norėtum tikėti, ir yra viena dažniausių saugumo klaidų realiame pasaulyje).
Ketvirta — nebijok eksperimentuoti su Git komandomis lokaliame testiniame repo. Sukurk tuščią aplanką, paleisk git init, ir žaisk su rebase, reset, merge konfliktais be baimės ką nors sugadinti realiame projekte. Būtent taip Git iš baisaus, nesuprantamo įrankio virsta antra prigimtimi — per praktiką, ne per teoriją.
Ir paskutinis dalykas — Git yra vienas iš tų įrankių, kurį iš tikrųjų verta pramokti giliau, nei minimaliai reikia darbui. Kiekviena valanda, investuota į supratimą, kaip Git veikia po apačia (o ne tik kokias komandas rašyti į terminalą), grąžins dešimteriopai, kai ateis diena, kai kažkas tikrai sugadinta ir reikia greito, tikslaus sprendimo, o ne panikos ir naujo klonavimo iš nulio.






