Meny for Per-Arne's Git Good!
Fag og prosjekt Per-Arne's Git Good!

Fetch, pull og push: hold deg i takt med teamet

Modul 3: Samarbeid · 12 min

Den interaktive leksjonen trenger JavaScript. Teksten under er hele leksjonen.

Etter leksjonen kan du

  • forklare hvorfor din origin/main ikke flytter seg når en kollega pusher, før du kjører git fetch forstå
  • velge mellom git fetch og git pull, og mellom --ff-only, --no-rebase og --rebase når du puller vurdere
  • publisere commits med git push, og løse en avvist push ved å hente, integrere og pushe igjen anvende

Forstå: Hvem vet hva, og når?

Du og Ada har hver deres klone av origin. Ada lager commit B og pusher. Main i origin flytter seg til B. Din main og din origin/main står fortsatt på A. Git sender deg ingen beskjed. Ingenting kommer til maskinen din av seg selv.

git fetch spør origin hva som er nytt, laster ned commit B og flytter origin/main dit. Din main flytter seg ikke, og filene dine er uendret. Derfor er fetch trygt når som helst.

Så må du integrere. Her er main bare bak, så en merge kan spole main fram til B, en fast-forward. git pull gjør begge stegene i én kommando: først fetch, så integrer.

Du lager commit C. Bare din main flytter seg.

git push sender C og ber origin flytte main. En vanlig push godtas bare som en fast-forward: den nye commiten må bygge på alt main i origin allerede har. C bygger på B, så det går, og din origin/main følger med.

Men Ada har ikke hentet. Hun lager D oppå B og pusher. D bygger ikke på C, så pushen er ikke en fast-forward, og origin avviser den. Å godta den ville kastet C ut av main. Ingenting er ødelagt.

Løsningen er alltid den samme: hent, integrer og push igjen. Ada kjører git pull --no-rebase, som henter C og lager merge-commiten M. Nå er pushen hennes en fast-forward. Å tvinge gjennom pushen med --force ville i stedet kastet din C ut av main.

Se demo: Ada pusher, du blir avvist, og dere løser det

Både du og Ada har en ny commit som ikke er pushet. Ada er først. Hun pusher, og main i origin flytter seg til commiten hennes. Se at din origin/main står igjen.

$ git push

Tilbake hos deg. Ada har nettopp pushet. Hva tror du git status sier?

$ git status

Vi prøver å pushe likevel. Origin sier nei: rejected, fetch first. Origin har en commit du aldri har lastet ned, og pushen din ville kastet den ut av main. Ingenting er endret. Les hintet fra Git.

$ git push

Først henter vi det som er nytt. git fetch laster ned Adas commit og flytter origin/main dit. Din main står stille. Nå ser du i grafen at linjene har delt seg.

$ git fetch

Samme spørsmål som i stad, nytt svar. Nå vet Git om Adas commit, og status sier at main og origin/main har divergert: én commit hver.

$ git status

Nå integrerer vi. git pull --no-rebase henter og merger. Valget sier at vi vil ha en merge-commit. Uten valget nekter moderne Git å gjette når linjene har delt seg. Se den nye commiten med to foreldre.

$ git pull --no-rebase

Nå inneholder main både din og Adas commit. Pushen er en fast-forward, og origin godtar den. Merk at Adas main ikke flytter seg. Den får commitene først når hun puller.

$ git push

Oppgaver

  1. Som Ada: publiser den nye commiten hennes til origin.
  2. Spør Git hvordan main ligger an mot upstream.
  3. Prøv å publisere commiten din, og les hva origin svarer.
  4. Hent det som er nytt i origin, uten å endre din egen main.
  5. Få Adas commit inn i din main.
  6. Publiser main nå som den inneholder Adas arbeid.

Øv: Overfør: denne gangen blir Ada avvist

Nå var du først: commiten din er allerede pushet, og Ada har en commit som ikke er pushet. Denne gangen integrerer Ada med rebase i stedet for en merge-commit. Git spiller da commiten hennes av på nytt oppå din, som en ny kopi, så main blir en rett linje. Det er trygt fordi commiten hennes bare finnes hos henne.

Oppgaver

  1. Som Ada: få commiten hennes inn i origin uten å miste din, og uten merge-commit.
  2. Tilbake hos deg: oppdater main så den er lik main i origin. Du har ingen egne commits som mangler der.

Sjekk deg selv: Hent det fram igjen

Etter git fetch sier git status at main og origin/main har divergert, med 2 og 1 commits. De to commitene dine ligger også på branchen docs i origin, og Ada bygger videre på dem. Hvordan integrerer du?

  • git pull --ff-only
  • git pull --rebase
  • git pull --no-rebase

Hva endrer git fetch i ditt repository?

  • Din main og filene i arbeidsmappen
  • Den laster ned commits og flytter remote-tracking-brancher som origin/main
  • Main inne i origin

Pushen din blir avvist som non-fast-forward. Hva gjør du?

  • git push --force, for min versjon er den riktige
  • Henter, integrerer de nye commitene og pusher igjen
  • git push --force-with-lease, den trygge varianten av force

git pull uten valg stopper med «Need to specify how to reconcile divergent brancher». Hvorfor?

  • Origin kan ikke nås
  • Du har filer som ikke er committet
  • Begge sider har nye commits, og Git vil ikke velge mellom merge og rebase for deg

Repetisjon fra modul 2: du står på main og kjører git merge feature. Når blir det en fast-forward?

  • Når det ikke er noen konflikter
  • Når main ikke har noen commits som feature mangler, så main bare kan flytte seg fram
  • Når feature bare har én commit

Vanlige misforståelser

  • «origin/main oppdaterer seg selv når noen pusher.» Git snakker med origin bare når du ber om det. Til da viser origin/main det du sist så.
  • «fetch endrer filene mine.» fetch laster ned commits og flytter remote-tracking-brancher. Din main og arbeidsmappen står urørt.
  • «En avvist push fikser man med --force.» Det fjerner kollegaens commits fra den delte main. Riktig løsning er å hente, integrere og pushe igjen.

Les mer

Fetch: oppdater notatene dine, ingenting annet

git fetch laster ned objektene du mangler og flytter remote-tracking-branchene dine. Hver linje i utskriften viser én ref som flytter seg fra en gammel til en ny commit:

$ git fetch
From /course/origin
   28e7a75..ef25355  main       -> origin/main

Branchene dine, staging-området og arbeidsmappen står urørt, så fetch er trygt når som helst, også midt i annet arbeid. Nyttige varianter:

  • git fetch --prune sletter også remote-tracking-brancher der branchen er slettet i remoten. git config --global fetch.prune true gjør det til standard.
  • git fetch --all henter fra alle remotes, noe som betyr noe når du har både origin og upstream.

Etter fetch, se før du integrerer: git status -sb skriver ## main...origin/main [ahead 1, behind 1], git log --oneline main..origin/main lister det som er nytt der borte, og git log --oneline --graph --all viser hvor linjene delte seg.

Pull: fetch pluss én integrasjon

git pull er git fetch etterfulgt av at upstream integreres i branchen du står på. Det finnes tre måter å integrere på, og du bør vite hvilken du ber om:

Valg Når branchen din bare er bak Når begge sider har nye commits
git pull --ff-only fast-forward nekter å integrere; branchen står stille, men fetch er gjort
git pull --no-rebase fast-forward merge-commit med to foreldre
git pull --rebase fast-forward dine commits som upstream mangler, spilles på nytt øverst, med nye ID-er

Hvis begge sider har flyttet seg og ingenting er konfigurert, stopper moderne Git og ber deg velge:

$ git pull
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
...
fatal: Need to specify how to reconcile divergent branches.

En god standard for nybegynnere er git config --global pull.ff only. Da spoler en vanlig pull bare fram, og når linjene har delt seg, bestemmer du hver gang med git pull --rebase eller git pull --no-rebase; valg på kommandolinjen overstyrer konfigurasjonen. Med --ff-only ser avvisningen slik ut:

$ git pull --ff-only
hint: Diverging branches can't be fast-forwarded, ...
fatal: Not possible to fast-forward, aborting.

Legg merke til at fetch-halvdelen allerede har skjedd: origin/main er oppdatert selv om din main ikke flyttet seg.

Merge eller rebase når du puller? Rebase gir en rett linje og passer for commits som bare finnes på din maskin. Rebase aldri commits som andre allerede har, for rebase lager nye commits og lar de gamle bli igjen i historikken deres. Er du i tvil, merge. Pull nekter også å starte når ucommittede endringer ville blitt overskrevet, og med --rebase når sporede filer har ucommittede endringer i det hele tatt. Commit eller stash dem først, eller bruk git pull --rebase --autostash, som stasher og legger dem tilbake for deg.

Push: be remoten flytte en branch

git push sender commitene dine og ber remoten flytte en branch. Med standardinnstillingen push.default=simple sender en vanlig push gjeldende branch til upstream-branchen med samme navn. En vanlig push godtas bare som en fast-forward: den nye commiten må inneholde alt branchen hadde. Det finnes to typiske avvisninger:

 ! [rejected]        main -> main (fetch first)
hint: Updates were rejected because the remote contains work that you do not
hint: have locally.
 ! [rejected]        main -> main (non-fast-forward)
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.

«fetch first» betyr at remoten har en commit du aldri har lastet ned, slik som i demoen. «non-fast-forward» betyr at du har commiten lokalt, typisk i origin/main, men ikke har integrert den. Begge betyr at pushen ikke er en fast-forward. Løsningen er den samme: git fetch, undersøk, integrer med merge eller rebase, kjør testene, git push. En avvisning ødelegger aldri noe.

Tagger sendes for seg: git push origin v1.0 sender én tag, og git push --follow-tags sender annoterte tagger som peker inn i commitene du pusher.

Ikke tving en avvist push

git push --force ber remoten flytte branchen uansett, og alt bare den branchen nådde, forsvinner fra den delte historikken. En avvisning på en delt branch betyr at noen andres arbeid ligger der: integrer det og push vanlig. Hostingtjenester kan forby tvungne pusher til beskyttede brancher, og på en egen server gjør receive.denyNonFastForwards det samme. Når og hvordan du tvinger en omskrevet branch av dine egne med --force-with-lease, og hvor den svikter, står i leksjonen «Rebase».

Offisiell dokumentasjon