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

Worktrees og større prosjekter

Modul 5: Verktøykassen · 13 min

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

Etter leksjonen kan du

  • jobbe på to brancher samtidig med git worktree add, og rydde opp etterpå med git worktree remove eller git worktree prune anvende
  • skrive mønstre i .gitignore og slette usporede filer trygt med git clean -n før git clean -f anvende
  • forklare hva Git LFS og hooks løser, og hva de ikke garanterer forstå

Forstå: Flere mapper, og ting som ikke hører hjemme i Git

En arbeidsmappe viser én branch om gangen. git worktree add lager en ekstra arbeidsmappe til det samme repositoryet, her med en ny branch, hotfix. Den har sin egen HEAD, sitt eget staging-område og sine egne filer, men commits og brancher er felles.

En commit i den ene mappen er straks synlig i den andre, fordi det er samme repository. Her får hotfix commiten H.

I større prosjekter dukker det opp filer som aldri skal i Git, som logger og byggeresultater. Mønstre i .gitignore gjør at Git slutter å vise dem som usporede. Det gjelder bare filer som ikke allerede er sporet, så .gitignore fjerner ingenting fra historikken.

git clean sletter usporede filer, og Git har ingen kopi å gjenopprette dem fra. Kjør derfor alltid git clean -n først, som bare viser hva som ville blitt slettet. Usporede mapper krever -d, og ignorerte filer blir stående med mindre du legger til -x.

Store binærfiler, som video eller datasett, passer dårlig i Git. Hver versjon blir liggende i historikken, og en vanlig klone laster ned alle. Git LFS er et eget tillegg som lagrer en liten peker i Git og selve filen på en egen server.

Hooks er skript Git kjører ved hendelser, for eksempel før en commit eller en push. De ligger i din lokale .git-mappe, blir ikke klonet og kan hoppes over. Regler teamet må kunne stole på, hører hjemme i CI eller på serveren.

Se demo: En hastesak i en egen mappe

Vi står på feature midt i arbeidet med en søkefunksjon. search.txt er endret og ikke committet. Så kommer en hastesak som skal rettes fra main.

$ git status --short

I stedet for å stashe lager vi en ny arbeidsmappe ved siden av denne, ../hotfix. Med -b får den en ny branch, hotfix, som starter på main. Hva skjer med den uferdige endringen?

$ git worktree add -b hotfix ../hotfix main

git worktree list viser begge mappene, hvilken commit de står på og hvilken branch de har sjekket ut. Det er fortsatt bare ett repository.

$ git worktree list

Vi går inn i den nye mappen. Grafen og filpanelet viser fortsatt den første mappen, men det er det samme repositoryet.

$ cd /course/hotfix

Her gjør vi rettelsen og committer den. Se på grafen: hotfix får en ny commit, selv om vi committet i en annen mappe.

$ echo "Contact: support@example.com" >> README.md && git commit -am "Add support contact"

Så går vi tilbake til den første mappen.

$ cd /course/you

Det uferdige arbeidet ligger der det lå. Vi har verken stashet, committet eller byttet branch her.

$ git status --short

Rettelsen er committet på hotfix, så mappen trengs ikke lenger. git worktree remove sletter mappen, men branchen og commiten blir igjen. Etterpå merger du hotfix inn i main som vanlig.

$ git worktree remove ../hotfix

Oppgaver

  1. Se hvilke filer som har uferdige endringer.
  2. Lag en ekstra arbeidsmappe ../hotfix med en ny branch hotfix som starter på main.
  3. List arbeidsmappene til repositoryet.
  4. Gjør rettelsen i hotfix-mappen: legg til en linje i README.md og commit den på hotfix.
  5. Fjern den ekstra arbeidsmappen, men behold branchen.

Se demo: Ignorer og rydd trygt

Et prosjekt samler fort rot: en byggemappe, en loggfil og et kladdenotat. Ingen av dem skal i Git.

$ mkdir -p build && echo "binary" > build/app.bin && echo "debug output" > debug.log && echo "draft idea" > scratch.txt

Git viser alle tre som usporede. Med mange slike filer drukner de viktige endringene i støy.

$ git status --short

Vi skriver to mønstre i .gitignore. Det første tar alle filer som slutter på punktum log. Det andre tar hele build-mappen.

$ printf '*.log\nbuild/\n' > .gitignore

.gitignore er en vanlig fil. Vi committer den, så hele teamet får de samme reglene. Nå er bare kladden igjen som usporet.

$ git add .gitignore && git commit -m "Ignore logs and build output"

Før vi sletter noe, spør vi hva som ville forsvunnet. -n betyr prøvekjøring. Hva tror du står på lista?

$ git clean -n

Lista så riktig ut, så vi sletter med -f. Kladden er borte for godt, mens loggen og byggemappen står igjen.

$ git clean -f

Oppgaver

  1. Få Git til å ignorere alle loggfiler og hele build-mappen.
  2. Commit ignoreringsreglene.
  3. Se hva Git ville slettet, uten å slette noe.
  4. Slett den usporede kladden, men la de ignorerte filene stå.

Øv: Overfør: en gjennomgang og en opprydding

Du står på main med en uferdig endring i README.md. Branchen docs finnes allerede. I mappen ligger også to usporede filer, og forrige uke slettet du en gammel worktree-mappe, ../old-review, for hånd. Ingen demo denne gangen.

Oppgaver

  1. Git husker fortsatt den gamle mappen ../old-review, selv om den er borte. Rydd opp i Gits oversikt over arbeidsmapper.
  2. En kollega ber deg rette guiden på branchen docs nå. Sjekk ut den eksisterende branchen docs i en egen mappe ../docs, uten å røre README-endringen din.
  3. I docs-mappen: legg til en linje i guide.txt og commit den. Fjern så den ekstra mappen. Commiten skal ligge igjen på docs.
  4. Rydd i hovedmappen. Få Git til å ignorere alle filer som slutter på .tmp, og commit regelen. Slett så de andre usporede filene. README-endringen din skal fortsatt være der.

Sjekk deg selv: Hent det fram igjen

Branchen hotfix er sjekket ut i ../hotfix. I hovedmappen skriver du git switch hotfix. Hva skjer?

  • Git bytter, og begge mappene viser nå hotfix
  • Git nekter, fordi branchen allerede er sjekket ut i en annen worktree
  • Git flytter mappen ../hotfix hit

secret.env ble committet forrige uke. I dag legger du secret.env inn i .gitignore og committer. Er hemmeligheten trygg nå?

  • Ja, ignorerte filer blir ikke lenger pushet
  • Nei. Filen er fortsatt sporet, og de gamle commitene inneholder hemmeligheten
  • Ja, etter at git clean -fX har fjernet den

Prosjektet skal ha store videofiler som endres ofte. Hva passer?

  • Commit dem som vanlige filer, Git komprimerer dem uansett
  • Git LFS, som lagrer en peker i Git og innholdet på en egen server
  • Legg dem i .gitignore, så tar de ikke plass

Teamet vil sikre at kode som ikke kompilerer, aldri havner på main. Holder det med en pre-push-hook?

  • Ja, hooks følger med til alle som kloner
  • Nei, hooks er lokale og kan hoppes over. Regelen må håndheves på serveren, med påkrevde CI-sjekker og beskyttet main
  • Ja, hvis hooken committes i en hooks/-mappe i repositoryet

Repetisjon fra «Fetch, pull og push»: Ada lager en commit i sin egen klone og pusher den til main. Når ser du den i ditt repository?

  • Med en gang, akkurat som i en worktree
  • Så snart hun pusher, uten at du gjør noe
  • Når du kjører git fetch, og da som origin/main

Vanlige misforståelser

  • «En worktree er en ny klone.» Alle worktrees deler det samme repositoryet, med de samme commitene og branchene.
  • «git clean kan angres.» Git har ingen lagret kopi av det usporede filer inneholder nå. Det som slettes, er borte.
  • «.gitignore skjuler hemmeligheter.» Den gjelder bare usporede filer. Det som allerede er committet, ligger fortsatt i historikken.

Les mer

Worktrees: hele livsløpet

Hver worktree har sin egen HEAD, sitt eget staging-område og sine egne filer, mens objektene, branchene, taggene og konfigurasjonen er felles. Git fører et lite register for hver ekstra worktree i .git/worktrees/. Kommandoene du trenger:

Kommando Hva den gjør
git worktree add ../review docs sjekker ut en eksisterende branch i en ny mappe
git worktree add -b hotfix ../hotfix main lager en ny branch på main og sjekker den ut der
git worktree add --detach ../try v1.0 ser på en commit uten noen branch
git worktree list viser alle worktrees, commiten og branchen deres
git worktree remove ../review sletter mappen og registeroppføringen; nekter hvis den har endrede eller usporede filer
git worktree prune glemmer oppføringer der mappen har forsvunnet
git worktree lock --reason "on USB disk" ../review beskytter en oppføring mot prune, for mapper på flyttbare disker
git worktree move ../review ../old-review flytter en worktree-mappe

Sletter du en worktree-mappe for hånd, husker Git den fortsatt, og branchen forblir «i bruk»:

$ git worktree list
/course/you    78e95ce [main]
/course/hotfix 78e95ce [hotfix]
/course/review 78e95ce [docs] prunable
$ git worktree prune --dry-run --verbose
Removing worktrees/review: gitdir file points to non-existent location
$ git worktree prune

En branch kan bare være sjekket ut i én worktree om gangen. Prøver du et annet sted, nekter Git med «fatal: 'hotfix' is already used by worktree at …». git worktree remove nekter å fjerne en mappe med endrede eller usporede filer; legger du til --force, slettes de for godt, så commit eller stash først.

Ignoreringsregler i detalj

Mønstrene i .gitignore leses linje for linje, og den siste regelen som matcher, vinner:

  • *.log matcher i alle mapper; /todo.txt med skråstrek først bare på toppnivå.
  • build/ med skråstrek til slutt matcher bare mapper.
  • **/tmp matcher tmp på alle nivåer.
  • !keep.log tar med igjen en fil en tidligere regel ignorerte, med mindre hele mappen er ignorert.
  • # starter en kommentar.

Regler for hele teamet hører hjemme i den committede .gitignore. Personlige regler legger du i .git/info/exclude eller i en global fil satt med core.excludesFile. Lurer du på hvorfor en fil er ignorert, spør Git:

$ git check-ignore -v build/output.log
.gitignore:2:build/ build/output.log

Ignoreringsregler gjelder bare usporede filer. Vil du slutte å spore en fil som allerede er committet, kjører du git rm --cached secret.env, legger den i .gitignore og committer. Filen ligger fortsatt i historikken, så en lekket hemmelighet må trekkes tilbake og byttes ut, ikke bare ignoreres.

Valgene til git clean

git clean krever -f for å slette noe. -n viser hva som ville forsvunnet, -d tar med usporede mapper, -x sletter også ignorerte filer, -X sletter bare ignorerte filer (nyttig for å fjerne byggeresultater), og -i spør interaktivt. Kjør alltid de samme valgene med -n først.

.gitattributes: linjeslutt og binærfiler

Windows skriver linjeslutt som CRLF, macOS og Linux som LF. En felles .gitattributes gjør repositoryet konsistent uansett hvem som committer:

$ printf '* text=auto\n*.sh text eol=lf\n*.png binary\n' > .gitattributes
$ git check-attr -a -- src/app.sh
src/app.sh: text: set
src/app.sh: eol: lf

text=auto lar Git lagre tekstfiler med LF; eol=lf holder skallskript på LF i arbeidsmappen også, og binary slår av diff og konvertering av linjeslutt. Innfører du slike regler i et eksisterende prosjekt, kjører du git add --renormalize . og committer resultatet alene, som en ren mekanisk commit. Bland det aldri med en ekte endring, for da kan ingen gjennomgå den ekte endringen.

Større repositoryer

  • Sparse checkout viser bare noen mapper i arbeidsmappen. Historikken og objektene er der fortsatt. git sparse-checkout set docs begrenser filene til docs og filene på toppnivå, git sparse-checkout disable tar alt tilbake.
  • Partial clone laster ned færre objekter med en gang: git clone --filter=blob:none URL henter commits og trær, og filinnhold ved behov. Serveren må støtte det.
  • Shallow clone med --depth 1 kutter historikken. Det er raskt i CI, men log, blame og bisect ser ikke forbi kuttet.
  • Git LFS er et eget verktøy. Det erstatter store filer med små pekerfiler i Git og lagrer innholdet på en LFS-server. Alle trenger LFS-klienten, og serveren må støtte det. Det krymper ikke historikk som allerede inneholder de store filene.
  • Submodules låser et annet repository til én bestemt commit. Det ytre repositoryet lagrer bare den commit-ID-en, som en spesiell oppføring:
$ git submodule add https://example.com/lib.git vendor/lib
$ git ls-files --stage vendor/lib
160000 2ac14ea53dae7e4f874f013b705d31dc30124a99 0   vendor/lib

De som kloner, trenger git clone --recurse-submodules, eller git submodule update --init etterpå. Å flytte låsen til en nyere commit er en bevisst commit i det ytre repositoryet.

Hooks og hva de garanterer

Hooks er kjørbare skript i .git/hooks (eller en mappe satt med core.hooksPath) som Git kjører ved hendelser som pre-commit, commit-msg og pre-push. De blir ikke klonet, hver person må installere dem, og --no-verify hopper over pre-commit- og commit-msg-hookene ved git commit og pre-push-hooken ved git push. Bruk dem til rask lokal tilbakemelding. Regler alle må følge, som «testene passerer» eller «ingen direkte push til main», hører hjemme i CI og i branchbeskyttelse på serveren. På samme måte beviser en signert commit eller tag hvilken nøkkel som signerte, ikke at innholdet er riktig eller at nøkkelen ikke var stjålet.

Vedlikehold og flyttbare backuper

git fsck sjekker objektdatabasen for skader, git count-objects -v viser størrelsen, og git gc pakker den, noe Git også gjør automatisk. Ikke kjør aggressiv pruning for å «fikse» et problem du ikke har diagnostisert: det fjerner nettopp de objektene ingen peker på, som du kanskje trenger for å redde noe. En bundle pakker refene du velger, her alle, og commitene de når, i én fil:

$ git bundle create ../backup.bundle --all
$ git bundle verify ../backup.bundle
...
../backup.bundle is okay

Du kan klone eller hente fra en bundle som fra en remote. Den inneholder bare commits og referanser: ucommittet arbeid, reflog, hooks og lokal konfigurasjon er ikke med.

Offisiell dokumentasjon