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

Cherry-pick: kopier én commit

Modul 5: Verktøykassen · 12 min

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

Etter leksjonen kan du

  • kopiere endringen fra én bestemt commit over på en annen branch med git cherry-pick anvende
  • forklare hvorfor kopien får en ny ID selv om endringen er den samme, og hva -x legger til forstå
  • løse eller avbryte en cherry-pick som gir konflikt anvende
  • bruke cherry-pick for én enkelt commit og merge for en hel branch anvende

Forstå: Én rettelse, ikke hele branchen

Versjon 1.0 er ute hos brukerne og lever på branchen release-1.0. På main har teamet jobbet videre med nye funksjoner. Midt i dem ligger en rettelse, D, som brukerne av 1.0 trenger nå.

En merge av main inn i release-1.0 ville tatt med alt, også dashbordet og grafene. De er ikke klare for en release. Du vil ha akkurat én endring.

Du står på release-1.0 og kjører git cherry-pick. Den ser på hva commit D endret i forhold til forelderen sin, og gjør den samme endringen her. Resultatet er en ny commit, tegnet som D merket.

Kopien har samme melding og samme endring, men en ny ID. Den har en annen forelder og et nytt tidspunkt, så det er en annen commit. Originalen D står urørt på main.

Med valget -x skriver Git en linje nederst i meldingen om hvilken commit kopien kom fra. Det gir et spor du kan følge senere. Bruk det når originalen allerede er delt med andre.

Passer ikke endringen der du står, får du en konflikt, akkurat som ved merge. Rett filen, kjør git add, og fullfør med git cherry-pick --continue. Vil du heller tilbake til der du var, bruker du git cherry-pick --abort.

Cherry-pick er for enkeltcommits, som en hastefiks til en release. Skal en hel branch inn, bruker du merge. Mange kopier gir duplikater med ulike ID-er, og de kan gi forvirrende konflikter når branchene møtes senere.

Se demo: Hastefiks til release-branchen

Først ser vi på begge branchene. release-1.0 står ved Add login. På main ligger rettelsen nest øverst, mellom dashbordet og grafene.

$ git log --oneline --graph --all

Kopien skal havne på release-branchen, så vi står der når vi kopierer.

$ git switch release-1.0

Nå kopierer vi rettelsen. main~1 er commiten rett under tuppen av main, altså Fix login timeout. Valget -x noterer hvor kopien kom fra. Hva får release-1.0?

$ git cherry-pick -x main~1

Sammenlign de to commitene som heter Fix login timeout. Emnelinjen er lik, men ID-ene er forskjellige. Det er to ulike commits med samme endring.

$ git log --oneline --graph --all

Den fulle meldingen viser linjen som -x la til. Den peker tilbake på den fulle ID-en til originalen på main.

$ git log -1

Til slutt sjekker vi hvilke filer branchen sporer. Bare README og login.txt. Dashbordet fra main ble ikke med, akkurat som vi ville.

$ git ls-files

Oppgaver

  1. Vis historikken til alle branchene som en graf, én linje per commit.
  2. Gå til branchen som skal få rettelsen.
  3. Kopier rettelsen Fix login timeout over til release-1.0, og la Git notere hvor den kom fra.
  4. Vis hele meldingen til den nyeste commiten på branchen din.

Øv: Overfør: plukk fra et eksperiment

Du står på main. Branchen experiment har tre commits: Change greeting, Add input validation og Try dark theme. Bare noen av dem skal inn.

Oppgaver

  1. main trenger bare commiten Add input validation fra experiment. Kopier den til main uten de andre eksperimentene.
  2. Kopier også Change greeting fra experiment. Den kolliderer med README på main. Løs konflikten slik at README får to linjer, først Hello og så Welcome to main, og fullfør kopieringen.

Øv: Overfør: avbryt en kopiering

Du står på main, midt i en cherry-pick av Change greeting fra experiment, og den ga konflikt i README. Ingen demo denne gangen.

Oppgaver

  1. Du bestemmer deg for at README på main skal forbli som den er. Avbryt kopieringen, slik at alt er nøyaktig som før den startet.

Sjekk deg selv: Hent det fram igjen

Du cherry-picker D over på release-1.0. Kopien D′ har samme endring som D, men en annen ID. Hvorfor?

  • Fordi -x la til en linje i meldingen
  • Fordi D′ har en annen forelder, og forelderen er en del av det ID-en beregnes fra
  • Fordi Git gir hver kopi en tilfeldig ny ID

Du cherry-picker commit D. Hva blir med?

  • D og alle commitene før den
  • Bare endringen D gjorde i forhold til forelderen sin
  • Hele snapshotet til D, som erstatter filene dine

En feature-branch med tolv commits er ferdig og skal inn i main. Hva passer best?

  • Cherry-pick alle tolv, én etter én
  • Merge branchen
  • Cherry-pick bare den siste, siden den inneholder alt

Repetisjon fra «Rebase»: En rebase er i praksis en rekke cherry-picks. Hva betyr det for commitene en rebase lager?

  • De er de samme commitene, bare flyttet til et nytt sted
  • De beholder ID-ene sine så lenge det ikke blir konflikt
  • De er nye commits med nye ID-er, så du rebaser bare commits ingen andre har

Vanlige misforståelser

  • «Cherry-pick flytter commiten.» Originalen blir liggende. Du får en ny commit med samme endring og en ny ID.
  • «Cherry-pick tar med alt fram til commiten.» Den kopierer bare det den ene commiten endret, ikke forfedrene.
  • «Cherry-pick er en grei måte å flytte en hel branch på.» Mange kopier gir duplikater som lett gir konflikter senere. Bruk merge for en hel branch.

Les mer

Hva cherry-pick regner ut

git cherry-pick C tar forskjellen mellom commit C og forelderen dens og legger den forskjellen inn på commiten du står på, som en treveis-merge. Går det rent, lager Git en ny commit med meldingen og forfatteren til C, men med din HEAD som forelder og et nytt committer-tidspunkt. Ny forelder og nytt tidspunkt betyr ny ID: originalen og kopien er to forskjellige commits som tilfeldigvis inneholder samme endring.

Det har to følger. En cherry-pick tar aldri med commitene før C, så hvis C bygger på noe som kom inn tidligere, kan kopien gi konflikt eller ikke virke. Og Git lagrer ingen kobling mellom original og kopi med mindre du ber om det.

Flere commits på én gang

Du kan nevne flere commits, eller et område. Et område A..B betyr «commitene som kan nås fra B, men ikke fra A», så A selv er ikke med. Commitene legges inn eldste først.

git cherry-pick 3a24001 d847db3
git cherry-pick main~3..main~1
git cherry-pick main~3^..main~1

Den andre linjen kopierer main~2 og main~1; den tredje tar også med main~3. Kjør git log --oneline main~3..main~1 først for å se nøyaktig hva et område inneholder.

Når den stopper: continue, skip, abort, quit

En cherry-pick av flere commits er en liten sekvens. Når ett steg gir konflikt, stopper Git og forteller hva du kan gjøre:

Kommando Virkning
git cherry-pick --continue etter at du har rettet filene og kjørt git add: commit og fortsett
git cherry-pick --skip dropp denne commiten og fortsett med neste
git cherry-pick --abort stopp og gå tilbake til nøyaktig der du var før du startet
git cherry-pick --quit stopp, men behold commitene som allerede er laget

Et steg kan også stoppe fordi endringen allerede finnes. Eksemplene herfra starter fra leksjonens repository etter demoen, men med én commit til på main, Add login retries, mellom rettelsen og Add charts. Da er main~2 rettelsen og main~1 den nye commiten. Rettelsen ble plukket i demoen, så en ny kopi ville ikke endret noe:

$ git cherry-pick main~3..main~1
The previous cherry-pick is now empty, possibly due to conflict resolution.
...
Otherwise, please use 'git cherry-pick --skip'
$ git cherry-pick --skip
[release-1.0 0c100af] Add login retries

git status forteller når en cherry-pick pågår. Så lenge .git/CHERRY_PICK_HEAD finnes, skal du ikke starte noe nytt: fullfør, hopp over eller avbryt først.

Legg igjen et spor: -x og backporting

Slik ser meldingen til kopien fra demoen ut rett etter git cherry-pick -x main~1:

$ git log -1 --format=%B
Fix login timeout
(cherry picked from commit 3a2400125cf44e636d8c5f3bbf72332003118ab6)

Linjen fra -x gjør at hvem som helst finner originalen senere. Bruk den når originalen allerede er publisert, som ved backporting: en rettelse som lages på main og kopieres til en eldre release-branch. En vanlig teamavtale er å rette på main først og så cherry-picke med -x til hver release-branch som fortsatt støttes, slik at rettelsen aldri bare finnes på en gammel branch. Ikke bruk -x for en privat commit du bare flytter rundt på; referansen ville pekt på noe ingen andre kan se.

To beslektede valg: -n (--no-commit) legger endringen i arbeidsmappen og staging-området uten å committe, nyttig når flere plukk skal bli én commit. -e lar deg redigere meldingen.

Hvilke commits er allerede kopiert?

Fordi ID-ene er forskjellige, kan ikke git log se at to commits er kopier. Git kan sammenligne selve endringene (patch-ID-en):

$ git cherry -v release-1.0 main
+ 4e3574ec0227522c30bffeb244837dd4aa75c372 Add dashboard
- 3a2400125cf44e636d8c5f3bbf72332003118ab6 Fix login timeout
- d847db3f9e6e7b0a1418475ecef595b2cdb0489b Add login retries
+ b3f3e101177d5334c4edfd45a2c59f8228550eb9 Add charts

Minus betyr at en tilsvarende endring allerede finnes på release-1.0; pluss betyr at den ikke gjør det. git log --oneline --cherry-pick --right-only release-1.0...main viser de samme pluss-commitene som en vanlig logg.

Cherry-pick, merge, rebase eller revert?

  • Merge når en hel branch skal inn. Historikken viser at den kom inn, og senere merger vet hva som allerede er med.
  • Cherry-pick for noen få utvalgte commits, typisk rettelser til en release-branch.
  • Rebase når du vil flytte dine egne upubliserte commits til et nytt grunnlag. Rebase er i praksis en rekke cherry-picks pluss at branchen flyttes.
  • Revert når en commit skal angres på en delt branch. git revert lager en ny commit med motsatt endring, i stedet for å kopiere den.

Å kopiere mange commits mellom langlivede brancher gir duplikater. Når branchene merges senere, kommer samme endring to ganger med ulike ID-er. Git løser det ofte i stillhet, men har koden endret seg i mellomtiden, får du forvirrende konflikter.

Offisiell dokumentasjon