-
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.6 Git Tools - Geschiedenis herschrijven
Geschiedenis herschrijven
Vaak zal je, als je met Git werkt, je commit geschiedenis om een of andere reden willen aanpassen. Eén van de mooie dingen van Git is dat het je in staat stelt om beslissingen op het laatst mogelijke moment te maken. Je kunt bepalen welke bestanden in welke commits gaan vlak voordat je commit, door middel van de staging area, je kunt besluiten dat je ergens toch nog niet aan had willen beginnen met het stash commando en je kunt commits herschrijven ook al zijn ze al gebeurd, waardoor het lijkt alsof ze op een andere manier gebeurd zijn. Dit kan bijvoorbeeld de volgorde van de commits betreffen, berichten of bestanden in een commit wijzigen, commits samenpersen (squashen) of opsplitsen, of complete commits weghalen — en dat allemaal voordat je jouw werk met anderen deelt.
In deze paragraaf zal je leren hoe je deze handige taken uitvoert, zodat je jouw commit geschiedenis er uit kunt laten zien zoals jij dat wilt, voordat je het met anderen deelt.
Noot
|
Push je werk niet voordat je er tevreden mee bent
Een van de meest belangrijke regels van Git is dat, omdat zoveel werk lokaal gedaan wordt binnen jouw kloon, een een hele hoge graad van vrijheid hebt om je history lokaal te herschrijven. Echter, als je je werk eenmaal hebt gepusht, wordt dit een heel ander verhaal, en je zult je gepushte werk moeten beschouwen als defintief tenzij je een hele goede reden hebt om het te wijzigen. Kortom: je moet het pushen van je werk vermijden tot je ermee tevreden bent en klaar bent om het met de rest van de wereld te delen. |
De laatste commit veranderen
De laatste commit veranderen is waarschijnlijk de meest voorkomende geschiedenis-wijziging die je zult doen. Vaak wil je twee basale dingen aan je laatste commit wijzigen: het commit bericht, of de snapshot dat je zojuist opgeslagen hebt, veranderen door het toevoegen, wijzigen of weghalen van bestanden.
Als je alleen je laatste commit bericht wilt wijzigen, dan is dat heel eenvoudig:
$ git commit --amend
Dat plaatst je in de teksteditor met je laatste commit bericht erin, klaar voor je om het bericht te wijzigen en de editor sluiten. Als je opslaat en de editor sluit, dan schrijft de editor een nieuwe commit met dat bericht en maakt dat je nieuwe laatste commit.
Als je echter de echte inhoud van je laatste commit wilt wijzigen, werkt het proces feitelijk hetzelfde — eerst de wijzigingen doen die je dacht te hebben vergeten, deze stagen en de daaropvolgende git commit --amend
vervangt die laatste commit met je nieuwe verbeterde commit.
Je moet wel oppassen met deze techniek, omdat het amenderen de SHA-1 van de commit wijzigt. Het is vergelijkbaar met een hele kleine rebase — niet je laatste commit wijzigen als je deze al gepusht hebt.
Tip
|
Een geamendeerde commit kan (of niet) een geammendeerd commit-bericht nodig hebben
Als je een commit ammendeert, heb je de kans om zowel het commit-bericht als de inhoud van de commit te wijzigen. Als je de inhoud van de commit substantieel wijzigt, moet je bijna zeker de commit-bericht wijzigen om deze gewijzigde inhoud weer te geven. Aan de andere kant, als je ammenderingen erg triviaal zijn (een stomme typefout hertsellen of een bestand toevoegen die je was vergeten te stagen) waarbij het eerdere commit-bericht nog steeds juist is, kan je simpelweg de wijzigingen maken, deze stagen en de onnodige editor-sessie helemaal vermijden met:
|
Meerdere commit berichten wijzigen
Om een commit te wijzigen die verder terug in je geschiedenis ligt, moet je meer complexe instrumenten gebruiken.
Git heeft geen geschiedenis-wijzig tool, maar je kunt de rebase tool gebruiken om een serie commits op de HEAD te rebasen waarop ze origineel gebaseerd, in plaats van ze naar een andere te verhuizen.
Met de interactieve rebase tool kun je dan na iedere commit die je wilt wijzigen stoppen en het bericht wijzigen, bestanden toevoegen, of doen wat je ook maar wilt.
Je kunt rebase interactief uitvoeren door de -i
optie aan git rebase
toe te voegen.
Je moet aangeven hoe ver terug je commits wilt herschrijven door het commando te vertellen op welke commit het moet rebasen.
Bijvoorbeeld, als je de laatste drie commit berichten wilt veranderen, of een van de commit berichten in die groep,
dan geef je de ouder van de laatste commit die je wilt wijzigen mee als argument aan git rebase -i
, wat HEAD~2^
of HEAD~3
is.
Het kan makkelijker zijn om de ~3
te onthouden, omdat je de laatste drie commits probeert te wijzigen; maar hou in gedachten dat je eigenlijk vier commits terug aangeeft; de ouder van de laatste commit die je wilt veranderen:
$ git rebase -i HEAD~3
Onthoud, nogmaals, dat dit een rebase commando is - iedere commit in de reeks HEAD~3..HEAD
zal worden herschreven, of je het bericht nu wijzigt of niet.
Voeg geen commit toe die je al naar een centrale server gepusht hebt - als je dit doet breng je andere gebruikers in de war omdat je ze een alternatieve versie van dezelfde wijziging te geeft.
Dit commando uitvoeren geeft je een lijst met commits in je tekst editor die er ongeveer zo uit ziet:
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
Het is belangrijk om op te merken dat deze commits in de tegengestelde volgorde getoond worden dan hoe je ze normaliter ziet als je het log
commando gebruikt.
Als je een log
uitvoert, zie je zoiets als dit:
$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d added cat-file
310154e updated README formatting and added blame
f7f3f6d changed my name a bit
Merk de omgekeerde volgorde op.
De interactieve rebase geeft je een script dat het gaat uitvoeren.
Het zal beginnen met de commit die je specificeert op de commando regel (HEAD~3
) en de wijzigingen in elk van deze commits van voor naar achter opnieuw afspelen.
Het toont de oudste het eerst in plaats van de nieuwste, omdat dat deze de eerste is die zal worden afgespeeld.
Je moet het script zodanig aanpassen dat het stopt bij de commit die je wilt wijzigen.
Om dat te doen moet je het woord pick
veranderen in het woord edit
voor elke commit waarbij je het script wilt laten stoppen.
Bijvoorbeeld, om alleen het derde commit bericht te wijzigen verander je het bestand zodat het er zo uitziet:
edit f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
Als je dit opslaat en de editor sluit, spoelt Git terug naar de laatste commit van die lijst en zet je op de commando regel met de volgende boodschap:
$ git rebase -i HEAD~3
Stopped at f7f3f6d... changed my name a bit
You can amend the commit now, with
git commit --amend
Once you’re satisfied with your changes, run
git rebase --continue
Deze instructies vertellen je precies wat je moet doen. Type
$ git commit --amend
Wijzig het commit bericht en verlaat de editor. Voer vervolgens dit uit
$ git rebase --continue
Dit commando zal de andere twee commits automatisch toepassen, en je bent klaar. Als je pick op meerdere regels in edit verandert, dan kan je deze stappen herhalen voor iedere commit die je in edit veranderd hebt. Elke keer zal Git stoppen, je de commit laten wijzigen en verder gaan als je klaar bent.
Commits anders rangschikken
Je kunt een interactieve rebase ook gebruiken om commits anders te rangschikken of ze geheel te verwijderen. Als je de “added cat-file” commit wilt verwijderen en de volgorde waarin de andere twee commits zijn geïntroduceerd wilt veranderen, dan kun je het rebase script van dit
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
veranderen in dit:
pick 310154e updated README formatting and added blame
pick f7f3f6d changed my name a bit
Als je dan opslaat en de editor sluit, zal Git je branch terugzetten naar de ouder van deze commits, eerst 310154e
en dan f7f3f6d
toepassen, en dan stoppen.
Feitelijk verander je de volgorde van die commits en verwijder je de “added cat-file”-commit volledig.
Een commit samenpersen (squashing)
Het is ook mogelijk een serie commits te pakken en ze in één enkele commit samen te persen (squash) met de interactieve rebase tool. Het script stopt behulpzame instructies in het rebase bericht:
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
Als je in plaats van “pick” of “edit”, “squash” specificeert zal Git zowel die verandering als de verandering die er direct aan vooraf gaat toepassen, en je helpen om de commit berichten samen te voegen. Dus als je een enkele commit van deze drie commits wil maken, laat je het script er zo uit zien:
pick f7f3f6d changed my name a bit
squash 310154e updated README formatting and added blame
squash a5f4a0d added cat-file
Als je de editor opslaat en sluit, zal Git alle drie wijzigingen toepassen en je terug in de editor brengen om de drie commit berichten samen te voegen:
# This is a combination of 3 commits.
# The first commit's message is:
changed my name a bit
# This is the 2nd commit message:
updated README formatting and added blame
# This is the 3rd commit message:
added cat-file
Als je dat opslaat, heb je een enkele commit die de veranderingen van alle drie vorige commits introduceert.
Een commit splitsen
Een commit opsplitsen zal een commit ongedaan maken, en dan net zo vaak gedeeltelijk stagen en committen als het aantal commits waar je mee wilt eindigen.
Bijvoorbeeld, stel dat je de middelste van je drie commits wilt splitsen.
In plaats van “updated README formatting and added blame” wil je het splitsen in twee commits: “updated README formatting” als eerste, en “added blame” als tweede.
Je kunt dat doen in het rebase -i
script door de instructie van de commit die je wilt splitsen te veranderen in “edit”:
pick f7f3f6d changed my name a bit
edit 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
Dan, als het script je naar de commando regel neemt, reset je die commit, neemt de wijzigingen die zijn gereset en maakt meerdere commits ervan.
Als je opslaat en de editor verlaat, spoelt Git terug naar de parent van de eerste commit in de lijst, past de eerste commit toe (f7f3f6d
), past de tweede toe (310154e
), en zet je dan in de console.
Daar kan je een gemengde reset doen van die commit met git reset HEAD^
, wat effectief de commit terugdraait en de gewijzigde bestanden unstaged laat.
Nu kan je de wijzigingen die gereset zijn nemen en er meerdere commits van maken, en dan git rebase --continue
uitvoeren zodra je klaar bent:
$ git reset HEAD^
$ git add README
$ git commit -m 'updated README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'added blame'
$ git rebase --continue
Git zal de laatste commit (a5f4a0d
) in het script toepassen, en je geschiedenis zal er zo uitzien:
$ git log -4 --pretty=format:"%h %s"
1c002dd added cat-file
9b29157 added blame
35cfb2b updated README formatting
f3cc40e changed my name a bit
Nogmaals, dit verandert alle SHA’s van alle commits in de lijst, dus zorg er voor dat er geen commit in die lijst zit die je al naar een gedeelde repository gepusht hebt.
De optie met atoomkracht: filter-branch
Er is nog een geschiedenis-herschrijvende optie, die je kunt gebruiken als je een groter aantal commits moet herschrijven op een gescripte manier.
Bijvoorbeeld, het globaal veranderen van je e-mail adres of een bestand uit iedere commit verwijderen.
Dit commando heet filter-branch
en het kan grote gedeelten van je geschiedenis herschrijven, dus je moet het niet gebruiken tenzij je project nog niet publiek is gemaakt, en andere mensen nog geen werk hebben gebaseerd op de commits die je op het punt staat te herschrijven.
Echter het kan heel handig zijn.
Je zult een paar veel gebruikte toepassingen zien zodat je een idee krijgt waar het toe in staat is.
Een bestand uit iedere commit verwijderen
Dit gebeurt nog wel eens.
Iemand voegt per ongeluk een enorm binair bestand toe met een achteloze git add .
, en je wilt het overal weghalen.
Misschien heb je per ongeluk een bestand dat een wachtwoord bevat gecommit, en je wilt dat project open source maken.
filter-branch
is dan het middel dat je wilt gebruiken om je hele geschiedenis schoon te poetsen.
Om een bestand met de naam passwords.txt uit je hele geschiedenis weg te halen, kun je de --tree-filter
optie toevoegen aan filter-branch
:
$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten
De --tree-filter
optie voert het gegeven commando uit na elke checkout van het project, en commit de resultaten weer.
In dit geval verwijder je een bestand genaamd passwords.txt van elke snapshot, of het bestaat of niet.
Als je alle abusievelijk toegevoegde editor backup bestanden wilt verwijderen, kan je bijvoorbeeld dit uitvoeren git filter-branch --tree-filter 'rm -f *~' HEAD
.
Je zult Git objectbomen en commits zien herschrijven en aan het eind de branch wijzer zien verplaatsen.
Het is over het algemeen een goed idee om dit in een test branch te doen, en dan je master branch te hard-resetten nadat je gecontroleerd hebt dat de uitkomst echt is als je het wilt hebben.
Om filter-branch
op al je branches uit te voeren, moet je --all
aan het commando meegeven.
Een subdirectory de nieuwe root maken
Stel dat je een import vanuit een ander versiebeheersysteem hebt gedaan, en subdirectories hebt die nergens op slaan (trunk, tags, enzovoort).
Als je de trunk
subdirectory de nieuwe root van het project wilt maken voor elke commit, kan filter-branch
je daar ook mee helpen:
$ git filter-branch --subdirectory-filter trunk HEAD
Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)
Ref 'refs/heads/master' was rewritten
Nu is de nieuwe project root elke keer de inhoud van de trunk
subdirectory.
Git zal ook automatisch commits verwijderen die geen betrekking hadden op die subdirectory.
E-mail adressen globaal veranderen
Een ander veel voorkomende toepassing is het geval dat je vergeten bent om git config
uit te voeren om je naam en e-mail adres in te stellen voordat je begon met werken, of misschien wil je een project op het werk open source maken en al je werk e-mail adressen veranderen naar je persoonlijke adres.
Hoe dan ook, je kunt e-mail adressen in meerdere commits ook in één klap veranderen met filter-branch
.
Je moet wel oppassen dat je alleen die e-mail adressen aanpast die van jou zijn, dus gebruik je --commit-filter
:
$ git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
then
GIT_AUTHOR_NAME="Scott Chacon";
GIT_AUTHOR_EMAIL="schacon@example.com";
git commit-tree "$@";
else
git commit-tree "$@";
fi' HEAD
Dit gaat alle commits door en herschrijft ze zodat deze jouw nieuwe adres bevatten. Om dat commits de SHA-1 waarde van hun ouders bevatten, zal dit commando iedere commit SHA in jouw geschiedenis veranderen, niet alleen diegene die het gezochte e-mailadres bevatten.