o istorie git ordonată, liniară

unul dintre lucrurile care sunt trecute cu vederea în multe proiecte bazate pe Git este valoarea unei istorii de comitere liniară. De fapt, multe instrumente și linii directoare descurajează fluxurile de lucru Git care vizează o istorie liniară. Mi se pare trist, deoarece o istorie ordonată este foarte valoroasă și există un flux de lucru direct care asigură o istorie liniară.

Istorie liniară vs neliniară

o istorie liniară este pur și simplu o istorie Git în care toate comiterile vin unul după altul. Adică. nu veți găsi fuziuni de ramuri cu istorii independente de comitere.

1 - neliniar-vs-liniar

de ce vrei o istorie liniară?

pe lângă faptul că este ordonat și logic, o istorie liniară vine la îndemână atunci când:

  • privind istoria. O istorie neliniară poate fi foarte greu de urmărit – uneori până la punctul în care istoria este doar de neînțeles.
  • modificări Backtracking. De exemplu: “caracteristica A a fost introdusă înainte sau după bugfix b?”.
  • urmărirea bug-uri. Git are o funcție foarte îngrijită numită git bisect, care poate fi utilizată pentru a găsi rapid care comite a introdus o eroare sau o regresie. Cu toate acestea, cu o istorie neliniară, git bisect devine greu sau chiar imposibil de utilizat.
  • modificări de revenire. Spuneți că ați găsit o comitere care a provocat o regresie sau doriți să eliminați o caracteristică care nu trebuia să apară într-o anumită versiune. Cu puțin noroc(dacă codul dvs. nu s-a schimbat prea mult), puteți reveni pur și simplu la comiterile nedorite folosind git revert. Cu toate acestea, dacă aveți o istorie neliniară, poate cu o mulțime de fuziuni între ramuri, acest lucru va fi semnificativ mai greu.

există probabil o altă mână de situații în care o istorie liniară este foarte valoroasă, în funcție de modul în care utilizați Git.

punctul este: cu cât istoria ta este mai puțin liniară, cu atât este mai puțin valoroasă.

cauzele unei istorii neliniare

pe scurt, fiecare comitere de îmbinare este o sursă potențială a unei istorii neliniare. Cu toate acestea, există diferite tipuri de comiteri de îmbinare.

Merge dintr-o ramură subiect în master

când ați terminat cu ramura subiect și doriți să-l integreze în master, o metodă comună este de a fuziona ramura subiect în master. Poate ceva de-a lungul liniilor:

git checkout mastergit pullgit merge --no-ff my-topic-branch

o proprietate frumoasă cu această metodă este că păstrați informațiile despre care comiteri au făcut parte din ramura dvs. de subiect (o alternativă ar fi să lăsați afară “–no-ff”, ceea ce ar permite Git să facă un fast-forward în loc de o îmbinare, caz în care este posibil să nu fie la fel de clar care dintre comiteri a aparținut de fapt ramurii dvs. de subiect).

problema cu fuzionarea la master apare atunci când ramura dvs. de subiect se bazează pe un master vechi în loc de cel mai recent sfat de master. În acest caz, veți obține în mod inevitabil o istorie neliniară.

2 - fuzionarea-old-branch

dacă aceasta va fi sau nu o problemă obișnuită depinde în mare măsură de cât de activ este depozitul Git, de câți dezvoltatori lucrează simultan etc.

Merge de la master într-o ramură subiect

uneori doriți să actualizați sucursala dvs. pentru a se potrivi cu cel mai recent master (de ex. există câteva caracteristici noi pe master pe care doriți să le intrați în ramura dvs. de subiect sau descoperiți că nu puteți îmbina ramura de subiect în master, deoarece există conflicte).

o metodă comună, care este chiar recomandată de unii, este de a îmbina vârful maestrului în ramura dvs. de subiect. Aceasta este o sursă majoră de istorie neliniară!

3 - fuzionarea-master-in-subiect

soluția: Rebase!

git rebase este o funcție foarte utilă pe care ar trebui să o utilizați dacă doriți un istoric liniar. Unii consideră că conceptul de rebasing este incomod, dar este într-adevăr destul de simplu: redați modificările (comiterile) din sucursala dvs. deasupra unei noi comiteri.

de exemplu, puteți utiliza git rebase pentru a schimba rădăcina ramurii de subiect de la un master vechi la vârful celui mai recent master. Presupunând că aveți sucursala subiect verificat, puteți face:

git fetch origingit rebase origin/master

4 - rebasing

rețineți că operația de îmbinare corespunzătoare ar fi să îmbinați vârful master în ramura subiect (așa cum este descris în figura anterioară). Conținutul rezultat al fișierelor din ramura subiect ar fi același, indiferent dacă faceți o rebase sau o îmbinare. Cu toate acestea, istoria este diferită (liniară vs neliniară!).

sună bine și bine. Cu toate acestea, există câteva avertismente cu rebase de care ar trebui să fiți conștienți.

avertisment 1: Rebase creează noi comite

Rebazarea o ramură va crea de fapt noi comite. Noile comiteri vor avea SHA:s diferit de comiterile vechi. De obicei, aceasta nu este o problemă, dar veți întâmpina probleme dacă re-stabiliți o sucursală care există în afara depozitului local (de exemplu, dacă sucursala dvs. există deja pe origin).

dacă ar fi să împingeți o ramură rebased într-un depozit de la distanță care conține deja aceeași ramură (dar cu istoria veche), v-ar:

  1. git push --force-with-lease), deoarece Git nu vă va permite să împingeți o nouă istorie într-o ramură existentă altfel. Acest lucru înlocuiește în mod eficient istoria pentru ramura de la distanță dată cu o nouă istorie.
  2. poate face pe altcineva foarte nefericit, deoarece versiunea lor locală a ramurii date nu se mai potrivește cu ramura de pe telecomandă, ceea ce poate duce la tot felul de probleme.

în general, evitați suprascrierea istoricului unei sucursale pe o telecomandă (singura excepție este rescrierea istoricului unei sucursale care se află sub revizuirea codului – în funcție de modul în care funcționează sistemul dvs. de revizuire a codului – dar aceasta este o discuție diferită).

dacă trebuie să reașezați o ramură care este partajată cu alte persoane pe o telecomandă, un flux de lucru simplu este să creați o ramură nouă pe care o reașezați, în loc să reașezați ramura originală. Presupunând că aveți my-topic-branch verificat, puteți face:

git checkout -b my-topic-branch-2git fetch origingit rebase origin/mastergit push -u origin my-topic-branch-2

…și apoi spuneți oamenilor care lucrează la my-topic-branch să continue să lucreze la my-topic-branch-2 în schimb. Ramura veche este apoi efectiv depășită și nu ar trebui să fie fuzionată înapoi la master.

avertisment 2: rezolvarea conflictelor într-o rebase poate fi mai mult de lucru decât într-o îmbinare

dacă aveți un conflict într-o operațiune de îmbinare, veți rezolva toate conflictele ca parte a comiterii de îmbinare.

cu toate acestea, într-o operațiune de rebase, puteți obține un conflict pentru fiecare comitere din ramura pe care o rebase.

de fapt, de multe ori veți descoperi că, dacă obțineți un conflict într-o comitere, veți întâlni conflicte conexe (foarte similare) în comiterile ulterioare din ramura dvs., pur și simplu pentru că comiterile dintr-o ramură de subiect tind să fie legate (de exemplu, modificarea acelorași părți ale codului).

cel mai bun mod de a minimiza conflictele este să țineți evidența a ceea ce se întâmplă în master și să evitați să lăsați o ramură de subiect să ruleze prea mult timp fără a rebasa. Confruntarea cu conflicte mici în față fiecare acum și apoi este mai ușor decât să se ocupe-le pe toate într-o mare mizerie conflict fericit la sfârșitul anului.

unele avertismente pentru utilizatorii GitHub

GitHub este mare la multe lucruri. Este fantastic pentru Git hosting, și are o interfață web minunat cu cod de navigare, funcționalitate Markdown frumos, Gist, etc.

cererile de tragere, pe de altă parte, au câteva funcții care împiedică în mod activ istoria liniară git. Ar fi foarte binevenit dacă GitHub a rezolvat efectiv aceste probleme, dar până atunci ar trebui să fiți conștienți de neajunsurile:

“Merge pull request” permite fuziuni neliniare la master

poate fi tentant să apăsați butonul verde, prietenos “Merge pull request” pentru a îmbina o ramură de subiect în master. Mai ales că scrie “această ramură este actualizată cu ramura de bază. Îmbinarea poate fi efectuată automat”.

GitHub merge cerere trage

ceea ce GitHub este într-adevăr spune aici, este că ramura poate fi fuzionat pentru a stăpâni fără conflicte. Nu verifică dacă ramura cererii de tragere se bazează sau nu pe cel mai recent master.

cu alte cuvinte, dacă doriți un istoric liniar, trebuie să vă asigurați că ramura de solicitare a tragerii este rebasată pe partea de sus a celui mai recent master. Din câte îmi dau seama, nu sunt disponibile astfel de informații prin interfața web GitHub (cu excepția cazului în care utilizați “ramuri protejate” – vezi mai jos), deci trebuie să o faceți de la clientul dvs. local git.

chiar dacă cererea de tragere este corect rebased, nu există nici o garanție că operațiunea de îmbinare în interfața web GitHub va fi atomic (adică. cineva poate împinge modificările la master înainte ca operațiunea dvs. de îmbinare să treacă – și GitHub nu se va plânge).

deci, într-adevăr, singura modalitate de a vă asigura că ramurile dvs. sunt corect rebasate pe partea de sus a celui mai recent master este să faceți operația de îmbinare local și să împingeți manual maestrul rezultat. Ceva de-a lungul liniilor:

git checkout mastergit pullgit checkout my-pullrequest-branchgit rebase mastergit checkout mastergit merge --no-ff my-pullrequest-branchgit push origin master

dacă aveți ghinion și cineva reușește să împingă modificările pentru a stăpâni între operațiunile dvs. de tragere și împingere, operațiunea dvs. de împingere va fi refuzată. Acesta este un lucru bun, totuși, deoarece garantează că operațiunea dvs. este atomică. Doar git reset --hard origin/master și repetați pașii de mai sus până când trece.

notă: respectați regulile proiectului w.R.T. revizuirea și testarea codului. De exemplu, dacă executați teste automate (compilări, analize statice, teste unitare,…) ca parte a unei cereri de tragere, ar trebui probabil să trimiteți din nou sucursala rebased (fie folosind git push-f, fie deschizând un nou PR), mai degrabă decât să actualizați manual sucursala principală.

funcționalitatea ramurilor protejate încurajează fuziunile de la master

dacă utilizați sucursale protejate și verificări de stare în proiectul dvs.

cu toate acestea… dacă ramura de solicitare a tragerii nu se bazează pe cel mai recent master, vi se prezintă un buton prietenos numit “actualizare ramură”, cu textul “această ramură este depășită cu ramura de bază. Îmbinați cele mai recente modificări de la master în această ramură”.

github-update-branch

în acest moment, cea mai bună opțiune este să re-bazezi ramura local și să o împingi forțat la cererea de tragere. Presupunând că aveți my-pullrequest-branch verificat, nu:

git fetch origingit rebase origin/mastergit push -f

din păcate, cererile de tragere GitHub nu se joacă bine cu împingeri de forță, astfel încât unele dintre informațiile de revizuire a codului se pot pierde în acest proces. Dacă acest lucru nu este acceptabil, luați în considerare crearea unei noi cereri de tragere (adică împingeți sucursala rebased la o nouă ramură la distanță și creați o cerere de tragere din noua ramură).

concluzie

dacă vă pasă de o istorie liniară:

  • redimensionați ramura subiectului în partea de sus a celui mai recent master înainte de a o îmbina cu master.
  • nu fuziona master în ramura subiect. Rebase în schimb.
  • când partajați ramura subiect cu alții, creați o nouă ramură ori de câte ori aveți nevoie să-l re-bază (my-topic-branch-2, my-topic-branch-3, …).
  • dacă utilizați cereri de tragere GitHub, fiți conștienți:
    • butonul “Merge” nu garantează că ramura PR se bazează pe cel mai recent master. Rebase manual atunci când este necesar.
    • dacă utilizați ramuri protejate cu verificări de stare, nu apăsați niciodată butonul “Actualizare ramură”. Rebase manual în schimb.

dacă nu vă pasă prea mult de o istorie liniară git-fuzionarea fericită!

Wishlist pentru GitHub

pentru a vă oameni fine care lucrează la GitHub: vă rugăm să adăugați suport pentru fluxurile de lucru rebase în cererile de tragere (acestea ar putea fi opt-in Opțiuni depozit).

  • adăugați posibilitatea de a dezactiva butonul de îmbinare/operația în cererile de tragere dacă ramura nu se bazează pe cel mai recent master (acest lucru nu ar trebui să necesite utilizarea verificărilor de stare).
  • adăugați un buton “Rebase on latest master”. În multe cazuri, aceasta ar trebui să fie o operațiune fără conflicte care poate fi realizată cu ușurință prin interfața web.
  • Păstrați istoricul solicitărilor de tragere (comiteri, comentarii, …) după o apăsare de rebază / forță.

Leave a Reply