Strategia „corectă” Git pentru echipe și staging/producție

Până acum nu am avut nevoie de o strategie foarte bine definită: ori eram singurul dev implicat activ în proiect, ori se lucra pe un singur branch, ori nu aveam mai multe medii (staging/live).

Doar că acum le am pe toate:

  • staging/master (în funcție de branch-ul pe care se face push, se duce în staging sau live)
  • 2 devs momentan, urmează încă 2-3 în viitorul apropiat
  • folosim bitbucket (decizii interne, nu am control asupra acestui lucru)

Cum procedăm acum (pe scurt, un soi de git-flow):

  1. master este protected, singurul mod în care ajunge cod acolo este prin PR
  2. pentru orice fix ce trebuie să ajungă ieri în producție, facem branch din master (e.g. hotfix/foo), rezolvăm, trimitem PR tot în master
  3. pentru orice feature/fix ce trebuie să ajungă cândva în producție, facem branch din master (e.g. feat/bar, fix/baz), trimitem PR în staging.
  4. când suntem gata de release, trimitem PR din staging în master.
  5. unul dintre noi face merge din master în staging pe local și face push.

Problemele de care ne-am lovit sunt următoarele:

  • un feature din staging depinde de un fix din prod
  • după ce un PR este merged în staging, găsim o problemă care are nevoie de un alt PR (presupun că ar trebui să tratăm asta ca un fix, dar mi se pare peste mână)
  • cea mai mare bubă: în ziua de release se întâmplă ca unele features/fix-uri ar trebui să ajungă în master dar altele nu.
  • legat de punctul anterior: uneori este nevoie să renunțăm la un feature complet, dar deja a fost merged în staging și altele după el.
  • mi se pare că history este poluat și greu de citit cu toate aceste PR-uri.
  • la unele branch-uri este absolut esențial să se păstreze TOATE commit-urile (pentru bisect), la altele se poate face squash.

Sunt sigur că nu sunt singurul cu problema asta și că sunt printre voi oameni care au rezolvat demult această provocare. Idei?

2 Likes

pana ajung la computer sa elaborez (candva in uichend), niste material de parcurs poate ajuta Branching strategies: choose wisely to minimise costs @phpDay Verona 2017 - Speaker Deck

short answer: nu exista “o” strategie “corecta”

intrebari:

  • monorepo asta descris e singurul implicat? sau e mai mare un pic sistemul
  • cate componente sunt real in monorepo? se poate sparge (e voie etc)?
  • cat e de mare un feature, pe cate commituri se poate intonde?
  • aveti voie sa rebase? chiar si selectiv (eu de ex nu am voie)

mai am, dar revenim

3 Likes

Știu, tocmai de aia am pus în ghilimele :smiley:

Sistemul e mai mare, sunt vreo 5 repo-uri, dar sunt rezonabil de decuplate între ele încât le putem ignora pe restul. Deci să zicem că este un singur repo.

OHOOO.

  • În primul rând avem WordPress.
  • Apoi avem plugin-urile stock. (presupun că astea am putea să le considerăm un singur repo)
  • Apoi avem câteva teme custom. (~10)
  • Apoi avem câteva plugin-uri custom (15-20).

Deci ar fi spre 30 de componente lejer.

Dar din păcate nu se poate sparge ușor, că ăsta a fost și primul meu gând (din cauza modului în care a fost gândit sistemul de deploy). Poate reușesc să conving organizația să se schimbe sistemul, dar e o chestiune care SIGUR o să dureze luni până va fi implementată.

Cel mai mic feature a fost un commit de câteva linii. Cel mai mare… s-a întins pe vreo două săptămâni și s-a lucrat la foc continuu, 10+ commits/zi.

Teoretic avem voie să orice, cât timp nu strică producția la modul FUBAR și cât minimizăm conflictele.


Te-am găsit și în varianta video :smiley:

3 Likes

Fiecare feature pe branch separat, merge cu master cand e gata, versionare cand e un master gata de productie pentru a te putea intoarce la el cand e nevoie de patch-uri.

1 Like

Păi așa am zis că facem acum, dar simt că s-ar putea un pic mai bine…

Dacă e monorepo:

  1. Evitați feature branches, ca să lucrezi creezi local un branch din main (nu contează numele), folosești mereu rebase, faci push și creezi imediat PR/MR-ul și dai squash la commits mereu la merge (nu îți trebuie history detaliat dacă nu modifici 100 de fisiere pe fiecare branch) . Un branch nu ar trebui să dureze mai mult de câteva zile. (Ștergi branch-ul la merge)
  • Feature branches sunt moartea CI/CD-ului, în special dacă folosiți Jenkins (indexează fiecare branch și indexarea ia workeri fără să fie util, dacă ai prea multe branch-uri ocupi fiecare worker cu indexarea la un branch nou fiindcă rulează pe toate branch-urile existente nu doar cel nou)
  1. Evitați staging, folosiți direct master/main/develop și implementati feature flag-uri. În acest fel sunteți mereu gata de release. Utilizați un bot care să pună rezultatele de la build-ul pentru qa într-un chat ca să puteți tag-ui echipa care a stricat ceva.
  2. Feature branch-urile implica release train-uri (trebuie merge-uite PR-urile in ordine), poți crea situații în care un branch singur să fie ok și când se combina cu alt branch pe trunk să ai probleme. Cu cât sunt mai mici, cu atât scad sansele de a avea combinații cu probleme.
  3. Feature branch-urile lungi sunt imposibil de verificat cum trebuie la code review, nu sta nimeni să se uite linie cu linie la 150 de fisiere schimbate. Distruge calitatea sau dacă cineva se uită și pune 200 de comentarii autorul o să își taie venele după.
    Mai dă posibilitatea ca cineva să scadă tot code coverage-ul atât de tare încât să fie la limită pentru toți ceilalți după ce e merge-uit și toată lumea suferă.

Altele:

  • Fiecare PR devine mai mare dacă lucrezi exclusiv per feature/fix și e mai greu să rezolvi conflictele cu rebase, creezi un PR cum ai făcut ceva și ai unit teste, nu trebuie să fie feature complet
  • Poți face tag la un commit pentru a crea o versiune în git (pentru QA/staging/prod), se poate rula un pipeline la tag
  • Puteți utiliza conventional commits și git hooks local pentru a avea commit-uri utilizabile în rapoarte generate pentru Jira la un release (util pentru un Change Release Board) Poți pune inclusiv un regex la validarea numelui la commit in proiect la bitbucket/gitlab/github.
  • Pui id-ul tichetului din Jira în commit
  • Ca să nu ai probleme cu git rebase pe branch folosești git commit cu amend si git push -f, in acest fel reduci drastic din numărul de commit-uri și nu îți dă același conflict la 10 commit-uri

Câteva comenzi utile: git rebase -i , git cherry-pick , git commit --amend

4 Likes

In situatia ta, in care ai un monorepo si putini devoloperi care contribuie eu as organiza asa:

Branch-ul de dev este master (e mai simplu ca el sa fie branch-ul care avanseaza repede). Fiecare developer isi face branch pt bug/feature, rezolva si face PR in master. Ajuta, daca e un feature mai mare, sa exista disciplina si fiecare sa isi faca pull foarte des din master. Din moment ce folosesti Bitbucket, eu as pune hook la merge in master sa fie fast-forward only by default (si sa am si optiunea de squash-merge). Daca sunt multe commits, e de preferat un squash merge (pentru istoric mai clean - chestia asta e subiectiva). Personal prefer sa fie facut “squah-ul” dupa code review (pt ca mai apar commits de rework care polueaza istoria) pe branch-ul de feature/fix si nu la merge deoarece Bitbucket nu te lasa sa ai acel commit cu mesaj nice. In final, cand se da merge ideal e sa ai un nr mic de commits si sa fie fast-forward.

In functie de cat de avansati sunteti cu CI/CD master poate fi si staging (merge doar daca nu se dezvolta activ features mari).

Cand master este intr-o stare buna, faci tag la acel commit si creezi un nou branch din acel tag care este branch-ul de mententanta de staging - sa zicem myapp_vxxx_staging. La tag, ai hook si mediul de staging se muta la acel tag si se face deploy. In caz ca sunt pb pe staging, se fac hotfix-uri pe acel branch si cherry-pick in master obligatoriu. Fix-urile pe branch-ul nou de staging se fac cu PR si eventual poti sa ai hook sa te oblige sa faci commit si in master, poti lega prin version tags in Jira etc.

Master-ul o ia inainte si cu alte features. Cand branchul de mentenanta de staging e gata de mutat in prod, faci un nou tag pe el cu myapp_vxxx_prod de ex si se face deploy pe prod din el. Acel branch acum este si pe staging si pe production. Devine automat branch-ul de mentenanta de prod. Aceeasi strategie se aplica, daca apar probleme de prod, se face fix in el si cherry-pick in master.

Acum master avanseaza, se fac noi features si poti sa faci un nou tag si se muta staging la el. Fix-uri de stabilitate pe el, tag nou de prod, mutat prod la acel tag, rinse and repeat.

Strategia asta presupune ca staging este un mediu de pre-release si ai si un deploy de “dev” sa zicem care merge o data cu masterul si care poate sa crape.

2 Likes

Să vă zic cum e la mine la $WORK. Pt partea de backend avem un monorepo pe github, cu CircleCI și SonarCube. (Aplicația e construită pe principiile microserviciilor).

Orice bug sau feature branch-ul lui iar la orice push CircleCI rulează toate testele iar SonarCube se asigură că testele au un anumit coverage (să zicem 70%)

Cînd totul e gata, se crează un PR, urmat de code review. Dacă totul e ok, codul din PR este mergiuit în master, CircleCI rulează testele, crează imaginile docker (este un proces care detectează care imagini sînt de recreat) și updateză pod-urile din dev (k8s). În același timp avem o suită de teste de integrare care rulează la fiecare 2 ore.

Echipa de QA decide cînd facem deploy la imaginile de la dev la stage și de la stage la prod (de obicei le ținem 24 de ore într-un loc înainte de deploy), mai puțin vinerea.

Hotfix-urile urmează cam același proces, doar că lucrurile se întîmplă mai repede.

2 Likes

Aici este una dintre provocări: unele PR-uri sunt ok să fie squashed (features, fixes), dar la altele este MUSAI să rămâna intacte (updates). Probabil aș putea automatiza în baza numelui.

btw: dacă țineți tot WP-ul într-un repo, VREȚI să aveți câte un commit pentru fiecare update/install de plugin, temă, core. Am avut un conflict introdus de o versiune a unui plugin, iar conflictul l-am descoperit după câteva săptămâni (și zeci de commit-uri). A fost o adevărată aventură să aflu care a fost problema.

Cealaltă provocare este: cum fac „undo” la un PR (și doar la acela PR)?. E.g. am branch-ul master, am PR1…9 deja merged. Doar că îmi zice clientul: „nu mai avem nevioe de PR2 iar PR6 este momentan pus pe pauză”. Reset, cherry pick și rebase la greu? Altfel?

Pana acum nu am simtit nevoia de a automatiza partea asta. Daca se face merge dupa un code review, este doar un click in bitbucket, oricum se uita cineva pe el, deci poate decide atunci ce fel de merge se face.

git revert <commit_id> ?
Daca se intampla asta des, atunci ori mergi catre client branches (split din punctul unde diverge si cherry-pick la rest) dar este extrem de painful si maintenace hell ori ar trebui split repo-ul si partea volatila sa aibe un flow separat (si aici e painful).

Revert-ul nu mai e simplu o data ce ai scris peste aceeasi fisiere, nu il mai poti face automat, va trebui sa rezolvi conflictele. (e practic un PR/merge cu schimbarile vechi pe schimbarile noi)

Ideal ar fi sa rulezi teste inainte si dupa instalarea unui plugin (smoke tests) ca sa vezi daca s-a stricat ceva ca sa poti da revert imediat dupa.

Evident, insa nu vad de ce ai vrea sa automatizezi o astfel de situatie. Pana la urma este o decizie in urma unei discutii cu clientul si, presupun, o situatie un pic exceptionala. Ce workflow ai putea avea in care ai nevoie sa dai revert la un commit vechi dintr-un pipeline automat?

Am folosit mai multe stategii si git flows, dar cea pe care o consider cea mai buna este:

  • fara branch de staging - este groaznic de sincronizat staging cu master cu fix branches;
  • feature branches apoi PR si totul ajunge in master / main. Cand se considera stabil, branch nou de release cu versiunea (release/v1.2 de exemplu), git tag cu versiunea v1.2.0 si apoi build artifact, artifact care este gata de deploy - ii poti face deploy pe staging sau live, unde vrei tu;
  • in caz ca se gaseste un bug, se lucreaza pe branchul de release, se rezolva bug-ul, git tag cu versiunea v1.2.1 si apoi build artifact → deploy. Mai este necesar un merge cu master, pentru a avea rezolvarea bug-ului si in master;

Ideea de baza ar fi ca sa nu se lucreze mult timp pe un feature branch.

Asta este aiurea, dar doable. Incepi acel feature pe un “release” branch si il tag-uiesti cu v1.2-cool-feature-1 (sau ce nume vrei) si apoi faci build la versiuni de acolo. Daca vrei si features din master, trebuie facut regulat merge cu master. Si daca se renunta la acel feature, abandonezi branch-ul si aia e.

Ideea de baza este sa ai un mechanism simplu de a avea diverse versiuni ale soft-ului in paralel, pentru care pot fi usor build-uri si apoi deploy-uri, poti jongla usor cu ele mai apoi. Se impute treaba daca ai modificari in DB schema insa.

1 Like

Din păcate sistemul este construit în așa fel încât avem două branch-uri hot, care fac deploy când se face push/merge pe ele: staging și production. Aceste două medii sunt musai, pentru că nu toată echipa poate/știe cum să facă un mediu local de test.

Cred că vom urma un flow pe linia asta:

  1. Va apărea un al treilea env, pentru dev (și evident, un branch nou permanent). Să-i spunem acestui branch dev.
  2. staging va fi branch-ul cu lucrurile care ajung în producție. Un fel de next sau release sau ceva de genul.
  3. Orice feature va fi merged local în dev și va fi creeat un PR în staging.
  4. Restul echipei va testa lucrurile în dev și va decide dacă mergem mai departe sau nu.
  5. Dacă mergem mai departe, PR-ul feature → staging este aprobat, dacă nu… nu.

Singura problemă aici este că trebuie să găsim o modalitate în care să ținem branch-ul dev cumva în sync cu schimbările la care am renunțat.

Nu faci undo la commituri de pe master, faci un nou commit ca sa schimbi ceva.

Noi facem in asa fel incat masterul (si cam tot repo-ul) sa fie o linie dreapta, e estetic si usor de urmarit asa:

  • branch-uri temporar de dev
  • dupa ce trec testele automate Gitlab pe branch-ul de dev, se face MR, se asigneaza unul dev cu tag-ul “to-review”
  • dupa ce se ofera eventual feedback, se mai corecteaza, se pune tag “reviewed” si se asigneaza unui tester
  • dupa ce e testat se face merge in master, tot timpul cu REBASE (sa iasa o linie dreapta in graph-ul de repo)
  • se fac release-uri, noi facem lunar un beta, dupa o saptamana un stable. Un release e doar un tag in repo cu versiunea
  • orice hotfix ajunge in master prima data cu workflow-ul de mai sus
  • daca trebuie release-uit intr-o versiune deja publica: cherry pick din master pe branch-ul respectiv

Cam atat, la noi merge foarte bine modelul asta.

2 Likes

Nici eu n-am reusit sa lucrez cu branch de staging, testam mediul prea tarziu si inevitabil trebuiau corecturi.

Ok, am inteles, aveti niste constrangeri la care nu aveti cum da renuntati.