Skip to content

📅 Git-adventskalender med tips og middels avansert workflow

Notifications You must be signed in to change notification settings

LarsSelbekk/git-adventskalender

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 

Repository files navigation

Git-julekalender

Dette er en adventskalender med git-fakta jeg lagde til julen 2020. Merk at dette var et lÊringsprosjekt, sÄ det kan finnes feil eller unÞyaktigheter, selv om jeg forsÞkt Ä unngÄ det. Hvis du finner noe kan du lage en issue eller pull request :)

01.

For Ă„ legge til flere avsnitt i commit-meldinger, bruk flere '-m'-argumenter, slik: git commit -m "Tittel" -m "Body".

02.

For Ă„ sĂžke gjennom commit-meldinger, bruk git log --grep="sĂžketekst".

03.

For Ä se hva som vil commites nÄr du har add-et, bruk git diff --staged. Det viser altsÄ ikke endringer som ikke er add-et.

04.

NÄr du ikke vet i hvilken commit en bug ble introdusert, bruk git bisect. Den binÊrsÞker commitene ved Ä checkoute en serie av dem, sÄ kjÞrer du koden og sjekker om buggen var i den commiten. Du kan spesifisere filer eller mapper for Ä filtrere commitsene som skal sjekkes pÄ med vanlig ---notasjon. Alternativt kan du kjÞre hele greia automatisk ved Ä skrive et script som tester koden og endrer hvilken exit-kode den returnerer avhengig av om buggen er til stede. Se her for detaljer.

For Ä starte manuell bisect, skriv git bisect start. Checkout den tidligste commiten du vet har buggen. Skriv git bisect bad. Checkout den nyeste commiten du vet ikke har buggen, og skriv git bisect good. Git vil da checke ut en commit, du sjekker om buggen eksisterer, og skriver git bisect good eller git bisect bad. Git gÄr da videre, og gjentar til du har funnet commiten som introduserte buggen.

SĂ„klart trenger ikke dette handle om Ă„ finne en bug, og da kan det vĂŠre tydeligere Ă„ skrive new og old i stedet for bad og good.

05.

git show <ref> er en rask mÄte Ä se meldingen og endringene i en commit pÄ - den gir diffen fra commiten til den forrige. Alternativt kan du se tilstanden til en fil i den commiten ved git show ref>/<filpath>.

06.

Man kan adde kun deler av en fil med git add -p [filer], du fÄr en interaktiv guide. Alternativt kan man bruke git add -i for Ä gjÞre hele add-prosessen interaktiv. For total kontroll over hva som addes kan du bruke "e"-modus i patch-uiet. Da fÄr du opp tekst-editor med en liten guide.

07.

For Ä fast-forwarde en branch uten Ä checkoute, skriv f.eks. git fetch origin master:master. Hvis du (som meg) blir promptet for ssh-passord hver gang du puller kan du bruke git fetch . origin/master:master. Forskjellen er at du spesifiserer det lokale repoet (.) som remote, sÄnn at ingenting mÄ hentes.

08.

For Ä stille en fil tilbake til tilstanden den hadde ved en viss commit, skriv git checkout [branch] -- <filer>. Hvis branch ikke spesifiseres tolkes det som det nÄvÊrende. Dette kan brukes for Ä un-stage endringer.

09.

For Ä pushe en tag til remote, bruk git push --follow-tags fra en commit som er en descendent av commiten taggen er pÄ (dvs. det mÄ gÄ en linje av commits bakover fra den du er pÄ til den med taggen). Kun annotated tags vil pushes. De lages med git tag -a <navn> (annotated) eller git tag -s <navn> (signed). I stedet for Ä spesifisere follow tags hver gang kan man sette git config --global push.followTags true. Alternativt kan man pushe én tag manuelt med git push <remote> <tag>.

10.

For Ă„ fĂ„ samme statistikk-oversikt i loggen som nĂ„r du puller, bruk git log --stat. For Ă„ fĂ„ et fint graf-view, bruk git log --all --decorate --oneline --graph (aka. "A DOG" 🐕). For Ă„ se GPG-signaturer, bruk git log --show-signature. For Ă„ skrive ditt eget log-format, bruk git log --format=<format>, hvor <format> er f.eks. "%Cred%h %Cblue%an: %Creset%s", som betyr: rĂžd farge; kort hash; mellomrom; blĂ„ farge; forfatters navn; kolon mellomrom; standard farge; commit-melding. For flere koder (og presets), se git log --help.

11.

For Ä forke et repo uten Ä bruke knappene til GitHub og GitLab, lag et tomt repo, clone repoet du vil forke. git remote set-url origin <url>, push. Hvis du vil beholde det du forket fra som upstream, kjÞr i stedet git remote rename origin upstream, git remote add origin <url>, push (sjekk at du pusher til riktig remote). Hvis GitHub eller GitLab lagde et commit du vil overskrive mÄ du force pushe.

12.

Dagen har kommet for interactive rebase. For detaljert gjennomgang av det viktige, se her. Interactive rebase brukes vanligvis for Ä skrive om historien. Jeg anbefaler Ä lage en ny branch fÞr man begynner Ä rebase. Begynn med git rebase -i <commit>, hvor <commit> er parent-en til den siste du vil redigere. Du kan referere til den commiten med f.eks. HEAD~<N>, hvor <N> er antall commits du vil redigere. Det betyr bare commit nr N bakover fra der du er (med HEAD~0 som nÄvÊrende). Du fÄr opp tekst-editor med commitene, men merk at de stÄr i omvendt rekkefÞlge, dvs. nyeste pÄ bunnen. Du kan se for deg hver linje som en kommando git kjÞrer, fra Þverst til nederst. For Ä gjÞre en handling pÄ en commit, endre "pick" fÞr commit-hashen og meldingen til et annet alternativ.

Dette er alternativene du fÄr for hver commit:

  • pick: behold commit, ikke gjĂžr endringer
  • reword: kun endre commit-melding
  • edit: sjekk ut commiten, rediger filer, amend, continue. Hvis du vil dele commiten i flere commits kan du kjĂžre git reset HEAD^ for Ă„ gĂ„ Ă©n commit tilbake men beholde endringene fra commiten som ikke-staged endringer. Du kan sĂ„ adde de delene du vil ha i fĂžrste commit, commite (uten amend), osv., og continue til slutt.
  • squash: kombinĂ©r denne commiten med den forrige. Hvis flere pĂ„fĂžlgende har squash, kombinĂ©r alle dem med den fĂžrste hĂžyere oppe som ikke har det. Du blir promptet for Ă„ skrive en ny commit-melding for dem kombinerte commiten.
  • fixup: som over, men du trenger ikke promptes for ny melding siden den bruker meldingen til den fĂžrste hĂžyere oppe som ikke er fixup
  • drop: samme som Ă„ fjerne linja med commiten. Vil fjerne hele commiten, inkludert endringer. FĂžlgende er ikke alternativer for et commit, men kommandoer man skriver alene pĂ„ en linje mellom to commits/kommandoer:
  • exec: kjĂžr en kommando, stopp hvis kommandoen "feiler" (returnerer en kode som ikke er 0). Eksempelvis kan man sjekke at testene passer pĂ„ en commit ved Ă„ skrive exec mvn test pĂ„ en ny linje under linja til commiten du vil teste. For Ă„ sjekke ut og teste alle kan du bruke ikke-interactive git rebase <parent til eldste commit> --exec "<kommando>".
  • break: pause her. Lar deg se hvordan det ser ut etter forrige commit
  • label, reset, merge: ignorer disse, de brukes internt av git til merging. Hvis du mĂ„ vite, se her.

13.

Dagens tema er git reset. Det finnes tre viktige moduser:

  • --mixed (hverken soft eller hard): Dette er standard hvis ikke annet er oppgitt. Den vil resette indexen (aka staging area, endringene som er addet) til oppgitte filer til tilstanden de hadde ved gitt commit, pluss at den vil flytte current branch til Ă„ peke pĂ„ gitt commit. Eksempelvis vil git reset README.md license.txt un-adde endringene i license.txt og README.md, fordi den resetter filene til siste commitede tilstand og branchen allerede peker pĂ„ HEAD. Vi mĂ„tte ikke spesifisere at vi ville resette dem likt HEAD (current commit) fordi det er default, men det hadde vĂŠrt sĂ„ lett som Ă„ fĂžye det til etter alle filene. Alternativt kan man bruke git reset <commit> -- <filer> for Ă„ vĂŠre sikker pĂ„ at git ikke prĂžver Ă„ tolke filer som commits eller omvendt. Husk ogsĂ„ at en branch bare er en referanse til en commit, sĂ„ det vil ogsĂ„ aksepteres i stedet for en spesifikk commit. Hvis vi vil fjerne alle filene fra indexen trenger vi kun git reset. NĂ„r mĂ„l-commiten er HEAD er dette den inverse formen av git add, og man kan til og med bruke -p for Ă„ velge delerav filene Ă„ resette, som i git add -p. Hvis man velger en annen commit enn HEAD kan man bygge videre pĂ„ en commit fra en annen branch, men merk at working directory ikke endres, og at den nĂ„vĂŠrende branchen flyttes til samme commit. Hvis det Ăžnskes Ă„ resette working directory lik index kan man lese pĂ„ hvordan det gjĂžres med git restore.
  • --soft: endrer kun hvilken commit current branch peker pĂ„, ikke indexen.
  • --hard: flytter branchen til Ă„ peke pĂ„ den oppgitte commiten, tĂžmmer index og setter working directory likt tilstanden i commiten.

14.

Dagens tema er hvordan git lagrer filer. Det kritiske Ä vite her er at git konseptuelt[1] ikke lagrer en delta (aka diff, aka liste over endringer) fra én commit til neste. I stedet lagrer git et snapshot av alle filene i hele repositoryet ved hver commit. For at dette ikke skal kreve ekstremt mye plass, vil git bare lagre en fil med et konstant innhold én gang. Det vil si at nÄr du kun endrer én fil, er det kun én fil som mÄ lagres. Dette gjÞres ved at hver fils innhold hashes med SHA-1, og innholdet lagres i et hashmap med hashen som nÞkkel.

NÄr man skjÞnner at git konseptuelt ikke lagrer deltaer, men hele filer, er det enklere Ä skjÞnne hvordan git-kommandoer fungerer. For Ä gjÞre et cherry-pick (velge kun én commit av en serie og legge den til en annen branch) mÄ git fÞrst regne ut diffen fra den commiten til forrige, og prÞve Ä patche target-branchen med den diffen.

[1] Trivia: NÄr det kommer til Ä faktisk lagre bitene kan git faktisk sette sammen flere versjoner av samme fil i ett komprimert lager som bruker deltaer i stedet for hele filen, men det gjÞr ingen praktisk forskjell for grensesnittet til git. For binÊrfiler kan ikke git finne deltaer, sÄ den mÄ alltid lagre hele filen pÄ nytt hver gang noe endres, hvilket er grunnen til at binÊrfiler ikke er anbefalt Ä putte i git.

15.

git restore er en ny (halv-eksperimentell) kommando (mulig man mÄ oppdatere git for Ä fÄ den) som skal gjÞre det mer brukervennlig Ä hÄndtere versjonen til filer. Den forsÞker Ä erstatte

  • git checkout <commit> -- <filer> for Ă„ resette filer til en annen versjon, med git restore [--source <commit>] <filer>. Hvis <commit> ikke oppgis er index (staging area) default.
  • git reset <filer> for Ă„ un-stage endringer, med git restore --staged <filer>.
  • Begge, med git restore [--source <commit>] --staged --workspace <filer>.
    --workspace kan erstattes med -W. --staged kan erstattes med -S. --source kan erstattes med -s. En ting som er kan vÊre verdt Ä merke seg er at nÄr jeg skriver <commit> ber git ofte egentlig om en "tree-ish", som betyr at du kan resette en fil til innholdet til en helt annen fil i en vilkÄrlig commit - eller til og med utenfor et commit. Se her hvis du er nysgjerrig.

16.

Git har hele tre nivÄer med config, hvor det mest spesifikke nivÄet alltid tar presedens. De er:

  1. git config --system <kommando>. Lagres i git-installasjonen
  2. git config --global <kommando>. Lagres i brukermappen.
  3. git config [--local] <kommando>. Lagres i det lokale repoet (.git-mappen i din clone av prosjektet). Et eksempel pÄ en kommando kan vÊre
  • --list, for Ă„ liste opp alle innstillingene i den fila. Bruk --show-origin nĂ„r du ikke spesifiserer fil for Ă„ se hvilken fil innstillingen er satt i. Brul --show-scope pĂ„ samme mĂ„te hvis du bare vil se "global", "local", etc. isf. path. Merk at med --list fĂ„r du alle filene hvis du ikke spesifiserer, i stedet for bare local.
  • <innstilling> <verdi> for Ă„ sette en innstilling
  • --get <innstilling> for Ă„ se den aktive verdien, dvs. den som faktisk tar presedens.
  • --get-all <innstilling> for Ă„ se alle linjene som definerer en gitt innstilling.
  • --unset <instilling> for Ă„ fjerne en innstilling (sette tilbake til default).
  • --unset-all <innstilling> for Ă„ fjerne alle linjer som definerer key-en. Merk at dette ikke har virkningsomrĂ„de over alle filene, men bare linjer med samme key i Ă©n fil.
  • --replace-all <innstilling> <verdi> for det samme som over pluss Ă„ sette ny verdi for den keyen i den gitte filen. Grunnen til at --<kommando>-all eksisterer er fordi git tillater flere linjer for samme key, men den bruker alltid den siste.

17.

Git clean er laget for Ä fjerne untrackede (og potensielt ignorerte) filer fra filsystemet, fra nÄvÊrende mappe og ned. Den ikke slette untracked mapper uten -d. Hvis du vil vite hva den kommer til Ä slette fÞr du kjÞrer kommandoen kan du bruke --dry-run, aka -n. Hvis du vil at den ogsÄ skal slette ignorerte filer, bruk -x. Hvis du bare vil slette ignorerte, bruk -X. Hvis du vil ekskludere noen filer, bruk --exclude=<pattern> (aka -e <pattern>). Hvis du kun vil cleane noen mapper kan du skrive dem etter hverandre til slutt. NÄr du har bygd ferdig kommandoen mÄ du bruke -f for Ä faktisk slette dem, eller sette config clean.requireForce false. Eksempel: git clean -df core/.

18.

Som mange Unix-baserte verktÞy bruker Git glob patterns nÄr du mÄ spesifisere filer/mapper. De er basically en veldig enkel versjon av regex, med fire wildcards[1]:

  • * for "hva som helst her uavhengig av lengde, bortsett fra /". Dvs. at kun filer i den nĂ„vĂŠrende mappen kan inkluderes med denne, siden filer i andre mapper vil ha skrĂ„strek i path-en.
  • ? for "Ă©n karakter her, bortsett fra /".
  • ** for "absolutt hva som helst her, ogsĂ„ /". Brukes for rekursive sĂžk.
  • [aBz] for Ă„ matche Ă©n av karakterene inni, som i regex. Ranges ([a-z]) funker ogsĂ„. Merk at ingen av disse spesifiserer antallet av det som kom rett fĂžr som i regex, de er frittstĂ„ende. Dvs. *.java som glob er ekvivalent med .*\.java som regex. Man kan ikke representere regexen [ab]+ i glob[2]. Merk ogsĂ„ at glob patterns alltid mĂ„ matche hele strengen, sĂ„ karl.java.banan blir ikke matcha av *.java.

.gitignore er ett av bruksomrÄdene. Den fungerer ved Ä gÄ inn i hver mappe rekursivt, sjekke om filnavnene i den matcher noen av patternene (unntatt patterns med en / et annet sted enn helt til slutt), og i sÄ fall markere filen som ignorert. Eksempelvis kan man bruke *.iml for Ä ignorere IntelliJ-data-filer. Hvis et pattern slutter med / matcher den bare mapper (uten vil den matche bÄde mapper og filer). Hvis den har en un-escaped / et annet sted matcher det patternet bare relativt til .gitignore-filen. E.g. vil idea/ ignorere alle mapper med det navnet, mens /idea/ bare ignorerer idea-mapper i samme mappe som .gitignore, og /core/idea/ eller core/idea/ bare matcher dem i undermappen core av mappen .gitignore er i. Flere .gitignore-filer kan brukes, den lavest i mappetreet tar presedens. Til slutt vil ! fÞr et pattern negate patternet, sÄnn at de som matcher vil inkluderes selv om de blir ignorert av et annet pattern. Det gÄr ikke an Ä override at disse inkluderes uten Ä ha en annen .gitignore med hÞyere presedens som ignorerer dem.

[1]: Wildcardene matcher vanligvis ikke en leading ., da disse er ment Ä indikere skjulte filer i Unix-baserte systemer. Man mÄ derfor spesifisere den eksplisitt, med f.eks. .* eller !.git*. Git ser ut til Ä fÞlge denne konvensjonen til vanlig (e.g. git add * vil matche .gitignore), men ikke i .gitignore-patterns (* vil ignorere .kanin (og interessant nok .gitignore)).

[2]: Det er derimot mulig i extended glob, men det stĂžtter ikke git.

Source.

19.

git reflog [ref] viser endringsloggen til en referanse (HEAD, branches). Default er HEAD. Vi kan bruke kommandoen for Ä finne ut hvor vi har vÊrt nylig (git sletter for gammel historie automatisk). Reflogen til HEAD viser hvilken branch den pekte pÄ, og hvilken commit branchen pekte pÄ, pÄ hvert tidspunkt. Man kan f.eks. bruke dette for Ä finne igjen en gammel commit fra fÞr en rebase. Da kan det vÊre lurt Ä sjekke reflogen til branchen i stedet for Ä filtrere litt mer. Man kan bruke shorthand <ref>@{<N>} for Ä referere til hvor referansen var for N moves siden. Merk at man pÄ noen plattformer (windows) mÄ putte hermetegn rundt alle uttrykk med @.

20.

Bruk git log --follow -- <fil> for Ä se commits som pÄvirker en fil, selv hvis den endret navn. Merk at git ikke lagrer renames noe sted, sÄ de mÄ finnes probabilistisk hver gang basert pÄ hvor mange linjer som er like. Merk at git bare gir opp hvis antall slettede filer pluss antall nye filer i en commit er stÞrre enn en konstant. Endre diff.renamelimit i configen hvis du trenger mer aggressiv sjekk. For Ä bare gjÞre det denne gangen, bruk git log --follow -c diff.renamelimit=<tall> -- <fil>.

21.

Dagens luke er en mÄte Ä merge to distinkte repos pÄ. Jeg brukte denne da jeg startet et eget OOP-repo, og sÄ senere ville hente inn Þvingene automatisk fra fagstab-repoet, hvis jeg husker riktig. Da merget jeg fagstabens inn i mitt, og beholdt fagstaben som upstream.

  1. GĂ„ til repoet du vil merge det andre inn i.
  2. git remote add <url/til/andre/repo> <remote-name>
  3. git fetch <remote-name>
  4. git branch --track <merge-branch-name> <remote-name>/<target-branch>
  5. git merge <merge-branch-name> --allow-unrelated-histories
  6. Hvis merge conflicts: resolve og commit.
  7. Du kan nÄ slette <merge-branch-name> (og <remote-name>, hvis du vil).

22.

Noen gang glemt hvor og nÄr i alle dager noe kode eksisterte? Git got your back. For Ä finne alle filer som har tekst akkurat nÄ som matcher sÞket (trenger ikke matche en hel linje), bruk git grep -F '<sÞk>. For regex, bruk git grep -e '<regex>'. For flere alternativer (det er altfor mange til Ä skrive her), se dokumentasjonendokumentasjonendokumentasjonendokumentasjonen.

Hvis du ikke vet hvilke(n) commit(s) teksten eksisterer i, bruk git rev-list --all | xargs git grep -e '<regex>'. Rev-list gir listen over alle commits som kan nÄs fra branches (dvs. at den ikke finner commits fra f.eks. slettede branches). Xargs mater outputet inn i git grep-kommandoen med magi sÄ det blir riktig argument. Git grep sÞker gjennom hvert av commitene den fÄr fra xargs for Ä lete etter regexen. Merk at dette ikke gir bare commiten som la til teksten; dette har Ä gjÞre med at commits ikke er differ. Git grep sÞker gjennom alle filene i repository-snapshotet som hÞrer til commiten, ikke diffen.

Hvis du bare vil ha commiten som la til teksten, kan du sette rev-list til Ä begynne ved starten, og sÄ endre litt pÄ kommandoen sÄ den stopper ved fÞrste resultat (f.eks. med ... | head -1). En enklere og bedre mÄte vil vÊre Ä bruke dette SO-svaret.

23.

NÄr du har kodet en stund og vil bryte opp det du har gjort i flere commits, kan du begynne med git add -i. NÄr du har lagt til alt du vil commite er det veldig lurt Ä sjekke om programmet kjÞrer i den commiten - et mÄl for git er at hvert eneste commit kan kjÞres. For Ä finne ut om det holder er dette min favorittmÄte:

  1. Add med git add -i
  2. git stash --keep-index --include-untracked (aka git stash -ku)
  3. KjĂžr koden og sjekk at alt funker/kjĂžr testene
  4. Eventuelt fiks feil og add
  5. Commit
  6. git stash pop hvis du er modig, git stash apply etterfulgt av git stash drop hvis det gikk bra for oss vanlige dĂždelige.
  7. Fiks eventuelle conflicts (kan skje hvis du mÄtte fikse feil)
  8. Repeat I forget for Ä bruke interactive adding kan man bruke git stash -p, men jeg synes det er mindre oversiktlig. Fordelen da er at man ikke trenger Ä stashe det som skal med i denne commiten, hvilket reduserer muligheten for konflikter hvis du mÄ endre noe.

Ekstratip: git checkout -- <filer> resetter til staged endringer.

24.

Gratulerer med vel gjennomfĂžrt kurs, nĂ„ har vi kommet til slutte. Den siste luka blir et krimmysterie som kan lĂžses med git: Gitstery 🎅

About

📅 Git-adventskalender med tips og middels avansert workflow

Topics

Resources

Stars

Watchers

Forks