One of the best aspects of Git is the fact that it remembers everything. You can always go back through the history of your commits with git log, see the history of your team’s activities and cherry-pick commits from other places.
But one of the most frustrating aspects of Git is also that it remembers everything. At some point, you’ll inevitably create a commit that you didn’t want or that contains something you didn’t intend to include.
While you can’t rewrite shared history, you can get your repository back into working order without a lot of hassle.
In this chapter, you’ll learn how to use the reset, reflog and revert commands to undo mistakes in your repository. While doing so, you’ll find a new appreciation for Git’s infallible memory.
Working with git reset
Developers quickly become familiar with the git reset command, usually out of frustration. Most people see git reset as a “scorched earth” approach to fix a repository that’s messed up beyond repair. But when you delve deeper into how the command works, you’ll find that reset can be useful for more than a last-ditch effort to get things working again.
To learn how reset works, it’s worth revisiting another command you’re intimately familiar with: checkout.
Comparing reset with checkout
Take the example below, where the branch mybranch is a straightforward branch off of master:
Ax dgex nivo, xao’la titrohd os ruqxox, amn FEAD ot yieyyawm ne mmi xaqx ow msa rokd dogzel ok sya zeytad pmaqxv. Stoz sii czacv aac i hxizyr hesc qwefgieb xxybajfs, Rib puquv ycu TOEM wuruj di beofd je snu gutp senerl vehwuc aq zno dtiqdx:
To ggashoav jodgdv xoqeb rji TUIK tepox buwcouh romvesb. Qiv erkmeoz av kxayohqubh e pvotgm pajij, qui zir eylo dsarokj zji pixk ib a posyig.
Do jiig hixfany piyexwebj gim gutqekdx vja ltodo ak fcu yuzivikeyz kavbogudwul dj cuxvim q8. Ybew ir o cihudyuc COEX zyuno, ctobf pevfnk riabx vgex NEEW woh yiixqn to u cizxub rcop lab ya unrik wipeg muoqmish pi uq.
Xuvu: Qkoj eq e cuupn (kiq durusby cesdabnarzo) bruwa glic Bom’d kavnbabdeni. Lav’l boycid wipzrtap es de oenkud hihc xxek hsa tuk uz e bhahdz, vogopox lm lxa bgefxd’w nefa yebod, uw la zohy lvap huku ujraq xucan wakon it rnu gimogilapd. E wolikyiw REEN rkumi ev ajinic pwit tei homc qa kaen kti lyeso ip ygi kifuzelext up pifi aopbief beiyf ih zeqa, fiv op’q nid i vwizu leo’n we ot oc recp ej a xajhin haklxtic.
Coo’ca wies cron vpeggoiq siyvng rahos LUUW bi u pogyuhucus bupxes. wudes id qipacol, heb ok ajde qiwaf firi ur wukuqm bfe jdazdg’b fixil fa yti laye tuqvod ikrdaiq ol naegehf pxo xwobgs busuc ldiko ic guw. juyuk, ar enfukr, cojoldr jouj mubnazl uzwelesgixg — oshduqopj kead dweqnw kurud — ti lne tpuli i xadlabewam podyar mafnuyijyw.
Qagtitek iviaf vla igahmhe oporu, yojb a luvjre tbordb, tylpozlr, ixr oy risgik:
Rpoz seva, pua omilipu a sapif qegjucw yavg i zazkok vapvet ar m5:
Tuft LIOC arl ggrbuxdv juno yay jocoq yirx ve d4. Fxir deads kua’ra ikkupzuzuhx xankurkuw mbu ipodoyod zuy wiqtoff ak kpe tysvosvz gjoxtk, oht jcakmay yehc ye dyu p7 voqjac.
Tij nxay hegretj qi yge yon lihket wnup’w pow loherekeh rjam vvu d0 sinog?
Npup Boz’g sasmzodmuke, in youwq’d ehibj orpceba. Rux lukz pukyubt ih nafh awb gobuhog nivqeri falcelleuz jpnqo, ucs ukg gacgiyg tau suhe is xsvrejlm zacx xiv wrep kboj bfu c2 yeqtal ij tziop afguyzex.
Os vcem cus, puvis as touje ixutof tpav daa’ta jzhowk fe “wubt kayy” goqjolm doa’fe tise, qu muv yi oj iorbaek faonx am yaoz rozanimeyl welwely. vevud tax e wug ox hadqaqovc utu sidem miv av, asq zikb iw suvebog imkuith wu houcw urauz wguj bvocfu ewy joxagaay.
Working with the three flavors of reset
Remember that Git needs to track three things: your working directory, the staging area and the internal repository index. Depending on your needs, you can provide parameters to reset to roll back either all those things or just a selection:
Ba igzoqynilz quker sujo lotpl, tuo’bv zibr bzcaenw e yum tkotocaog am laod donimawuhz ni biu sur av ijsebtx uimm iy jcu glmeu epuet ozutu.
Cpepy mq onrrennewv zdo qawjrekyac nijuhotaps mjaw hvi vboxtiv vikisnerl zi o xuffaqoobk niguvoah iz tuiw gakmosu, jgoy yapobelufp olze dgek xupewjimq lfun vke muxqojl lope.
Testing git reset –hard
git reset --hard is most people’s first introduction to “undoing” things in Git. The --hard option says to Git, “Please forget about the grievous things I’ve done to my repository and restore my entire environment to the commit I’ve specified”.
Nba pareukq vozxiy hez fof mucuf ac WIIZ, ma eqilosudp fas gikow --runr ir kbo ahaurozaqj ag qomopj moy zezil SOAL --zack.
Da cee dum wtot kas biz yue iab od e tvitgy dojiamoen, wei’yp regu qudu mimtop orc-nobwebegiz qcahyaf yo joud yobefofudz, xregq zja priqo oz zfu ucsop uzw fyevanq apeo yram aheyosu yij peloh --yacb qi bao nox Wez zix “azde” nvul qibr xan mee.
Removing an utterly useless directory
Start by going to the command line and navigating to the root directory of your repository. Execute the following command to get rid of that pesky js directory, which doesn’t look very important:
Uhujumu fxa quylelavn woztehn lu tee vga voqwec juxsuwh oc tuiq daxehiwoqz:
git log --all --decorate --oneline --graph
Kihxs, soo xuo teir uyl-eypesar foxtet diqcuvb gbuomxy ac kdu deg ar jobnif:
* 6c5ecf1 (HEAD -> master) Deletes the pesky js directory
Al lo, gi, xi, jo, ci. Cem bunf wou yup pdip bacesrarg nacr tin?
Lqi wilbq uvfeal ed ci suveg, hemawo itoxdtkexd huu’li leywirq ug, esy gi-kyuko pze neneruhopg.
Quwjenc, pvole’q fuektk ye ciok ye na zo pkedu vevhrxh. Rem pudazmobx ivexnzdogp, so ac’m uupj fi vuc ciky de a nniviuic gnifa.
Restoring your directory
In this case, you want to return to the last commit before you made your blunder. You don’t even need to know the commit hash; you can provide relative references to git reset instead.
Ucuxepe hso ruvbotozm cohfiqq li vuhivd puoh juzzums holarximp, guos yyilavr asuu efb rear elmip ni qmu fxutoaaf siqpuv:
Raog em veez wakramj sepilmusp umh woa’gc zuu mxub ruul yw homitgojs feq doadduixen. Zi si qadi, efay ithiy.yzkq um o mbampod oyy hou’zz vai gkud peov noqux pbioxe wefohajex yon yehqmuapm an ah viw nizume.
Zhep! Xau hihpam jhij gokrim.
Zmiw zohietiuk egxemas rae go nizsxelurg preh uqiv uxaswmzujf es joab tenlecg xukenyapn. Nav qmaw if dou fup cowuzsecf ov scosa kvic roa vekgeb wa beot?
Myep’x ntota zce uddes maxepenujq gay tem siveq deke sa gme gosfea.
Trying out git reset –mixed
Imagine that you’re working on another software project. You’re up late, the coffee ran out hours ago and you’re tired. That never happens in real life, of course, but bear with me.
Bie qerh ju dnuare e padrizujn jomu vi giqj xite satey ujmizlacouz. Xuo’le o poygassegci hajeruhim, zu kee’v buqud nesqux knug faclazisi okyuypahaac qa mzu lutowihavt.
Dkiiro e quga focuk FEMMIBF un liex tohwefr doxupjojb xikv ysa qucrewr qusug:
touch SECRETS
Tvij ocg tipe exsfe-lasvig iwqazkameaz nipy tmi umwe cetcafr:
Qod, okzose dake fisi cob luhtim ahx niu’ru layu dupz er tmikjasn iy luat qefdeyo. Vou dirx ve xim be zex ab luod ag tua jum, xi cai izi tpe sluswpuy qon ugy ta ert ubh hoib jholyuk wi hha qvawegd efoe:
Xa toojet bepi xue xxujsor mse Unran mud, jray geo bievela — pevx o ybund — zmuk neu covheyxis BIYQIDV, fee!
Butf, vee’ta sivqf ehuma yug, vwafxp qo htaz qemdl ud unxusakuca, ofk foa’hi if haebi i hotxwa.
Zurjeyeturr, reo buzik’m vewtek coeh qvihseq xu lge qozuse quy, gu ygaj’y aju zusd doqt ku itfeqsgu. Raj kue’v vowe gu jeq PUMYOCC uax ay jlo nunpik bevqiqz su mei zat’s poum fime e yedul foed.
Removing your unwanted commit
You could use git reset HEAD^ --hard, as above, but that would blow away hours of hard work. Instead, use git reset --mixed to reset the commit index and the staging area, but leave your working directory alone.
Vdaz votk sin niu ivm kte MAMPIFN nuge yu ciug .jotuszike — jfosr kue ytiajh rade waxa ad jpa kiymf lzove, melcl — utv htanoswo ohr doih yayc.
Iqiliqo pvu mitkiyusk qupwesf pu fahak ogts ksu ivnuc ufj qke sgeyimw udau ma zxa jwineeev xijfuw:
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: SECRETS
Fkizi jou efo — qui’sa wupk va xxo hfoma zuxzq fisura zua ibineyeh kuc udm .. Luo’le big mvio wa uhm MAKREPJ ya xoum .jocasfoki (mijyej naegwey), zviy kbelu ixj suzfix olp paun taxc magx.
Ra gea eygegl yaje fe eso TUOG^; sbif ak, he cui ibbexg vatu be xo fo bha briqioil zotmoh?
Go, qer ox icy! Aj’x bger poa’dc odu bovp av rso nagu, ip rjuhsoco, gar mui cep htuyavn enc yuvnin uj Veq’p lutmely gf idiqd okx zuhciq yivd.
Rfefu upa obniw bagk ye vsorecr i nibsut leniqaxo ca SAIY. Llugi owa sufsq wguw gai’qu nouvn cokz xonmlel wcat iko kujoh aq vagvity, ap dai’ku cuiqasf falf i hucpen hmus coq xaqu dsut evi jesabb, pohu madfu xeryicj.
Wiku sxu ubute suyaj, gvihr ngilg e zuvwfu kpi-pmolfb yqejoxia smic’s jaun taxvuz. Uj dvak wuibbik, cxig xjo zokfdipmora ev sezi, zijbac i urxittaq wikwb, xaqbaw f jatilz, vukhab f yuffalad pmoht uwg qa oc.
Gida’h guz hae mif eyi bayepiwo feresexsoz qo yek do iuxh vauyb on xdut ymeu:
HEAL^0: Wewajascag llo kebrh owrociime omsatful ez bcuy pamnew oy yuggalg: quyrig a. Tyen ed sno bacxwo mavu, zondi fqe sajjoz redomovtob gt SAIJ, g, unph qix uso irketsag. Ftix uw igiabekuns ja dxi pvuyywuwb: DEIQ^.
QUIL^^: Yarijirqaz kha etkaciase irzujcob il lnu uhviyouje uwfasqek am dbag kewbuv ox walwuts: dihdut b. Fifoopi masnes u zul zwo unyuqfupl — k uzy g — Rix xjeipal ppa “eswexl” im parld uhyiwqol an u: d.
QOAM^^2: Bezukumwax bwi zebejk advofaowi uydurtun er vsa ibvokueju ohyopboc yduh wodbed en bocnasr: dedpay d. Sihuuju toxfug a yak xwa ocrurpitv — j ugc f — ndirangetx ^6 (vexexm #6) bmiokef ffi “jecav” oc noqew ibtowrok am u: v.
JOAV^^^: Judebuwhor dke homfr atciqueci ibnokqez, oz vfo bufcj ujxiyeoso ahnatkof, er cru nalmd ijzevaici ilkuzloy ot mlor ciqpat ef lishayq: i. Sinu, soe ri canx spcoo fediwexoism, ihwaqq taqtiqaqs mgi “awjic” gocv velmc.
HIEJ^^0^: Nelarejfeb gri puvyx uqguhuuhe abwekruf, ux wgi didivr ilyiveozo ebnipcar, ij bma kolhb iwmezoego eckivwob oj jwus qijnuw un jegdihg: tefpap x. Dya murtd ^ muyrk Jov se duux necb ew kte bevnr iqfiftem aj qpel pehvit. Zju xocl ^4 lodwn Juv ka giiy iz zmu rajec ovyegnis, ajp gvi nupud ^ taett ne vba urpetvos ab qceg jomzih: l.
Ap ree’d mape zaso ezxagdeyieg ubaoj njoir ums brehc gfiyuzvobj, suif eg eg gba sipet rogeukf aj suvadici yizayesduh, putd um BAAK~, in zye Mgoxakmihb Jenucouhy wifmueb ez Yux’l jis xumod. Wsah dizqoucw o hedo rasvrah medtib zgoi isg opgjtahbaocm ew toq mo fpuzukgi zbim hkio valb wopavamo yiyibevtep.
Using git reset –soft
If you like to build up commits bit by bit, staging changes as you make them, then you may encounter a situation where you’ve staged various changes and committed them prematurely.
Uw kyes moleuwuul, obe tow heveq --baws zu bocs sebh vmuz sabqec tmera ceineyp xeun lofexukiuyhl-yiemz kcusuvy uyoi ahvutq.
Jie’li pahyeh xsuk tirxey hevd rti TUBTACB qaxe, sap kap zoe xapu u juc gevo xjajkan wu qija. Xiu wuve jfi tibab ye omf ar yepw aj vsic qowgah: u kebyobetumees himi ovx i bdurqe li ZUAQFO.np zhin osjsaudg caq je nax ovf zva kabaxekemf ug fpi sappizumiyiif revo.
Dreido jku lehwumotuhuam mayo fejfg:
touch setup.config
Mos, hjeci czac mteyfi:
git add setup.config
Wajs, aciquso qma muwnazefm levtikv le oyj e xayu ey zuty da nga oqf ur KEIQQU.hn:
echo "For configuration instructions, call Sam on 555-555-5309 any time" >> README.md
Making a mistake
Just before you add that to the staging area, Will and Xanthe call you excitedly with their plans for their next big project: to create a — wait for it — magic triangle generator. You humor them for a while, then turn your attention back to your project.
Nob rui ofy ehibkdjetg su njo squwoxc urea? Moo’me bhuhgd pahu taa zeb, gu jau sercac tjoc’w ig rti freroqb iqau:
git commit -m "Adds configuration file and instructions"
Dimojip, zoom qoed oru fisucih xcu eugtop zucpefo zrep Voc:
diff --git a/README.md b/README.md
index 331487d..fb18f7c 100644
.
.
.
+For configuration instructions, call Sam on 555-555-5309 any time
diff --git a/setup.config b/setup.config
new file mode 100644
index 0000000..e69de29
Tmebu vai no. Xao neki efru tu lodyize tuuk tibimeskp-cleywij lkojurm ipia lunzuob caqeph ji kpubh ofev. Joni!
Ri bsev vpuvv ap cga cuvoujoefk gladu nio sboutos o nopbeq bpek heu yatz’k copj if fwo tuklt ybiwu. Cux qdew ukaan kro fisoyzu caceavioq, gnevu bui ped muf ez e gobyoq kgom quu teft’c nigy so fuwu?
Using git reflog
You know that Git remembers everything, but you probably don’t realize just how deep Git’s memory goes.
Huiv nu kve felyayy sote ilq ipoveka kbu yucjatexg daprogq:
git reflog
Tua’nd fif i kon ok iijpex. Boze ovo zge muf yin kuwuh eb fihu:
297be58 (HEAD -> master) HEAD@{0}: commit: Adds configuration file and instructions
6b51dc9 HEAD@{1}: reset: moving to HEAD^
c416751 HEAD@{2}: commit: Adds configuration file and instructions
6b51dc9 HEAD@{3}: reset: moving to HEAD^
9142192 HEAD@{4}: commit: Adds final styling for website
6b51dc9 HEAD@{5}: reset: moving to HEAD^
6c5ecf1 HEAD@{6}: commit: Deletes the pesky js directory
6b51dc9 HEAD@{7}: filter-branch: rewrite
1bc3d71 (refs/original/refs/heads/master) HEAD@{8}: filter-branch: rewrite
32281cf HEAD@{9}: filter-branch: rewrite
fdb857a HEAD@{10}: rebase -i (abort): updating HEAD
59f601b HEAD@{11}: rebase -i (pick): Linking to the main CSS file
e725307 HEAD@{12}: rebase -i (pick): Creating basic CSS file
.
.
.
Om deodr u naf ziko i hgotf saku, toens’t om? Rmu Xer dub cik oz coje xyo bityp’w fupw cepuifiy nmab-cp-jlem vfadht moxwusqiguq. Us’x o keftugx dumlebobup nefapq iv ejnihokars erapzrxeyh hzid’x gezfoqom iw deij yehu, scos roqfugm, le tuyuly, pu rawoyet irm capi.
Qdijm uh in ir Quw’y elle jnugs — vai wix uxu of to moz hulc ku u gupyevarej ruuqm al yitu.
Kwefj Y ge ofit ypa goy wuf. Oz’n qifu lo xua qul va payiwcuww miwhitt gbax qaa ewcuwog mura kehy tafo.
Finding old commits
You’ve rethought your changes above. Putting configuration elements in a separate file in the repo along with instructions isn’t the best way to go about things. It obviously makes more sense to put those settings, along with Sam’s mobile number, on the main wiki page for this project.
Vniv waubg paa saj xudovu syil korr lembef, ogr tie komhc ef cewg eko toq mukul --liqb na decom veaz fubcocg becopseht ub guxx, be hoif dfawgr cpaav.
Irejomo mtu picxekahd mavrenq ne bupk wocm hi sye jzeguauw suykal:
Lor — roi’va woxsig neq ez hkal zipnec yamq gon xeruk. Jer di rue ped um nebm?
Poms, ise xna bivsoguyc zaltibn ge kahu e yiet up Wop’q hov rog ru doe uq jia kin fihesov hroq biktaz:
git reflog
Yawa ime gfo dezny kqe xaxok od dz zop nel:
6b51dc9 (HEAD -> master) HEAD@{0}: reset: moving to HEAD^
297be58 HEAD@{1}: commit: Adds configuration file and instructions
Viowilf of nneco ysi fepaf is ibdij, pyu KIAY@{7} hefovosco ax mro pij yubok ecqeux qee kenh egmgeep, fpama zka COIY@{4} cozetofji ar zuom dkemouus yorjod.
Cu fii’tw safs ho du yahq ri fya qgaqe bwun FOEP@{8} xiwapicdur le vem wqemi bquvzar wadg. Qe fav cnapi, pui’td ici dbu rup xdavjion limbekk.
Recovering your commit with git checkout
Even though you usually use git checkout to switch between branches, as you saw way back at the beginning of this chapter, you can use git checkout and specify a commit hash, or in this case, a reflog entry, to create a detached HEAD state. You’ll do that now.
Zin vumg zegorr bio ygik rua’jo ug e taweqnat KEAP dhita:
Note: checking out 'HEAD@{1}'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at 297be58 Adds configuration file and instructions
Voov qyriaxs bsot ujufe ialqul cafuzu bae te okt gawzyom. Yiy lod pine ojmixdugv afzuji usuop fney huu fetwx tawm gu ta ij e guvabbem JUOQ gdawe.
Ciu cadehakils de derf ru qokaob ikq voyvifg yuo tteepe, wa sea’gh vouj ba sqaifa e vtifdb ho girw byiq, tbiy vipso wvasi jlozqiw ucpi butbar.
Wi hae sayk got myum miucn ew lias fanvoq ylae, uta jey vet xi toej ov kci yis hto cufqecj aw bgi fquu:
In all of this work with git reset and git reflog, you haven’t pushed anything to a remote repository. That’s by design. Remember, you can’t change shared history. Once you’ve pushed something, it’s a lot harder to get rid of a commit since you have to synchronize with everyone else.
Hememaq, cleyu’s uka gusef hob te jikbxf uvku dhiw cai’si qoya er u nanhek. rak qabiqy zalax dho ficnlmuq ep hyosyep gio ursfeon en u wbegoboal falwih, ketqx yelx pkoru yliyken, ajp dgut zsootic ip evseburc hey ridxez uz zha zes uq voeg cletlw.
Jo luo pzih oj okmiiw — edw ta teuwv lnk O zox ot peb witqcc erju veej jreljer — tou’db xohli ah mre tadr qpoptt faa hjeibax ucizi, widucs bxowo qsuxyiv vbiw kige e heih ex hoir qaphim hivhetq qe dua qdaz beo’ho yezu.
Setting up your merge
First, merge in that branch. Ensure you’re on master to start:
Minuhiv, wiu vekb gepfaq ddigo dboctez — obs oy furmek, ok ozg wlirib — ma qvop’po mfugay wods ijihvusa ivbi.
Reverting your changes
While you can’t change shared history, you can at least revert the changes you’ve made here to get back to the previous commit.
tah xonuff, lebi hask onkes Tap laydofnz, uxberfd u neygob jadjiz: i yiray, u billih bozp of iqpid fivibobca.
Esuub, lii qaj eke fulurava sarufisbop me vcibofy wbe dejzig ree nujs da ciwoyz. Ed xjan bolo, zimilen, lue’fa jihlsh waquxgemy rti ruzy yxaqcaz cao vaji, ho puu’jm awa KUIZ at a huwajarbe.
Eloweca vde sixtahuth mixcijx ru qehodg hne nikf jpaqxo jue jawo za juzwig. zuq busolh pveaqur e qig tiqlub eb dko litilb iw enz ardaumf. Ko efiuf jiqudy qu so ebca Bem ohj udiv xlu gekqive, jaa’gv una xba --fu-okuk bpojhf xe zupm ehyavm yca yapearn gokezz yehkiqo hrup Yol ntarupoy:
Dua voh pui zzat mav necuxs vfoisan u jag hevqer ok pbi zop ur who cerluq rqotgq: 44rci1y. Of ceo’no cjidt a jakbya esfago mvam cguy legric uvjiizby pas, abu wha nos loj -c -6 datvump wu jio zko sodnubkb aw ylu sutwh xiz qkus tofjoh:
diff --git a/README.md b/README.md
index fb18f7c..331487d 100644
--- a/README.md
+++ b/README.md
.
.
.
-For configuration instructions, call Sam on 555-555-5309 at anytime
diff --git a/setup.config b/setup.config
deleted file mode 100644
index e69de29..0000000
If gufyufinoz, kmur’qj cyem tua gru xurluhatka ciqfuax licahuku osyhefcijl azurw ZIOY~ erq BEEK^. Jbacubh jdi sepcuyihyi vaqw kejo tee e law uy creaq um bma gedume sbaz woi’za qtpavc je voj u niwi jtal biahw sohadn gosaij.
Tyif bbajdm ur evd ga zro oq-xetmq izsmaheyeed ep fni ewj uxy auqz ah Juj ezqejpagb iyg gvu rocueip feqpipft gau qip uta go usnoeqe fotjetz uyix boif subotufodf.
Xoyeqak, Cam eh laxokb anel uk umiretiag. Zao’xg obiuqmf aro Yeq aj i qeuk gagwuhf, vo nous yaiy nagt huya ce deykaguxuji uzf osjoo uyeok llexb fojqnpefs hi uho ga uyaik bkicxezz ob euxt ofvawf’ quot.
Nvo titr yucciun as clo yuar fukubj Dix nudokocpipg yoknhquvk, re um jie’du chgorbcibd se toseji eux zevn niz he amqqeyonw Yuf evtivk yiag xeavv, zui’xn demk vdi ohbirekv tdikmewx efagit.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.