-
1. Aan de slag
- 1.1 Over versiebeheer
- 1.2 Een kort historisch overzicht van Git
- 1.3 Wat is Git?
- 1.4 De commando-regel
- 1.5 Git installeren
- 1.6 Git klaarmaken voor eerste gebruik
- 1.7 Hulp krijgen
- 1.8 Samenvatting
-
2. Git Basics
-
3. Branchen in Git
- 3.1 Branches in vogelvlucht
- 3.2 Eenvoudig branchen en mergen
- 3.3 Branch-beheer
- 3.4 Branch workflows
- 3.5 Branches op afstand (Remote branches)
- 3.6 Rebasen
- 3.7 Samenvatting
-
4. Git op de server
- 4.1 De protocollen
- 4.2 Git op een server krijgen
- 4.3 Je publieke SSH sleutel genereren
- 4.4 De server opzetten
- 4.5 Git Daemon
- 4.6 Slimme HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Hosting oplossingen van derden
- 4.10 Samenvatting
-
5. Gedistribueerd Git
-
6. GitHub
-
7. Git Tools
- 7.1 Revisie Selectie
- 7.2 Interactief stagen
- 7.3 Stashen en opschonen
- 7.4 Je werk tekenen
- 7.5 Zoeken
- 7.6 Geschiedenis herschrijven
- 7.7 Reset ontrafeld
- 7.8 Mergen voor gevorderden
- 7.9 Rerere
- 7.10 Debuggen met Git
- 7.11 Submodules
- 7.12 Bundelen
- 7.13 Vervangen
- 7.14 Het opslaan van inloggegevens
- 7.15 Samenvatting
-
8. Git aanpassen
- 8.1 Git configuratie
- 8.2 Git attributen
- 8.3 Git Hooks
- 8.4 Een voorbeeld van Git-afgedwongen beleid
- 8.5 Samenvatting
-
9. Git en andere systemen
- 9.1 Git als een client
- 9.2 Migreren naar Git
- 9.3 Samenvatting
-
10. Git Binnenwerk
- 10.1 Binnenwerk en koetswerk (plumbing and porcelain)
- 10.2 Git objecten
- 10.3 Git Referenties
- 10.4 Packfiles
- 10.5 De Refspec
- 10.6 Uitwisseling protocollen
- 10.7 Onderhoud en gegevensherstel
- 10.8 Omgevingsvariabelen
- 10.9 Samenvatting
-
A1. Bijlage A: Git in andere omgevingen
- A1.1 Grafische interfaces
- A1.2 Git in Visual Studio
- A1.3 Git in Visual Studio Code
- A1.4 Git in Eclipse
- A1.5 Git in Sublime Text
- A1.6 Git in Bash
- A1.7 Git in Zsh
- A1.8 Git in PowerShell
- A1.9 Samenvatting
-
A2. Bijlage B: Git in je applicaties inbouwen
- A2.1 Commando-regel Git
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Bijlage C: Git Commando’s
- A3.1 Setup en configuratie
- A3.2 Projecten ophalen en maken
- A3.3 Basic Snapshotten
- A3.4 Branchen en mergen
- A3.5 Projecten delen en bijwerken
- A3.6 Inspectie en vergelijking
- A3.7 Debuggen
- A3.8 Patchen
- A3.9 Email
- A3.10 Externe systemen
- A3.11 Beheer
- A3.12 Binnenwerk commando’s (plumbing commando’s)
7.7 Git Tools - Reset ontrafeld
Reset ontrafeld
Voordat we doorgaan naar de meer gespecialiseerde instrumenten, laten we eerst de reset
en checkout
commando’s bespreken.
Deze commando’s zijn twee van de meest verwarrende delen van Git als je ze voor het eerst tegenkomt.
Ze doen zo veel dingen, dat het bijkans onmogelijk is om ze echt te begrijpen en juist toe te passen.
Hiervoor stellen we een eenvoudige metafoor voor.
De drie boomstructuren
Een eenvoudiger manier om je reset
en checkout
voor te stellen is door je voor te stellen dat Git een gegevensbeheerder is van drie boomstructuren.
Met “boom” bedoelen we hier eigenlijk “verzameling van bestanden”, en niet een bepaalde gegevensstructuur.
(Er zijn een paar gevallen waarbij de index zich niet echt als een boomstructuur gedraagt, maar voor dit doeleinde is het eenvoudiger om het je als zodanig voor te stellen).
Git als systeem beheert en manipuleert deze boomstructuren bij de gewone operaties:
Boom | Rol |
---|---|
HEAD |
Laatste commit snapshot, volgende ouder |
Index |
Voorgestelde volgende commit snapshot |
Working Directory |
Speeltuin |
De HEAD
HEAD is de verwijzing naar de huidige branch referentie, wat op zijn beurt een verwijzing is naar de laatste commit die gemaakt is op die branch. Dat houdt in dat HEAD de ouder zal zijn van de volgende commit die wordt gemaakt. Het is over het algemeen het eenvoudigste om HEAD te zien als de snapshot van je laatste commit op die branch.
Het is in feite redelijk eenvoudig om te zien hoe die snapshot eruit ziet. Hier is een voorbeeld hoe de echte directory inhoud en SHA-1 checksum voor elk bestand in de HEAD snapshot te krijgen:
$ git cat-file -p HEAD
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
author Scott Chacon 1301511835 -0700
committer Scott Chacon 1301511835 -0700
initial commit
$ git ls-tree -r HEAD
100644 blob a906cb2a4a904a152... README
100644 blob 8f94139338f9404f2... Rakefile
040000 tree 99f1a6d12cb4b6f19... lib
De cat-file
en ls-tree
commando’s zijn “binnenwerk” (plumbing) commando’s die gebruikt worden door de lagere functies en niet echt gebruikt worden in dagelijkse toepassingen, maar ze helpen ons om te zien wat er eigenlijk gebeurt.
De index
De index is je voorstel voor de volgende commit.
We hebben hieraan ook gerefereerd als de “staging area” van Git, omdat dit is waar Git naar kijkt als je git commit
aanroept.
Git vult deze index met een lijst van de inhoud van alle bestanden die als laatste waren uitgechecked naar je werk directory en hoe ze eruit zagen toen ze oorspronkelijk waren uitgecheckt.
Je vervangt enkele van deze bestanden met nieuwe versies ervan, en git commit
converteert dit dan naar de boomstructuur voor een nieuwe commit.
$ git ls-files -s
100644 a906cb2a4a904a152e80877d4088654daad0c859 0 README
100644 8f94139338f9404f26296befa88755fc2598c289 0 Rakefile
100644 47c6340d6459e05787f644c2447d2595f5d3a54b 0 lib/simplegit.rb
Hier gebruiken we nogmaals ls-files
, wat meer een achter-de-schermen commando is dat je laat zien hoe je index er op dit moment uitziet.
Technisch gesproken is de index geen boomstructuur — het wordt eigenlijk geïmplementeerd als een geplette manifest — maar voor dit doeleinde is het goed genoeg.
De werk directory
En als laatste is er je werk directory (working directory, ook vaak aan gerefereerd als de “working tree”).
De andere twee boomstructuren slaan hun inhoud op een efficient maar onhandige manier op, in de .git
directory.
De werk directory pakt ze uit in echte bestanden, wat het makkelijker voor je maakt om ze te bewerken.
Zie de werk directory als een speeltuin, waar je wijzigingen kunt uitproberen voordat je ze naar je staging area (index) commit en daarna naar de historie.
$ tree
.
├── README
├── Rakefile
└── lib
└── simplegit.rb
1 directory, 3 files
De Workflow
Het voornaamste doel van Git is om opeenvolgende snapshots te op te slaan van verbeteringen aan je project, door deze drie bomen te manipuleren.
Laten we dit proces eens visualiseren: stel je gaat een nieuwe directory in waarin een enkel bestand staat.
We noemen dit v1 van het bestand, en we geven het in blauw weer.
Nu roepen we git init
aan, wat een Git repository aanmaakt met een HEAD referentie die verwijst naar een ongeboren branch (master
bestaat nog niet).
Op dit moment heeft alleen de boom van de werk directory inhoud.
Nu willen we dit bestand committen, dus we gebruiken git add
om de inhoud van de werk directory te pakken en dit in de index te kopiëren.
Dan roepen we git commit
aan, wat de inhoud van de index pakt en deze bewaart als een permanente snapshot, een commit object aanmaakt die naar die snapshot wijst en dan master
update die naar die commit wijst.
Als we nu git status
aanroepen zien we geen wijzigingen, omdat alle drie bomen hetzelfde zijn.
Nu willen we dat bestand wijzigen en deze committen. We volgen hetzelfde proces; eerst wijzigen we het bestand in onze werk directory. Laten we deze v2 van het bestand noemen, en deze in het rood weergeven.
Als we nu git status
aanroepen, zullen we het bestand in het rood zien als “Changes not staged for commit,” omdat deze versie van het bestand verschilt tussen de index en de werk directory.
Nu gaan we git add
aanroepen om het in onze index te stagen ("to stage": klaarzetten).
Als we op dit moment git status
aanroepen zullen we het bestand in het groen zien onder “Changes to be committed” omdat de index en HEAD verschillen — dat wil zeggen, onze voorgestelde volgende commit verschilt nu van onze laatste commit.
Tot slot roepen we git commit
aan om de commit af te ronden.
Nu zal git status
geen uitvoer laten zien, omdat alle drie bomen weer hetzelfde zijn.
Tussen branches overschakelen of klonen volgen een vergelijkbaar proces. Als je een branch uitcheckt, wijzigt dit HEAD door het te laten wijzen naar de nieuwe branch referentie, het vult je index met de snapshot van die commit, en kopieert dan de inhoud van de index naar je werk directory.
De rol van reset
Het reset
commando krijgt in dit licht meer betekenis.
Laten we, voor het doel van deze voorbeelden, stellen dat we file.txt
weer gewijzigd hebben en het voor de derde keer gecommit.
Nu ziet onze historie er dus als volgt uit:
Laten we nu een stap voor stap bespreken wat reset
doet als je het aanroept.
Het manipuleert deze drie bomen op een eenvoudige en voorspelbare manier.
Het voert tot drie basale handelingen uit.
Stap 1: Verplaats HEAD
Het eerste wat reset
zal doen is hetgeen waar HEAD naar verwijst verplaatsen.
Dit is niet hetzelfde als HEAD zelf wijzigen (dat is wat checkout
doet); reset
verplaatst de branch waar HEAD naar verwijst.
Dit houdt in dat als HEAD naar de master
-branch wijst (d.i. je bent nu op de master
-branch), het aanroepen
van git reset 9e5e6a4
zal beginnen met master
naar 9e5e6a4
te laten wijzen.
Het maakt niet uit welke variant van reset
met een commit je aanroept, dit is het eerste wat het altijd zal proberen te doen.
Met reset --soft
, zal het eenvoudigweg daar stoppen.
Kijk nu nog een keer naar het diagram en besef wat er gebeurd is: het heeft feitelijk de laatste git commit
commando ongedaan gemaakt.
Als je git commit
aanroept, maakt Git een nieuwe commit en verplaatst de branch waar HEAD naar wijst daarnaar toe.
Als je naar HEAD~
(de ouder van HEAD) terug reset
, verplaats je de branch terug naar waar het was, zonder de index of werk directory te wijzigen.
Je kunt de index nu bijwerken en git commit
nogmaals aanroepen om te bereiken wat git commit --amend
gedaan zou hebben (zie De laatste commit veranderen).
Stap 2: De Index bijwerken (--mixed)
Merk op dat als je git status
nu aanroept dat je het verschil tussen de index en wat de nieuwe HEAD is in het groen ziet.
Het volgende wat reset
zal gaan doen is de index bijwerken met de inhoud van de snapshot waar HEAD nu naar wijst.
Als je de --mixed
optie hebt opgegeven, zal reset
op dit punt stoppen.
Dit is ook het standaard gedrag, dus als je geen enkele optie hebt opgegeven (dus in dit geval alleen git reset HEAD~
), is dit waar het commando zal stoppen.
Kijk nu nog een keer naar het diagram en besef wat er gebeurd is: het heeft nog steeds je laatste commit
ongedaan gemaakt, maar nu ook alles unstaged.
Stap 3: De working directory bijwerken (--hard)
Het derde wat reset
zal doen is ervoor zorgen dat de werk directory gaat lijken op de index.
Als je de --hard
optie gebruikt, zal het doorgaan naar dit stadium.
Laten we eens overdenken wat er zojuist is gebeurd.
Je hebt je laatste commit ongedaan gemaakt, de git add
en git commit
commando’s, en al het werk wat je in je werk directory gedaan hebt.
Het is belangrijk op te merken dat deze vlag (--hard
) de enige manier is om het reset
commando gevaarlijk te maken, en een van de weinige gevallen waar Git daadwerkelijk gegevens zal vernietigen.
Elke andere aanroep van reset
kan redelijk eenvoudig worden teruggedraaid, maar de --hard
optie kan dat niet, omdat het keihard de bestanden in de werk directory overschrijft.
In dit specifieke geval, hebben we nog steeds de v3 versie van ons bestand in een commit in onze Git database, en we zouden het kunnen terughalen door naar onze reflog
te kijken, maar als we het niet zouden hebben gecommit, zou Git het bestand nog steeds hebben overschreven en zou het niet meer te herstellen zijn.
Samenvattend
Het reset
commando overschrijft deze drie bomen in een vastgestelde volgorde, en stopt waar je het toe opdraagt:
-
Verplaats de branch waar HEAD naar wijst (stop hier als
--soft
) -
Laat de index eruit zien als HEAD (stop hier tenzij
--hard
) -
Laat de werk directory eruit zien als de index
Reset met een pad (path)
Dit dekt het gedrag van reset
in zijn eenvoudige vorm, maar je kunt er ook een path bij opgeven waar het op moet acteren.
Als je een path opgeeft, zal reset
stap 1 overslaan, en de rest van de acties beperken tot een specifiek bestand of groep van bestanden.
Dit is ergens wel logisch — HEAD is maar een verwijzing, en je kunt niet naar een deel van een commit wijzen en deels naar een andere.
Maar de index en werk directory kunnen deels worden bijgewerkt, dus reset gaat verder met stappen 2 en 3.
Dus, laten we aannemen dat we git reset file.txt
aanroepen.
Deze vorm (omdat je niet een specifieke SHA-1 van een commit of branch meegeeft, en je hebt geen --soft
of --hard
meegegeven) is dit een verkorte vorm van git reset --mixed HEAD file.txt
en dit zal:
-
De branch waar HEAD naar wijst verplaatsen (overgeslagen)
-
De index eruit laten zien als HEAD (stop hier)
Dus effectief wordt alleen file.txt
van HEAD naar de index gekopiëerd.
Dit heeft het praktische effect van het bestand unstagen.
Als we kijken naar het diagram voor dat commando en denken aan wat git add
doet, zijn ze exact elkaars tegenpolen.
Dit is de reden waarom de uitvoer van het git status
commando je aanraadt om dit aan te roepen om een bestand te unstagen.
(Zie Een gestaged bestand unstagen voor meer hierover.)
We hadden net zo makkelijk Git niet laten aannemen dat we “pull de data van HEAD” bedoelen door een specifieke commit op te geven om die versie van het bestand te pullen.
We hadden ook iets als git reset eb43bf file.txt
kunnen aanroepen.
Feitelijk gebeurt hier hetzelfde als wanneer we de inhoud van het bestand naar v1 in de werk directory hadden teruggedraaid, git add
ervoor hadden aangeroepen, en daarna het weer hadden teruggedraaid naar v3 (zonder daadwerkelijk al deze stappen te hebben gevolgd).
Als we nu git commit
aanroepen, zal het een wijziging vastleggen die het bestand naar v1 terugdraait, ook al hebben we het nooit echt weer in onze werk directory gehad.
Het is ook interessant om op te merken dat net als git add
, het reset
commando een --patch
optie accepteert om inhoud in deelsgewijs te unstagen.
Dus je kunt naar keuze inhoud unstagen of terugdraaien (revert).
Samenpersen (Squashing)
Laten we nu kijken hoe we iets interessants kunnen doen met deze vers ontdekte krachten — commits samenpersen (squashen).
Stel dat je een reeks van commits met berichten als “oops.”, “WIP” en “dit bestand vergeten”.
Je kunt reset
gebruiken om deze snel en makkelijk in een enkele commit te samenpersen waardoor je ontzettend slim zult lijken.
(Een commit samenpersen (squashing) laat je een andere manier zien om dit te doen, maar in dit voorbeeld is het makkelijker om reset
te gebruiken.)
Stel dat je een project hebt waar de eerste commit een bestand heeft, de tweede commit een nieuw bestand toevoegde en het eerste wijzigde, en de derde commit het eerste bestand weer wijzigde. De tweede commit was een onderhanden werk en je wilt het samenpersen.
Je kun git reset --soft HEAD~2
uitvoeren om de HEAD branch terug naar een oudere commit te verplaatsen (de eerste commit wil je behouden):
En daarna eenvoudigweg git commit
weer aanroepen:
Je kunt nu zien dat je bereikbare historie, de historie die je zou gaan pushen, nu eruit ziet alsof je een commit had met file-a.txt
v1, dan een tweede die zowel file-a.txt
naar v3 wijzigt en file-b.txt
toevoegt.
De commit met de v2 versie van het bestand is niet meer in de historie aanwezig.
Check It Out
Als laatste, je kunt je afvragen wat het verschil is tussen checkout
en reset
.
Net als reset
, bewerkt checkout
de drie bomen, en het verschilt enigszins afhankelijk van of je het commando een bestandspath geeft of niet.
Zonder paths
Het aanroepen van git checkout [branch]
is vergelijkbaar met het aanroepen van git reset --hard [branch]
in die zin dat het alle drie bomen voor je laat uitzien als [branch]
, maar er zijn twee belangrijke verschillen.
Ten eerste, in tegenstelling tot reset --hard
, is checkout
veilig voor de werk-directory; het zal controleren dat het geen bestanden weggooit waar wijzigingen in gemaakt zijn.
Eigenlijk is het nog iets slimmer dan dat — het probeert een triviale merge in de werk directory te doen, zodat alle bestanden die je niet gewijzigd hebt bijgewerkt worden.
Aan de ander kant zal reset --hard
eenvoudigweg alles zonder controleren vervangen.
Het tweede belangrijke verschil is hoe het HEAD update.
Waar reset
de branch waar HEAD naar verwijst zal verplaatsen, zal checkout
de HEAD zelf verplaatsen om naar een andere branch te wijzen.
Bijvoorbeeld, stel dat we master
en develop
-branches hebben die naar verschillende commits wijzen, en we staan op dit moment op develop
(dus HEAD wijst daar naar).
Als we git reset master
aanroepen, zal develop
zelf wijzen naar dezelfde commit waar master
naar wijst.
Als we echter git checkout master
aanroepen, zal develop
niet verplaatsen, HEAD wordt zelf verplaatst.
HEAD zal nu naar master
wijzen.
Dus in beide gevallen verplaatsen we HEAD om naar commit A te wijzen, maar hoe we dit doen verschilt enorm.
reset
zal de branch waar HEAD naar verwijst verplaatsen, checkout
verplaatst HEAD zelf.
Met paths
De andere manier om checkout
aan te roepen is met een bestands path die, zoals reset
, HEAD niet verplaatst.
Het is precies als git reset [branch] file
in die zin dat het de index update met dat bestand op die commit, maar het overschrijft ook het bestand in de werk directory.
Het zou precies zijn als git reset --hard [branch] file
(als reset
je dat zou toestaan) - het is niet veilig voor de werk directory, en het verplaatst HEAD niet.
En, zoals git reset
en git add
, accepteert checkout
een --patch
optie zodat je selectief stukje bij beetje bestandsinhoud kunt terugdraaien.
Samenvatting
Hopelijk begrijp je nu het reset
commando en voel je je er meer mee op je gemak, maar je zult waarschijnlijk nog een beetje in verwarring zijn in hoe het precies verschilt van checkout
en zul je je waarschijnlijk ook niet alle regels van verschillende aanroepen herinneren.
Hier is een spiekbrief voor welke commando’s welke bomen beïnvloeden. In de “HEAD” kolom staat “REF” als dat commando de referentie (branch) waar HEAD naar wijst verplaatst, en “HEAD” als het HEAD zelf verplaatst. Let met name op de WD Safe? kolom - als daar NO in staat, bedenk je een tweede keer voordat je dat commando gebruikt.
HEAD | Index | Workdir | WD Safe? | |
---|---|---|---|---|
Commit Level |
||||
|
REF |
NO |
NO |
YES |
|
REF |
YES |
NO |
YES |
|
REF |
YES |
YES |
NO |
|
HEAD |
YES |
YES |
YES |
File Level |
||||
|
NO |
YES |
NO |
YES |
|
NO |
YES |
YES |
NO |