Chapters ▾ 2nd Edition

7.7 Git Tools - Reset Ontmystifiseer (Reset Demystified)

Reset Ontmystifiseer (Reset Demystified)

Voordat ons aanbeweeg na meer gespesialiseerde gereedskap, kom ons gesels eers oor die Git reset en checkout opdragte. Hierdie opdragte is twee van die mees verwarrende dele van Git wanneer jy hulle vir die eerste keer teëkom. Hulle doen so baie dinge dat dit hopeloos lyk om hulle werklik te verstaan en behoorlik aan te wend. Hiervoor beveel ons 'n eenvoudige metafoor aan.

Die Drie Bome (The Three Trees)

'n Makliker manier om oor reset en checkout te dink, is deur die raamwerk van Git as 'n inhoudsbestuurder van drie verskillende bome. Deur “boom” (“tree”) hier, bedoel ons regtig “versameling van lêers”, nie spesifiek die datastruktuur nie. (Daar is 'n paar gevalle waar die indeks (index) nie presies soos 'n boom optree nie, maar vir ons doeleindes is dit makliker om vir eers so daaroor te dink.)

Git as 'n stelsel bestuur en manipuleer drie bome in sy normale werking:

Boom (Tree) Rol (Role)

HEAD

Laaste vaslegging momentopname (commit snapshot), volgende ouer

Indeks (Index)

Voorgestelde volgende vaslegging momentopname

Werkgids (Working Directory)

Sandput (Sandbox)

Die HEAD (The HEAD)

HEAD is die wyser na die huidige tak-verwysing (branch reference), wat op sy beurt 'n wyser is na die laaste vaslegging gemaak op daardie tak. Dit beteken HEAD sal die ouer wees van die volgende vaslegging wat geskep word. Dit is oor die algemeen die eenvoudigste om aan HEAD te dink as die momentopname (snapshot) van jou laaste vaslegging op daardie tak.

Trouens, dit is redelik maklik om te sien hoe daardie momentopname lyk. Hier is 'n voorbeeld van hoe om die werklike gidslys en SHA-1-kontrolesomme vir elke lêer in die HEAD-momentopname te kry:

$ 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

Die Git cat-file en ls-tree opdragte is “loodgieterswerk” (“plumbing”) opdragte wat vir laervlak dinge gebruik word en nie regtig in dag-tot-dag werk gebruik word nie, maar hulle help ons om te sien wat hier aangaan.

Die Indeks (The Index)

Die indeks is jou voorgestelde volgende vaslegging. Ons het ook na hierdie konsep verwys as Git se “Voorbereidingsarea” (“Staging Area”) aangesien dit is waarna Git kyk wanneer jy git commit uitvoer.

Git vul hierdie indeks met 'n lys van al die lêerinhoude wat die laaste keer in jou werkgids uitgetrek is (checked out) en hoe hulle gelyk het toe hulle oorspronklik uitgetrek is. Jy vervang dan sommige van daardie lêers met nuwe weergawes daarvan, en git commit omskep dit in die boom vir 'n nuwe vaslegging.

$ git ls-files -s
100644 a906cb2a4a904a152e80877d4088654daad0c859 0	README
100644 8f94139338f9404f26296befa88755fc2598c289 0	Rakefile
100644 47c6340d6459e05787f644c2447d2595f5d3a54b 0	lib/simplegit.rb

Weereens, hier gebruik ons git ls-files, wat meer 'n agter-die-skerms opdrag is wat vir jou wys hoe jou indeks tans lyk.

Die indeks is nie tegnies 'n boomstruktuur nie — dit is eintlik as 'n platgemaakte manifes geïmplementeer — maar vir ons doeleindes is dit goed genoeg.

Die Werkgids (The Working Directory)

Laastens het jy jou werkgids (working directory, ook dikwels verwys na as die “working tree”). Die ander twee bome stoor hul inhoud op 'n doeltreffende maar ongerieflike manier binne die .git gids. Die werkgids pak dit uit in werklike lêers, wat dit vir jou baie makliker maak om dit te redigeer. Dink aan die werkgids as 'n sandput, waar jy veranderings kan uitprobeer voordat jy dit in jou voorbereidingsarea (indeks) en dan in die geskiedenis vaslê.

$ tree
.
├── README
├── Rakefile
└── lib
    └── simplegit.rb

1 directory, 3 files

Die Werkvloei (The Workflow)

Git se tipiese werkvloei is om momentopnames van jou projek in agtereenvolgens beter toestande op te neem deur hierdie drie bome te manipuleer.

Git’s typical workflow
Figure 150. Git se tipiese werkvloei

Kom ons visualiseer hierdie proses: sê nou jy gaan na 'n nuwe gids met 'n enkele lêer daarin. Ons sal dit v1 van die lêer noem, en ons sal dit in blou aandui. Nou voer ons git init uit, wat 'n Git-bewaarplek sal skep met 'n HEAD-verwysing wat na die ongebore master tak wys.

Newly-initialized Git repository with unstaged file in the working directory
Figure 151. Nuut-geïnisialiseerde Git-bewaarplek met onvoorbereide (unstaged) lêer in die werkgids

Op hierdie punt het slegs die werkgidsboom enige inhoud.

Nou wil ons hierdie lêer vaslê, so ons gebruik git add om inhoud in die werkgids te neem en dit na die indeks te kopieer.

File is copied to index on `git add`
Figure 152. Lêer word na indeks gekopieer met git add

Dan voer ons git commit uit, wat die inhoud van die indeks neem en dit as 'n permanente momentopname stoor, 'n vasleggingsobjek skep wat na daardie momentopname wys, en master opdateer om na daardie vaslegging te wys.

The `git commit` step
Figure 153. Die git commit stap

As ons git status uitvoer, sal ons geen veranderings sien nie, want al drie bome is dieselfde.

Nou wil ons 'n verandering aan daardie lêer maak en dit vaslê. Ons sal deur dieselfde proses gaan; eers verander ons die lêer in ons werkgids. Kom ons noem dit v2 van die lêer, en dui dit in rooi aan.

Git repository with changed file in the working directory
Figure 154. Git-bewaarplek met gewysigde lêer in die werkgids

As ons git status dadelik uitvoer, sal ons die lêer in rooi sien as “Changes not staged for commit”, omdat daardie inskrywing verskil tussen die indeks en die werkgids. Volgende voer ons git add daarop uit om dit in ons indeks voor te berei (stage).

Staging change to index
Figure 155. Voorbereiding (Staging) van verandering in indeks

Op hierdie punt, as ons git status uitvoer, sal ons die lêer in groen sien onder “Changes to be committed” omdat die indeks en HEAD verskil — dit wil sê, ons voorgestelde volgende vaslegging verskil nou van ons laaste vaslegging. Laastens voer ons git commit uit om die vaslegging te finaliseer.

The `git commit` step with changed file
Figure 156. Die git commit stap met gewysigde lêer

Nou sal git status ons geen afvoer gee nie, want al drie bome is weer dieselfde.

Die omskakeling van takke of die kloning van 'n bewaarplek gaan deur 'n soortgelyke proses. Wanneer jy 'n tak uitcheck, verander dit HEAD om na die nuwe tak-verwysing (ref) te wys, vul jou indeks met die momentopname van daardie vaslegging, kopieer dan die inhoud van die indeks na jou werkgids.

Die Rol van Reset (The Role of Reset)

Die reset opdrag maak meer sin wanneer dit in hierdie konteks beskou word.

Vir die doeleindes van hierdie voorbeelde, kom ons sê ons het file.txt weer gewysig en dit 'n derde keer vasgelê. So nou lyk ons geskiedenis so:

Git repository with three commits
Figure 157. Git-bewaarplek met drie vasleggings

Kom ons stap nou presies deur wat reset doen wanneer jy dit aanroep. Dit manipuleer direk hierdie drie bome op 'n eenvoudige en voorspelbare manier. Dit doen tot drie basiese bewerkings.

Stap 1: Skuif HEAD (Move HEAD)

Die eerste ding wat reset sal doen, is om dit waarna HEAD wys, te skuif. Dit is nie dieselfde as om HEAD self te verander nie (wat is wat checkout doen); reset skuif die tak waarna HEAD wys. Dit beteken as HEAD op die master tak gestel is (m.a.w. jy is tans op die master tak), sal die uitvoering van git reset 9e5e6a4 begin deur te maak dat master na 9e5e6a4 wys.

Soft reset
Figure 158. Sagte reset (Soft reset)

Maak nie saak watter vorm van reset jy met 'n vaslegging aanroep nie, dit is die eerste ding wat dit altyd sal probeer doen. Met reset --soft, sal dit bloot daar stop.

Neem nou 'n oomblik om na daardie diagram te kyk en te besef wat gebeur het: dit het basies die laaste git commit opdrag ongedaan gemaak. Wanneer jy git commit uitvoer, skep Git 'n nuwe vaslegging en skuif die tak waarna HEAD wys op na dit. Wanneer jy terug reset na HEAD~ (die ouer van HEAD), skuif jy die tak terug na waar dit was, sonder om die indeks of werkgids te verander. Jy kan nou die indeks opdateer en weer git commit uitvoer om te bereik wat git commit --amend sou gedoen het (sien Verandering van die Laaste Vaslegging (Changing the Last Commit)).

Stap 2: Opdatering van die Indeks (Updating the Index - --mixed)

Let op dat as jy nou git status uitvoer, sal jy die verskil in groen sien tussen die indeks en wat die nuwe HEAD is.

Die volgende ding wat reset sal doen, is om die indeks op te dateer met die inhoud van watter momentopname HEAD nou ook al na wys.

Mixed reset
Figure 159. Gemengde reset (Mixed reset)

As jy die --mixed opsie spesifiseer, sal reset op hierdie punt stop. Dit is ook die verstelling, so as jy hoegenaamd geen opsie spesifiseer nie (net git reset HEAD~ in hierdie geval), is dit waar die opdrag sal stop.

Neem nou nog 'n oomblik om na daardie diagram te kyk en te besef wat gebeur het: dit het steeds jou laaste commit ongedaan gemaak, maar het ook alles onvoorbereid (unstaged) gemaak. Jy het teruggerol (rolled back) na voor jy al jou git add en git commit opdragte uitgevoer het.

Stap 3: Opdatering van die Werkgids (Updating the Working Directory - --hard)

Die derde ding wat reset sal doen, is om te maak dat die werkgids soos die indeks lyk. As jy die --hard opsie gebruik, sal dit voortgaan na hierdie stadium.

Hard reset
Figure 160. Harde reset (Hard reset)

So kom ons dink oor wat nou net gebeur het. Jy het jou laaste vaslegging, die git add en git commit opdragte ongedaan gemaak, en al die werk wat jy in jou werkgids gedoen het.

Dit is belangrik om op te let dat hierdie vlag (--hard) die enigste manier is om die reset opdrag gevaarlik te maak, en een van die baie min gevalle waar Git werklik data sal vernietig. Enige ander aanroeping van reset kan redelik maklik ongedaan gemaak word, maar die --hard opsie kan nie, aangesien dit lêers in die werkgids met geweld oorskryf. In hierdie spesifieke geval het ons steeds die v3 weergawe van ons lêer in 'n vaslegging in ons Git-databasis, en ons kan dit terugkry deur na ons reflog te kyk, maar as ons dit nie vasgelê het nie, sou Git steeds die lêer oorskryf het en dit onherwinbaar wees.

Opsomming (Recap)

Die reset opdrag oorskryf hierdie drie bome in 'n spesifieke volgorde, en stop wanneer jy sê dit moet:

  1. Skuif die tak waarna HEAD wys (stop hier as --soft).

  2. Maak die indeks soos HEAD lyk (stop hier behalwe as --hard).

  3. Maak die werkgids soos die indeks lyk.

Reset Met 'n Pad (Reset With a Path)

Dit dek die gedrag van reset in sy basiese vorm, maar jy kan dit ook voorsien van 'n pad (path) om op op te tree. As jy 'n pad spesifiseer, sal reset stap 1 oorslaan, en die res van sy aksies beperk tot 'n spesifieke lêer of stel lêers. Dit maak eintlik 'n bietjie sin — HEAD is net 'n wyser, en jy kan nie na 'n deel van een vaslegging en 'n deel van 'n ander wys nie. Maar die indeks en werkgids kan gedeeltelik opgedateer word, so reset gaan voort met stappe 2 en 3.

So, neem aan ons voer git reset file.txt uit. Hierdie vorm (aangesien jy nie 'n vaslegging SHA-1 of tak gespesifiseer het nie, en jy nie --soft of --hard gespesifiseer het nie) is 'n kortskrif vir git reset --mixed HEAD file.txt, wat sal:

  1. Die tak waarna HEAD wys skuif (oorgeslaan).

  2. Die indeks soos HEAD laat lyk (stop hier).

So dit kopieer in wese net file.txt van HEAD na die indeks.

Mixed reset with a path
Figure 161. Gemengde reset met 'n pad

Dit het die praktiese effek om die lêer onvoorbereid (unstaging) te maak. As ons na die diagram vir daardie opdrag kyk en dink oor wat git add doen, is hulle presies die teenoorgesteldes.

Staging file to index
Figure 162. Voorbereiding (Staging) van lêer na indeks

Dit is hoekom die afvoer van die git status opdrag voorstel dat jy dit uitvoer om 'n lêer onvoorbereid te maak (sien 'n Voorbereide lêer onttrek (Unstaging a Staged File) vir meer hieroor).

Ons kon net so maklik Git nie laat aanneem het ons bedoel “trek die data van HEAD af” deur 'n spesifieke vaslegging te spesifiseer om daardie lêerweergawe van af te trek (pull). Ons sou net iets soos git reset eb43bf file.txt uitvoer.

Soft reset with a path to a specific commit
Figure 163. Sagte reset met 'n pad na 'n spesifieke vaslegging

Dit doen effektief dieselfde ding asof ons die inhoud van die lêer teruggerol (reverted) het na v1 in die werkgids, git add daarop uitgevoer het, en dit dan weer teruggerol het na v3 (sonder om eintlik deur al daardie stappe te gaan). As ons nou git commit uitvoer, sal dit 'n verandering opneem wat daardie lêer terugrol na v1, al het ons dit nooit regtig weer in ons werkgids gehad nie.

Dit is ook interessant om op te let dat, net soos git add, sal die reset opdrag 'n --patch opsie aanvaar om inhoud onvoorbereid (unstage) te maak op 'n brok-vir-brok (hunk-by-hunk) basis. Jy kan dus selektief inhoud onvoorbereid maak of terugrol.

Saampersing (Squashing)

Kom ons kyk hoe om iets interessants met hierdie nuutgevonde krag te doen — die saampers (squashing) van vasleggings.

Sê jy het 'n reeks vasleggings met boodskappe soos “oops.”, “WIP” en “forgot this file”. Jy kan reset gebruik om hulle vinnig en maklik in 'n enkele vaslegging saam te pers wat jou baie slim laat lyk. Saampersing van Vasleggings (Squashing Commits) wys 'n ander manier om dit te doen, maar in hierdie voorbeeld is dit makliker om reset te gebruik.

Kom ons sê jy het 'n projek waar die eerste vaslegging een lêer het, die tweede vaslegging 'n nuwe lêer bygevoeg het en die eerste verander het, en die derde vaslegging die eerste lêer weer verander het. Die tweede vaslegging was werk in proses en jy wil dit saampers.

Git repository
Figure 164. Git-bewaarplek

Jy kan git reset --soft HEAD~2 uitvoer om die HEAD-tak terug te skuif na 'n ouer vaslegging (die mees onlangse vaslegging wat jy wil behou):

Moving HEAD with soft reset
Figure 165. Verskuiwing van HEAD met sagte reset

En voer dan eenvoudig git commit weer uit:

Git repository with squashed commit
Figure 166. Git-bewaarplek met saamgeperste (squashed) vaslegging

Nou kan jy sien dat jou bereikbare geskiedenis, die geskiedenis wat jy sou opstuur (push), nou lyk asof jy een vaslegging gehad het met file-a.txt v1, dan 'n tweede wat beide file-a.txt verander het na v3 en file-b.txt bygevoeg het. Die vaslegging met die v2 weergawe van die lêer is nie meer in die geskiedenis nie.

Check It Out

Laastens mag jy wonder wat die verskil tussen checkout en reset is. Soos reset, manipuleer checkout die drie bome, en dit is 'n bietjie anders afhangende van of jy die opdrag 'n lêerpad (file path) gee of nie.

Sonder Paaie (Without Paths)

Die uitvoering van git checkout [branch] is redelik soortgelyk aan die uitvoering van git reset --hard [branch] in die sin dat dit al drie bome vir jou opdateer om soos [branch] te lyk, maar daar is twee belangrike verskille.

Eerstens, anders as reset --hard, is checkout werkgids-veilig (working-directory safe); dit sal kyk om seker te maak dit wis nie lêers weg wat veranderings aan hulle het nie. Eintlik is dit 'n bietjie slimmer as dit — dit probeer om 'n triviale saamsmelting in die werkgids te doen, sodat al die lêers wat jy nie verander het nie, opgedateer sal word. reset --hard, aan die ander kant, sal eenvoudig alles oorkruis vervang sonder om te kyk.

Die tweede belangrike verskil is hoe checkout HEAD opdateer. Waar reset die tak sal skuif waarna HEAD wys, sal checkout HEAD self skuif om na 'n ander tak te wys.

Byvoorbeeld, sê ons het master en develop takke wat na verskillende vasleggings wys, en ons is tans op develop (so HEAD wys daarna). As ons git reset master uitvoer, sal develop self nou na dieselfde vaslegging wys as wat master doen. As ons in plaas daarvan git checkout master uitvoer, beweeg develop nie, HEAD self doen. HEAD sal nou na master wys.

So, in beide gevalle verskuif ons HEAD om na vaslegging A te wys, maar hoe ons dit doen is baie verskillend. reset sal die tak waarna HEAD wys skuif, checkout skuif HEAD self.

`git checkout` and `git reset`
Figure 167. git checkout en git reset

Met Paaie (With Paths)

Die ander manier om checkout uit te voer, is met 'n lêerpad (file path), wat, soos reset, nie HEAD skuif nie. Dit is net soos git reset [branch] file in die sin dat dit die indeks met daardie lêer by daardie vaslegging opdateer, maar dit oorskryf ook die lêer in die werkgids. Dit sou presies wees soos git reset --hard [branch] file (as reset jou dit sou toelaat om dit uit te voer) — dit is nie werkgids-veilig nie, en dit skuif nie HEAD nie.

Ook, soos git reset en git add, sal checkout 'n --patch opsie aanvaar om jou toe te laat om lêerinhoud selektief terug te rol (revert) op 'n brok-vir-brok (hunk-by-hunk) basis.

Opsomming (Summary)

Hopelik verstaan jy nou en voel jy gemakliker met die reset opdrag, maar is waarskynlik steeds 'n bietjie verward oor hoe presies dit verskil van checkout en sou moontlik nie al die reëls van die verskillende oproepings (invocations) kon onthou nie.

Hier is 'n spiekbriefie vir watter opdragte watter bome beïnvloed. Die “HEAD” kolom lees “REF” as daardie opdrag die verwysing (tak) skuif waarna HEAD wys, en “HEAD” as dit HEAD self skuif. Gee veral aandag aan die 'WD Safe?' (Werkgids Veilig?) kolom — as dit sê NO, dink 'n oomblik na voordat jy daardie opdrag uitvoer.

HEAD Indeks Werkgids WD Veilig?

Vasleggingsvlak (Commit Level)

reset --soft [commit]

REF

NO

NO

YES

reset [commit]

REF

YES

NO

YES

reset --hard [commit]

REF

YES

YES

NO

checkout <commit>

HEAD

YES

YES

YES

Lêervlak (File Level)

reset [commit] <paths>

NO

YES

NO

YES

checkout [commit] <paths>

NO

YES

YES

NO