123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230(*
* Copyright (c) 2018-2021 Tarides <contact@tarides.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)open!ImportincludeInode_intfmoduleMake_internal(Conf:Conf.S)(H:Irmin.Hash.S)(Node:Irmin.Private.Node.Swithtypehash=H.t)=structlet()=ifConf.entries>Conf.stable_hashtheninvalid_arg"entries should be lower or equal to stable_hash"moduleNode=structincludeNodemoduleH=Irmin.Hash.Typed(H)(Node)lethash=H.hashendmoduleT=structtypehash=H.t[@@derivingirmin]typestep=Node.step[@@derivingirmin]typemetadata=Node.metadata[@@derivingirmin]letdefault=Node.defaulttypevalue=Node.valueletvalue_t=Node.value_tletpp_hash=Irmin.Type.(pphash_t)endmoduleStepMap=structincludeMap.Make(structtypet=T.stepletcompare=Irmin.Type.(unstage(compareT.step_t))end)letof_listl=List.fold_left(funacc(k,v)->addkvacc)emptylend(* Binary representation, useful to compute hashes *)moduleBin=structopenTtypeptr={index:int;hash:H.t}[@@derivingirmin]typetree={depth:int;length:int;entries:ptrlist}[@@derivingirmin]typev=Valuesof(step*value)list|Treeoftree[@@derivingirmin]moduleV=Irmin.Hash.Typed(H)(structtypet=vlett=v_tend)typet={hash:H.tLazy.t;stable:bool;v:v}letpre_hash_v=Irmin.Type.(unstage(pre_hashv_t))lett:tIrmin.Type.t=letopenIrmin.Typeinletpre_hash=stage(funx->pre_hash_vx.v)inrecord"Bin.t"(funhashstablev->{hash=lazyhash;stable;v})|+field"hash"H.t(funt->Lazy.forcet.hash)|+field"stable"bool(funt->t.stable)|+field"v"v_t(funt->t.v)|>sealr|>like~pre_hashletv~stable~hashv={stable;hash;v}lethasht=Lazy.forcet.hashend(* Compressed binary representation *)moduleCompress=structopenTtypename=Indirectofint|Directofsteptypeaddress=Indirectofint63|DirectofH.tletaddress_t:addressIrmin.Type.t=letopenIrmin.Typeinvariant"Compress.address"(funid->function|Indirectx->ix|Directx->dx)|~case1"Indirect"int63_t(funx->Indirectx)|~case1"Direct"H.t(funx->Directx)|>sealvtypeptr={index:int;hash:address}letptr_t:ptrIrmin.Type.t=letopenIrmin.Typeinrecord"Compress.ptr"(funindexhash->{index;hash})|+field"index"int(funt->t.index)|+field"hash"address_t(funt->t.hash)|>sealrtypetree={depth:int;length:int;entries:ptrlist}lettree_t:treeIrmin.Type.t=letopenIrmin.Typeinrecord"Compress.tree"(fundepthlengthentries->{depth;length;entries})|+field"depth"int(funt->t.depth)|+field"length"int(funt->t.length)|+field"entries"(listptr_t)(funt->t.entries)|>sealrtypevalue=|Contentsofname*address*metadata|Nodeofname*addressletis_default=Irmin.Type.(unstage(equalT.metadata_t))T.defaultletvalue_t:valueIrmin.Type.t=letopenIrmin.Typeinvariant"Compress.value"(funcontents_iicontents_x_iinode_iicontents_idcontents_x_idnode_idcontents_dicontents_x_dinode_dicontents_ddcontents_x_ddnode_dd->function|Contents(Indirectn,Indirecth,m)->ifis_defaultmthencontents_ii(n,h)elsecontents_x_ii(n,h,m)|Node(Indirectn,Indirecth)->node_ii(n,h)|Contents(Indirectn,Directh,m)->ifis_defaultmthencontents_id(n,h)elsecontents_x_id(n,h,m)|Node(Indirectn,Directh)->node_id(n,h)|Contents(Directn,Indirecth,m)->ifis_defaultmthencontents_di(n,h)elsecontents_x_di(n,h,m)|Node(Directn,Indirecth)->node_di(n,h)|Contents(Directn,Directh,m)->ifis_defaultmthencontents_dd(n,h)elsecontents_x_dd(n,h,m)|Node(Directn,Directh)->node_dd(n,h))|~case1"contents-ii"(pairintInt63.t)(fun(n,i)->Contents(Indirectn,Indirecti,T.default))|~case1"contents-x-ii"(tripleintint63_tmetadata_t)(fun(n,i,m)->Contents(Indirectn,Indirecti,m))|~case1"node-ii"(pairintInt63.t)(fun(n,i)->Node(Indirectn,Indirecti))|~case1"contents-id"(pairintH.t)(fun(n,h)->Contents(Indirectn,Directh,T.default))|~case1"contents-x-id"(tripleintH.tmetadata_t)(fun(n,h,m)->Contents(Indirectn,Directh,m))|~case1"node-id"(pairintH.t)(fun(n,h)->Node(Indirectn,Directh))|~case1"contents-di"(pairstep_tInt63.t)(fun(n,i)->Contents(Directn,Indirecti,T.default))|~case1"contents-x-di"(triplestep_tint63_tmetadata_t)(fun(n,i,m)->Contents(Directn,Indirecti,m))|~case1"node-di"(pairstep_tInt63.t)(fun(n,i)->Node(Directn,Indirecti))|~case1"contents-dd"(pairstep_tH.t)(fun(n,i)->Contents(Directn,Directi,T.default))|~case1"contents-x-dd"(triplestep_tH.tmetadata_t)(fun(n,i,m)->Contents(Directn,Directi,m))|~case1"node-dd"(pairstep_tH.t)(fun(n,i)->Node(Directn,Directi))|>sealvtypev=Valuesofvaluelist|Treeoftreeletv_t:vIrmin.Type.t=letopenIrmin.Typeinvariant"Compress.v"(funvaluestree->function|Valuesx->valuesx|Treex->treex)|~case1"Values"(listvalue_t)(funx->Valuesx)|~case1"Tree"tree_t(funx->Treex)|>sealvtypet={hash:H.t;stable:bool;v:v}letv~stable~hashv={hash;stable;v}letkind_node=Pack_value.Kind.Nodeletkind_inode=Pack_value.Kind.Inodeletmagic_node=Pack_value.Kind.to_magickind_nodeletmagic_inode=Pack_value.Kind.to_magickind_inodeletstable_t:boolIrmin.Type.t=Irmin.Type.(mapchar)(funn->n=magic_node)(functiontrue->magic_node|false->magic_inode)lett=letopenIrmin.Typeinrecord"Compress.t"(funhashstablev->{hash;stable;v})|+field"hash"H.t(funt->t.hash)|+field"stable"stable_t(funt->t.stable)|+field"v"v_t(funt->t.v)|>sealrend(** [Val_impl] defines the recursive structure of inodes.
{3 Inode Layout}
{4 Layout Types}
The layout ['a layout] associated to an inode ['a t] defines certain
properties of the inode:
- When [Total], the inode is self contained and immutable.
- When [Partial], chunks of the inode might be missing but they can be
fetched from the backend when needed using the available [find] function
stored in the layout. Mutable pointers act as cache.
- When [Truncated], chunks of the inode might be missing. Those chunks are
unreachable because the pointer to the backend is missing. The inode is
immutable.
{4 Layout Instantiation}
The layout of an inode is determined from the module [Val], it depends on
the way the inode was constructed:
- When [Total], it originates from [Val.v] or [Val.empty].
- When [Partial], it originates from [Val.of_bin], which is only used by
[Inode.find].
- When [Truncated], it originates from an [Irmin.Type] deserialisation
made possible by [Val.t].
Almost all other functions in [Val_impl] are polymorphic regarding the
layout of the manipulated inode.
{4 Details on the [Truncated] Layout}
The [Truncated] layout is identical to [Partial] except for the missing
[find] function.
On the one hand, when creating the root of a [Truncated] inode, the
pointers to children inodes - if any - are set to the [Broken] tag,
meaning that we know the hash to such children but we will have to way to
load them in the future. On the other hand, when adding children to a
[Truncated] inode, there is no such problem, the pointer is then set to
the [Intact] tag.
As of Irmin 2.4 (February 2021), inode deserialisation using Repr happens
in [irmin/slice.ml] and [irmin/sync_ext.ml], and maybe some other places.
At some point we might want to forbid such deserialisations and instead
use something in the flavour of [Val.of_bin] to create [Partial] inodes.
{3 Topmost Inode Ancestor}
[Val_impl.t] is a recursive type, it is labelled with a [depth] integer
that indicates the recursion depth. An inode with [depth = 0] corresponds
to the root of a directory, its hash is the hash of the directory.
A [Val.t] points to the topmost [Val_impl.t] of an inode tree. In most
scenarios, that topmost inode has [depth = 0], but it is also legal for
the topmost inode to be an intermediate inode, i.e. with [depth > 0].
The only way for an inode tree to have an intermediate inode as root is to
fetch it from the backend by calling [Make_ext.find], using the hash of
that inode.
Write-only operations are not permitted when the root is an intermediate
inode. *)moduleVal_impl=structopenTletequal_value=Irmin.Type.(unstage(equalvalue_t))type_layout=|Total:total_ptrlayout|Partial:(hash->partial_ptrtoption)->partial_ptrlayout|Truncated:truncated_ptrlayoutandpartial_ptr={target_hash:hashLazy.t;mutabletarget:partial_ptrtoption;}(** [mutable target : partial_ptr t option] could be turned to
[target : partial_ptr t Lazy.t] to make the code even clearer (we never
set it back to [None]), but we might soon implement a garbage collection
method for inodes that will necessitate that mutable option (among other
things). *)andtotal_ptr=Total_ptroftotal_ptrt[@@unboxed]andtruncated_ptr=Brokenofhash|Intactoftruncated_ptrtand'ptrtree={depth:int;length:int;entries:'ptroptionarray}and'ptrv=ValuesofvalueStepMap.t|Treeof'ptrtreeand'ptrt={hash:hashLazy.t;stable:bool;v:'ptrv}modulePtr=structlethash:typeptr.ptrlayout->ptr->_=function|Total->fun(Total_ptrptr)->Lazy.forceptr.hash|Partial_->fun{target_hash;_}->Lazy.forcetarget_hash|Truncated->(functionBrokenh->h|Intactptr->Lazy.forceptr.hash)lettarget:typeptr.ptrlayout->ptr->ptrt=funlayout->matchlayoutwith|Total->fun(Total_ptrt)->t|Partialfind->(function|{target=Someentry;_}->entry|t->(leth=hashlayouttinmatchfindhwith|None->Fmt.failwith"%a: unknown key"pp_hashh|Somex->t.target<-Somex;x))|Truncated->(function|Intactentry->entry|_->failwith"Impossible to load the subtree on an inode deserialized \
using Repr")letof_target:typeptr.ptrlayout->ptrt->ptr=function|Total->funtarget->Total_ptrtarget|Partial_->funtarget->{target=Sometarget;target_hash=target.hash}|Truncated->funtarget->Intacttargetletof_hash:typeptr.ptrlayout->hash->ptr=function|Total->assertfalse|Partial_->funhash->{target=None;target_hash=lazyhash}|Truncated->funhash->Brokenhashletiter_if_loaded:typeptr.broken:(hash->unit)->ptrlayout->(ptrt->unit)->ptr->unit=fun~broken->function|Total->funf(Total_ptrentry)->fentry|Partial_->(funf->function{target=Someentry;_}->fentry|_->())|Truncated->(funf->functionBrokenh->brokenh|Intactentry->fentry)endletpredlayoutt=matcht.vwith|Treei->lethash_of_ptr=Ptr.hashlayoutinArray.fold_left(funacc->function|None->acc|Someptr->`Inode(hash_of_ptrptr)::acc)[]i.entries|Valuesl->StepMap.fold(fun_vacc->letv=matchvwith|`Node_ask->k|`Contents(k,_)->`Contentskinv::acc)l[]letlength_of_v=function|Valuesvs->StepMap.cardinalvs|Treevs->vs.lengthletlengtht=length_of_vt.vletstablet=t.stabletypeacc={cursor:int;values:(step*value)listlist;remaining:int;}letempty_accn={cursor=0;values=[];remaining=n}letreclist_entrylayout~offset~lengthacc=function|None->acc|Somei->list_valueslayout~offset~lengthacc(Ptr.targetlayouti).vandlist_treelayout~offset~lengthacct=ifacc.remaining<=0||offset+length<=acc.cursorthenaccelseifacc.cursor+t.length<offsetthen{accwithcursor=t.length+acc.cursor}elseArray.fold_left(list_entrylayout~offset~length)acct.entriesandlist_valueslayout~offset~lengthaccv=ifacc.remaining<=0||offset+length<=acc.cursorthenaccelsematchvwith|Valuesvs->letlen=StepMap.cardinalvsinifacc.cursor+len<offsetthen{accwithcursor=len+acc.cursor}elseletto_drop=ifacc.cursor>offsetthen0elseoffset-acc.cursorinletvs=StepMap.to_seqvs|>Seq.dropto_drop|>Seq.takeacc.remaininginletn=List.lengthvsin{values=vs::acc.values;cursor=acc.cursor+len;remaining=acc.remaining-n;}|Treet->list_treelayout~offset~lengthacctletlist_vlayout?(offset=0)?lengthv=letlength=matchlengthwith|Somen->n|None->(matchvwith|Valuesvs->StepMap.cardinalvs-offset|Treei->i.length-offset)inletentries=list_valueslayout~offset~length(empty_acclength)vinList.concat(List.reventries.values)letlistlayout?offset?lengtht=list_vlayout?offset?lengtht.vletto_bin_vlayout=function|Valuesvs->letvs=StepMap.bindingsvsinBin.Valuesvs|Treet->lethash_of_ptr=Ptr.hashlayoutinlet_,entries=Array.fold_left(fun(i,acc)->function|None->(i+1,acc)|Someptr->lethash=hash_of_ptrptrin(i+1,{Bin.index=i;hash}::acc))(0,[])t.entriesinletentries=List.reventriesinBin.Tree{depth=t.depth;length=t.length;entries}letto_binlayoutt=letv=to_bin_vlayoutt.vinBin.v~stable:t.stable~hash:t.hashvmoduleConcrete=structtypekind=Contents|Contents_xofmetadata|Node[@@derivingirmin]typeentry={name:step;kind:kind;hash:hash}[@@derivingirmin]type'apointer={index:int;pointer:hash;tree:'a}[@@derivingirmin]type'atree={depth:int;length:int;pointers:'apointerlist}[@@derivingirmin]typet=Treeofttree|Valueofentrylist[@@derivingirmin]letmetadata_equal=Irmin.Type.(unstage(equalmetadata_t))letto_entry(name,v)=matchvwith|`Contents(hash,m)->ifmetadata_equalmNode.defaultthen{name;kind=Contents;hash}else{name;kind=Contents_xm;hash}|`Nodehash->{name;kind=Node;hash}letof_entrye=(e.name,matche.kindwith|Contents->`Contents(e.hash,Node.default)|Contents_xm->`Contents(e.hash,m)|Node->`Nodee.hash)typeerror=[`Invalid_hashofhash*hash*t|`Invalid_depthofint*int*t|`Invalid_lengthofint*int*t|`Duplicated_entriesoft|`Duplicated_pointersoft|`Unsorted_entriesoft|`Unsorted_pointersoft|`Empty][@@derivingirmin]letreclength=function|Valuel->List.lengthl|Treet->List.fold_left(funaccp->acc+lengthp.tree)0t.pointersletpp=Irmin.Type.pp_jsontletpp_errorppf=function|`Invalid_hash(got,expected,t)->Fmt.pfppf"invalid hash for %a@,got: %a@,expecting: %a"pptpp_hashgotpp_hashexpected|`Invalid_depth(got,expected,t)->Fmt.pfppf"invalid depth for %a@,got: %d@,expecting: %d"pptgotexpected|`Invalid_length(got,expected,t)->Fmt.pfppf"invalid length for %a@,got: %d@,expecting: %d"pptgotexpected|`Duplicated_entriest->Fmt.pfppf"duplicated entries: %a"ppt|`Duplicated_pointerst->Fmt.pfppf"duplicated pointers: %a"ppt|`Unsorted_entriest->Fmt.pfppf"entries should be sorted: %a"ppt|`Unsorted_pointerst->Fmt.pfppf"pointers should be sorted: %a"ppt|`Empty->Fmt.pfppf"concrete subtrees cannot be empty"endletto_concrete(la:'ptrlayout)(t:'ptrt)=letrecauxt=matcht.vwith|Treetr->(Lazy.forcet.hash,Concrete.Tree{depth=tr.depth;length=tr.length;pointers=Array.fold_left(fun(i,acc)e->matchewith|None->(i+1,acc)|Somet->letpointer,tree=aux(Ptr.targetlat)in(i+1,{Concrete.index=i;tree;pointer}::acc))(0,[])tr.entries|>snd|>List.rev;})|Valuesl->(Lazy.forcet.hash,Concrete.Value(List.mapConcrete.to_entry(StepMap.bindingsl)))insnd(auxt)exceptionInvalid_hashofhash*hash*Concrete.texceptionInvalid_depthofint*int*Concrete.texceptionInvalid_lengthofint*int*Concrete.texceptionEmptyexceptionDuplicated_entriesofConcrete.texceptionDuplicated_pointersofConcrete.texceptionUnsorted_entriesofConcrete.texceptionUnsorted_pointersofConcrete.tlethash_equal=Irmin.Type.(unstage(equalhash_t))letof_concrete_exnt=letsort_entries=List.sort_uniq(funxy->comparex.Concrete.namey.Concrete.name)inletsort_pointers=List.sort_uniq(funxy->comparex.Concrete.indexy.Concrete.index)inletcheck_entriestes=ifes=[]thenraiseEmpty;lets=sort_entriesesinifList.lengths<>List.lengthesthenraise(Duplicated_entriest);ifs<>esthenraise(Unsorted_entriest)inletcheck_pointerstps=ifps=[]thenraiseEmpty;lets=sort_pointerspsinifList.lengths<>List.lengthpsthenraise(Duplicated_pointerst);ifs<>psthenraise(Unsorted_pointerst)inlethashv=Bin.V.hash(to_bin_vTotalv)inletrecauxdeptht=matchtwith|Concrete.Valuel->check_entriestl;Values(StepMap.of_list(List.mapConcrete.of_entryl))|Concrete.Treetr->letentries=Array.makeConf.entriesNoneincheck_pointersttr.pointers;List.iter(fun{Concrete.index;pointer;tree}->letv=aux(depth+1)treeinlethash=hashvinifnot(hash_equalhashpointer)thenraise(Invalid_hash(hash,pointer,t));lett={hash=lazypointer;stable=false;v}inentries.(index)<-Some(Ptr.of_targetTotalt))tr.pointers;letlength=Concrete.lengthtinifdepth<>tr.depththenraise(Invalid_depth(depth,tr.depth,t));iflength<>tr.lengththenraise(Invalid_length(length,tr.length,t));Tree{depth=tr.depth;length=tr.length;entries}inletv=aux0tinletlength=length_of_vvinletstable,hash=iflength>Conf.stable_hashthen(false,hashv)elseletnode=Node.v(list_vTotalv)in(true,Node.hashnode)in{hash=lazyhash;stable;v}letof_concretet=tryOk(of_concrete_exnt)with|Invalid_hash(x,y,z)->Error(`Invalid_hash(x,y,z))|Invalid_depth(x,y,z)->Error(`Invalid_depth(x,y,z))|Invalid_length(x,y,z)->Error(`Invalid_length(x,y,z))|Empty->Error`Empty|Duplicated_entriest->Error(`Duplicated_entriest)|Duplicated_pointerst->Error(`Duplicated_pointerst)|Unsorted_entriest->Error(`Unsorted_entriest)|Unsorted_pointerst->Error(`Unsorted_pointerst)lethasht=Lazy.forcet.hashletis_roott=matcht.vwith|Tree{depth;_}->depth=0|Values_->(* When [t] is of tag [Values], then [t] is root iff [t] is stable. It
is implied by the following.
When [t] is stable, then [t] is a root, because:
- Only 2 functions produce stable inodes: [stabilize] and [empty].
- Only the roots are output of [stabilize].
- An empty map can only be located at the root.
When [t] is a root of tag [Value], then [t] is stable, because:
- All the roots are output of [stabilize].
- When an unstable inode enters [stabilize], it becomes stable if
it has at most [Conf.stable_hash] leaves.
- A [Value] has at most [Conf.stable_hash] leaves because
[Conf.entries <= Conf.stable_hash] is enforced.
*)t.stableletcheck_write_op_supportedt=ifnot@@is_roottthenfailwith"Cannot perform operation on non-root inode value."letstabilizelayoutt=ift.stablethentelseletn=lengthtinifn>Conf.stable_hashthentelselethash=lazy(letvs=listlayouttinNode.hash(Node.vvs))in{hash;stable=true;v=t.v}lethash_key=Irmin.Type.(unstage(short_hashstep_t))letindex~depthk=abs(hash_key~seed:depthk)modConf.entries(** This function shouldn't be called with the [Total] layout. In the
future, we could add a polymorphic variant to the GADT parameter to
enfoce that. *)letof_binlayoutt=letv=matcht.Bin.vwith|Bin.Valuesvs->letvs=StepMap.of_listvsinValuesvs|Treet->letentries=Array.makeConf.entriesNoneinletptr_of_hash=Ptr.of_hashlayoutinList.iter(fun{Bin.index;hash}->entries.(index)<-Some(ptr_of_hashhash))t.entries;Tree{depth=t.Bin.depth;length=t.length;entries}in{hash=t.Bin.hash;stable=t.Bin.stable;v}letempty:'a.'alayout->'at=fun_->lethash=lazy(Node.hashNode.empty)in{stable=true;hash;v=ValuesStepMap.empty}letvalueslayoutvs=letlength=StepMap.cardinalvsiniflength=0thenemptylayoutelseletv=Valuesvsinlethash=lazy(Bin.V.hash(to_bin_vlayoutv))in{hash;stable=false;v}lettreelayoutis=letv=Treeisinlethash=lazy(Bin.V.hash(to_bin_vlayoutv))in{hash;stable=false;v}letof_valueslayoutl=valueslayout(StepMap.of_listl)letis_emptyt=matcht.vwithValuesvs->StepMap.is_emptyvs|Tree_->falseletfind_valuelayout~depthts=lettarget_of_ptr=Ptr.targetlayoutinletrecaux~depth=function|Valuesvs->(trySome(StepMap.findsvs)withNot_found->None)|Treet->(leti=index~depthsinletx=t.entries.(i)inmatchxwith|None->None|Somei->aux~depth:(depth+1)(target_of_ptri).v)inaux~deptht.vletfindlayoutts=find_value~depth:0layouttsletrecaddlayout~depth~copy~replacetsvk=matcht.vwith|Valuesvs->letlength=ifreplacethenStepMap.cardinalvselseStepMap.cardinalvs+1inlett=iflength<=Conf.entriesthenvalueslayout(StepMap.addsvvs)elseletvs=StepMap.bindings(StepMap.addsvvs)inletempty=treelayout{length=0;depth;entries=Array.makeConf.entriesNone}inletauxt(s,v)=(add[@tailcall])layout~depth~copy:false~replacetsv(funx->x)inList.fold_leftauxemptyvsinkt|Treet->(letlength=ifreplacethent.lengthelset.length+1inletentries=ifcopythenArray.copyt.entrieselset.entriesinleti=index~depthsinmatchentries.(i)with|None->lettarget=valueslayout(StepMap.singletonsv)inentries.(i)<-Some(Ptr.of_targetlayouttarget);lett=treelayout{depth;length;entries}inkt|Somen->lett=Ptr.targetlayoutnin(add[@tailcall])layout~depth:(depth+1)~copy~replacetsv@@funtarget->entries.(i)<-Some(Ptr.of_targetlayouttarget);lett=treelayout{depth;length;entries}inkt)letaddlayout~copytsv=(* XXX: [find_value ~depth:42] should break the unit tests. It doesn't. *)matchfind_value~depth:0layouttswith|Somev'whenequal_valuevv'->stabilizelayoutt|Some_->add~depth:0layout~copy~replace:truetsvFun.id|>stabilizelayout|None->add~depth:0layout~copy~replace:falsetsvFun.id|>stabilizelayoutletrecremovelayout~depthtsk=matcht.vwith|Valuesvs->lett=valueslayout(StepMap.removesvs)inkt|Treet->(letlen=t.length-1iniflen<=Conf.entriesthenletvs=list_treelayout~offset:0~length:t.length(empty_acct.length)tinletvs=List.concat(List.revvs.values)inletvs=StepMap.of_listvsinletvs=StepMap.removesvsinlett=valueslayoutvsinktelseletentries=Array.copyt.entriesinleti=index~depthsinmatchentries.(i)with|None->assertfalse|Somet->lett=Ptr.targetlayouttiniflengtht=1then(entries.(i)<-None;lett=treelayout{depth;length=len;entries}inkt)elseremove~depth:(depth+1)layoutts@@funtarget->entries.(i)<-Some(Ptr.of_targetlayouttarget);lett=treelayout{depth;length=len;entries}inkt)letremovelayoutts=(* XXX: [find_value ~depth:42] should break the unit tests. It doesn't. *)matchfind_valuelayout~depth:0tswith|None->stabilizelayoutt|Some_->removelayout~depth:0tsFun.id|>stabilizelayoutletvl=letlen=List.lengthlinlett=iflen<=Conf.entriesthenof_valuesTotallelseletauxacc(s,v)=addTotal~copy:falseaccsvinList.fold_leftaux(emptyTotal)linstabilizeTotaltletsavelayout~add~memt=letiter_entries=letbrokenh=(* This function is called when we encounter a Broken pointer with
Truncated layouts. *)ifnot@@memhthenFmt.failwith"You are trying to save to the backend an inode deserialized \
using [Irmin.Type] that used to contain pointer(s) to inodes \
which are unknown to the backend. Hash: %a"pp_hashhelse(* The backend already knows this target inode, there is no need to
traverse further down. This happens during the unit tests. *)()inletiter_ptr=Ptr.iter_if_loaded~brokenlayoutinfunfarr->Array.iter(Option.iter(iter_ptrf))arrinletrecaux~deptht=Log.debug(funl->l"save depth:%d"depth);matcht.vwith|Values_->add(Lazy.forcet.hash)(to_binlayoutt)|Treen->iter_entries(funt->lethash=Lazy.forcet.hashinifmemhashthen()elseaux~depth:(depth+1)t)n.entries;add(Lazy.forcet.hash)(to_binlayoutt)inaux~depth:0tletcheck_stablelayoutt=lettarget_of_ptr=Ptr.targetlayoutinletrecchecktany_stable_ancestor=letstable=t.stable||any_stable_ancestorinmatcht.vwith|Values_->true|Treetree->Array.for_all(function|None->true|Somet->lett=target_of_ptrtin(ifstablethennott.stableelsetrue)&&checktstable)tree.entriesinchecktt.stableletcontains_empty_maplayoutt=lettarget_of_ptr=Ptr.targetlayoutinletreccheck_lowert=matcht.vwith|ValueslwhenStepMap.is_emptyl->true|Values_->false|Treeinodes->Array.exists(function|None->false|Somet->target_of_ptrt|>check_lower)inodes.entriesincheck_lowertletis_treet=matcht.vwithTree_->true|Values_->falseendmoduleRaw=structtypehash=H.ttypet=Bin.tlett=Bin.tletkind(t:t)=ift.stablethenCompress.kind_nodeelseCompress.kind_inodelethasht=Bin.hashtletstep_to_bin=Irmin.Type.(unstage(to_bin_stringT.step_t))letstep_of_bin=Irmin.Type.(unstage(of_bin_stringT.step_t))letencode_compress=Irmin.Type.(unstage(encode_binCompress.t))letdecode_compress=Irmin.Type.(unstage(decode_binCompress.t))letdecode_compress_length=matchIrmin.Type.Size.of_encodingCompress.twith|Unknown|Static_->assertfalse|Dynamicf->fletencode_bin~dict~offset(t:t)k=letsteps:Compress.name=letstr=step_to_binsinifString.lengthstr<=3thenDirectselsematchdictstrwithSomei->Indirecti|None->Directsinlethashh:Compress.address=matchoffsethwith|None->Compress.Directh|Someoff->Compress.Indirectoffinletptr:Bin.ptr->Compress.ptr=funn->lethash=hashn.hashin{index=n.index;hash}inletvalue:T.step*T.value->Compress.value=function|s,`Contents(c,m)->lets=stepsinletv=hashcinCompress.Contents(s,v,m)|s,`Noden->lets=stepsinletv=hashninCompress.Node(s,v)in(* List.map is fine here as the number of entries is small *)letv:Bin.v->Compress.v=function|Valuesvs->Values(List.mapvaluevs)|Tree{depth;length;entries}->letentries=List.mapptrentriesinTree{Compress.depth;length;entries}inlett=Compress.v~stable:t.stable~hash:k(vt.v)inencode_compresstexceptionExitof[`Msgofstring]letdecode_bin~dict~hashtoff:int*t=letoff,i=decode_compresstoffinletstep:Compress.name->T.step=function|Directn->n|Indirects->(matchdictswith|None->raise_notrace(Exit(`Msg"dict"))|Somes->(matchstep_of_binswith|Errore->raise_notrace(Exite)|Okv->v))inlethash:Compress.address->H.t=function|Indirectoff->hashoff|Directn->ninletptr:Compress.ptr->Bin.ptr=funn->lethash=hashn.hashin{index=n.index;hash}inletvalue:Compress.value->T.step*T.value=function|Contents(n,h,metadata)->letname=stepninlethash=hashhin(name,`Contents(hash,metadata))|Node(n,h)->letname=stepninlethash=hashhin(name,`Nodehash)inlett:Compress.v->Bin.v=function|Valuesvs->Values(List.rev_mapvalue(List.revvs))|Tree{depth;length;entries}->letentries=List.mapptrentriesinTree{depth;length;entries}inlett=Bin.v~stable:i.stable~hash:(lazyi.hash)(ti.v)in(off,t)letdecode_bin_length=decode_compress_lengthendtypehash=T.hashletpp_hash=T.pp_hashmoduleVal=structincludeTmoduleI=Val_impltypet=|TotalofI.total_ptrI.t|PartialofI.partial_ptrI.layout*I.partial_ptrI.t|TruncatedofI.truncated_ptrI.ttype'bapply_fn={f:'a.'aI.layout->'aI.t->'b}[@@unboxed]letapply:t->'bapply_fn->'b=funtf->matchtwith|Totalv->f.fI.Totalv|Partial(layout,v)->f.flayoutv|Truncatedv->f.fI.Truncatedvtypemap_fn={f:'a.'aI.layout->'aI.t->'aI.t}[@@unboxed]letmap:t->map_fn->t=funtf->matchtwith|Totalv->letv'=f.fI.Totalvinifv==v'thentelseTotalv'|Partial(layout,v)->letv'=f.flayoutvinifv==v'thentelsePartial(layout,v')|Truncatedv->letv'=f.fI.Truncatedvinifv==v'thentelseTruncatedv'letpredt=applyt{f=(funlayoutv->I.predlayoutv)}letvl=Total(I.vl)letlist?offset?lengtht=applyt{f=(funlayoutv->I.listlayout?offset?lengthv)}letempty=v[]letis_emptyt=applyt{f=(fun_v->I.is_emptyv)}letfindts=applyt{f=(funlayoutv->I.findlayoutvs)}letaddtsvalue=letflayoutv=I.check_write_op_supportedv;I.add~copy:truelayoutvsvalueinmapt{f}letremovets=letflayoutv=I.check_write_op_supportedv;I.removelayoutvsinmapt{f}letpre_hash_binv=Irmin.Type.(unstage(pre_hashBin.v_t))letpre_hash_node=Irmin.Type.(unstage(pre_hashNode.t))lett:tIrmin.Type.t=letpre_hash=Irmin.Type.stage@@funx->letstable=applyx{f=(fun_v->I.stablev)}inifnotstablethenletbin=applyx{f=(funlayoutv->I.to_binlayoutv)}inpre_hash_binvbin.velseletvs=listxinpre_hash_node(Node.vvs)inIrmin.Type.map~pre_hashBin.t(funbin->Truncated(I.of_binI.Truncatedbin))(funx->applyx{f=(funlayoutv->I.to_binlayoutv)})lethasht=applyt{f=(fun_v->I.hashv)}letsave~add~memt=letflayoutv=I.check_write_op_supportedv;I.savelayout~add~memvinapplyt{f}letof_rawfind'v=letrecfindh=matchfind'hwithNone->None|Somev->Some(I.of_binlayoutv)andlayout=I.PartialfindinPartial(layout,I.of_binlayoutv)letto_rawt=applyt{f=(funlayoutv->I.to_binlayoutv)}letstablet=applyt{f=(fun_v->I.stablev)}letlengtht=applyt{f=(fun_v->I.lengthv)}letindex=I.indexletintegrity_checkt=letflayoutv=letcheck_stable()=letcheck()=I.check_stablelayoutvinletn=lengthtinifn>Conf.stable_hashthen(not(stablet))&&check()elsestablet&&check()inletcontains_empty_map_non_root()=letcheck()=I.contains_empty_maplayoutvin(* we are only looking for empty maps that are not at the root *)ifI.is_treevthencheck()elsefalseincheck_stable()&¬(contains_empty_map_non_root())inapplyt{f}moduleConcrete=I.Concreteletto_concretet=applyt{f=(funlav->I.to_concretelav)}letof_concretet=matchI.of_concretetwithOkt->Ok(Totalt)|Error_ase->eendendmoduleMake(H:Irmin.Hash.S)(Node:Irmin.Private.Node.Swithtypehash=H.t)(Inter:Internalwithtypehash=H.tandtypeVal.metadata=Node.metadataandtypeVal.step=Node.step)(Pack:Content_addressable.Swithtypekey=H.tandtypevalue=Inter.Raw.t)=structmoduleKey=HmoduleVal=Inter.Valtype'at='aPack.ttypekey=Key.ttypevalue=Inter.Val.tletmemtk=Pack.memtkletfindtk=Pack.findtk>|=function|None->None|Somev->letfind=Pack.unsafe_find~check_integrity:truetinletv=Val.of_rawfindvinSomevletsavetv=letaddkv=Pack.unsafe_append~ensure_unique:true~overcommit:falsetkvinVal.save~add~mem:(Pack.unsafe_memt)vlethashv=Val.hashvletaddtv=savetv;Lwt.return(hashv)letequal_hash=Irmin.Type.(unstage(equalH.t))letcheck_hashexpectedgot=ifequal_hashexpectedgotthen()elseFmt.invalid_arg"corrupted value: got %a, expecting %a"Inter.pp_hashexpectedInter.pp_hashgotletunsafe_addtkv=check_hashk(hashv);savetv;Lwt.return_unitletbatch=Pack.batchletclose=Pack.closeletclear=Pack.clearletdecode_bin_length=Inter.Raw.decode_bin_lengthletintegrity_check_inodestk=findtk>|=function|None->(* we are traversing the node graph, should find all values *)assertfalse|Somev->ifInter.Val.integrity_checkvthenOk()elseletmsg=Fmt.str"Problematic inode %a"(Irmin.Type.ppInter.Val.t)vinErrormsgendmoduleMake_persistent(H:Irmin.Hash.S)(Node:Irmin.Private.Node.Swithtypehash=H.t)(Inter:Internalwithtypehash=H.tandtypeVal.metadata=Node.metadataandtypeVal.step=Node.step)(CA:Pack_store.Makerwithtypekey=H.tandtypeindex=Pack_index.Make(H).t)=structmodulePersistent_pack=CA.Make(Inter.Raw)modulePack=Persistent_packincludeMake(H)(Node)(Inter)(Pack)typeindex=Pack.indexletv=Pack.vletsync=Pack.syncletintegrity_check=Pack.integrity_checkletclear_caches=Pack.clear_cachesend