1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489(*
* 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.hashend(* Keep at most 50 bits of information. *)letmax_depth=int_of_float(log(2.**50.)/.log(floatConf.entries))moduleT=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)emptylendexceptionDangling_hashof{context:string;hash:T.hash}exceptionMax_depthofintlet()=Printexc.register_printer(function|Dangling_hash{context;hash}->Some(Fmt.str"Irmin_pack.Inode.%s: encountered dangling hash %a"contextT.pp_hashhash)|_->None)(* 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_hashx=pre_hash_vx.vinrecord"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=|Dirtyofpartial_ptrt|Lazyofhash|Lazy_loadedofpartial_ptrt(** A partial pointer differentiates the [Dirty] and [Lazy_loaded]
cases in order to remember that only the latter should be
collected when [clear] is called.
The child in [Lazy_loaded] can only emanate from the disk. It can
be savely collected on [clear].
The child in [Dirty] can only emanate from a user modification,
e.g. through the [add] or [to_concrete] functions. It shouldn't be
collected on [clear] because it will be needed for [save]. *)andpartial_ptr={mutabletarget:partial_ptr_target}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}->matchtargetwith|Lazyhash->hash|Dirty{hash;_}|Lazy_loaded{hash;_}->Lazy.forcehash)|Truncated->(functionBrokenh->h|Intactptr->Lazy.forceptr.hash)lettarget:typeptr.cache:bool->force:bool->string->ptrlayout->ptr->ptrt=fun~cache~forcecontextlayout->matchlayoutwith|Total->fun(Total_ptrt)->t|Partialfind->(function|{target=Dirtyentry}|{target=Lazy_loadedentry}->(* [target] is already cached. [cache] is only concerned with
new cache entries, not the older ones for which the irmin
users can discard using [clear]. *)entry|{target=Lazy_}ast->(leth=hashlayouttinifnotforcethenraise(Dangling_hash{context;hash=h})elsematchfindhwith|None->Fmt.failwith"%a: unknown key"pp_hashh|Somex->ifcachethent.target<-Lazy_loadedx;x))|Truncated->(function|Intactentry->entry|Brokenh->raise(Dangling_hash{context;hash=h}))letof_target:typeptr.ptrlayout->ptrt->ptr=function|Total->funtarget->Total_ptrtarget|Partial_->funtarget->{target=Dirtytarget}|Truncated->funtarget->Intacttargetletof_hash:typeptr.ptrlayout->hash->ptr=function|Total->assertfalse|Partial_->funhash->{target=Lazyhash}|Truncated->funhash->Brokenhashletsave:typeptr.broken:(hash->unit)->save_dirty:(ptrt->unit)->clear:bool->ptrlayout->ptr->unit=fun~broken~save_dirty~clear->function|Total->fun(Total_ptrentry)->(save_dirty[@tailcall])entry|Partial_->(function|{target=Dirtyentry}asbox->ifclearthenbox.target<-Lazy(Lazy.forceentry.hash)else(* Promote from dirty to lazy as it will be saved during
[save_dirty]. *)box.target<-Lazy_loadedentry;(save_dirty[@tailcall])entry|{target=Lazy_loadedentry}asbox->ifclearthenbox.target<-Lazy(Lazy.forceentry.hash);(save_dirty[@tailcall])entry|{target=Lazy_}->())|Truncated->(function|Brokenh->(broken[@tailcall])h|Intactentry->(save_dirty[@tailcall])entry)letclear:typeptr.iter_dirty:(ptrlayout->ptrt->unit)->ptrlayout->ptr->unit=fun~iter_dirtylayoutptr->matchlayoutwith|Partial_->(matchptrwith|{target=Lazy_}->()|{target=Dirtyptr}->iter_dirtylayoutptr|{target=Lazy_loadedptr}asbox->lethash=Lazy.forceptr.hashin(* Since a [Lazy_loaded] used to be a [Lazy], the hash is always
available. *)box.target<-Lazyhash)|Total|Truncated->()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.vletrecclearlayoutt=matcht.vwith|Treei->Array.iter(Option.iter(Ptr.clear~iter_dirty:clearlayout))i.entries|Values_->()letstablet=t.stabletypecont=off:int->len:int->(step*value)Seq.nodeletrecseq_treelayoutbucket_seq~cache:cont->cont=funk~off~len->assert(off>=0);assert(len>0);matchbucket_seq()with|Seq.Nil->k~off~len|Seq.Cons(None,rest)->seq_treelayoutrest~cachek~off~len|Seq.Cons(Somei,rest)->lettrg=Ptr.target~cache~force:true"seq_tree"layoutiinlettrg_len=lengthtrginifoff-trg_len>=0then(* Skip a branch of the inode tree in case the user asked for a
specific starting offset.
Without this branch the algorithm would keep the same semantic
because [seq_value] would handles the pagination value by value
instead. *)letoff=off-trg_leninseq_treelayoutrest~cachek~off~lenelseseq_vlayouttrg.v~cache(seq_treelayoutrest~cachek)~off~lenandseq_valueslayoutvalue_seq:cont->cont=funk~off~len->assert(off>=0);assert(len>0);matchvalue_seq()with|Seq.Nil->k~off~len|Cons(x,rest)->ifoff=0thenletlen=len-1iniflen=0then(* Yield the current value and skip the rest of the inode tree in
case the user asked for a specific length. *)Seq.Cons(x,Seq.empty)elseSeq.Cons(x,fun()->seq_valueslayoutrestk~off~len)else(* Skip one value in case the user asked for a specific starting
offset. *)letoff=off-1inseq_valueslayoutrestk~off~lenandseq_vlayoutv~cache:cont->cont=funk~off~len->assert(off>=0);assert(len>0);matchvwith|Treet->seq_treelayout(Array.to_seqt.entries)~cachek~off~len|Valuesvs->seq_valueslayout(StepMap.to_seqvs)k~off~lenletempty_continuation:cont=fun~off:_~len:_->Seq.Nilletseqlayout?offset:(off=0)?length:(len=Int.max_int)?(cache=true)t:(step*value)Seq.t=ifoff<0theninvalid_arg"Invalid pagination offset";iflen<0theninvalid_arg"Invalid pagination length";iflen=0thenSeq.emptyelsefun()->seq_vlayoutt.v~cacheempty_continuation~off~lenletseq_treelayout?(cache=true)i:(step*value)Seq.t=letoff=0inletlen=Int.max_intinfun()->seq_vlayout(Treei)~cacheempty_continuation~off~lenletseq_vlayout?(cache=true)v:(step*value)Seq.t=letoff=0inletlen=Int.max_intinfun()->seq_vlayoutv~cacheempty_continuation~off~lenletto_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.hashvtypelen=[`Eqofint|`Geofint][@@derivingirmin]moduleConcrete=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|Valuesofentrylist|Blinded[@@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_lengthoflen*int*t|`Duplicated_entriesoft|`Duplicated_pointersoft|`Unsorted_entriesoft|`Unsorted_pointersoft|`Blinded_root|`Empty][@@derivingirmin]letreclength=function|Valuesl->`Eq(List.lengthl)|Treet->List.fold_left(funaccp->match(acc,lengthp.tree)with|`Eqx,`Eqy->`Eq(x+y)|(`Eqx|`Gex),(`Eqy|`Gey)->`Ge(x+y))(`Eq0)t.pointers|Blinded->`Ge0letpp=Irmin.Type.pp_jsontletpp_lenppf=function|`Eqe->Fmt.pfppf"%d"e|`Gee->Fmt.pfppf"'at least %d'"eletpp_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: %a@,expecting: %d"pptpp_lengotexpected|`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|`Blinded_root->Fmt.pfppf"blinded root"|`Empty->Fmt.pfppf"concrete subtrees cannot be empty"endletto_concrete~force(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=tryaux(Ptr.target~cache:true~force"to_concrete"lat)withDangling_hash{hash;_}->(hash,Concrete.Blinded)in(i+1,{Concrete.index=i;tree;pointer}::acc))(0,[])tr.entries|>snd|>List.rev;})|Valuesl->(Lazy.forcet.hash,Concrete.Values(List.mapConcrete.to_entry(StepMap.bindingsl)))insnd(auxt)exceptionInvalid_hashofhash*hash*Concrete.texceptionInvalid_depthofint*int*Concrete.texceptionInvalid_lengthoflen*int*Concrete.texceptionEmptyexceptionDuplicated_entriesofConcrete.texceptionDuplicated_pointersofConcrete.texceptionUnsorted_entriesofConcrete.texceptionUnsorted_pointersofConcrete.texceptionBlinded_rootlethash_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_vTruncatedv)inletrecauxdeptht=matchtwith|Concrete.Blinded->None|Concrete.Valuesl->check_entriestl;Some(Values(StepMap.of_list(List.mapConcrete.of_entryl)))|Concrete.Treetr->letentries=Array.makeConf.entriesNoneincheck_pointersttr.pointers;List.iter(fun{Concrete.index;pointer;tree}->matchaux(depth+1)treewith|None->entries.(index)<-Some(Brokenpointer)|Somev->lethash=hashvinifnot(hash_equalhashpointer)thenraise(Invalid_hash(hash,pointer,t));lett={hash=lazypointer;stable=false;v}inentries.(index)<-Some(Ptr.of_targetTruncatedt))tr.pointers;ifdepth<>tr.depththenraise(Invalid_depth(depth,tr.depth,t));let()=matchConcrete.lengthtwith|`Eqlength->iflength<>tr.lengththenraise(Invalid_length(`Eqlength,tr.length,t))|`Gelength->iflength>tr.lengththenraise(Invalid_length(`Gelength,tr.length,t))inSome(Tree{depth=tr.depth;length=tr.length;entries})inletv=matchaux0twithNone->raiseBlinded_root|Somev->vinletlength=length_of_vvinletstable,hash=iflength>Conf.stable_hashthen(false,hashv)elseletnode=Node.of_seq(seq_vTruncatedv)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)|Blinded_root->Error`Blinded_rootlethasht=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=seqlayouttinNode.hash(Node.of_seqvs))in{hash;stable=true;v=t.v}lethash_key=Irmin.Type.(unstage(short_hashstep_t))letindex~depthk=ifdepth>=max_depththenraise(Max_depthdepth);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}letis_emptyt=matcht.vwithValuesvs->StepMap.is_emptyvs|Tree_->falseletfind_value~cachelayout~depthts=lettarget_of_ptr=Ptr.target~cache~force:true"find_value"layoutinletrecaux~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.vletfind?(cache=true)layoutts=find_value~cache~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=(* [cache] is unimportant here as we've already called
[find_value] for that path.*)Ptr.target~cache:true~force:true"add"layoutnin(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~cache:true~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=seq_treelayouttinletvs=StepMap.of_seqvsinletvs=StepMap.removesvsinlett=valueslayoutvsinktelseletentries=Array.copyt.entriesinleti=index~depthsinmatchentries.(i)with|None->assertfalse|Somet->lett=(* [cache] is unimportant here as we've already called
[find_value] for that path.*)Ptr.target~cache:true~force:true"remove"layouttiniflengtht=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_value~cache:truelayout~depth:0tswith|None->stabilizelayoutt|Some_->removelayout~depth:0tsFun.id|>stabilizelayoutletof_seql=lett=letrecaux_bigseqinode=matchseq()with|Seq.Nil->inode|Seq.Cons((s,v),rest)->aux_bigrest(addTotal~copy:falseinodesv)inletlen=(* [StepMap.cardinal] is (a bit) expensive to compute, let's track the
size of the map in a [ref] while doing [StepMap.update]. *)ref0inletrecaux_smallseqmap=matchseq()with|Seq.Nil->assert(!len<=Conf.entries);valuesTotalmap|Seq.Cons((s,v),rest)->letmap=StepMap.updates(function|None->incrlen;Somev|Some_->Somev)mapinif!len=Conf.entriesthenaux_bigrest(valuesTotalmap)elseaux_smallrestmapinaux_smalllStepMap.emptyinstabilizeTotaltletsavelayout~add~memt=letclear=(* When set to [true], collect the loaded inodes as soon as they're
saved.
This parameter is not exposed yet. Ideally it would be exposed and
be forwarded from [Tree.export ?clear] through [P.Node.add].
It is currently set to true in order to preserve behaviour *)falseinletiter_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. *)()infunsave_dirtyarr->letiter_ptr=Ptr.save~broken~save_dirty~clearlayoutinArray.iter(Option.iteriter_ptr)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.target~cache:true~force:true"check_stable"layoutinletrecchecktany_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.target~cache:true~force:true"contains_empty_map"layoutinletreccheck_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_->falsetypeproof=(hash,step,value)Irmin.Private.Node.Proof.t[@@derivingirmin]moduleProof=structletrecproof_of_concreteh:Concrete.t->proof=function|Blinded->Blinded(Lazy.forceh)|Valuesvs->Values(List.mapConcrete.of_entryvs)|Treetr->letproofs=List.fold_left(funacc(e:_Concrete.pointer)->letp=proof_of_concrete(lazye.pointer)e.treeinlete=(e.index,p)ine::acc)[](List.revtr.pointers)inInode{length=tr.length;proofs}lethash_vv=Bin.V.hash(to_bin_vTruncatedv)letrechash:int->proof->hash=fundepth->function|Valuesl->hash_v(Values(StepMap.of_listl))|Inode{length;proofs}->letes=List.fold_left(funacc(index,proof)->letpointer=hash(depth+1)proofin(index,Brokenpointer)::acc)[]proofsinletentries=Array.makeConf.entriesNoneinList.iter(fun(index,ptr)->entries.(index)<-Someptr)es;letv:truncated_ptrv=Tree{depth;length;entries}inhash_vv|Blindedh->hletrecconcrete_of_proofdepth:proof->Concrete.t=function|Blinded_->Concrete.Blinded|Valuesvs->Concrete.Values(List.mapConcrete.to_entryvs)|Inode{length;proofs}->letpointers=List.fold_left(funacc(index,proof)->lettree=concrete_of_proof(depth+1)proofinletpointer=hash(depth+1)proofin{Concrete.tree;pointer;index}::acc)[](List.revproofs)inConcrete.Tree{depth;length;pointers}letto_prooflat=letp=ift.stablethen(* To preserve the stable hash, the proof needs to contain
all the underlying values. *)letbindings=seqlat|>Seq.mapConcrete.to_entry|>List.of_seq|>List.fast_sort(funxy->comparex.Concrete.namey.Concrete.name)inConcrete.Valuesbindingselseto_concrete~force:falselatinproof_of_concretet.hashpletof_proof(proof:proof)=letc=concrete_of_proof0proofinof_concrete_exncletof_concretet=proof_of_concrete(lazy(failwith"blinded root"))tletto_concrete=concrete_of_proof0endendmoduleRaw=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)}letof_seql=Total(I.of_seql)letof_listl=of_seq(List.to_seql)letseq?offset?length?cachet=applyt{f=(funlayoutv->I.seqlayout?offset?length?cachev)}letlist?offset?length?cachet=List.of_seq(seq?offset?length?cachet)letempty=of_list[]letis_emptyt=applyt{f=(fun_v->I.is_emptyv)}letfind?cachets=applyt{f=(funlayoutv->I.find?cachelayoutvs)}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_hashx=letstable=applyx{f=(fun_v->I.stablev)}inifnotstablethenletbin=applyx{f=(funlayoutv->I.to_binlayoutv)}inpre_hash_binvbin.velseletvs=seqxinpre_hash_node(Node.of_seqvs)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)}letcleart=applyt{f=(funlayoutv->I.clearlayoutv)}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.ConcretemoduleProof=I.Proofletto_concretet=applyt{f=(funlav->I.to_concrete~force:truelav)}letof_concretet=matchI.of_concretetwithOkt->Ok(Truncatedt)|Error_ase->etypeproof=I.proof[@@derivingirmin]letto_proof(t:t):proof=applyt{f=(funlav->I.Proof.to_prooflav)}letof_proof(p:proof)=Truncated(I.Proof.of_proofp)endendmoduleMake(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