12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637(*
* Copyright (c) 2013-2017 Thomas Gazagnaire <thomas@gazagnaire.org>
* Copyright (c) 2017 Grégoire Henry <gregoire.henry@ocamlpro.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!ImportincludeTree_intfletsrc=Logs.Src.create"irmin.tree"~doc:"Persistent lazy trees for Irmin"moduleLog=(valLogs.src_logsrc:Logs.LOG)typefuzzy_bool=False|True|Maybetype('a,'r)cont=('a->'r)->'rtype('a,'r)cont_lwt=('a,'rLwt.t)contletokx=Lwt.return(Okx)(* assume l1 and l2 are key-sorted *)letalist_iter2compare_kfl1l2=letrecauxl1l2=match(l1,l2)with|[],t->List.iter(fun(key,v)->fkey(`Rightv))t|t,[]->List.iter(fun(key,v)->fkey(`Leftv))t|(k1,v1)::t1,(k2,v2)::t2->(matchcompare_kk1k2with|0->fk1(`Both(v1,v2));(aux[@tailcall])t1t2|x->ifx<0then(fk1(`Leftv1);(aux[@tailcall])t1l2)else(fk2(`Rightv2);(aux[@tailcall])l1t2))inauxl1l2(* assume l1 and l2 are key-sorted *)letalist_iter2_lwtcompare_kfl1l2=letl3=ref[]inalist_iter2compare_k(funleftright->l3:=fleftright::!l3)l1l2;Lwt_list.iter_s(funb->b>>=fun()->Lwt.return_unit)(List.rev!l3)moduleMake(P:Private.S)=structtypecounters={mutablecontents_hash:int;mutablecontents_find:int;mutablecontents_add:int;mutablenode_hash:int;mutablenode_mem:int;mutablenode_add:int;mutablenode_find:int;mutablenode_val_v:int;mutablenode_val_find:int;mutablenode_val_list:int;}[@@derivingirmin]letdump_countersppft=Type.pp_json~minify:falsecounters_tppftletfresh_counters()={contents_hash=0;contents_add=0;contents_find=0;node_hash=0;node_mem=0;node_add=0;node_find=0;node_val_v=0;node_val_find=0;node_val_list=0;}letreset_counterst=t.contents_hash<-0;t.contents_add<-0;t.contents_find<-0;t.node_hash<-0;t.node_mem<-0;t.node_add<-0;t.node_find<-0;t.node_val_v<-0;t.node_val_find<-0;t.node_val_list<-0letcnt=fresh_counters()modulePath=P.Node.PathmoduleStepMap=structmoduleX=structtypet=Path.steplett=Path.step_tletcompare=Type.(unstage(comparePath.step_t))endincludeMap.Make(X)letstdlib_merge=mergeincludeMerge.Map(X)endmoduleMetadata=P.Node.Metadatatypekey=Path.ttypehash=P.Hash.ttype'aor_error=('a,[`Dangling_hashofhash])resultletget_ok:typea.aor_error->a=function|Okx->x|Error(`Dangling_hashhash)->Fmt.failwith"Encountered dangling hash %a"(Type.ppP.Hash.t)hashtypestep=Path.steptypecontents=P.Contents.valuetyperepo=P.Repo.tletpp_hash=Type.ppP.Hash.tletpp_step=Type.ppPath.step_tletpp_path=Type.ppPath.tmoduleHashes=Hashtbl.Make(structtypet=hashlethash=P.Hash.short_hashletequal=Type.(unstage(equalP.Hash.t))end)letdummy_marks=Hashes.create0typemarks=unitHashes.tletempty_marks()=Hashes.create39type'aforce=[`True|`Falseofkey->'a->'aLwt.t|`And_clear]typeuniq=[`False|`True|`Marksofmarks]type'anode_fn=key->steplist->'a->'aLwt.ttypedepth=[`Eqofint|`Leofint|`Ltofint|`Geofint|`Gtofint][@@derivingirmin]letequal_contents=Type.(unstage(equalP.Contents.Val.t))letequal_metadata=Type.(unstage(equalMetadata.t))letequal_hash=Type.(unstage(equalP.Hash.t))letequal_node=Type.(unstage(equalP.Node.Val.t))moduleContents=structtypev=Hashofrepo*hash|Valueofcontentstypeinfo={mutablehash:hashoption;mutablevalue:contentsoption}typet={mutablev:v;mutableinfo:info}letinfo_is_emptyi=i.hash=None&&i.value=Noneletv=letopenTypeinvariant"Node.Contents.v"(funhashvalue->function|Hash(_,x)->hashx|Valuev->valuev)|~case1"hash"P.Hash.t(fun_->assertfalse)|~case1"value"P.Contents.Val.t(funv->Valuev)|>sealvletclear_infoi=ifnot(info_is_emptyi)then(i.value<-None;i.hash<-None)letcleart=clear_infot.infoletof_vv=lethash,value=matchvwithHash(_,k)->(Somek,None)|Valuev->(None,Somev)inletinfo={hash;value}in{v;info}letexport?clear:(c=true)repotk=lethash=t.info.hashinifcthencleart;match(t.v,hash)with|Hash(_,k),_->t.v<-Hash(repo,k)|Value_,None->t.v<-Hash(repo,k)|Value_,Somek->t.v<-Hash(repo,k)letof_valuec=of_v(Valuec)letof_hashrepok=of_v(Hash(repo,k))letcached_hasht=match(t.v,t.info.hash)with|Hash(_,k),None->leth=Somekint.info.hash<-h;h|_,h->hletcached_valuet=match(t.v,t.info.value)with|Valuev,None->letv=Somevint.info.value<-v;v|_,v->vlethashc=matchcached_hashcwith|Somek->k|None->(matchcached_valuecwith|None->assertfalse|Somev->cnt.contents_hash<-cnt.contents_hash+1;letk=P.Contents.Key.hashvinc.info.hash<-Somek;k)letvalue_of_hashtrepok=cnt.contents_find<-cnt.contents_find+1;P.Contents.find(P.Repo.contents_trepo)k>|=function|None->Error(`Dangling_hashk)|Somevassome_v->t.info.value<-some_v;Okvletto_valuet=matchcached_valuetwith|Somev->Lwt.return(Okv)|None->(matcht.vwith|Valuev->Lwt.return(Okv)|Hash(repo,k)->value_of_hashtrepok)letforce=to_valueletforce_exnt=forcet>|=function|Okv->v|Error(`Dangling_hashh)->Fmt.failwith"Can't force dangling contents hash: %a"pp_hashhletequal(x:t)(y:t)=x==y||match(cached_hashx,cached_hashy)with|Somex,Somey->equal_hashxy|_->(match(cached_valuex,cached_valuey)with|Somex,Somey->equal_contentsxy|_->equal_hash(hashx)(hashy))lett=Type.map~equal:(Type.stageequal)vof_v(funt->t.v)letmerge:tMerge.t=letf~oldxy=letold=Merge.bind_promiseold(funold()->let+c=to_valueold>|=Option.of_resultinOk(Somec))inlet*x=to_valuex>|=Option.of_resultinlet*y=to_valuey>|=Option.of_resultinMerge.(fP.Contents.Val.merge)~oldxy>|=function|Ok(Somec)->Ok(of_valuec)|OkNone->Error(`Conflict"empty contents")|Error_ase->einMerge.vtfletfold~force~pathftacc=matchforcewith|`True|`And_clear->let*c=to_valuetinifforce=`And_clearthencleart;fpath(get_okc)acc|`Falseskip->(matchcached_valuetwith|None->skippathacc|Somec->fpathcacc)endmoduleNode=structtypevalue=P.Node.Val.ttypeelt=[`Nodeoft|`ContentsofContents.t*Metadata.t]andupdate=Addofelt|Removeandupdatemap=updateStepMap.tandmap=eltStepMap.tandinfo={mutablevalue:valueoption;mutablemap:mapoption;mutablehash:hashoption;mutablefindv_cache:mapoption;}andv=|Mapofmap|Hashofrepo*hash|Valueofrepo*value*updatemapoptionandt={mutablev:v;mutableinfo:info}(** [t.v] has 3 possible states:
- A [Map], only after a [Tree.of_concrete] operation.
- A [Value], only after an add, a remove, temporarily during an export
or at the end of a merge.
- It is otherwise a [Hash].
[t.info.map] is only populated during a call to [Node.to_map]. *)letelt_t(t:tType.t):eltType.t=letopenTypeinvariant"Node.value"(funnodecontentscontents_m->function|`Nodex->nodex|`Contents(c,m)->ifequal_metadatamMetadata.defaultthencontentscelsecontents_m(c,m))|~case1"Node"t(funx->`Nodex)|~case1"Contents"Contents.t(funx->`Contents(x,Metadata.default))|~case1"Contents-x"(pairContents.tMetadata.t)(funx->`Contentsx)|>sealvletstepmap_t:'a.'aType.t->'aStepMap.tType.t=funelt->letopenTypeinletto_mapx=List.fold_left(funacc(k,v)->StepMap.addkvacc)StepMap.emptyxinletof_mapm=StepMap.fold(funkvacc->(k,v)::acc)m[]inmap(list(pairPath.step_telt))to_mapof_mapletupdate_t(elt:eltType.t):updateType.t=letopenTypeinvariant"Node.update"(funaddremove->function|Addelt->addelt|Remove->remove)|~case1"add"elt(funelt->Addelt)|~case0"remove"Remove|>sealvletv_t(elt:eltType.t):vType.t=letm=stepmap_teltinletum=stepmap_t(update_telt)inletopenTypeinvariant"Node.node"(funmaphashvalue->function|Mapm->mapm|Hash(_,y)->hashy|Value(_,v,m)->value(v,m))|~case1"map"m(funm->Mapm)|~case1"hash"P.Hash.t(fun_->assertfalse)|~case1"value"(pairP.Node.Val.t(optionum))(fun_->assertfalse)|>sealvletinfo_is_emptyi=i.map=None&&i.value=None&&i.findv_cache=None&&i.hash=Noneletof_vv=lethash,map,value=matchvwith|Mapm->(None,Somem,None)|Hash(_,k)->(Somek,None,None)|Value(_,v,None)->(None,None,Somev)|Value_->(None,None,None)inletfindv_cache=Noneinletinfo={hash;map;value;findv_cache}in{v;info}letrecclear_elt~max_depthdepth(_,v)=matchvwith|`Contents(c,_)->ifdepth+1>max_depththenContents.clearc|`Nodet->clear~max_depth(depth+1)tandclear_map~max_depthdepth=List.iter(clear_elt~max_depthdepth)andclear_maps~max_depthdepth=List.iter(clear_map~max_depthdepth)andclear_info~max_depth?vdepthi=letadded=matchvwith|Some(Value(_,_,Someum))->StepMap.bindingsum|>List.filter_map(function|_,Remove->None|k,Addv->Some(k,v))|_->[]inletmap=match(v,i.map)with|Some(Mapm),_|_,Somem->StepMap.bindingsm|_->[]inletfindv=matchi.findv_cachewithSomem->StepMap.bindingsm|None->[]inifdepth>=max_depth&¬(info_is_emptyi)then(i.value<-None;i.map<-None;i.hash<-None;i.findv_cache<-None);clear_maps~max_depthdepth[map;added;findv]andclear~max_depthdeptht=clear_info~v:t.v~max_depthdeptht.infoletclear?depth:dn=letmax_depth=matchdwithNone->0|Somemax_depth->max_depthinclear~max_depth0n(* export t to the given repo and clear the cache *)letexport?clear:(c=true)repotk=lethash=t.info.hashinifcthencleart;matcht.vwith|Hash(_,k)->t.v<-Hash(repo,k)|Value(_,v,None)whenP.Node.Val.is_emptyv->()|MapmwhenStepMap.is_emptym->t.v<-Value(repo,P.Node.Val.empty,None)|_->(matchhashwith|None->t.v<-Hash(repo,k)|Somek->t.v<-Hash(repo,k))letof_mapm=of_v(Mapm)letof_hashrepok=of_v(Hash(repo,k))letof_value?updatesrepov=of_v(Value(repo,v,updates))letempty=function|{v=Hash(repo,_)|Value(repo,_,_);_}->of_valuerepoP.Node.Val.empty|_->of_mapStepMap.emptyletmap_of_valuerepo(n:value):map=cnt.node_val_list<-cnt.node_val_list+1;letentries=P.Node.Val.listninletaux=function|`Nodeh->`Node(of_hashrepoh)|`Contents(c,m)->`Contents(Contents.of_hashrepoc,m)inList.fold_left(funacc(k,v)->StepMap.addk(auxv)acc)StepMap.emptyentriesletcached_hasht=match(t.v,t.info.hash)with|Hash(_,h),None->leth=Somehint.info.hash<-h;h|_,h->hletcached_mapt=match(t.v,t.info.map)with|Mapm,None->letm=Somemint.info.map<-m;m|_,m->mletcached_valuet=match(t.v,t.info.value)with|Value(_,v,None),None->letv=Somevint.info.value<-v;v|_,v->vletrechash:typea.t->(hash->a)->a=funtk->matchcached_hashtwith|Someh->kh|None->(leta_of_valuev=cnt.node_hash<-cnt.node_hash+1;leth=P.Node.Key.hashvint.info.hash<-Someh;khinmatchcached_valuetwith|Somev->a_of_valuev|None->(matcht.vwith|Hash(_,h)->kh|Value(_,v,None)->a_of_valuev|Value(_,v,Someum)->value_of_updatestvuma_of_value|Mapm->value_of_maptma_of_value))andvalue_of_map:typer.t->map->(value,r)cont=funtmapk->ifStepMap.is_emptymapthen(t.info.value<-SomeP.Node.Val.empty;kP.Node.Val.empty)elseletalist=StepMap.bindingsmapinletrecauxacc=function|[]->cnt.node_val_v<-cnt.node_val_v+1;letv=P.Node.Val.v(List.revacc)int.info.value<-Somev;kv|(step,v)::rest->(matchvwith|`Contents(c,m)->letv=`Contents(Contents.hashc,m)in(aux[@tailcall])((step,v)::acc)rest|`Noden->hashn(funh->aux((step,`Nodeh)::acc)rest))inaux[]alistandvalue_of_elt:typer.elt->(P.Node.Val.value,r)cont=funek->matchewith|`Contents(c,m)->k(`Contents(Contents.hashc,m))|`Noden->hashn(funh->k(`Nodeh))andvalue_of_updates:typer.t->value->_->(value,r)cont=funtvupdatesk->letupdates=StepMap.bindingsupdatesinletrecauxacc=function|[]->t.info.value<-Someacc;kacc|(k,Adde)::rest->value_of_elte(fune->aux(P.Node.Val.addaccke)rest)|(k,Remove)::rest->aux(P.Node.Val.removeacck)restinauxvupdateslethashk=hashk(funx->x)letvalue_of_hashtrepok=matchcached_valuetwith|Somev->Lwt.return_okv|None->(cnt.node_find<-cnt.node_find+1;P.Node.find(P.Repo.node_trepo)k>|=function|None->Error(`Dangling_hashk)|Somevassome_v->t.info.value<-some_v;Okv)letto_valuet=matchcached_valuetwith|Somev->okv|None->(matcht.vwith|Value(_,v,None)->okv|Value(_,v,Someum)->value_of_updatestvumok|Mapm->value_of_maptmok|Hash(repo,h)->value_of_hashtrepoh)letto_mapt=matchcached_maptwith|Somem->Lwt.return(Okm)|None->(letof_valuerepovupdates=letm=map_of_valuerepovinletm=matchupdateswith|None->m|Someupdates->StepMap.stdlib_merge(fun_leftright->match(left,right)with|None,None->assertfalse|(Some_asv),None->v|_,Some(Addv)->Somev|_,SomeRemove->None)mupdatesint.info.map<-Somem;minmatcht.vwith|Mapm->Lwt.return(Okm)|Value(repo,v,m)->Lwt.return(Ok(of_valuerepovm))|Hash(repo,k)->(value_of_hashtrepok>|=function|Error_ase->e|Okv->Ok(of_valuerepovNone)))lethash_equalxy=x==y||equal_hashxyletcontents_equal((c1,m1)asx1)((c2,m2)asx2)=x1==x2||(Contents.equalc1c2&&equal_metadatam1m2)letrecelt_equal(x:elt)(y:elt)=x==y||match(x,y)with|`Contentsx,`Contentsy->contents_equalxy|`Nodex,`Nodey->equalxy|_->falseandmap_equal(x:map)(y:map)=StepMap.equalelt_equalxyandequal(x:t)(y:t)=x==y||match(cached_hashx,cached_hashy)with|Somex,Somey->equal_hashxy|_->(match(cached_valuex,cached_valuey)with|Somex,Somey->equal_nodexy|_->(match(cached_mapx,cached_mapy)with|Somex,Somey->map_equalxy|_->hash_equal(hashx)(hashy)))(* same as [equal] but do not compare in-memory maps
recursively. *)letmaybe_equal(x:t)(y:t)=ifx==ythenTrueelsematch(cached_hashx,cached_hashy)with|Somex,Somey->ifequal_hashxythenTrueelseFalse|_->(match(cached_valuex,cached_valuey)with|Somex,Somey->ifequal_nodexythenTrueelseFalse|_->Maybe)(** Does [um] empties [v]?
Gotcha: Some [Remove] entries in [um] might not be in [v]. *)letis_empty_after_updatesvum=letany_add=StepMap.to_sequm|>Seq.exists(function_,Remove->false|_,Add_->true)inifany_addthenfalseelseletval_is_empty=P.Node.Val.is_emptyvinifval_is_emptythentrueelseletremove_count=StepMap.cardinaluminif(notval_is_empty)&&remove_count=0thenfalseelseifP.Node.Val.lengthv>remove_countthenfalseelse((* Starting from this point the function is expensive, but there is
no alternative. *)cnt.node_val_list<-cnt.node_val_list+1;letentries=P.Node.Val.listvinList.for_all(fun(step,_)->StepMap.memstepum)entries)letlengtht=matchcached_maptwith|Somem->StepMap.cardinalm|>Lwt.return|None->(let+v=to_valuetinmatchvwith|Okv->P.Node.Val.lengthv|Error(`Dangling_hashhash)->Fmt.failwith"length: encountered dangling hash %a"(Type.ppP.Hash.t)hash)letis_empty=letempty_hash=hash(of_mapStepMap.empty)infunt->matchcached_maptwith|Somem->StepMap.is_emptym|None->(matchcached_valuetwith|Somev->P.Node.Val.is_emptyv|None->(matcht.vwith|Value(_,v,Someum)->is_empty_after_updatesvum|Hash(_,h)->hash_equalempty_hashh|Map_->assertfalse(* [cached_map <> None] *)|Value(_,_,None)->assertfalse(* [cached_value <> None] *)))letadd_to_findv_cachetstepv=matcht.info.findv_cachewith|None->t.info.findv_cache<-Some(StepMap.singletonstepv)|Somem->t.info.findv_cache<-Some(StepMap.addstepvm)letfindvtstep=letof_mapm=trySome(StepMap.findstepm)withNot_found->Noneinletof_valuerepov=matchP.Node.Val.findvstepwith|None->None|Some(`Contents(c,m))->letc=Contents.of_hashrepocinlet(v:elt)=`Contents(c,m)inadd_to_findv_cachetstepv;Somev|Some(`Noden)->letn=of_hashreponinletv=`Nodeninadd_to_findv_cachetstepv;Somevinletof_t()=matcht.vwith|Mapm->Lwt.return(of_mapm)|Value(repo,v,None)->Lwt.return(of_valuerepov)|Value(repo,v,Someum)->(matchStepMap.find_optstepumwith|Some(Addv)->Lwt.return(Somev)|SomeRemove->Lwt.returnNone|None->Lwt.return(of_valuerepov))|Hash(repo,h)->(matchcached_valuetwith|Somev->Lwt.return(of_valuerepov)|None->(value_of_hashtrepoh>|=function|Error(`Dangling_hash_)->None|Okv->of_valuerepov))inmatchcached_maptwith|Somem->Lwt.return(of_mapm)|None->(matcht.info.findv_cachewith|None->of_t()|Somem->(matchof_mapmwith|None->of_t()|Some_asr->Lwt.returnr))letlist_of_map?(offset=0)?lengthm:(step*elt)list=lettake_lengthseq=matchlengthwithNone->List.of_seqseq|Somen->Seq.takenseqinStepMap.to_seqm|>Seq.dropoffset|>take_lengthletlist_of_valuerepo?offset?lengthv:(step*elt)list=cnt.node_val_list<-cnt.node_val_list+1;lett=P.Node.Val.list?offset?lengthvinList.fold_left(funacc(k,v)->matchvwith|`Noden->letn=`Node(of_hashrepon)in(k,n)::acc|`Contents(c,m)->letc=Contents.of_hashrepocin(k,`Contents(c,m))::acc)[](List.revt)letlist?offset?lengtht:(step*elt)listor_errorLwt.t=matchcached_maptwith|Somem->ok(list_of_map?offset?lengthm)|None->(matcht.vwith|Value(repo,n,None)->ok(list_of_value?offset?lengthrepon)|Hash(repo,h)->(value_of_hashtrepoh>>=function|Error_ase->Lwt.returne|Okv->ok(list_of_value?offset?lengthrepov))|_->(to_mapt>>=function|Error_ase->Lwt.returne|Okm->ok(list_of_map?offset?lengthm)))letbindingst=to_mapt>|=function|Error_ase->e|Okm->Ok(StepMap.bindingsm)type('v,'acc,'r)folder=path:key->'acc->int->'v->('acc,'r)cont_lwt(** A ('val, 'acc, 'r) folder is a CPS, threaded fold function over values
of type ['v] producing an accumulator of type ['acc]. *)letfold:typeacc.force:accforce->uniq:uniq->pre:accnode_fn->post:accnode_fn->path:Path.t->?depth:depth->node:(key->_->acc->accLwt.t)->contents:(key->contents->acc->accLwt.t)->t->acc->accLwt.t=fun~force~uniq~pre~post~path?depth~node~contentstacc->letmarks=matchuniqwith|`False->dummy_marks|`True->empty_marks()|`Marksn->ninletrecaux:typer.(t,acc,r)folder=fun~pathaccdtk->letapplyacc=nodepathtaccinletnextacc=matchforcewith|`True|`And_clear->(to_mapt>>=function|Okm->ifforce=`And_clearthenclear~depth:0t;(map[@tailcall])~pathaccd(Somem)k|Error(`Dangling_hash_)->(map[@tailcall])~pathaccdNonek)|`Falseskip->(matchcached_maptwith|Somen->(map[@tailcall])~pathaccd(Somen)k|None->skippathacc>>=k)inmatchdepthwith|None->applyacc>>=next|Some(`Eqdepth)->ifd<depththennextaccelseapplyacc>>=k|Some(`Ledepth)->ifd<depththenapplyacc>>=nextelseapplyacc>>=k|Some(`Ltdepth)->ifd<depth-1thenapplyacc>>=nextelseapplyacc>>=k|Some(`Gedepth)->ifd<depththennextaccelseapplyacc>>=next|Some(`Gtdepth)->ifd<=depththennextaccelseapplyacc>>=nextandaux_uniq:typer.(t,acc,r)folder=fun~pathaccdtk->ifuniq=`Falsethen(aux[@tailcall])~pathaccdtkelseleth=hashtinifHashes.memmarkshthenkaccelse(Hashes.addmarksh();(aux[@tailcall])~pathaccdtk)andstep:typer.(step*elt,acc,r)folder=fun~pathaccd(s,v)k->letpath=Path.rconspathsinmatchvwith|`Noden->(aux_uniq[@tailcall])~pathacc(d+1)nk|`Contentsc->(letapply()=Contents.fold~force~pathcontents(fstc)acc>>=kinmatchdepthwith|None->apply()|Some(`Eqdepth)->ifd=depth-1thenapply()elsekacc|Some(`Ledepth)->ifd<depththenapply()elsekacc|Some(`Ltdepth)->ifd<depth-1thenapply()elsekacc|Some(`Gedepth)->ifd>=depth-1thenapply()elsekacc|Some(`Gtdepth)->ifd>=depththenapply()elsekacc)andsteps:typer.((step*elt)list,acc,r)folder=fun~pathaccdsk->matchswith|[]->kacc|h::t->(step[@tailcall])~pathaccdh@@funacc->(steps[@tailcall])~pathaccdtkandmap:typer.(mapoption,acc,r)folder=fun~pathaccdmk->matchmwith|None->kacc|Somem->letbindings=StepMap.bindingsminlets=List.rev_mapfstbindingsinlet*acc=prepathsaccin(steps[@tailcall])~pathaccdbindings@@funacc->postpathsacc>>=kinaux_uniq~pathacc0tLwt.returnletupdatetstepup=letof_mapm=letm'=matchupwith|Remove->StepMap.removestepm|Addv->StepMap.addstepvminifm==m'thentelseof_mapm'inletof_valuereponupdates=letupdates'=StepMap.addstepupupdatesinifupdates==updates'thentelseof_valuerepon~updates:updates'inmatcht.vwith|Mapm->Lwt.return(of_mapm)|Value(repo,n,None)->Lwt.return(of_valuereponStepMap.empty)|Value(repo,n,Someum)->Lwt.return(of_valuereponum)|Hash(repo,h)->(match(cached_valuet,cached_mapt)with|Somev,_->Lwt.return(of_valuerepovStepMap.empty)|_,Somem->Lwt.return(of_mapm)|None,None->let+v=value_of_hashtrepoh>|=function|Okv->v|Error(`Dangling_hash_)->P.Node.Val.emptyinof_valuerepovStepMap.empty)letremovetstep=updatetstepRemoveletaddtstepv=updatetstep(Addv)lettnode=Type.map~equal:(Type.stageequal)nodeof_v(funt->t.v)let_,t=Type.mu2(fun_y->letelt=elt_tyinletv=v_teltinlett=tvin(v,t))letelt_t=elt_ttletdump=Type.pp_json~minify:falsetletrecmerge:typea.(tMerge.t->a)->a=funk->letf~oldxy=letold=Merge.bind_promiseold(funold()->let+m=to_mapold>|=Option.of_resultinOk(Somem))inlet*x=to_mapx>|=Option.of_resultinlet*y=to_mapy>|=Option.of_resultinletm=StepMap.mergeelt_t(fun_step->(merge_elt[@tailcall])Merge.option)inMerge.(f@@optionm)~oldxy>|=function|Ok(Somemap)->Ok(of_mapmap)|OkNone->Error(`Conflict"empty map")|Error_ase->eink(Merge.vtf)andmerge_elt:typer.(eltMerge.t,r)cont=funk->letopenMerge.Infixinletf:eltMerge.f=fun~oldxy->match(x,y)with|`Contents(x,cx),`Contents(y,cy)->letmold=Merge.bind_promiseold(funold()->matcholdwith|`Contents(_,m)->Lwt.return(Ok(Somem))|`Node_->Lwt.return(OkNone))inMerge.(fMetadata.merge)~old:moldcxcy>>=*funm->letold=Merge.bind_promiseold(funold()->matcholdwith|`Contents(c,_)->Lwt.return(Ok(Somec))|`Node_->Lwt.return(OkNone))inMerge.(fContents.merge)~oldxy>>=*func->Merge.ok(`Contents(c,m))|`Nodex,`Nodey->(merge[@tailcall])(funm->letold=Merge.bind_promiseold(funold()->matcholdwith|`Contents_->Lwt.return(OkNone)|`Noden->Lwt.return(Ok(Somen)))inMerge.(fm~oldxy)>>=*funn->Merge.ok(`Noden))|_->Merge.conflict"add/add values"ink(Merge.seq[Merge.defaultelt_t;Merge.velt_tf])letmerge_elt=merge_elt(funx->x)endtypenode=Node.t[@@derivingirmin]typemetadata=Metadata.ttypet=[`Nodeofnode|`ContentsofContents.t*Metadata.t][@@derivingirmin{name="tree_t"}]letof_private_noderepon=Node.of_valuereponletto_private_node=Node.to_valueletdumpppf=function|`Noden->Fmt.pfppf"node: %a"Node.dumpn|`Contents(c,_)->Fmt.pfppf"contents: %a"(Type.ppContents.t)cletcontents_equal((c1,m1)asx1)((c2,m2)asx2)=x1==x2||(c1==c2&&m1==m2)||(Contents.equalc1c2&&equal_metadatam1m2)letequal(x:t)(y:t)=x==y||match(x,y)with|`Nodex,`Nodey->Node.equalxy|`Contentsx,`Contentsy->contents_equalxy|`Node_,`Contents_|`Contents_,`Node_->falseletis_empty=function`Noden->Node.is_emptyn|`Contents_->falsetypeelt=[`Nodeofnode|`Contentsofcontents*metadata]letof_noden=`Nodenletof_contents?(metadata=Metadata.default)c=`Contents(Contents.of_valuec,metadata)letv:elt->t=function|`Contents(c,meta)->`Contents(Contents.of_valuec,meta)|`Noden->`Nodenletdestructx=xletclear?depth=function|`Noden->Node.clear?depthn|`Contents_->()letsubtpath=letrecauxnodepath=matchPath.deconspathwith|None->Lwt.return_somenode|Some(h,p)->(Node.findvnodeh>>=function|None|Some(`Contents_)->Lwt.return_none|Some(`Noden)->(aux[@tailcall])np)inmatchtwith|`Noden->(aux[@tailcall])npath|`Contents_->Lwt.return_noneletfind_tree(t:t)path=Log.debug(funl->l"Tree.find_tree %a"pp_pathpath);match(t,Path.rdeconspath)with|v,None->Lwt.return_somev|_,Some(path,file)->(subtpath>>=function|None->Lwt.return_none|Somen->Node.findvnfile)letid__acc=Lwt.returnaccletfold?(force=`And_clear)?(uniq=`False)?(pre=id)?(post=id)?depth?(contents=id)?(node=id)(t:t)acc=matchtwith|`Contents(c,_)->Contents.fold~force~path:Path.emptycontentscacc|`Noden->Node.fold~force~uniq~pre~post~path:Path.empty?depth~contents~nodenacctypestats={nodes:int;leafs:int;skips:int;depth:int;width:int;}[@@derivingirmin]letempty_stats={nodes=0;leafs=0;skips=0;depth=0;width=0}letincr_nodess={swithnodes=s.nodes+1}letincr_leafss={swithleafs=s.leafs+1}letincr_skipss={swithskips=s.skips+1}letset_depthps=letn_depth=List.length(Path.mapp(fun_->()))inletdepth=maxn_depths.depthin{swithdepth}letset_widthchildss=letwidth=maxs.width(List.lengthchilds)in{swithwidth}leterr_not_foundnk=Fmt.kstrfinvalid_arg"Irmin.Tree.%s: %a not found"npp_pathkletget_tree(t:t)path=find_treetpath>|=function|None->err_not_found"get_tree"path|Somev->vletfind_alltk=find_treetk>>=function|None|Some(`Node_)->Lwt.return_none|Some(`Contents(c,m))->let+c=Contents.to_valuecinSome(get_okc,m)letfindtk=find_alltk>|=functionNone->None|Some(c,_)->Somecletget_alltk=find_alltk>>=function|None->err_not_found"get"k|Somev->Lwt.returnvletgettk=get_alltk>|=fun(c,_)->cletmemtk=findtk>|=functionNone->false|_->trueletmem_treetk=find_treetk>|=functionNone->false|_->trueletkindtpath=Log.debug(funl->l"Tree.kind %a"pp_pathpath);match(t,Path.rdeconspath)with|`Contents_,None->Lwt.return_some`Contents|`Node_,None->Lwt.return_some`Node|_,Some(dir,file)->(subtdir>>=function|None->Lwt.return_none|Somem->(Node.findvmfile>>=function|None->Lwt.return_none|Some(`Contents_)->Lwt.return_some`Contents|Some(`Node_)->Lwt.return_some`Node))letlength=Node.lengthletlistt?offset?lengthpath:(step*t)listLwt.t=Log.debug(funl->l"Tree.list %a"pp_pathpath);subtpath>>=function|None->Lwt.return[]|Somen->(Node.list?offset?lengthn>|=functionError_->[]|Okl->l)letempty=`Node(Node.of_mapStepMap.empty)letempty_node=function|`Noden->Node.emptyn|`Contents_->Node.of_mapStepMap.empty(** During recursive updates, we keep track of whether or not we've made a
modification in order to avoid unnecessary allocations of identical tree
objects. *)type'aupdated=Changedof'a|Unchangedletmaybe_equal(x:t)(y:t)=ifx==ythenTrueelsematch(x,y)with|`Nodex,`Nodey->Node.maybe_equalxy|_->ifequalxythenTrueelseFalseletupdate_tree~f_might_return_empty_node~froot_treepath=letempty_node=empty_noderoot_treein(* User-introduced empty nodes will be removed immediately if necessary. *)letprune_empty:node->bool=ifnotf_might_return_empty_nodethenFun.constfalseelseNode.is_emptyinmatchPath.rdeconspathwith|None->(letempty_tree=matchis_emptyroot_treewith|true->root_tree|false->`Nodeempty_nodeinf(Someroot_tree)>>=function(* Here we consider "deleting" a root contents value or node to consist
of changing it to an empty node. Note that this introduces
sensitivity to ordering of subtree operations: updating in a subtree
and adding the subtree are not necessarily commutative. *)|None->Lwt.returnempty_tree|Some(`Node_asnew_root)->(matchmaybe_equalroot_treenew_rootwith|True->Lwt.returnroot_tree|Maybe|False->Lwt.returnnew_root)|Some(`Contentsc'asnew_root)->(matchroot_treewith|`Contentscwhencontents_equalcc'->Lwt.returnroot_tree|_->Lwt.returnnew_root))|Some(path,file)->(letrecaux:typer.key->node->(nodeupdated,r)cont_lwt=funpathparent_nodek->letchangedn=k(Changedn)inmatchPath.deconspathwith|None->(letwith_new_childt=Node.addparent_nodefilet>>=changedinlet*old_binding=Node.findvparent_nodefileinlet*new_binding=fold_bindinginmatch(old_binding,new_binding)with|None,None->kUnchanged|None,Some(`Contents_ast)->with_new_childt|None,Some(`Nodenast)->(matchprune_emptynwith|true->kUnchanged|false->with_new_childt)|Some_,None->Node.removeparent_nodefile>>=changed|Someold_value,Some(`Nodenast)->(matchprune_emptynwith|true->Node.removeparent_nodefile>>=changed|false->(matchmaybe_equalold_valuetwith|True->kUnchanged|Maybe|False->with_new_childt))|Some(`Contentsc),Some(`Contentsc'ast)->(matchcontents_equalcc'with|true->kUnchanged|false->with_new_childt)|Some(`Node_),Some(`Contents_ast)->with_new_childt)|Some(step,key_suffix)->(let*old_binding=Node.findvparent_nodestepinletto_recurse=matchold_bindingwith|Some(`Nodechild)->child|None|Some(`Contents_)->empty_nodein(aux[@tailcall])key_suffixto_recurse@@function|Unchanged->(* This includes [remove]s in an empty node, in which case we
want to avoid adding a binding anyway. *)kUnchanged|Changedchild->(matchNode.is_emptychildwith|true->(* A [remove] has emptied previously non-empty child with
binding [h], so we remove the binding. *)Node.removeparent_nodestep>>=changed|false->Node.addparent_nodestep(`Nodechild)>>=changed))inlettop_node=matchroot_treewith`Noden->n|`Contents_->empty_nodeinauxpathtop_node@@function|Unchanged->Lwt.returnroot_tree|Changednode->Lwt.return(`Nodenode))letupdatetk?(metadata=Metadata.default)f=Log.debug(funl->l"Tree.update %a"pp_pathk);update_treetk~f_might_return_empty_node:false~f:(funt->let+old_contents=matchtwith|Some(`Node_)|None->Lwt.return_none|Some(`Contents(c,_))->let+c=Contents.to_valuecinSome(get_okc)inmatchfold_contentswith|None->None|Somec->Some(`Contents(Contents.of_valuec,metadata)))letaddtk?(metadata=Metadata.default)c=Log.debug(funl->l"Tree.add %a"pp_pathk);update_treetk~f:(fun_->Lwt.return_some(`Contents(Contents.of_valuec,metadata)))~f_might_return_empty_node:falseletadd_treetkv=Log.debug(funl->l"Tree.add_tree %a"pp_pathk);update_treetk~f:(fun_->Lwt.return_somev)~f_might_return_empty_node:trueletremovetk=Log.debug(funl->l"Tree.remove %a"pp_pathk);update_treetk~f:(fun_->Lwt.return_none)~f_might_return_empty_node:falseletupdate_treetkf=Log.debug(funl->l"Tree.update_tree %a"pp_pathk);update_treetk~f:(Lwt.wrap1f)~f_might_return_empty_node:trueletimportrepo=function|`Contents(k,m)->(P.Contents.mem(P.Repo.contents_trepo)k>|=function|true->Some(`Contents(Contents.of_hashrepok,m))|false->None)|`Nodek->(cnt.node_mem<-cnt.node_mem+1;P.Node.mem(P.Repo.node_trepo)k>|=function|true->Some(`Node(Node.of_hashrepok))|false->None)letimport_no_checkrepo=function|`Nodek->`Node(Node.of_hashrepok)|`Contents(k,m)->`Contents(Contents.of_hashrepok,m)letvalue_of_maptmap=Node.value_of_maptmap(funx->x)letexport?clearrepocontents_tnode_tn=letseen=Hashes.create127inletadd_nodenv()=cnt.node_add<-cnt.node_add+1;let+k=P.Node.addnode_tvinletk'=Node.hashninassert(equal_hashkk');Node.export?clearreponkinletadd_contentscx()=cnt.contents_add<-cnt.contents_add+1;let+k=P.Contents.addcontents_txinletk'=Contents.hashcinassert(equal_hashkk');Contents.export?clearrepockinletadd_node_mapnx()=add_noden(value_of_mapnx)()inlettodo=Stack.create()inletrecadd_to_todo:typea._->(unit->aLwt.t)->aLwt.t=funnk->leth=Node.hashninifHashes.memseenhthenk()else(Hashes.addseenh();matchn.Node.vwith|Node.Hash_->Node.export?clearreponh;k()|Node.Value(_,x,None)->Stack.push(add_nodenx)todo;k()|Map_|Value(_,_,Some_)->(cnt.node_mem<-cnt.node_mem+1;P.Node.memnode_th>>=function|true->Node.export?clearreponh;k()|false->(matchn.vwith|Hash_|Value(_,_,None)->(* might happen if the node has already been added
(while the thread was block on P.Node.mem *)k()|Mapchildren->letl=StepMap.bindingschildren|>List.mapsndinadd_steps_to_todolnk|Value(_,_,Somechildren)->letl=StepMap.bindingschildren|>List.filter_map(function|_,Node.Remove->None|_,Node.Addv->Somev)inadd_steps_to_todolnk)))andadd_steps_to_todo:typea._->_->(unit->aLwt.t)->aLwt.t=funlnk->(* 1. convert partial values to total values *)let*()=matchn.Node.vwith|Value(_,_,Some_)->(Node.to_valuen>|=function|Error(`Dangling_hash_)->()|Okv->n.v<-Value(repo,v,None))|_->Lwt.return_unitin(* 2. push the current node job on the stack. *)let()=match(n.v,Node.cached_valuen)with|_,Somev->Stack.push(add_nodenv)todo|Mapx,None->Stack.push(add_node_mapnx)todo|_->assertfalseinletcontents=ref[]inletnodes=ref[]inList.iter(function|`Contentsc->contents:=c::!contents|`Noden->nodes:=n::!nodes)l;(* 2. push the contents job on the stack. *)List.iter(fun(c,_)->leth=Contents.hashcinifHashes.memseenhthen()else(Hashes.addseenh();matchc.Contents.vwith|Contents.Hash_->()|Contents.Valuex->Stack.push(add_contentscx)todo))!contents;(* 3. push the children jobs on the stack. *)List.iter(funn->Stack.push(fun()->(add_to_todo[@tailcall])nLwt.return)todo)!nodes;k()inletrecloop()=lettask=trySome(Stack.poptodo)withStack.Empty->NoneinmatchtaskwithNone->Lwt.return_unit|Somet->t()>>=loopin(add_to_todo[@tailcall])n@@fun()->loop()>|=fun()->letx=Node.hashninLog.debug(funl->l"Tree.export -> %a"pp_hashx);xletmerge:tMerge.t=letf~old(x:t)y=Merge.(fNode.merge_elt)~oldxy>>=function|Okt->Merge.okt|Errore->Lwt.return(Errore)inMerge.vtree_tfletentriespathtree=letrecauxacc=function|[]->Lwt.returnacc|(path,h)::todo->let*childs=Node.bindingsh>|=get_okinletacc,todo=List.fold_left(fun(acc,todo)(k,v)->letpath=Path.rconspathkinmatchvwith|`Nodev->(acc,(path,v)::todo)|`Contentsc->((path,c)::acc,todo))(acc,todo)childsin(aux[@tailcall])acctodoin(aux[@tailcall])[][(path,tree)](** Given two forced lazy values, return an empty diff if they both use the
same dangling hash. *)letdiff_force_result(typeab)~(empty:b)~(diff_ok:a*a->b)(x:aor_error)(y:aor_error):b=match(x,y)with|Error(`Dangling_hashh1),Error(`Dangling_hashh2)->(matchequal_hashh1h2withtrue->empty|false->assertfalse)|Error_,Ok_->assertfalse|Ok_,Error_->assertfalse|Okx,Oky->diff_ok(x,y)letdiff_contentsxy=ifNode.contents_equalxythenLwt.return_nilelselet*cx=Contents.to_value(fstx)inlet+cy=Contents.to_value(fsty)indiff_force_resultcxcy~empty:[]~diff_ok:(fun(cx,cy)->[`Updated((cx,sndx),(cy,sndy))])letcompare_step=Type.(unstage(comparePath.step_t))letdiff_node(x:node)(y:node)=letbindingsn=Node.to_mapn>|=function|Okm->Ok(StepMap.bindingsm)|Error_ase->einletremovedacc(k,(c,m))=let+c=Contents.to_valuec>|=get_okin(k,`Removed(c,m))::accinletaddedacc(k,(c,m))=let+c=Contents.to_valuec>|=get_okin(k,`Added(c,m))::accinletrecdiff_bindingsacctodopathxy=letacc=refaccinlettodo=reftodoinlet*()=alist_iter2_lwtcompare_step(funkeyv->letpath=Path.rconspathkeyinmatchvwith(* Left *)|`Left(`Contentsx)->let+x=removed!acc(path,x)inacc:=x|`Left(`Nodex)->let*xs=entriespathxinlet+xs=Lwt_list.fold_left_sremoved!accxsinacc:=xs(* Right *)|`Right(`Contentsy)->let+y=added!acc(path,y)inacc:=y|`Right(`Nodey)->let*ys=entriespathyinlet+ys=Lwt_list.fold_left_sadded!accysinacc:=ys(* Both *)|`Both(`Nodex,`Nodey)->todo:=(path,x,y)::!todo;Lwt.return_unit|`Both(`Contentsx,`Nodey)->let*ys=entriespathyinlet*x=removed!acc(path,x)inlet+ys=Lwt_list.fold_left_saddedxysinacc:=ys|`Both(`Nodex,`Contentsy)->let*xs=entriespathxinlet*y=added!acc(path,y)inlet+ys=Lwt_list.fold_left_sremovedyxsinacc:=ys|`Both(`Contentsx,`Contentsy)->let+content_diffs=diff_contentsxy>|=List.map(fund->(path,d))inacc:=content_diffs@!acc)xyin(diff_node[@tailcall])!acc!todoanddiff_nodeacc=function|[]->Lwt.returnacc|(path,x,y)::todo->ifNode.equalxythen(diff_node[@tailcall])acctodoelselet*x=bindingsxinlet*y=bindingsyindiff_force_result~empty:Lwt.return_nil~diff_ok:(fun(x,y)->diff_bindingsacctodopathxy)xyin(diff_node[@tailcall])[][(Path.empty,x,y)]letdiff(x:t)(y:t)=match(x,y)with|`Contents((c1,m1)asx),`Contents((c2,m2)asy)->ifcontents_equalxythenLwt.return_nilelselet*c1=Contents.to_valuec1>|=get_okinlet*c2=Contents.to_valuec2>|=get_okinLwt.return[(Path.empty,`Updated((c1,m1),(c2,m2)))]|`Nodex,`Nodey->diff_nodexy|`Contents(x,m),`Nodey->letempty=Node.emptyyinlet*diff=diff_nodeemptyyinlet+x=Contents.to_valuex>|=get_okin(Path.empty,`Removed(x,m))::diff|`Nodex,`Contents(y,m)->letempty=Node.emptyxinlet*diff=diff_nodexemptyinlet+y=Contents.to_valuey>|=get_okin(Path.empty,`Added(y,m))::difftypeconcrete=[`Treeof(Path.step*concrete)list|`ContentsofP.Contents.Val.t*Metadata.t][@@derivingirmin]type'aor_empty=Empty|Non_emptyof'aletof_concretec=letrecconcrete:typer.concrete->(tor_empty,r)cont=funtk->matchtwith|`Contents(c,m)->k(Non_empty(`Contents(Contents.of_valuec,m)))|`Treechilds->treeStepMap.emptychilds(function|Empty->kEmpty|Non_emptyn->k(Non_empty(`Noden)))andtree:typer.Node.eltStepMap.t->(step*concrete)list->(nodeor_empty,r)cont=funmaptk->matchtwith|[]->k(ifStepMap.is_emptymapthenEmptyelseNon_empty(Node.of_mapmap))|(s,n)::t->(concrete[@tailcall])n(funv->(tree[@tailcall])(StepMap.updates(function|None->(matchvwith|Empty->None(* Discard empty sub-directories *)|Non_emptyv->Somev)|Some_->Fmt.invalid_arg"of_concrete: duplicate bindings for step `%a`"pp_steps)map)tk)in(concrete[@tailcall])c(functionEmpty->empty|Non_emptyx->x)letto_concretet=letrectree:typer.t->(concrete,r)cont_lwt=funtk->matchtwith|`Contentsc->contentsck|`Noden->let*m=Node.to_mapninletbindings=m|>get_ok|>StepMap.bindingsin(node[@tailcall])[]bindings(funn->letn=List.sort(fun(s,_)(s',_)->compare_stepss')nink(`Treen))andcontents:typer.Contents.t*metadata->(concrete,r)cont_lwt=fun(c,m)k->let*c=Contents.to_valuec>|=get_okink(`Contents(c,m))andnode:typer.(step*concrete)list->(step*Node.elt)list->((step*concrete)list,r)cont_lwt=funchildsxk->matchxwith|[]->kchilds|(s,n)::t->(matchnwith|`Node_asn->(tree[@tailcall])n(funtree->node((s,tree)::childs)tk)|`Contentsc->(contents[@tailcall])c(func->(node[@tailcall])((s,c)::childs)tk))intreet(funx->Lwt.returnx)lethash(t:t)=Log.debug(funl->l"Tree.hash");matchtwith|`Noden->`Node(Node.hashn)|`Contents(c,m)->`Contents(Contents.hashc,m)letstats?(force=false)(t:t)=letforce=ifforcethen`Trueelse`False(funks->set_depthks|>incr_skips|>Lwt.return)inletcontentsk_s=set_depthks|>incr_leafs|>Lwt.returninletprekchildss=ifchilds=[]thenLwt.returnselseset_depthks|>set_widthchilds|>incr_nodes|>Lwt.returninletpost__acc=Lwt.returnaccinfold~force~pre~post~contentstempty_statsletcounters()=cntletdump_countersppf()=dump_countersppfcntletreset_counters()=reset_counterscntletinspect=function|`Contents_->`Contents|`Noden->`Node(matchn.Node.vwith|Map_->`Map|Value_->`Value|Hash_->`Hash)end