Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file hg_private.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597openCoremoduleTime=Time_float_unixmoduleNode=structmodulePublic=structtypet={global_id:string;local_revision:int}[@@derivingsexp,fields,compare]endincludePublicendmoduleChangeset_info=structmodulePublic=structtypet={node:Node.t;parents:[`Zero|`OneofNode.t|`TwoofNode.t*Node.t];author:string;time:Time.t;tags:stringlist;description:string;files:[`Omitted|`Filesofstringlist][@sexp.default`Omitted][@sexp_drop_default.equal]}[@@derivingsexp,fields,compare]letto_hg_style_stringt=letnode{Node.global_id;local_revision}=sprintf"%i:%s"local_revision(String.subglobal_id~pos:0~len:12)inletlinelabelvalue=sprintf"%-13s%s"(label^":")valueinletparents=matcht.parentswith|`Zero->[]|`Onep->[p]|`Two(p1,p2)->[p1;p2]in[[line"changeset"(nodet.node)];List.mapt.tags~f:(funtag->line"tag"tag);List.mapparents~f:(funparent->line"parent"(nodeparent));[line"user"t.author;line"date"(Time.to_stringt.time);line"summary"t.description]]|>List.concat|>String.concat~sep:"\n";;endincludePublicmoduleTemplate=structtypet={include_files:bool}letto_string{include_files}=["{node} {rev}";"{p1.node} {p1.rev}";"{p2.node} {p2.rev}";"{author|emailuser}";"{date|hgdate}";"{tags}";(ifinclude_filesthen{|{join(files, '\000')}|}else"");"{desc|tabindent}";""]|>String.concat~sep:"\\n";;endlettime_of_hgtimehgdate=(* The format is '<unix timestamp of commit> <timezone offset of commit in
seconds>'. We only need the second part if we want to know the where the commit
happened. *)matchString.splithgdate~on:' 'with|[unix_timestamp;_zone_offset_in_seconds]->Time.of_span_since_epoch(Time.Span.of_sec(Float.of_stringunix_timestamp))|_->failwithf"Bad hgdate value '%s' hgdate"hgdate();;let%test_unit_=[%test_result:Time.t](time_of_hgtime"1429736177 14400")~expect:(Time.of_string"2015-04-22 16:56:17-04:00");;let%test_unit_=[%test_result:Time.t](time_of_hgtime"1429715393 -3600")~expect:(Time.of_string"2015-04-22 16:09:53+01:00");;letof_templated_stdoutstdout{Template.include_files}=(* Every log entry has a newline appended to it. So [String.split ~on:'\n'] will
necessarily give us an extra empty chunk. This is true even for empty input.
Using [String.split_lines] would maybe be nicer, but it also splits on \r\n so it
loses information. *)letlines=String.split~on:'\n'stdout|>List.rev|>(function|""::rest->rest|_lines->failwith"Does not end with a newline character")|>List.revinletrecauxacclines=matchlineswith|[]->List.revacc|node::p1::p2::author::time::tags::files::first_desc::tl->letend_desc,remainder=List.split_whiletl~f:(funline->(* tabindent does *not* change blank lines to "\t", which is a little
annoying *)String.is_emptyline||Char.equalline.[0]'\t')inletdescription=String.concat~sep:"\n"(first_desc::List.mapend_desc~f:String.strip)inletnode_of_stringstr=letglobal_id,local_revision=String.lsplit2_exn~on:' 'strinletlocal_revision=Int.of_stringlocal_revisionin{Node.global_id;local_revision}inletfiles=matchinclude_fileswith|false->`Omitted|true->(matchfileswith|""->`Files[]|files_str->`Files(String.splitfiles_str~on:'\000'))inletnode=node_of_stringnodeinletparents=letp1_node=node_of_stringp1inletp2_node=node_of_stringp2inmatchp1_node.local_revision,p2_node.local_revisionwith|-1,-1->`Zero|_,-1->`Onep1_node|_,_->`Two(p1_node,p2_node)inletchangeset={node;parents;author;time=time_of_hgtimetime;tags=String.split~on:' 'tags|>List.filter~f:(funs->not(String.is_emptys));description;files}inaux(changeset::acc)remainder|_->failwith"Unexpected number of lines"inOr_error.tag_arg(Or_error.try_with(fun()->aux[]lines))"Malformed output"stdoutsexp_of_string;;let%test_unit_=letstdout="0123456789abcdef0123456789abcdef01234567 4\n\
fedcba9876543210fedcba9876543210fedcba98 2\n\
123454321098767890abcdefedcba12345432109 3\n\
username\n\
1609662832 18000\n\
first-tag second-tag third-tag\n\
fakefilename.txt\n\
rebase to [123454321098] with ancestor [fedcba987654]\n\
fedcba9876543210fedcba9876543210fedcba98 2\n\
fedcbabcdef678909876543212345fedcbabcdef 1\n\
0000000000000000000000000000000000000000 -1\n\
username\n\
1609511270 18000\n\n\
fakefilename2.txt\n\
some commit description\n"inlett1={node={global_id="0123456789abcdef0123456789abcdef01234567";local_revision=4};parents=`Two({global_id="fedcba9876543210fedcba9876543210fedcba98";local_revision=2},{global_id="123454321098767890abcdefedcba12345432109";local_revision=3});author="username";time=Time.of_string"2021-01-03 03:33:52-05:00";tags=["first-tag";"second-tag";"third-tag"];description="rebase to [123454321098] with ancestor [fedcba987654]";files=`Files["fakefilename.txt"]}inlett2={node={global_id="fedcba9876543210fedcba9876543210fedcba98";local_revision=2};parents=`One{global_id="fedcbabcdef678909876543212345fedcbabcdef";local_revision=1};author="username";time=Time.of_string"2021-01-01 09:27:50-05:00";tags=[];description="some commit description";files=`Files["fakefilename2.txt"]}in[%test_result:tlistOr_error.t](of_templated_stdoutstdout{include_files=true})~expect:(Ok[t1;t2]);[%test_result:string]([t1;t2]|>List.map~f:to_hg_style_string|>String.concat~sep:"\n\n")~expect:"changeset: 4:0123456789ab\n\
tag: first-tag\n\
tag: second-tag\n\
tag: third-tag\n\
parent: 2:fedcba987654\n\
parent: 3:123454321098\n\
user: username\n\
date: 2021-01-03 03:33:52.000000-05:00\n\
summary: rebase to [123454321098] with ancestor [fedcba987654]\n\n\
changeset: 2:fedcba987654\n\
parent: 1:fedcbabcdef6\n\
user: username\n\
date: 2021-01-01 09:27:50.000000-05:00\n\
summary: some commit description";;let%test_unit"null bytes separating files"=letstdout="0123456789abcdef0123456789abcdef01234567 4\n\
fedcba9876543210fedcba9876543210fedcba98 2\n\
123454321098767890abcdefedcba12345432109 3\n\
username\n\
1609662832 18000\n\
first-tag second-tag third-tag\n\
fakefilename.txt\000fakefilename2.txt\n\
rebase to [123454321098] with ancestor [fedcba987654]\n"inlett1={node={global_id="0123456789abcdef0123456789abcdef01234567";local_revision=4};parents=`Two({global_id="fedcba9876543210fedcba9876543210fedcba98";local_revision=2},{global_id="123454321098767890abcdefedcba12345432109";local_revision=3});author="username";time=Time.of_string"2021-01-03 03:33:52-05:00";tags=["first-tag";"second-tag";"third-tag"];description="rebase to [123454321098] with ancestor [fedcba987654]";files=`Files["fakefilename.txt";"fakefilename2.txt"]}in[%test_result:tlistOr_error.t](of_templated_stdoutstdout{include_files=true})~expect:(Ok[t1]);;let%test_unit"heads in empty repo"=letstdout="0000000000000000000000000000000000000000 -1\n\
0000000000000000000000000000000000000000 -1\n\
0000000000000000000000000000000000000000 -1\n\n\
0 0\n\n\n\n"inletexpected={node={global_id="0000000000000000000000000000000000000000";local_revision=-1};parents=`Zero;author="";time=Time.epoch;tags=[];description="";files=`Omitted}in[%test_result:tlistOr_error.t](of_templated_stdoutstdout{include_files=false})~expect:(Ok[expected]);[%test_result:tlistOr_error.t](of_templated_stdoutstdout{include_files=true})~expect:(Ok[{expectedwithfiles=`Files[]}]);;let%test_unit"empty stdout"=letstdout=""in[%test_result:tlistOr_error.t](of_templated_stdoutstdout{include_files=false})~expect:(Ok[]);;endmoduleBookmark=structmodulePublic=structtypet={active:bool;name:string;revision_id:string}[@@derivingsexp,fields]endincludePublicletof_lines=function|["no bookmarks set"]->[]|bookmark_lines->List.mapbookmark_lines~f:(funline->letactive=Char.equalline.[1]'*'in(* get rid of the space where the asterisk may appear *)letline=String.sliceline30inletraw_name,revision=String.rsplit2_exnline~on:' 'inletname=String.stripraw_nameinlet_,revision_id=String.rsplit2_exnrevision~on:':'in{active;name;revision_id});;endmoduleTag=structmodulePublic=structtypet={tag:string;revision_num:int;revision_id:string}[@@derivingsexp,fields]endincludePublicletof_lineline=lettag=String.splitline~on:' '|>List.filter~f:(funl->not(String.is_emptyl))inmatchtagwith|[]->None|[tag;rev]->letrevision_num,revision_id=String.rsplit2_exnrev~on:':'inletrevision_num=Int.of_stringrevision_numinSome{tag;revision_num;revision_id}|_->failwithf"unexpected hg output: '%s'"line();;endmoduleFile_status=structmodulePublic=structtypet=|Modifiedofstring|Addedofstring|Removedofstring|Copiedof{src:string;dst:[`New_fileofstring|`Overwrittenofstring]}|Missingofstring|Not_trackedofstring[@@derivingsexp]endincludePublicend(** See "hg help dates". *)moduleTime_with_utc_offset=structtypet={time:Time.t;utc_offset:Time.Span.t}letof_time_with_zone~zonetime={time;utc_offset=Time.utc_offset~zonetime}letto_stringt=letsecs_since_epoch=Time.to_span_since_epocht.time|>Time.Span.to_sec|>Float.iround_towards_zero_exninletsecs_west_of_utc=t.utc_offset|>Time.Span.neg|>Time.Span.to_sec|>Float.iround_towards_zero_exninsprintf"%d %d"secs_since_epochsecs_west_of_utc;;let%expect_test"to_string"=(*
$ TZ=Etc/Utc date --date '2001-02-03 04:05:06' +%s
981173106
*)lettime=Time.of_string_with_utc_offset"2001-02-03 04:05:06Z"inof_time_with_zone~zone:Time.Zone.utctime|>to_string|>print_endline;[%expect{| 981173106 0 |}];of_time_with_zone~zone:(Time.Zone.of_string"America/New_York")time|>to_string|>print_endline;[%expect{| 981173106 18000 |}];;endmoduleDate_param=structmodulePublic=structmoduleTime_point=structtypet=|DateofDate.t|TimeofTime.t[@@derivingsexp]endtypet=|ExactofTime_point.t|On_or_beforeofTime_point.t|On_or_afterofTime_point.t|Inclusive_rangeof{from:Time_point.t;to_:Time_point.t}[@@derivingsexp]endincludePublicmoduleTime_point=structincludeTime_pointletto_string=function|Datedate->Date.to_stringdate|Timetime->String.concat[Time.to_sec_string~zone:Time.Zone.utctime;" UTC"];;endletto_string=function|Exacttime->sprintf!"%{Time_point}"time|On_or_beforetime->sprintf!"<%{Time_point}"time|On_or_aftertime->sprintf!">%{Time_point}"time|Inclusive_range{from;to_}->sprintf!"%{Time_point} to %{Time_point}"fromto_;;let%expect_test"Date serializes correctly"=letfrom_date=Date.create_exn~y:2005~m:Month.Apr~d:30|>Time_point.Dateinletto_date=Date.create_exn~y:2017~m:Month.Dec~d:1|>Time_point.Dateinletfrom_time=Time_point.TimeTime.epochinletto_time=Time.of_date_ofday~zone:(Time.Zone.of_utc_offset~hours:(-4))(Date.create_exn~y:2019~m:Mar~d:28)(Time.Ofday.create~hr:14~min:32~sec:20~ms:412~us:15~ns:14())|>Time_point.Timeinto_string(Inclusive_range{from=from_date;to_=to_date})|>print_endline;[%expect{| 2005-04-30 to 2017-12-01 |}];to_string(Inclusive_range{from=from_time;to_=to_time})|>print_endline;[%expect{| 1970-01-01 00:00:00 UTC to 2019-03-28 18:32:20 UTC |}];to_string(Inclusive_range{from=from_date;to_=to_time})|>print_endline;[%expect{| 2005-04-30 to 2019-03-28 18:32:20 UTC |}];to_string(Exactto_time)|>print_endline;[%expect{| 2019-03-28 18:32:20 UTC |}];to_string(Exactto_date)|>print_endline;[%expect{| 2017-12-01 |}];to_string(On_or_beforeto_time)|>print_endline;[%expect{| <2019-03-28 18:32:20 UTC |}];to_string(On_or_beforeto_date)|>print_endline;[%expect{| <2017-12-01 |}];to_string(On_or_afterfrom_time)|>print_endline;[%expect{| >1970-01-01 00:00:00 UTC |}];to_string(On_or_afterfrom_date)|>print_endline;[%expect{| >2005-04-30 |}];;endmoduleDestination=structmodulePublic=structtype_t=|String:stringt|File:string->unittendincludePubliclethandle_output(typea)(t:at)(o:Async.Process.Output.t):a=matchtwith|File_->()|String->o.stdout;;end(** This is just a wrapper around [Or_error.t]. It exists because it is for very simple
errors (e.g. "unexpected exit status") which you should tag to produce better errors
containing, e.g., the final list of arguments passed to hg. *)moduleOr_simple_error=structmodulePublic=structtype'at='aOr_error.tlettag=Or_error.tag_argletsimple_error=Fn.idendincludePublicletcreate=Fn.idend(* Helpful functions for implementing hg command wrappers. *)moduleCommand_helpers=structletrepeatedname=function|None->[]|Somel->List.concat_mapl~f:(funarg->[name;arg]);;letbookmarks_args=repeated"--bookmark"letbranches_args=repeated"--branch"letexcludes_args=repeated"--exclude"letincludes_args=repeated"--include"letkeywords_args=repeated"--keyword"letoptions_args=repeated"--option"letrevs_args=repeated"--rev"letwith_arg'nameto_string=function|None->[]|Somearg->[name;to_stringarg];;letwith_argname=with_arg'nameFn.idletremotecmd_args=with_arg"--remotecmd"letrev_args=with_arg"--rev"letssh_args=with_arg"--ssh"lettemplate_argsflag=letarg=matchflagwith|`Customtemplate->template|`For_changeset_infotemplate->Some(Changeset_info.Template.to_stringtemplate)inwith_arg"--template"arg;;letdate_range_args=with_arg'"--date"Date_param.to_stringlettime_args=with_arg'"--date"Time_with_utc_offset.to_stringletlimit_args=with_arg'"--limit"Int.to_stringletsimilarity_args=with_arg'"--similarity"Int.to_stringletunified_args=with_arg'"--unified"Int.to_stringletno_argname=function|None->[]|Some()->[name];;letafter_args=no_arg"--after"letforce_args=no_arg"--force"letforget_args=no_arg"--forget"letinsecure_args=no_arg"--insecure"letall_args=no_arg"--all"(* common output handlers *)letnon_0_exit_erroroutput=Or_error.error"non-zero exit status"outputAsync.Process.Output.sexp_of_t;;letunexpected_exit_erroroutput=Or_error.error"unexpected exit status"outputAsync.Process.Output.sexp_of_t;;letexpect_0(o:Async.Process.Output.t)=matcho.exit_statuswith|Ok()->Ok()|Error_->non_0_exit_erroro;;letexpect_0_stdout(o:Async.Process.Output.t)=matcho.exit_statuswith|Ok()->Oko.stdout|Error_->non_0_exit_erroro;;letexpect_0_stdout_list(o:Async.Process.Output.t)=Or_error.map(expect_0_stdouto)~f:String.split_lines;;endmodulePublic=structmoduleNode=Node.PublicmoduleChangeset_info=Changeset_info.PublicmoduleBookmark=Bookmark.PublicmoduleTag=Tag.PublicmoduleFile_status=File_status.PublicmoduleDate_param=Date_param.PublicmoduleDestination=Destination.PublicmoduleOr_simple_error=Or_simple_error.Publicend