Tutorial de git. Ghid practic

Cerinte:
  1. Ati citit si inteles conceptele de baza din git.

  2. Un serviciu de git unde sa ne stocam codul (github, bitbucket, gitlab, git-ul intern de la firma, etc)

  3. SSH-ul configurat pentru acces la serviciul de git (vezi tutorial) preferabil cu OpenSSH.

  4. Rabdare si “tutun” si ceva giga de spatiu pe HDD.

In principal instructiunile de mai jos sunt pentru windows. Pe linux, instalati git-ul si cam gata. IDE-urile ar trebui sa il gaseasca implicit.

Tool-uri

Recomand sa instalati notepad++ ca editor de text generic. Poate deschide mult mai multe formate ca notepad-ul clasic din Windows si e si mai prietenos.

  • Notepad++

  • git-scm

  • SourceTree | GitExtensions | TortoiseGit | GitFork | tig | GitHub Desktop

  • kdiff3

  • IDE puternic

git-scm

Insturmentul de baza, este, evident, git-ul. TOATE tool-urile si IDE-urile se asteapta sa fie instalat pe masina pe care lucrati.

Atat pe linux cat si pe windows, folosim git-scm de aici: https://git-scm.com/ Pe Linux, se instaleaza (daca nu e deja instalat) cu package manager-ul. (Ex: sudo apt-get install git pentru Debian like)

Cautam pe https://git-scm.com/ versiunea pentru OS-ul nostru si instalam. Pentru windows, lasam majoritatea optiunilor implicite.

Trebuie avut grija ca la clientul de SSH sa fie OpenSSH si nu Putty. Selectati Putty doar daca stiti ce faceti (aka ati instalat Putty ca si client de SSH)

Dupa ce s-a termiant instalarea, deschideti o consola git (eg Git CLI din windows) sau o consola normala din Linux si tastati:

git --version

Daca arata ok, inseamna ca ati instalat git-ul.

Git GUI

Git-ul poate folosit si doar din linia de comanda. Dar NU va recomand, mai ales la proiecte unde lucreaza mai mult de o persoana.

E util sa stii sa dai un commit sau sa faci un push din linia de comanda, cand esti pe un workstation cu acces doar in mod consola, dar, cand lucram zi de zi, o imagine a branch-urilor si a status-ului curent face cat 3 nopti petrecute sa refaci prostiile facute cu o comanda git gresita. (been there, done that)

GitExtensions

Pe Windows, o optiune incercata (de mine) e GitExtensions.

Instalati de la: https://github.com/gitextensions/gitextensions/releases/latest

Apoi, porniti interfata grafica si mergeti la Settings. Neaparat sa va treceti adresa de mail si numele. Acestea vor fi vizibile pe fiecare commit pe care il veti face. Se poate si global (din Settings) sau pe fiecare repo in parte (eg aveti si un repo de la firma pe calculator). Neaparat numele sa fie real.

SourceTree

O optiune (re)descoperita recent este SourceTree de la Atlassian. Arata MULT mai bine decat GitExtensions.

Ea se bazeaza tot pe git-scm (trebuie sa fie instalat si configurat). Spre deosebire de GitExtensions are cateva dezavantaje:

  • Nu merge fara cont pe bitbucket

  • Trebuie configurat manual modul de autentificare (OpenSSH) si specificat manual cheia.

  • Nu e asa de incercat in practica, cel putin de mine.

Si aici, trebuie configurate global numele si email-ul celui care lucreaza. Acest lucru se salveaza la nivel de git. Ca si la GitExtensions, ne apare zona de staging, spearat. Din pacate si SourceTree se bazeaza pe un diff/merge extern.

Merge tool

Acest instrument este esential. Trebuie sa fie usor de folosit, intuitiv, si sa isi faca treaba bine. Personal, recomand sa folositi IDE-ul pentru merge si pentru conflicte.

Avand in vedere ca toate IDE-urile folosesc git-ul, cand exista un merge conflict, acesta va fi detectat si de IDE si de tool-ul vizual. Astfel il puteti vizualiza poate si in IDE (syntax highlight, etc) si nu doar in kdiff3.

Totusi, ca backup, va recomand sa instalati kdiff3: https://sourceforge.net/projects/kdiff3/files/

Dupa ce il instalati, configurati de ex GitExtensions ca sa il foloseasca ca si tool de diff si merge

Ca si avertisment, incercati sa folositi doar un singul tool pentru interactiunea cu git-ul. Ori doar din IDE, ori doar din instrumente grafice externe. Cateodata, GitExtensions nu isi da seama ca un conflict a fost rezolvat si blocheaza merge-ul. (aici 1:0 pentru SourceTree) Din pacate, nu toate IDE-urile sunt la fel de puternice, unele au suport minimal pentru git. Asa ca e util sa avem variante.

IDE-uri

IDE-uri incercate (de mine) care au destul de bine integrat tot tooling-ul de git (si fac instrumente ca GitExtensions redundante):
  • Eclipse IDE: C/C++, Java

  • IntelliJ, PyCharm de la JetBrains

  • Suport partial prin plugin-uri: Visual Studio Code, Atom,

  • Suport inca in dezvoltare de la Visual Studio 2019, Community Edition

Workflow

Avem acces prin ssh, avem tool-urile instalate, sa vedem un exemplu de utilizare.

One time setup

Primul pas, e sa avem un repo remote. Adica, un repo facut pe github (sau pe alt serviciu de git). Posibil ca acesta sa fie facut deja de profesor. Daca nu, din github, in dreapta, apare Repositories si se alege New

Apare fereastra de mai jos:

_images/github_new_repo.png

Recomand sa bifati Initialize this repository with a README si sa puneti o licenta GPL v3. Puteti sa selectati si un .gitignore pentru limbajul dorit. Acesta oricum o sa il editam mai incolo.

Bun, am dat New, si s-a creat repo-ul

Acum trebuie sa il clonam local, sa putem lucra. Mergeti in Clone or download si selectati Clone with SSH:

_images/github_clone_or_download.png

Copy paste la URL-ul de acolo (sau click pe butonul de clipboard)

Apoi, deschideti GitExtensions si alegeti Clone Repository. Va populeaza automat din clipboard URL-ul repo-ului:

_images/github_gitext_01.png

Alegeti un folder pe disc unde va fi clonat repo-ul. NU pe desktop. Daca aveti passphrase pe cheia de SSH, acum va va fi ceruta.

Apare o fereastra in care arata progresul. Daca la sfarsit scrie “Done”, totul este ok. Daca nu, GOOGLE/intrebati.

Se va deschide pagina principala din GitExtensions in care arata toate detaliile. (1) Branch-ul in care suntem, (2)serverele remote care sunt urmarite, (3) graficul commit-urilor, precum si daca avem sau nu fisiere de commit-uit (4):

_images/github_gitext_02.png

Bun, cu gitextensions am cam terminat deocamdata, acum ne putem apuca de lucru.

Pentru SourceTree, elementele sunt cam la fel. Doar aranjarea vizuala e diferita.

Lucrul la cod

Primul lucru, inainte sa scriem un caracted in cod, ne asiguram ca nu sunt modificari pe server, care nu sunt si local. Facem un git pull de tip merge (va ramane selectat implicit la buton)

_images/github_gitext_03.png

Apoi facem branch nou: CTRL+B sau Commands->Create Branch...

Aici dam un nume de tipul: initiale SLASH nume_descriptiv. Fara spatii. Exemplu: cv/modific_readme.

Observam schimbarea branch-ului pe care suntem. Cum nu am facut modificari pe acest branch, si master-ul pointeaza tot spre acelasi commit din history:

_images/github_gitext_04.png

Acum putem modifica textul din fisierul readme.md, adauga fisiere noi, etc.

Dupa ce ne-am terminat treaba, verificam ca totul sa fie ok dpdv al codului (eg rulam teste) putem sa commit-uim munca.

In GitExtensions ne apare butonul Commit si cate fisiere ar trebui commit-uite.

Dam pe buton si ne apare fereastra de commit.

_images/github_gitext_05.png

[In SourceTree, modificarile apar mai “human readable”.]

Mai tineti minte zona de staging, care ziceam ca e “ascunsa” de majoritatea IDE-urilor? Ei, GitExtensions nu o ascunde. Trebuie sa dati pe sageata de Stage, apoi sa scrieti un mesaj de commit (cateva linii in care sa descrieti ce ati facut)

De obicei, la firme, exista un protocol mai complex si un format standard la commit. Aici deocamdata nu ne complicam.

Bun, am dat Stage am scris si un mesaj informativ, e timpul de commit. Puteti sa si dati commit & push pentru a trimite modificarile la server.

Odata ce avem munca incheiata e timpul sa facem merge in branch-ul principal. Adica, sa imi pun munca la un loc cu a colegilor.

Pasul 1 e integrarea ultimelor modificari in codul nostru.

Pentru inceput trebuie sa ne asiguram ca nu sunt modificari pe master, care sa nu fie prezente la noi. Din GitExtensions se face un merge al branch-ului master in branch-ul nostru:

_images/github_gitext_06.png

Trebuie ca branch-ul curent sa fie cel in care lucram. Click dreapta pe master si alegem Merge into current branch. Daca avem conflicte, le rezolvam. Testam din nou codul, sa nu fie ceva crapat.

Acum suntem pregatiti de pasul 2, integrarea in branch-ul principal.

Recomand sa faceti merge request din interfata web. Aici difera un pic notiunile cat si interfata exact. Pe github, navighez la Branches, aleg branch-ul pe care am lucrat si aleg New Pull Request. Ideal, github-ul ar trebui sa zica ca branch-urile se pot merge-ui automat. Se scrie un mic mesaj si se da Create Pull Request Ideal, unul din colegi ar trebui sa se uite pe modificari pentru a se familiariza cu ce anume s-a modificat.

Mai jos, un exemplu de merge(pull) request de pe github:

_images/github_gitext_07.png

La proiecte mai mari, exista si un server ce testeaza automat daca ceea ce vreti sa introduceti pe master nu strica codul existent. Serverul va rula toate testele, cu codul vostru integrat. Deocamdata nu avem asa ceva, dar, e important de stiut. Se poate configura serviciul de git astfel incat merge request-ul sa nu fie acceptat pana nu a luat Ok-ul de la serverul de test.

De obicei branch-ul de lucru trebuie sters. Dupa ce s-a acceptat merge-ul, puteti reveni in GitExtensions si sa faceti pull. Apoi va mutati pe master (Faceti checkout la master).

Se poate sa creati un nou branch, din branch-ul curent, fara sa mai asteptati sa se aprobe merge-ul. In general nu e recomandat si trebuie sa fiti foarte atenti cand faceti merge-uri din si in master, la ce anume faceti merge.

.gitignore

Ultima componenta la care trebuie sa fiti atenti e acest fisier de configurare. Il gasiti (sau il creati) in radacina proiectului. Acesta contine extensiile si folderele care NU vreti sa va apara in git si/sau sa fie urmarite de git.

Aici se trec configurarile IDE-ului (de ex folder-ul .idea/ pentru JetBrains), fisiere binare (.exe, .obj), directori de build (build/) etc etc etc. Din nou, orice este generat automat trebuie sa stea afara din git. Daca includeti astfel de fisiere, spatiul de lucru va fi foarte poluat! Colegul vostru va avea 10000 de mii de conflicte, 99.99% complet irelevante, pe fisierele binare. Iar conflictul de pe fisierul sursa, acela care conteaza a fi rezolvat, va fi ignorat. De unde tone de frustrari si discutii condimentate pe chat.

Cand faceti un commit si va apar fisiere suspecte, notati extensia/folderul fisierului, dati cancel si deschideti .gitignore. Adaugati acolo extensia (ex *.exe).

Mai multe detalii despre cum ignorati foldere, exceptii, etc. in documentatia oficiala (https://git-scm.com/docs/gitignore) sau tutoriale de pe net.

De retinut, daca e fisier ce se modifica manual (eg codul sursa) acesta va trebui versionat. Daca este ceva autogenerat atunci va trebui trecut in .gitignore.

Binarele mari, chiar daca sunt generate “manual” vor trebui si ele sa traiasca departe de git. Mai ales la proiectele de ML, avem fisiere (nu neaparat binare) mari. Acestea de preferabil sa se stocheze in afara directorului de lucru.

Checklist cand lucram:

  • git pull cum se incarca OS-ul.

  • Verificam sa fie totul ok (fara conflicte)

  • Rulam codul si teste daca avem

  • Facem un branch nou

  • Codam ce avem de codat

  • git commit cand facem un pas mai important

  • cand am terminat de codat, verificam sa mearga codul,

  • facem commit la ce am lucrat,

  • facem merge din master la noi in branch si rezolvam conflicte daca sunt. Neaparat re-verificam codul.

  • git push

  • merge request | pull request | merge la branch-ul nostru, in master, odata ce avem o siguranta mai mare.

Flow alternativ

Unii colegi mi-au recomandat sa povestesc si despre rebase. Pentru mine a fost o experienta neplacuta, dar, experienta voastra poate fi diferita.

Rebase pe scurt (google it pentru mai multe detalii):

Cand aduceti modificarile de pe master, v-am recomandat sa le aduceti cu git merge. Dar mai e o alternativa, si anume, rebase-ul. Acesta ia toate modificarile facute, aduce modificarile de pe master (zero conflicte pentru ca stie de unde si pana unde sunt modificarile noastre) le aplica pe working tree apoi pune pe rand, modificarile (commit-urile) noastre.

The catch: Se modifica istoria. Daca modificarea e locala, totul e ok. Daca insa am facut push la vreun commit, adio rebase. Vor incepe tot felul de erori si conflicte de merge.

Modul meu de lucru presupune ca regulat fac push si lucrez cand pe o masina cand pe alta, asa ca, rebase, pentru mine, nu prea are sens. Dar, pentru alt mod de lucru, s-ar putea sa fie mai ok. Experimentati daca puteti.

Tips and tricks pentru lucru fara frictiuni

Conflictele dese de merge, indica un cod mai putin bine structurat. Va recomand cu foarte mare caldura sa reluati cateva concepte de OOP, sa dati pe google despre SOLID, DRY, KISS si YAGI (in context de oop). Chiar daca faceti programare functionala sau doar procedurala, o organizare mai buna a codului va ajuta. Aveti de scris cod care sa incarce o poza, sa aplice 3 metode si apoi intreaba daca salveaza sau afiseaza? Well, deja sunt cateva responsabilitati independente. “Spargeti” codul in 2-3 functii.

De obicei merge conflict apare cand sunt doua editari pe acelasi fisier. In Java este cumva “obligatoriu” sa am fisiere separate pentru fiecare clasa, in C++ nu ne obliga nimeni, dar se poate.

Poate ca programul la care lucrati are nevoie de resurse binare. Nu la toata lumea caile spre resurse sunt identice. Daca le scrieti direct in cod (eg char *cale = "D:\\lucru\\vlad\\binarele_mele"; s-ar putea sa apara conflicte cand un alt coleg pe care nu il cheama Vlad, ia codul si il pune la el pe masina. O solutie eleganta este ca aceste “setari” sa fie incarcate dintr-un fisier local, fisier ce este trecut in .gitignore. Acest fisier se seteaza in momentul cand se face clone la proiect Treceti explicatii si model de fisier in readme si gata. Fiecare va fi vesel cu setarile locale.

Pentru proiecte foarte mici, overhead-ul citirii unui fisier de configurare e mare, asa ca o solutie quick and dirty este sa aveti o structura de if-uri care seteaza aceste constante. Ori cu #ifdef si declarati din configurarile IDE-ului, sau o variabila la nivelul sistemului de operare ($DEV="POPESCU" si in cod cititi variabila sistem numita DEV). Puteti citi ceva ID unic pentru calculator (eg hostname) si sa va legati de el caile. Aici decizia depinde de limbajul de programare folosit, de perspectiva de crestere a proiectului, etc etc.

Atentie! Nu scrieti parole, chei de acces token-uri in surse!!!

Alte observatii

Exista o gramada de moduri de a organiza history-ul si de a scrie mesajele de commit. Sau modul de lucru. Unii folosesc rebase-ul in loc de merge, sau pun link la issue in commit, sunt servere de test care verifica merge-ul, squash la branch, etc etc etc.

Ce am pus mai sus este un mod minim de a folosi git-ul, util pentru proiecte de laborator si/sau proiecte mici.

Deasemenea, nu am atins elemente puternice din git ca git reset, git bisect, git stash, etc.