Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file commit.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533(*
* Copyright (c) 2013-2017 Thomas Gazagnaire <thomas@gazagnaire.org>
*
* 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!ImportopenSincludeCommit_intfopenMerge.Infixletsrc=Logs.Src.create"irmin.commit"~doc:"Irmin commits"moduleLog=(valLogs.src_logsrc:Logs.LOG)moduleMake(K:Type.S)=structtypehash=K.t[@@derivingirmin]typet={node:hash;parents:hashlist;info:Info.t}[@@derivingirmin]letparentst=t.parentsletnodet=t.nodeletinfot=t.infoletcompare_hash=Type.(unstage(compareK.t))letv~info~node~parents=letparents=List.fast_sortcompare_hashparentsin{node;parents;info}endmoduleStore(N:Node.STORE)(S:sigincludeCONTENT_ADDRESSABLE_STOREwithtypekey=N.keymoduleKey:Hash.Swithtypet=keymoduleVal:Swithtypet=valueandtypehash=keyend)=structmoduleNode=Ntype'at='aN.t*'aS.ttypekey=S.keytypevalue=S.valueletadd(_,t)=S.addtletunsafe_add(_,t)=S.unsafe_addtletmem(_,t)=S.memtletfind(_,t)=S.findtletclear(_,t)=S.cleartletmerge_node(t,_)=Merge.f(N.merget)letpp_key=Type.ppS.Key.tleterr_not_foundk=Fmt.kstrfinvalid_arg"Commit.get: %a not found"pp_keykletget(_,t)k=S.findtk>>=functionNone->err_not_foundk|Somev->Lwt.returnvletempty_if_none(n,_)=function|None->N.addnN.Val.empty|Somenode->Lwt.returnnodeletequal_opt_keys=Type.(unstage(equal(optionS.Key.t)))letmerge_commitinfot~oldk1k2=let*v1=gettk1inlet*v2=gettk2inifList.memk1(S.Val.parentsv2)thenMerge.okk2elseifList.memk2(S.Val.parentsv1)thenMerge.okk1else(* If we get an error while looking the the lca, then we
assume that there is no common ancestor. Maybe we want to
expose this to the user in a more structured way. But maybe
that's too much low-level details. *)let*old=old()>>=function|Error(`Conflictmsg)->Log.debug(funf->f"old: conflict %s"msg);Lwt.return_none|Oko->Lwt.returnoinifequal_opt_keysold(Somek1)thenMerge.okk2elseifequal_opt_keysold(Somek2)thenMerge.okk1elseletold()=matcholdwith|None->Merge.okNone|Someold->let*vold=gettoldinMerge.ok(Some(Some(S.Val.nodevold)))inmerge_nodet~old(Some(S.Val.nodev1))(Some(S.Val.nodev2))>>=*funnode->let*node=empty_if_nonetnodeinletparents=[k1;k2]inletcommit=S.Val.v~node~parents~info:(info())inlet*key=addtcommitinMerge.okkeyletmerget~info=Merge.(option(vS.Key.t(merge_commitinfot)))moduleKey=Hash.Typed(S.Key)(S.Val)moduleVal=S.ValendmoduleHistory(S:STORE)=structtypecommit=S.Key.t[@@derivingirmin]typenode=S.Node.keytype'at='aS.ttypev=S.Val.tletmerget~info=letf~oldc1c2=letsomify=Merge.map_promise(funx->Somex)inletmerge=S.merget~infoinMerge.fmerge~old:(somifyold)(Somec1)(Somec2)>>=*function|None->Merge.conflict"History.merge"|Somex->Merge.okxinMerge.vS.Key.tfletvt~node~parents~info=letcommit=S.Val.v~node~parents~infoinlet+hash=S.addtcommitin(hash,commit)letpp_key=Type.ppS.Key.tletparentstc=Log.debug(funf->f"parents %a"pp_keyc);S.findtc>|=functionNone->[]|Somec->S.Val.parentscmoduleU=structtypet=unit[@@derivingirmin]endmoduleGraph=Object_graph.Make(S.Key)(U)letedgest=Log.debug(funf->f"edges");[`Node(S.Val.nodet)]@List.map(funk->`Commitk)(S.Val.parentst)letclosuret~min~max=Log.debug(funf->f"closure");letpred=function|`Commitk->(S.findtk>|=functionSomer->edgesr|None->[])|_->Lwt.return_nilinletmin=List.map(funk->`Commitk)mininletmax=List.map(funk->`Commitk)maxinlet+g=Graph.closure~pred~min~max()inList.fold_left(funacc->function`Commitk->k::acc|_->acc)[](Graph.vertexg)letignore_lwt_=Lwt.return_unitletitert~min~max?(commit=ignore_lwt)?edge?(skip=fun_->Lwt.return_false)?(rev=true)()=letmax=List.map(funx->`Commitx)maxinletmin=List.map(funx->`Commitx)mininletnode=function`Commitx->commitx|_->assertfalseinletskip=function`Commitx->skipx|_->assertfalseinletpred=function|`Commitk->parentstk>|=List.map(funx->`Commitx)|_->assertfalseinletedge=Option.map(funedgenpred->match(n,pred)with|`Commitsrc,`Commitdst->edgesrcdst|_->assertfalse)edgeinGraph.iter~pred~min~max~node?edge~skip~rev()moduleK=structtypet=S.Key.tletcompare=Type.(unstage(compareS.Key.t))lethash=S.Key.short_hashletequal=Type.(unstage(equalS.Key.t))endmoduleKSet=Set.Make(K)moduleKHashtbl=Hashtbl.Make(K)letread_parentstcommit=S.findtcommit>|=function|None->KSet.empty|Somec->KSet.of_list(S.Val.parentsc)letequal_keys=Type.(unstage(equalS.Key.t))letstr_keyk=String.sub(Type.to_stringS.Key.tk)04letpp_key=Fmt.of_to_stringstr_keyletpp_keysppfkeys=letkeys=KSet.elementskeysinFmt.pfppf"[%a]"Fmt.(list~sep:(unit" ")pp_key)keysletstr_keys=Fmt.to_to_stringpp_keysletlca_calls=ref0letrecunqueuetodoseen=ifQueue.is_emptytodothenNoneelselet((_,commit)aspop)=Queue.poptodoinifKSet.memcommitseenthenunqueuetodoseenelseSomepop(* Traverse the graph of commits using a breadth first search
strategy. Start by visiting the commits in [init] and stops
either when [check] returns [`Stop] or when all the ancestors of
[init] have been visited. *)lettraverse_bfst~f~pp:_~check~init~return=lettodo=Queue.create()inletadd_tododx=Queue.add(d,x)todoinKSet.iter(add_todo0)init;letrecauxseen=matchcheck()with|(`Too_many_lcas|`Max_depth_reached)asx->Lwt.return(Errorx)|`Stop->return()|`Continue->(matchunqueuetodoseenwith|None->return()|Some(depth,commit)->(* Log.debug "lca %d: %s.%d %a"
!lca_calls (pp_key commit) depth force (pp ()); *)letseen=KSet.addcommitseeninlet*parents=read_parentstcommitinlet()=fdepthcommitparentsinletparents=KSet.diffparentsseeninKSet.iter(add_todo(depth+1))parents;auxseen)inauxKSet.empty(* Initially the first node is marked as [Seen1] and the second as [Seen2].
Marks are updated as the search progresses, and may change. *)typemark=|Seen1(* reachable from the first commit *)|Seen2(* reachable from the second commit *)|SeenBoth(* reachable from both, but below an LCA *)|LCA(* reachable from both; candidate for the answer set *)let_pp_mark=function|Seen1->"seen1"|Seen2->"seen2"|SeenBoth->"seenBoth"|LCA->"LCA"(* Exploration state *)typestate={marks:markKHashtbl.t;(* marks of commits already explored *)parents:KSet.tKHashtbl.t;(* parents of commits already explored *)layers:(int,KSet.t)Hashtbl.t;(* layers of commit, sorted by depth *)c1:S.key;(* initial state 1 *)c2:S.key;(* initial state 2 *)mutabledepth:int;(* the current exploration depth *)mutablelcas:int;(* number of commit marked with LCA *)mutablecomplete:bool;(* is the exploration complete? *)}letpp_statet=lazy(letppm=KHashtbl.fold(funkvacc->ifv=mthenstr_keyk::accelseacc)t.marks[]|>String.concat" "inFmt.strf"d: %d, seen1: %s, seen2: %s, seenboth: %s, lcas: %s (%d) %s"t.depth(ppSeen1)(ppSeen2)(ppSeenBoth)(ppLCA)t.lcas(String.concat" | "(Hashtbl.fold(fundksacc->Fmt.strf"(%d: %s)"d(str_keysks)::acc)t.layers[])))letget_mark_exntelt=KHashtbl.findt.markseltletget_marktelt=trySome(get_mark_exntelt)withNot_found->Noneletset_markteltmark=KHashtbl.replacet.markseltmarkletget_layertd=tryHashtbl.findt.layersdwithNot_found->KSet.emptyletadd_to_layertdk=Hashtbl.replacet.layersd(KSet.addk(get_layertd))letadd_parenttcp=KHashtbl.addt.parentscpletget_parenttc=tryKHashtbl.findt.parentscwithNot_found->KSet.emptyletincr_lcast=t.lcas<-t.lcas+1letdecr_lcast=t.lcas<-t.lcas-1letboth_seentk=matchget_marktkwith|None|SomeSeen1|SomeSeen2->false|_->trueletempty_statec1c2=lett={marks=KHashtbl.create10;parents=KHashtbl.create10;layers=Hashtbl.create10;c1;c2;depth=0;lcas=0;complete=false;}inset_marktc1Seen1;set_marktc2Seen2;t(* update the parent mark and keep the number of lcas up-to-date. *)letupdate_marktmarkcommit=letnew_mark=match(mark,get_marktcommit)with|Seen1,SomeSeen1|Seen1,None->Seen1|Seen2,SomeSeen2|Seen2,None->Seen2|SeenBoth,SomeLCA->decr_lcast;SeenBoth|SeenBoth,_->SeenBoth|Seen1,SomeSeen2|Seen2,SomeSeen1->incr_lcast;LCA|_,SomeLCA->LCA|_->SeenBothin(* check for fast-forwards *)letis_init()=equal_keyscommitt.c1||equal_keyscommitt.c2inletis_shared()=new_mark=SeenBoth||new_mark=LCAinifis_shared()&&is_init()then(Log.debug(funf->f"fast-forward");t.complete<-true);set_marktcommitnew_mark;new_mark(* update the ancestors which have already been visisted. *)letupdate_ancestors_markstmarkcommit=lettodo=Queue.create()inQueue.addcommittodo;letrecloopmark=ifQueue.is_emptytodothen()elseleta=Queue.poptodoinletold_mark=get_marktainletmark=update_marktmarkainlet()=matchold_markwith|Some(SeenBoth|LCA)->()(* Can't be an LCA lower down *)|Someoldwhenold=mark->()(* No change *)|_->KSet.iter(funx->Queue.pushxtodo)(get_parentta)inloop(ifmark=LCAthenSeenBothelsemark)inloopmark(* We are looking for LCAs, doing a breadth-first-search from the two starting commits.
This is called each time we visit a new commit. *)letupdate_parentstdepthcommitparents=add_parenttcommitparents;add_to_layertdepthcommit;ifdepth<>t.depththen(assert(depth=t.depth+1);(* before starting to explore a new layer, check if we really
have some work to do, ie. do we still have a commit seen only
by one node? *)letlayer=get_layertt.depthinletcomplete=KSet.for_all(both_seent)layerinifcompletethent.complete<-trueelset.depth<-depth);letmark=get_mark_exntcommitinKSet.iter(update_ancestors_markstmark)parentsletlcast=KHashtbl.fold(funkvacc->ifv=LCAthenk::accelseacc)t.marks[]letcheck~max_depth~nt=ift.depth>max_depththen`Max_depth_reachedelseift.lcas>nthen`Too_many_lcaselseift.lcas=n||t.completethen`Stopelse`Continueletlcast?(max_depth=max_int)?(n=max_int)c1c2=incrlca_calls;ifmax_depth<0thenLwt.return(Error`Max_depth_reached)elseifn<=0thenLwt.return(Error`Too_many_lcas)elseifequal_keysc1c2thenLwt.return(Ok[c1])elseletinit=KSet.of_list[c1;c2]inlets=empty_statec1c2inletcheck()=check~max_depth~nsinletpp()=pp_statesinletreturn()=Lwt.return(Ok(lcass))inlett0=Sys.time()inLwt.finalize(fun()->traverse_bfst~f:(update_parentss)~pp~check~init~return)(fun()->lett1=Sys.time()-.t0inLog.debug(funf->f"lcas %d: depth=%d time=%.4fs"!lca_callss.deptht1);Lwt.return_unit)letrecthree_way_merget~info?max_depth?nc1c2=Log.debug(funf->f"3-way merge between %a and %a"pp_keyc1pp_keyc2);ifequal_keysc1c2thenMerge.okc1elselet*lcas=lcast?max_depth?nc1c2inletold()=matchlcaswith|Error`Too_many_lcas->Merge.conflict"Too many lcas"|Error`Max_depth_reached->Merge.conflict"Max depth reached"|Ok[]->Merge.okNone(* no common ancestor *)|Ok(old::olds)->letrecauxacc=function|[]->Merge.ok(Someacc)|old::olds->three_way_merget~infoaccold>>=*funacc->auxaccoldsinauxoldoldsinletmerge=merget~info|>Merge.with_conflict(funmsg->Fmt.strf"Recursive merging of common ancestors: %s"msg)|>Merge.finmerge~oldc1c2letlca_auxt~info?max_depth?nc1c2=ifequal_keysc1c2thenMerge.ok(Somec1)elselcast?max_depth?nc1c2>>=function|Error`Too_many_lcas->Merge.conflict"Too many lcas"|Error`Max_depth_reached->Merge.conflict"Max depth reached"|Ok[]->Merge.okNone(* no common ancestor *)|Ok[x]->Merge.ok(Somex)|Ok(c::cs)->letrecauxacc=function|[]->Merge.ok(Someacc)|c::cs->(three_way_merget~info?max_depth?naccc>>=function|Error(`Conflict_)->Merge.okNone|Okacc->auxacccs)inauxccsletreclcat~info?max_depth?n=function|[]->Merge.conflict"History.lca: empty"|[c]->Merge.ok(Somec)|c1::c2::cs->(lca_auxt~info?max_depth?nc1c2>>=*function|None->Merge.okNone|Somec->lcat~info?max_depth?n(c::cs))endmoduleV1(C:S)=structmoduleK=structleth=Type.string_of`Int64lethash_to_bin_string=Type.(unstage(to_bin_stringC.hash_t))lethash_of_bin_string=Type.(unstage(of_bin_stringC.hash_t))letsize_of=letsize_of=Type.(unstage(size_ofh))inType.stage(funx->size_of(hash_to_bin_stringx))letencode_bin=letencode_bin=Type.(unstage(encode_binh))inType.stage@@funek->encode_bin(hash_to_bin_stringe)kletdecode_bin=letdecode_bin=Type.(unstage(decode_binh))inType.stage@@funbufoff->letn,v=decode_binbufoffin(n,matchhash_of_bin_stringvwith|Okv->v|Error(`Msge)->Fmt.failwith"decode_bin: %s"e)lett=Type.likeC.hash_t~bin:(encode_bin,decode_bin,size_of)endtypehash=C.hash[@@derivingirmin]typet={parents:hashlist;c:C.t}letimportc={c;parents=C.parentsc}letexportt=t.cletnodet=C.nodet.cletparentst=t.parentsletinfot=C.infot.cletv~info~node~parents={parents;c=C.v~node~parents~info}letmake=vletinfo_t:Info.tType.t=letopenTypeinrecord"info"(fundateauthormessage->Info.v~date~authormessage)|+field"date"int64(funt->Info.datet)|+field"author"(string_of`Int64)(funt->Info.authort)|+field"message"(string_of`Int64)(funt->Info.messaget)|>sealrlett:tType.t=letopenTypeinrecord"commit"(funnodeparentsinfo->make~info~node~parents)|+field"node"K.tnode|+field"parents"(list~len:`Int64K.t)parents|+field"info"info_tinfo|>sealrend