en ryddig, lineær Git historie

en av de tingene som er oversett i Mange Git baserte prosjekter er verdien av en lineær forplikte historie. Faktisk motvirker mange verktøy Og retningslinjer Git-arbeidsflyter som tar sikte på en lineær historie. Jeg finner dette trist, siden en ryddig historie er svært verdifull, og det er en rett frem arbeidsflyt som sikrer en lineær historie.

Lineær vs ikke-lineær historie

en lineær historie er ganske enkelt En Git-historie der alle forpliktelser kommer etter hverandre. Dvs. du vil ikke finne noen sammenslåinger av grener med uavhengige forplikte historier.

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

Hvorfor vil du ha en lineær historie?

foruten å være ryddig og logisk, kommer en lineær historie til nytte når:

  • Ser på historien. En ikke-lineær historie kan være svært vanskelig å følge-noen ganger til det punktet at historien er bare uforståelig.
  • Backtracking endringer. For eksempel: “har funksjonen a bli introdusert før Eller etter bugfix B?”.
  • Sporing av feil. Git har en veldig fin funksjon kalt Git bisect, som kan brukes til å raskt finne hvilken commit introduserte en feil eller regresjon. Men Med en ikke-lineær historie blir Git bisect vanskelig eller umulig å bruke.
  • Tilbakestiller endringer. Si at du fant en forpliktelse som forårsaket en regresjon, eller du vil fjerne en funksjon som ikke skulle gå ut i en bestemt utgivelse. Med litt flaks(hvis koden din ikke har endret seg for mye) kan du bare gå tilbake uønskede commit (s) ved Hjelp Av Git revert. Men hvis du har en ikke-lineær historie, kanskje med mange tverrgrener, vil dette bli betydelig vanskeligere.

det er sannsynligvis en annen håndfull situasjoner der en lineær historie er svært verdifull, avhengig av hvordan Du bruker Git.

Punkt er: jo mindre lineær historien din er, desto mindre verdifull er den.

Årsaker til en ikke-lineær historie

kort sagt, hver sammenslåing er en potensiell kilde til en ikke-lineær historie. Det er imidlertid forskjellige typer fusjonsforpliktelser.

Slå sammen fra en emnegren til master

når du er ferdig med emnegrenen din og vil integrere den i master, er en vanlig metode å slå sammen emnegrenen til master. Kanskje noe langs linjene:

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

En fin egenskap med denne metoden er at du beholder informasjonen om hvilke innlegginger som var en del av emnegrenen din (et alternativ ville være å utelate “–no-ff”, noe Som ville tillate Git å gjøre en spole fremover i stedet for en sammenslåing, i så fall kan Det ikke være så klart hvilke av innleggene som faktisk tilhørte emnegrenen din).

problemet med sammenslåing til master oppstår når emnegrenen din er basert på en gammel mester i stedet for det siste tipset til master. I dette tilfellet vil du uunngåelig få en ikke-lineær historie.

2 - sammenslåing-old-branch

Hvorvidt dette vil være et vanlig problem eller ikke, avhenger i stor grad av hvor aktiv Git-depotet er, hvor mange utviklere jobber samtidig, etc.

Slå sammen fra master til en emnegren

noen ganger vil du oppdatere grenen slik at den samsvarer med den nyeste master (f. eks. det er noen nye funksjoner på master som du vil komme inn i emnegrenen din, eller du finner ut at du ikke kan slå sammen emnegrenen din i master fordi det er konflikter).

en vanlig metode, som til og med anbefales av noen, er å slå sammen spissen av master i emnegrenen din. Dette er en viktig kilde til ikke-lineær historie!

3 - sammenslåing-master-til-emne

løsningen: Rebase!

Git rebase er en veldig nyttig funksjon som du bør bruke hvis du vil ha en lineær historie. Noen finner begrepet rebasing vanskelig, men det er egentlig ganske enkelt: gjenta endringene (innleggene) i grenen din på toppen av en ny innlegging.

du kan for eksempel bruke Git rebase til å endre roten til emnegrenen fra en gammel mester til spissen av den nyeste mesteren. Forutsatt at du har emnet grenen sjekket ut, kan du gjøre:

git fetch origingit rebase origin/master

4 - rebasing

Merk at den tilsvarende sammenslåingsoperasjonen ville være å slå sammen spissen av master i emnegrenen din(som vist i forrige figur). Det resulterende innholdet i filene i emnegrenen vil være det samme, uansett om du gjør en rebase eller en fletting. Historien er imidlertid forskjellig (lineær vs ikke-lineær!).

dette høres alt bra ut. Det er imidlertid et par advarsler med rebase som du bør være oppmerksom på.

Advarsel 1: Rebase oppretter nye forpliktelser

Rebasing en gren vil faktisk skape nye forpliktelser. De nye forpliktelsene vil ha forskjellige SHA: s enn de gamle forpliktelsene. Dette er vanligvis ikke et problem, men du vil få problemer hvis du rebase en gren som eksisterer utenfor ditt lokale depot (f. eks. hvis grenen din allerede eksisterer på origin).

hvis du skulle skyve en rebasert gren til et eksternt depot som allerede inneholder samme gren( men med den gamle historien), ville du:

  1. git push --force-with-lease), siden Git ikke vil tillate deg å skyve en ny historie til en eksisterende gren ellers. Dette erstatter effektivt historien for den gitte eksterne grenen med en ny historie.
  2. Muligens gjør noen andre veldig ulykkelige, siden deres lokale versjon av den gitte grenen ikke lenger samsvarer med grenen på fjernkontrollen, noe som kan føre til alle slags problemer.

generelt bør du unngå å overskrive historikken til en gren på en fjernkontroll (det ene unntaket er å omskrive historikken til en gren som er under kodegjennomgang-avhengig av hvordan kodegjennomgangssystemet fungerer-men det er en annen diskusjon).

hvis du trenger å rebase en gren som er delt med andre på en ekstern, er en enkel arbeidsflyt å opprette en ny gren som du rebase, i stedet for rebasing den opprinnelige grenen. Forutsatt at du har my-topic-branch sjekket ut, kan du gjøre:

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

…og så fortell folk som jobber med my-topic-branch for å fortsette å jobbe med my-topic-branch-2 i stedet. Den gamle grenen er da effektivt foreldet, og bør ikke slås sammen tilbake til master.

Advarsel 2: Løsning av konflikter i en rebase kan være mer arbeid enn i en sammenslåing

hvis du får en konflikt i en sammenslåingsoperasjon, løser du alle konfliktene som en del av sammenslåingen.

men i en rebase-operasjon kan du potensielt få en konflikt for hver forpliktelse i grenen du rebase.

faktisk vil du mange ganger finne at hvis du får en konflikt i en forpliktelse, vil du møte relaterte (svært like) konflikter i etterfølgende forpliktelser i grenen din, bare fordi forpliktelser i en emnegren har en tendens til å være relatert(for eksempel å endre de samme delene av koden).

den beste måten å minimere konflikter på er å holde oversikt over hva som skjer i master, og unngå å la en emnegren løpe for lenge uten å rebasing. Håndtere små konflikter opp foran nå og da er enklere enn å håndtere dem alle i en stor lykkelig konflikt rot på slutten.

noen advarsler for GitHub-brukere

GitHub er flott på mange ting. Det er fantastisk For Git hosting, og det har et fantastisk webgrensesnitt med kodebrowsing, fin Markdown-funksjonalitet, Gist, etc.

Pull forespørsler, derimot, har noen funksjoner som aktivt hindrer lineær Git historie. Det ville være veldig velkommen Hvis GitHub faktisk løst disse problemene, men til da bør du være oppmerksom på manglene:

“Merge pull request” tillater ikke-lineære sammenslåinger å mestre

Det kan være fristende å trykke på den grønne, vennlige “Merge pull request” – knappen for å slå sammen en emnegren til master. Spesielt som det står “Denne grenen er up-to-date med basen grenen. Sammenslåing kan utføres automatisk”.

GitHub merge pull request

Hva GitHub egentlig sier her, er at grenen kan slås sammen for å mestre uten konflikter. Det kontrollerer ikke om pull request-grenen er basert på den nyeste masteren.

Med andre ord, hvis du vil ha en lineær historie, må du sørge for at pull request-grenen er rebased på toppen av den nyeste mesteren selv. Så vidt jeg kan fortelle, er ingen slik informasjon tilgjengelig via GitHub webgrensesnitt (med mindre du bruker “beskyttede grener” – se nedenfor), så du må gjøre det fra din lokale Git-klient.

selv om pull-forespørselen er riktig rebased, er det ingen garanti for at fusjonsoperasjonen i GitHub – webgrensesnittet vil være atomisk (dvs. noen kan presse endringer for å mestre før fusjonsoperasjonen går gjennom-og GitHub vil ikke klage).

så egentlig, den eneste måten å være sikker på at grenene dine er riktig rebased på toppen av den siste mesteren, er å gjøre sammenslåingsoperasjonen lokalt og skyve den resulterende mesteren manuelt. Noe langs linjene:

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 noen klarer å presse endringer for å mestre mellom pull og push-operasjoner, vil push-operasjonen bli nektet. Dette er imidlertid en god ting, siden det garanterer at operasjonen din er atomisk. Bare git reset --hard origin/master og gjenta trinnene ovenfor til det går gjennom.

Merk: Respekter prosjektretningslinjene dine med kodegjennomgang og testing. Hvis du kjører automatiske tester (bygger, statisk analyse, enhetstester, …) som en del av en pull-forespørsel, bør du sannsynligvis sende inn din rebased gren (enten ved hjelp av git push-f, eller ved å åpne en ny PR) i stedet for bare å oppdatere hovedgrenen manuelt.

Beskyttede grener funksjonalitet oppfordrer fletter fra master

hvis du bruker beskyttede grener og statuskontroller i GitHub-prosjektet, får du faktisk beskyttelse mot å slå sammen en pull request-gren til master, med mindre den er basert på den nyeste mesteren (jeg tror begrunnelsen er at statuskontroller utført PÅ PR-grenen fortsatt skal være gyldige etter sammenslåing til master).

Men… Hvis pull request-grenen ikke er basert på den nyeste masteren, blir du presentert med en vennlig knapp kalt “Update branch”, med teksten ” Denne grenen er utdatert med basisgrenen. Slå sammen de siste endringene fra master i denne grenen”.

github-update-branch

På dette punktet er det beste alternativet å rebase grenen lokalt og tvinge den til trekkforespørselen. Forutsatt at du har my-pullrequest-branch sjekket ut, gjør:

git fetch origingit rebase origin/mastergit push -f

Dessverre Spiller GitHub pull-forespørsler ikke bra med force pushes, så noe av kodevurderingsinformasjonen kan gå seg vill i prosessen. Hvis det ikke er akseptabelt, bør du vurdere å opprette en ny pull-forespørsel (dvs.skyv den ombaserte grenen til en ny ekstern gren og opprett en pull-forespørsel fra den nye grenen).

Konklusjon

hvis du bryr deg om en lineær historie:

  • Rebase emnet grenen på toppen av den nyeste master før sammenslåing det å mestre.
  • ikke slå sammen master i emnegrenen. Rebase i stedet.
  • når du deler emnegrenen din med andre, oppretter du en ny gren når du trenger å rebase den (my-topic-branch-2, my-topic-branch-3, …).
  • hvis Du bruker GitHub pull-forespørsler, vær oppmerksom på:
    • “Merge” – knappen garanterer ikke AT pr-grenen er basert på den nyeste masteren. Rebase manuelt når det er nødvendig.
    • hvis du bruker beskyttede grener med statuskontroller, må du aldri trykke på” Oppdater gren ” – knappen. Rebase manuelt i stedet.

hvis du ikke bryr deg for mye om en lineær Git historie – glad sammenslåing!

Ønskeliste For GitHub

til deg fine folk som jobber På GitHub: vennligst legg til støtte for rebase-arbeidsflyter i trekkforespørsler(disse kan være opt-in depotalternativer).

  • Legg til muligheten for å deaktivere Fletteknappen / operasjonen i pull-forespørsler hvis grenen ikke er basert på den nyeste masteren(dette bør ikke kreve bruk av statuskontroller).
  • Legg til en” Rebase on latest master ” – knapp. I mange tilfeller bør dette være en konfliktfri operasjon som enkelt kan gjøres via webgrensesnittet.
  • Bevar pull forespørsel historie (inger, kommentarer, …) etter en rebase / force push.

Leave a Reply