Treveis merge: når begge linjene har flyttet seg
Den interaktive leksjonen trenger JavaScript. Teksten under er hele leksjonen.
Etter leksjonen kan du
- forklare hva en merge-base er, og finne den med
git merge-baseanvende - forutsi når en merge gir en ny merge-commit med to foreldre forstå
- lese en merget historikk med
git log --oneline --graph, og brukeHEAD^1ogHEAD^2til å finne hver side av en merge anvende
Forstå: Tre snapshots, to foreldre
Nå har begge linjene fått nye commits etter A. main har B, og feature har C. Ingen av dem er stamfar til den andre.
Git leter bakover fra begge tuppene og finner den nærmeste felles stamfaren. Den kalles merge-base. Her er det A, der linjene skilte lag.
Så sammenligner Git tre snapshots: basen, vår side og deres side. En endring som bare er gjort på én side, blir med i resultatet. Derfor heter dette en treveis merge.
Resultatet lagres i en ny commit, en merge-commit, med to foreldre. main flytter til den. feature blir stående, og B og C er uendret.
Rekkefølgen på foreldrene betyr noe. Første forelder er commiten du stod på, altså B. Andre forelder er den du merget inn, altså C. Git kaller dem HEAD^1 og HEAD^2.
En merge uten feilmeldinger betyr bare at tekstene passet sammen. Den beviser ikke at programmet virker. Kjør testene før du deler resultatet.
Se demo: En ekte merge-commit
Vi står på main. Grafen deler seg: main og feature har hver sin nye commit etter den første.
$ git log --oneline --graph --all
Vi spør Git om merge-basen. Svaret er en commit-ID. Sammenlign med loggen: det er den første commiten, der linjene skilte lag.
$ git merge-base main feature
Nå merger vi inn feature. --no-edit godtar meldingen Git foreslår. Se grafen: en ny commit med to foreldre dukker opp, og main flytter dit.
$ git merge --no-edit feature
Les grafen ovenfra. Øverst står merge-commiten. Rett under deler streken seg i to: én vei til hver forelder. Nederst møtes de igjen i merge-basen.
$ git log --oneline --graph
Første forelder, HEAD^1, er der vi stod før mergen: Add contact page fra main.
$ git log --oneline -1 HEAD^1
Andre forelder, HEAD^2, er det vi merget inn: Add login form fra feature.
$ git log --oneline -1 HEAD^2
Oppgaver
- Vis grafen med alle brancher.
- Finn den felles stamfaren til
mainogfeature. - Merge
featureinn imain. - Vis den andre forelderen til merge-commiten.
Øv: Overfør: en ny merge
Både main og api har fått en ny commit. Du står på api.
Oppgaver
- Merge
apiinn imain. - Lag en branch
before-apisom peker på commitenmainstod på rett før mergen.
Sjekk deg selv: Hent det fram igjen
Hva er merge-basen?
- Den nyeste commiten på
main - Den nærmeste commiten som begge branchene har felles i historikken
- Den aller første commiten i repositoryet
Du stod på main og kjørte git merge feature, og Git laget en merge-commit. Hva er HEAD^2 nå?
- Commiten to steg tilbake på
main - Den siste commiten fra
feature, som ble merget inn - Commiten
mainpekte på før mergen
Hva skjer med commitene B og C når Git lager merge-commiten M?
- De slås sammen til M og forsvinner
- De er uendret. M peker på begge som foreldre.
- De kopieres over på
main
En merge gikk gjennom uten konflikt. Hva vet du da?
- At programmet fungerer
- At Git kunne kombinere tekstendringene. Programmet må fortsatt testes.
- At ingen har endret de samme filene
Repetisjon: Begge sider endret README.md før mergen, og HEAD er merge-commiten. Du vil se README.md slik den var på main rett før mergen. Hvilken kommando viser det?
git show HEAD^2:README.mdgit show HEAD^1:README.mdgit diff HEAD^1 HEAD
Vanlige misforståelser
- «En merge kopierer eller skriver om commitene.» De eksisterende commitene er uendret. Merge-commiten peker bare på begge.
- «
HEAD^2betyr to commits tilbake.»^2velger den andre forelderen. Det er~2som går to generasjoner tilbake. - «En merge uten konflikt betyr at koden virker.» Git sjekker at tekstene kan kombineres, ikke at programmet oppfører seg riktig.
Les mer
Treveisregelen
Når begge linjene har flyttet seg, sammenligner Git tre snapshots for hver fil, og innenfor en fil for hvert endret område: merge-base, vår side (ours, branchen du står på) og deres side (theirs, branchen du merger inn).
| Base | Vår | Deres | Resultat |
|---|---|---|---|
| A | A | A | A |
| A | B | A | B — bare vi endret |
| A | A | C | C — bare de endret |
| A | B | B | B — samme endring på begge sider |
| A | B | C | konflikt — neste leksjon |
Det er basen som får dette til å virke. Med bare vår og deres side kunne Git se at versjonene er ulike, men ikke hvem som endret hva. Med basen vet Git hvilken side som gjorde endringen, og kan ta den med. Derfor merges endringer i ulike filer, og til og med i ulike deler av samme fil, vanligvis av seg selv. Ulike endringer på samme eller nabolinjer blir en konflikt.
Finne basen
$ git merge-base main feature
df6c7db3a1e0c9f5b8d2e7a4c6f1b9d0e3a5c7f2
Svaret er den nærmeste commiten begge brancher kan nå, målt langs grafen, ikke etter dato. git log --oneline main..feature lister det feature har og main mangler, altså det merge vil ta inn, og git diff main...feature med tre punktum viser endringene på feature siden basen. I uvanlige historikker der brancher er merget fram og tilbake, kan det finnes flere like gode baser. Gits standardstrategi, ort, merger da først disse basene. Det trenger du sjelden å tenke på.
Anatomien til en merge-commit
$ git merge feature
Merge made by the 'ort' strategy.
login.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 login.txt
$ git show --no-patch --format='%h %p %s' HEAD
c3d971a cecf599 2379bbb Merge branch 'feature'
%p skriver ut foreldrene. Den første, cecf599, er der main var. Den andre, 2379bbb, er tuppen av feature. git cat-file -p HEAD viser det samme som én tree-linje fulgt av to parent-linjer: en merge-commit er en vanlig commit med ett snapshot, det samlede resultatet, og to foreldre. Commitene på begge brancher blir verken kopiert eller endret.
I en ekte terminal åpner Git editoren med meldingen «Merge branch 'feature'». Lagre for å godta den, bruk --no-edit for å hoppe over editoren, eller -m for å skrive din egen.
Navigere mellom foreldre: ^ og ~
| Uttrykk | Betyr |
|---|---|
HEAD^ eller HEAD^1 |
første forelder: der du stod |
HEAD^2 |
andre forelder: det du merget inn |
HEAD~2 |
to steg tilbake, alltid via første forelder (samme som HEAD^^) |
HEAD^2~1 |
ett steg tilbake langs branchen som ble merget inn |
^ velger hvilken forelder. ~ går bakover i generasjoner.
Lese en merget historikk
* c3d971a Merge branch 'feature'
|\
| * 2379bbb Style login form
| * 27a6985 Add login form
* | cecf599 Add docs
|/
* df6c7db Initial snapshot
Venstre kolonne er første-forelder-linjen: historien til main. Nyttige visninger:
git log --oneline --first-parentviser bare stegenemainselv tok, her c3d971a, cecf599 og df6c7db.git log --mergesviser bare merge-commits.git diff HEAD^1 HEADviser hva merge brakte inn imain.
Test før du committer mergen
En merge uten konflikter betyr bare at tekstene passet sammen. Eksempel: på main gir noen funksjonen greet() nytt navn, welcome(). På feature legger en annen til et nytt kall til greet() i en annen fil. Git merger begge uten problemer, og programmet virker ikke lenger. Kjør testene etter hver merge, før du pusher.
Vil du se på eller teste resultatet før det lagres, bruk git merge --no-commit --no-ff feature. Git merger, stopper før commit og skriver Automatic merge went well; stopped before committing as requested. Test, og fullfør med git commit, eller trekk deg ut med git merge --abort. Uten --no-ff blir en mulig fast-forward likevel gjennomført.
Strategivalg, brukt med omhu
-X ourseller-X theirs: når et område er i konflikt, ta vår eller deres versjon automatisk. Endringer uten konflikt fra begge sider merges fortsatt. Praktisk, men du hopper over avgjørelsen en konflikt ber om.-s ourser noe helt annet: den registrerer en merge, men beholder ditt snapshot uendret og kaster alle endringene deres. Sjelden det du vil.- Å merge flere brancher samtidig,
git merge a b c, gir en «octopus»-merge med mer enn to foreldre. Den nekter hvis noe er i konflikt.
Angre en merge
- Mens en merge fortsatt pågår, på grunn av konflikt eller
--no-commit:git merge --abort. - En fullført merge du ikke har delt:
ORIG_HEADholder der branchen var; leksjonen om reset viser hvordan du går tilbake. - En merge som allerede er pushet:
git revert -m 1 <merge>lager en ny commit som angrer den.-m 1sier at første forelders side er den som skal beholdes. Se leksjonen om revert.
Offisiell dokumentasjon
- git merge, inkludert merge-strategier
- git merge-base
- gitrevisions, for
^og~ - Pro Git: Basic Branching and Merging
Per-Arne's Git Good!