en ryddelig, lineær Git-historie

en af de ting, der overses i mange Git-baserede projekter, er værdien af en lineær commit-historie. Faktisk modvirker mange værktøjer og retningslinjer Git-arbejdsgange, der sigter mod en lineær historie. Jeg finder det trist, da en ryddelig historie er meget værdifuld, og der er en ligetil arbejdsgang, der sikrer en lineær historie.

lineær vs Ikke-lineær historie

en lineær historie er simpelthen en Git-historie, hvor alle forpligtelser kommer efter hinanden. Dvs. du vil ikke finde nogen fusioner af filialer med uafhængige begå historier.

1 - ikke-lineær-vs-lineær

Hvorfor vil du have en lineær historie?

udover at være ryddelig og logisk, er en lineær historie praktisk, når:

  • kigger på historien. En ikke-lineær historie kan være meget svær at følge – nogle gange til det punkt, at historien bare er uforståelig.
  • Backtracking ændringer. For eksempel: “blev funktionen a introduceret før eller efter fejlrettelse B?”.
  • sporing af fejl. Git har en meget pæn funktion kaldet Git bisect, som kan bruges til hurtigt at finde, hvilken begå indført en fejl eller regression. Men med en ikke-lineær historie bliver Git bisect hård eller endog umulig at bruge.
  • tilbageførsel af ændringer. Sig, at du har fundet en forpligtelse, der forårsagede en regression, eller du vil fjerne en funktion, der ikke skulle gå ud i en bestemt udgivelse. Med lidt held(hvis din kode ikke har ændret sig for meget) kan du blot vende tilbage de uønskede commit (er) ved hjælp af Git revert. Men hvis du har en ikke-lineær historie, måske med masser af tværgrenede fusioner, vil dette være betydeligt sværere.

der er sandsynligvis en anden håndfuld situationer, hvor en lineær historie er meget værdifuld, afhængigt af hvordan du bruger Git.

pointen er: jo mindre lineær din historie er, jo mindre værdifuld er den.

årsager til en ikke-lineær historie

kort sagt, hver fusionsforpligtelse er en potentiel kilde til en ikke-lineær historie. Der er dog forskellige former for fusionsforpligtelser.

Flet fra en emnegren til master

når du er færdig med din emnegren og vil integrere den i master, er en almindelig metode at flette emnegrenen til master. Måske noget i retning:

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

en god egenskab med denne metode er, at du bevarer oplysningerne om, hvilke forpligtelser der var en del af din emnegren (et alternativ ville være at udelade “–no-ff”, hvilket ville gøre det muligt for Git at gøre en hurtig fremad i stedet for en fusion, i hvilket tilfælde det måske ikke er så klart, hvilken af forpligtelserne der faktisk tilhørte din emnegren).

problemet med sammenlægning til master opstår, når din emnegren er baseret på en gammel master i stedet for det seneste tip fra master. I dette tilfælde vil du uundgåeligt få en ikke-lineær historie.

2 - fusion-old-branch

hvorvidt dette vil være et almindeligt problem eller ej, afhænger i vid udstrækning af, hvor aktivt Git-depotet er, hvor mange udviklere der arbejder samtidigt osv.

Flet fra master til en emnegren

nogle gange vil du opdatere din filial, så den matcher den nyeste master (f. eks. der er nogle nye funktioner på master, som du vil komme ind i din emnegren, eller du finder ud af, at du ikke kan flette din emnegren til master, fordi der er konflikter).

en fælles metode, som endda anbefales af nogle, er at flette spidsen af master i din emnegren. Dette er en vigtig kilde til ikke-lineær historie!

3 - fletning-master-i-emne

løsningen: Rebase!

Git rebase er en meget nyttig funktion, som du skal bruge, hvis du vil have en lineær historie. Nogle finder begrebet rebasing akavet, men det er virkelig ret simpelt: gentag ændringerne (forpligter) i din filial oven på en ny forpligtelse.

for eksempel kan du bruge Git rebase til at ændre roden til din emnegren fra en gammel master til spidsen af den nyeste master. Forudsat at du har tjekket din emnegren ud, kan du gøre:

git fetch origingit rebase origin/master

4 - rebasing

Bemærk, at den tilsvarende fletningsoperation ville være at flette spidsen af master i din emnegren (som vist i den foregående figur). Det resulterende indhold af filerne i din emnegren ville være det samme, uanset om du foretager en rebase eller en fletning. Historien er dog anderledes (lineær vs Ikke-lineær!).

det lyder fint og godt. Der er dog et par advarsler med rebase, som du skal være opmærksom på.

advarsel 1: Rebase opretter nye forpligtelser

Rebasing en filial vil faktisk oprette nye forpligtelser. De nye forpligtelser vil have forskellige SHA: s end de gamle forpligtelser. Dette er normalt ikke et problem, men du vil løbe ind i problemer, hvis du rebase en filial, der findes uden for dit lokale lager (f.eks.

hvis du skulle skubbe en rebased gren til et fjernlager, der allerede indeholder den samme gren (men med den gamle historie), ville du:

  1. git push --force-with-lease), da Git ikke tillader dig at skubbe en ny historie til en eksisterende gren ellers. Dette erstatter effektivt historien for den givne fjerngren med en ny historie.
  2. muligvis gøre en anden meget ulykkelig, da deres lokale version af den givne gren ikke længere matcher grenen på fjernbetjeningen, hvilket kan føre til alle mulige problemer.

generelt skal du undgå at overskrive historikken for en gren på en fjernbetjening (den ene undtagelse er at omskrive historikken for en gren, der er under kodeanmeldelse – afhængigt af hvordan dit kodeanmeldelsessystem fungerer – men det er en anden diskussion).

hvis du har brug for at rebase en gren, der deles med andre på en fjernbetjening, er en simpel arbejdsproces at oprette en ny gren, som du rebaserer, i stedet for at rebasere den oprindelige gren. Forudsat at du har my-topic-branch tjekket ud, kan du gøre det:

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

…og så fortæl folk, der arbejder på my-topic-branch, at fortsætte med at arbejde på my-topic-branch-2 i stedet. Den gamle gren er så effektivt forældet og bør ikke flettes tilbage til master.

advarsel 2: Løsning af konflikter i en rebase kan være mere arbejde end i en fletning

hvis du får en konflikt i en fletningsoperation, vil du løse alle konflikter som en del af denne fletningsforpligtelse.

i en rebase-operation kan du dog potentielt få en konflikt for hver forpligtelse i den gren, du rebaser.

faktisk vil du mange gange opdage, at hvis du får en konflikt i en forpligtelse, vil du støde på relaterede (meget lignende) konflikter i efterfølgende forpligtelser i din filial, simpelthen fordi forpligtelser i en emnegren har tendens til at være relateret (f.eks. ændre de samme dele af koden).

den bedste måde at minimere konflikter på er at holde styr på, hvad der sker i master, og undgå at lade en emnegren køre for længe uden at rebasere. Håndtering af små konflikter op foran nu og da er nemmere end at håndtere dem alle i en stor glad konflikt rod i slutningen.

nogle advarsler til GitHub brugere

GitHub er fantastisk til mange ting. Det er fantastisk til Git-hosting, og det har en vidunderlig internetgrænseflade med kodesøgning, flot Markeringsfunktionalitet, kerne osv.

Pull-anmodninger har på den anden side et par funktioner, der aktivt modvirker lineær Git-historie. Det ville være meget velkommen, hvis GitHub faktisk fik løst disse problemer, men indtil da skal du være opmærksom på manglerne:

“Flet pull-anmodning” tillader ikke-lineære fusioner at mestre

det kan være fristende at trykke på den grønne, venlige “Flet pull-anmodning” – knap for at flette en emnegren til master. Især som det lyder “denne gren er opdateret med basisgrenen. Fletning kan udføres automatisk”.

GitHub Flet pull anmodning

hvad GitHub virkelig siger her, er, at filialen kan flettes til master uden konflikter. Det kontrollerer ikke, om pull-anmodningsgrenen er baseret på den nyeste master eller ej.

med andre ord, hvis du vil have en lineær historie, skal du sørge for, at trækforespørgselgrenen rebaseres oven på den nyeste master selv. Så vidt jeg kan se, er sådanne oplysninger ikke tilgængelige via GitHub – internetgrænsefladen (medmindre du bruger “beskyttede filialer” – se nedenfor), så du skal gøre det fra din lokale Git-klient.

selvom pull-anmodningen er korrekt rebased, er der ingen garanti for, at fletningsoperationen i GitHub-netgrænsefladen vil være atomisk (dvs. nogen kan skubbe ændringer til master før din fusion operation går igennem-og GitHub vil ikke klage).

så virkelig er den eneste måde at være sikker på, at dine filialer er korrekt rebased oven på den nyeste master, at gøre fletningen lokalt og skubbe den resulterende master manuelt. Noget i retning:

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

hvis du er uheldig, og nogen formår at skubbe ændringer til at mestre mellem dine pull-og push-operationer, nægtes din push-operation. Dette er dog en god ting, da det garanterer, at din operation er atomisk. Bare git reset --hard origin/master og gentag ovenstående trin, indtil det går igennem.

Bemærk: respekter dine projektretningslinjer med gennemgang og test af kode. Hvis du f.eks. kører automatiske tests (builds, static analysis, unit tests, …) som en del af en pull-anmodning, skal du sandsynligvis indsende din rebased branch (enten ved hjælp af git push-f eller ved at åbne en ny PR) i stedet for blot at opdatere mastergrenen manuelt.

beskyttede filialer funktionalitet tilskynder fusioner fra master

hvis du bruger beskyttede filialer og statuskontrol i dit GitHub-projekt, får du faktisk beskyttelse mod at flette en pull-anmodningsgren til master, medmindre den er baseret på den nyeste master (jeg tror, at begrundelsen er, at statuskontrol udført på PR-filialen stadig skal være gyldig efter fletning til master).

men… hvis pull-anmodningsgrenen ikke er baseret på den nyeste master, får du en venlig knap kaldet “Opdater gren” med teksten “Denne gren er forældet med basisgrenen. Flet de seneste ændringer fra master ind i denne gren”.

github-update-branch

på dette tidspunkt er din bedste mulighed at rebase filialen lokalt og tvinge den til pull-anmodningen. Forudsat at du har my-pullrequest-branch tjekket ud, gør:

git fetch origingit rebase origin/mastergit push -f

desværre spiller GitHub pull-anmodninger ikke godt med force pushes, så nogle af kodeanmeldelsesoplysningerne kan gå tabt i processen. Hvis det ikke er acceptabelt, kan du overveje at oprette en ny pull-anmodning (dvs.skubbe din rebased-gren til en ny fjernafdeling og oprette en pull-anmodning fra den nye filial).

konklusion

hvis du interesserer dig for en lineær historie:

  • Rebase din emnegren oven på den nyeste master, før du fusionerer den til master.
  • Flet ikke master ind i din emnegren. Rebase i stedet.
  • når du deler din emnegren med andre, skal du oprette en ny gren, når du har brug for at rebase den (my-topic-branch-2, my-topic-branch-3, …).
  • hvis du bruger GitHub pull-anmodninger, skal du være opmærksom på:
    • knappen “Flet” garanterer ikke, at PR-filialen er baseret på den nyeste master. Rebase manuelt, når det er nødvendigt.
    • hvis du bruger beskyttede grene med statuskontrol, skal du aldrig trykke på knappen “Opdater gren”. Rebase manuelt i stedet.

hvis du ikke er ligeglad med en lineær Git-historie-glad sammensmeltning!

ønskeliste til GitHub

til dig fine mennesker, der arbejder hos GitHub: Tilføj venligst support til rebase-arbejdsgange i pull-anmodninger (disse kan være opt-in-opbevaringsindstillinger).

  • Tilføj muligheden for at deaktivere Fletningsknappen/betjeningen i pull-anmodninger, hvis filialen ikke er baseret på den nyeste master (dette bør ikke kræve brug af statuskontrol).
  • Tilføj en “Rebase onto nyeste master” knap. I mange tilfælde bør dette være en konfliktfri operation, der let kan udføres via internetgrænsefladen.
  • Bevar pull anmodning historie (forpligter, kommentarer, …) efter en rebase / force push.

Leave a Reply