Versjonskontroll

Denne presentasjonen:

https://slides.com/evestera/versjonskontroll/

 

Bruk mellomromtasten for å gå til "neste" slide

Hva er versjonskontroll?

  • Versjonskontroll er verktøy for å lagre forskjellige versjoner av (filene i) et prosjekt
  • Forskjellige versjoner kan være:
    • Historien til prosjektet (gir bl.a. "avansert undo")
    • Flere som jobber på samme prosjekt på hver sin maskin
    • At du prøver ut forskjellige ting med samme fungerende kode som utgangspunkt
    • En kombinasjon

git

  • Det finnes mange versjonskontrollsystemer (subversion mercurial, git, ...)
  • git ble laget av Linus Torvalds til Linux-prosjektet og er for tiden det mest populære
  • git er det som kalles distribuert versjonskontroll (det vil si at du ikke å ha en server å jobbe opp mot)

Ordforklaringer

  • Repo (repository): En mappe som har versjonshistorie kaller vi et repo.
  • Commit: En commit er ett punkt i historien til prosjektet ditt. Én commit er da ett sett med endringer i prosjektet.

First commit, "hello world"

Added pretty colors

Added sayHello method

Removed silly colors

Added more varied greetings

Added even more stuff

Hva hvis du nå vil ha tilbake koden for farger???

Vi navigerer historien!

Men først må vi ha en historie å navigere, og da må vi lære noen kommandoer for å lage den...

git init

Gjør en vanlig mappe om til et repo. Det eneste kommandoen gjør er altså å gjøre det mulig å bruke de andre git-kommandoene

~/prosjekt $ git init
Initialized empty Git repository in ~/prosjekt/.git/​

(Kommandoen lager en skjult mappe som heter .git, men den trenger man aldri å se på)

.gitignore

Ikke en kommando, men en fil. Ikke en filending, men et filnavn.

~/prosjekt $ touch .gitignore
~/prosjekt $ echo "*.class" >> .gitignore
~/prosjekt $ echo "target/" >> .gitignore
​*.class
target/

Det som er i fila blir ignorert av git. Man ignorerer vanligvis alt som blir lagd av maskina, f.eks. .class-filer i Java.

Fila over ignorerer alle filer som slutter på ".class" og hele mappa "target". Man kan lage fila direkte i terminalen:

(Men man kan selvsagt også lage den med et redigeringsprogram)

git status

Viser status i repoet/mappa du er i. Brukes hele tiden.

~/prosjekt $ git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .gitignore

nothing added to commit but untracked files present (use "git add" to track)

Legg merke til at standardutskriften nesten hele tiden sier deg hva du trenger å gjøre.

git status (eksempel)
~/prosjekt $ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   .gitignore

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    Main.java

no changes added to commit (use "git add" and/or "git commit -a")

"Changes not staged for commit" betyr at det er endringer i filer du har bedt git holde styr på

"Untracked files" er filer du ikke har ignorert, men som ikke git har fått beskjed om å holde styr på. (I starten er det alle filene)

git add

Legger til endringer (oftest hele filer) i det du skal lagre i historikken.

~/prosjekt $ git add Main.java

Du kan legge til hele mapper på en gang, for eksempel hele mappa du er i for øyeblikket:

~/prosjekt $ git add .
git add

Vi bruker "git status" for å se at vi fikk lagt til det vi skulle

~/prosjekt $ git add Main.java
~/prosjekt $ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   Main.java

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   .gitignore
git commit

Lager et nytt punkt i historien til prosjektet.

~/prosjekt $ git commit -m "Added main class"
[master a2fd616] Added Main class
 1 file changed, 2 insertions(+)
 create mode 100644 Main.java
~/prosjekt $ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   .gitignore

no changes added to commit (use "git add" and/or "git commit -a")
git config

Før aller første commit (på maskina, ikke hvert prosjekt) må man fortelle git hvem man er:

~/prosjekt $ git config --global user.name "Ole Olsen"
~/prosjekt $ git config --global user.email "ololse@uio.no"

Mental modell for git

Du kan tenke på filene dine som i tre kategorier i git.

Untracked

Unstaged

Staged

History

Modified

add
commit

Alle nye endringer

Man må altså gjøre en "git add" før det er noe vits å gjøre en "git commit"

git log

Viser historien, nyeste commit først

~/prosjekt $ git log
commit a2fd61653dcba6d1a558eee8b0b81ce4564aa0b2
Author: Erik Vesteraas <erikadr@ifi.uio.no>
Date:   Wed Feb 3 21:11:20 2016 +0100

    Added Main class

commit ea81a7d4a3adf01b331d36a09ae65a0cf8559f93
Author: Erik Vesteraas <erikadr@ifi.uio.no>
Date:   Wed Feb 3 20:17:51 2016 +0100

    An older commit
git log

Standard-loggen er dessverre ganske unyttig, en bedre variant er:

~/prosjekt $ git log --all --oneline --graph --decorate
* a2fd616 (HEAD -> master) Added Main class
* ea81a7d An older commit
git config --global alias

Vi kan lage alias for kommandoer vi bruker ofte. For eksempel, for å lage alias for:

~/prosjekt $ git log --all --oneline --graph --decorate
~/prosjekt $ git config --global alias.lg "log --all --oneline --graph --decorate"
~/prosjekt $ git lg

Det vi har lyst til å slippe å skrive

Det vi vil skrive i stedet

git config --global alias

Flere eksempel (fra mine egne alias):

git config --global alias.st "status -s"

git config --global alias.co "checkout"
git config --global alias.com "checkout master"​
git config --global alias.cob "checkout -b"​

git config --global alias.preview "diff --cached"

git config --global alias.pc "push origin HEAD"

(Alt du gjør med "git config --global" blir forøvrig lagret i fila "~/.gitconfig")

Oppsummering så langt

~/prosjekt $ git init
~/prosjekt $ touch .gitignore
~/prosjekt $ echo "*.class" >> .gitignore
~/prosjekt $ git add .
~/prosjekt $ git commit -m "Initial commit"
~/prosjekt $ git status
~/prosjekt $ git log

First commit, "hello world"

Added pretty colors

Added sayHello method

Removed silly colors

Added more varied greetings

Added even more stuff (HEAD -> master)

Branching

Historien vår starter som en rett linje, med bare en "gren". Denne grenen har navnet "master"

First commit, "hello world"

Added pretty colors

Added sayHello method

Removed silly colors

Added more varied greetings

Added even more stuff (master)

Branching

(HEAD -> colors)

Men ofte er det nyttig å lage flere grener, her lager vi for eksempel en gren som starter et stykke bak i historien, med navnet "colors"

First commit, "hello world"

Added pretty colors

Added sayHello method

Removed silly colors

Added more varied greetings

Added even more stuff (master)

Branching

Added really colorful greetings (HEAD -> colors)

Navnet – som fungerer som en referanse – flytter seg etter hvert som du jobber på en gren.

First commit, "hello world"

Added pretty colors

Added sayHello method

Removed silly colors

Added more varied greetings

Added even more stuff (master)

Branching

Added really colorful greetings

Added emoji support (HEAD -> colors)

På denne måten kan vi få ikke bare én historie, men flere parallelle historier. 

First commit, "hello world"

Added pretty colors

Added sayHello method

Removed silly colors

Added more varied greetings

Added even more stuff (HEAD -> master)

Branching

Added really colorful greetings

Added emoji support (colors)

Referansen HEAD viser hvor du jobber på ethvert tidspunkt

Samme historie med aliaset gitt i git config-sliden

~/prosjekt $ git lg
* 20c0bfc (colors) Added emoji support
* 0db44f2 Added really colorful greetings
| * 07d5696 (HEAD -> master) Added even more stuff
| * f24a2b2 Added more varied greetings
| * c56db59 Removed silly colors
|/
* 62ac2f6 Added sayHello method
* 6d6b53c Added pretty colors
* a50d6c7 First commit, "hello world"
git checkout

Dette er kommandoen vi bruker for å faktisk navigere historien

~/prosjekt $ git checkout <referanse>

<referanse> kan være navnet på en gren, en tag (laget med git tag) eller hashkoden til en spesifikk commit.

~/prosjekt $ git log --all --oneline --graph --decorate
* a2fd616 (HEAD -> master) Added Main class
* ea81a7d An older commit
~/prosjekt $ git checkout ea81

For eksempel:

Man trenger bare nok av hashkoden til at det kun matcher én commit

git checkout

Man kan også bruke git checkout til å lage en ny gren. Med "-b" (for "--branch") lages en ny gren der HEAD er for øyeblikket.

~/prosjekt $ git checkout -b <navn-på-gren>
git checkout
~/prosjekt $ git lg
* a2fd616 (HEAD -> master) Added Main class
* ea81a7d An older commit

~/prosjekt $ git checkout ea81
~/prosjekt $ git lg
* a2fd616 (master) Added Main class
* ea81a7d (HEAD) An older commit

~/prosjekt $ git checkout -b colors
~/prosjekt $ git lg
* a2fd616 (master) Added Main class
* ea81a7d (HEAD -> colors) An older commit

For eksempel:

Kalles "detached HEAD state" og bør ikke jobbes i. Lag en branch.

git checkout
~/prosjekt $ git checkout <path>

Vi kan også bruke git checkout til å hente spesifikke versjoner av enkeltfiler.

~/prosjekt $ git checkout <referanse> <path>

Hvis vi ikke sier noen referanse henter vi fra nyeste commit. Altså er den nyttig for å forkaste endringer siden siste commit vi gjorde.

~/prosjekt $ git checkout colors Main.java
~/prosjekt $ git checkout Main.java

"Hent versjonen av Main.java som ligger i grenen colors"

github.uio.no

github.uio.no

  • Samme brukernavn og passord som du bruker på ifi-maskiner og devilry
  • Gratis (dvs. UiO betaler) så lenge du er UiO-student
  • Med andre ord bra til obliger som ikke kan ligge offentlig, men ikke til ting du vil publisere/lagre permanent.
  • (Blir faktisk brukt til enkelte fag, altså et krav å lære før eller senere)

Lage nytt repo

Trykk her for å lage et nytt repo

To alternativer:

  1. Du starter et helt nytt prosjekt og har ikke noe eksisterende kode
  2. Du har allerede et prosjekt, og skal bare "dytte det opp" til GitHub

1: Helt nytt prosjekt

Readme er alltid fint

Velg språket du skal bruke, så får du en ferdig .gitignore-fil

Kopier addressen herfra og kjør

 ​git clone "Adressen du kopierte"

i terminalen (kommandoen lager en mappe til prosjektet)

2: Du har allerede et prosjekt

Ikke kryss av her

Og ikke velg noe språk her

Hvis vi ikke allerede har gjort prosjektet om til et repo må vi gjøre det nå. Altså:

  1. Kjør kommandoen "git init"​

  2. Lag en .gitignore-fil

  3. Gjør første "git commit"

2: Du har allerede et prosjekt

Kjør denne koden i terminalen i repoet (mappa) til prosjektet ditt

git clone

Kopierer et annet repo (og beholder en referanse med navnet "origin")

~/prosjekt $ git clone <some other repo>

<some other repo> kan være en web-adresse (HTTP), en mappe på en annen maskin (via SSH) eller en mappe på samme maskin

git pull

Laster commits fra et annet repo inn i ditt eget.

~/prosjekt $ git pull <remote> <branch>

Altså veldig ofte:

~/prosjekt $ git pull origin master

Hvis man har satt "tracking branch" (se neste slide) holder det med:

~/prosjekt $ git pull
git push

Sender dine commits til et annet repo.

~/prosjekt $ git push <remote> <branch>

Hvis man bruker valget -u setter man "tracking branch" og  slipper å skrive <remote> og <branch>. Altså:

~/prosjekt $ git push -u origin master
~/prosjekt $ git push

Og deretter:

Added sayHello method

Removed silly colors

Added more varied greetings

Added even more stuff (HEAD -> master)

Merging

Added really colorful greetings

Added emoji support (colors)

Av og til må vi kombinere historielinjer. Dette kalles en "merge". For eksempel kan det skje hvis vi gjør en "git pull" og serveren har en annen historie enn vi har selv.

Konflikter

Når du gjør en "git pull" og det er forskjell på historiene vil git vanligvis prøve å fikse problemet selv. Hvis endringene er i forskjellige filer er det ikke noe problem.

Men av og til er det umulig for git å finne ut av problemet selv:

​...
if (foo == bar) {
    doSomething();
}
...
​...
if (foo == bar) {
    sayHello();
}
...

Vil vi "doSomething", "sayHello" eller begge deler?

Konflikter

​...
if (foo == bar) {
    doSomething();
}
...
​...
if (foo == bar) {
    sayHello();
}
...
​...
if (foo == bar) {
<<<<<<< HEAD
    sayHello();
=======
    doSomething();
>>>>>>> do-something-branch
}
...

Innholdet fra branchen du var i

Innholdet fra branchen som merges inn

​...
if (foo == bar) {
<<<<<<< HEAD
    sayHello();
=======
    doSomething();
>>>>>>> do-something-branch
}
...
​...
if (foo == bar) {
    sayHello();
    doSomething();
}
...

Hvis du ville ha begge deler er det da bare å redigere på denne måten:

Å avslutte en merge

Så er det bare å gjøre "git add" og "git commit" så er du ferdig. Altså, hvis filen var FooBar.java:

~/prosjekt $ git add FooBar.java
~/prosjekt $ git commit -m "Merged do-something-branch"
*   1487f92 (HEAD -> master) Merged do-something-branch
|\
| * dae1ef1 (do-something-branch) Doing something
* | e0ef412 Saying hello
| |
...

Som gir oss en historie, som ser omtrent slik ut:

Hvis du finner ut at du ikke vil gjøre en merge likevel har du denne kommandoen:

~/prosjekt $ git merge --abort
git fetch & git merge

git pull er egentlig en kombinasjon av to andre kommandoer (som dermed kan gjøres separat):

 

git fetch henter historieinformasjonen som ligger i det eksterne repoet, men oppdaterer ikke noen av dine referanser (som master og HEAD)

 

git merge kombinerer to grener, som forklart i forrige seksjon

~/prosjekt $ git fetch origin master
~/prosjekt $ git merge origin/master
~/prosjekt $ git pull origin master

=

git stash

Denne kommandoen skjuler endringer midlertidig, uten å lage en commit. Filer som er "untracked" blir ikke påvirket.

~/prosjekt $ git status
On branch master
Changes not staged for commit:
  ...
    modified:   .gitignore
Untracked files:
  ...
    Util.java
~/prosjekt $ git stash
~/prosjekt $ git status
On branch master
Untracked files:
  ...
    Util.java
git stash

git stash pop gir deg endringene tilbake igjen.

~/prosjekt $ git stash pop
On branch master
Changes not staged for commit:
  ...
    modified:   .gitignore
Untracked files:
  ...
    Util.java
git rebase

En "fancy" måte å kombinere to historielinjer på for å unngå merge-punkter. Konseptet er at man "spiller av" den ene historielinja oppå den andre.

Hvis du vil lære rebase anbefaler jeg denne:

 

Interaktiv tutorial til mer avansert branching og rebasing:

http://pcottle.github.io/learnGitBranching/

SSH

Når du bruker HTTPS til remote repositories må man vanligvis skrive passord hver gang. For å slippe det kan man bruke SSH i stedet.

Linker

GitHub sin interaktive intro til Git:

http://try.github.io/

Interaktiv tutorial til mer avansert branching og rebasing:

http://pcottle.github.io/learnGitBranching/

En fin liste med nyttige Git-kommandoer:

https://gist.github.com/hofmannsven/6814451

Andre kommandoer

git cherry-pick

git commit --amend

git diff

git reset

git reflog

Versjonskontroll

By Erik Vesteraas

Versjonskontroll

  • 2,146