123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343(**************************************************************************)(* *)(* Copyright 2012-2019 OCamlPro *)(* Copyright 2012 INRIA *)(* *)(* All rights reserved. This file is distributed under the terms of the *)(* GNU Lesser General Public License version 2.1, with the special *)(* exception on linking described in the file LICENSE. *)(* *)(**************************************************************************)openOpamFilename.OpopenOpamProcess.Job.Op(* let log fmt = OpamConsole.log "GIT" fmt *)moduleVCS:OpamVCS.VCS=structletname=`gitletexistsrepo_root=OpamFilename.exists_dir(repo_root/".git")||OpamFilename.exists(repo_root//".git")letcygpath=OpamSystem.get_cygpath_function~command:"git"letgitrepo_root=letdir=OpamFilename.Dir.to_stringrepo_rootin(* If the ?env arg is restored here, then the caching for the Cygwin-ness
of git will need to change, as altering PATH could select a different
Git *)fun?verbose?stdoutargs->OpamSystem.make_command~dir?verbose?stdout"git"argsletinitrepo_rootrepo_url=OpamFilename.mkdirrepo_root;OpamProcess.Job.of_list[gitrepo_root["init"];(* Enforce this option, it can break our use of git if set *)gitrepo_root["config";"--local";"fetch.prune";"false"];(* We reset diff.noprefix to ensure we get a `-p1` patch and avoid <https://github.com/ocaml/opam/issues/3627>. *)gitrepo_root["config";"--local";"diff.noprefix";"false"];(* Disable automatic line-ending conversion and switch core.eol to Unix.
THIS DOES NOT MEAN ALL FILES GET LF-ONLY LINE-ENDINGS!
This combination of settings means that files will be checked out
exactly as they appear in the repository, so if files are checked in
with CRLF line-endings (either by not having .gitattributes with
core.autocrlf = false, or having an explicit eol=crlf in
.gitattributes), then they will still be checked out with CRLF endings.
*)gitrepo_root["config";"--local";"core.autocrlf";"false"];gitrepo_root["config";"--local";"core.eol";"lf"];(* Document the remote for user-friendliness (we don't use it) *)gitrepo_root["remote";"add";"origin";OpamUrl.base_urlrepo_url];]@@+function|None->Done()|Some(_,err)->OpamSystem.process_errorerrletremote_refurl=matchurl.OpamUrl.hashwith|Someh->"refs/remotes/opam-ref-"^h|None->"refs/remotes/opam-ref"letfetch?cache_dir?subpathrepo_rootrepo_url=(matchsubpathwith|Somesp->gitrepo_root["config";"--local";"core.sparseCheckout";"true"]@@>funr->OpamSystem.raise_on_process_errorr;OpamFilename.write(repo_root/".git"/"info"//"sparse-checkout")sp;Done()|None->Done())@@+fun_->(matchcache_dirwith|SomecwhenOpamUrl.local_dirrepo_url=None->letdir=c/"git"inifnot(OpamFilename.exists_dirdir)then(OpamFilename.mkdirdir;gitdir["init";"--bare"]@@>funr->OpamSystem.raise_on_process_errorr;Done(Somedir))elseDone(Somedir)|_->DoneNone)@@+funglobal_cache->letrepo_url=OpamUrl.map_file_url(Lazy.forcecygpath)repo_urlinletorigin=OpamUrl.base_urlrepo_urlinletbranch=OpamStd.Option.default"HEAD"repo_url.OpamUrl.hashinletopam_ref=remote_refrepo_urlinletrefspec=Printf.sprintf"+%s:%s"branchopam_refingitrepo_root["remote";"set-url";"origin";origin]@@>fun_->OpamStd.Option.iter(funcache->letalternates=repo_root/".git"/"objects"/"info"//"alternates"inifnot(OpamFilename.existsalternates)thenOpamFilename.writealternates(OpamFilename.Dir.to_string(cache/"objects")))global_cache;gitrepo_root["fetch";"-q";origin;"--update-shallow";refspec]@@>funr->ifOpamProcess.check_success_and_cleanuprthenletrefspec=Printf.sprintf"+%s:refs/remotes/%s"opam_ref(Digest.to_hex(Digest.string(OpamUrl.to_stringrepo_url)))inmatchglobal_cachewith|Somecache->gitrepo_root["push";OpamFilename.Dir.to_stringcache;refspec]@@>fun_->Done()|None->Done()else(* fallback to fetching all first (workaround, git 2.1 fails silently
on 'fetch HASH' when HASH isn't available locally already).
Also, remove the [--update-shallow] option in case git is so old that
it didn't exist yet, as that is not needed in the general case *)gitrepo_root["fetch";"-q"]@@>funr->OpamSystem.raise_on_process_errorr;(* retry to fetch the specific branch *)gitrepo_root["fetch";"-q";origin;refspec]@@>funr->ifOpamProcess.check_success_and_cleanuprthenDone()elseifOpamStd.String.fold_left(funaccc->matchacc,cwith|true,('0'..'9'|'a'..'f'|'A'..'F')->true|_->false)truebranchthen(* the above might still fail on raw, untracked hashes: try to bind to
the direct refspec, if found *)(gitrepo_root["update-ref";opam_ref;branch]@@>funr->ifOpamProcess.check_success_and_cleanuprthenDone()else(* check if the commit exists *)(gitrepo_root["fetch";"-q"]@@>funr->OpamSystem.raise_on_process_errorr;gitrepo_root["show";"-s";"--format=%H";branch]@@>funr->ifOpamProcess.check_success_and_cleanuprthenfailwith"Commit found, but unreachable: enable uploadpack.allowReachableSHA1InWant on server"elsefailwith"Commit not found on repository"))elseleterror=ringitrepo_root["ls-files"]@@>function|{OpamProcess.r_code=0;OpamProcess.r_stdout=[];_}->gitrepo_root["show"]@@>funr->ifOpamProcess.is_failurerthenfailwith"Git repository seems just initialized, \
try again after your first commit"elseOpamSystem.process_errorerror|_->OpamSystem.process_errorerrorletrevisionrepo_root=gitrepo_root~verbose:false["rev-parse";"HEAD"]@@>funr->ifr.OpamProcess.r_code=128then(OpamProcess.cleanup~force:truer;DoneNone)else(OpamSystem.raise_on_process_errorr;matchr.OpamProcess.r_stdoutwith|[]->DoneNone|full::_->ifString.lengthfull>8thenDone(Some(String.subfull08))elseDone(Somefull))letreset_treerepo_rootrepo_url=letrref=remote_refrepo_urlingitrepo_root["reset";"--hard";rref;"--"]@@>funr->ifOpamProcess.is_failurerthenOpamSystem.internal_error"Git error: %s not found."rrefelsegitrepo_root["clean";"-fdx"]@@>funr->ifOpamProcess.is_failurerthenOpamSystem.internal_error"Git error: %s not found."rrefelseifOpamFilename.exists(repo_root//".gitmodules")thengitrepo_root["submodule";"update";"--init";"--recursive"]@@>funr->ifOpamProcess.is_failurerthenOpamConsole.warning"Git submodule update failed in %s"(OpamFilename.Dir.to_stringrepo_root);Done()elseDone()letpatch_applied__=(* This might be a good place to do 'git reset --soft' and check for
unstaged changes. See <https://github.com/ocaml/opam/pull/3283>. *)Done()letdiffrepo_rootrepo_url=letrref=remote_refrepo_urlinletpatch_file=OpamSystem.temp_file~auto_clean:false"git-diff"inletfinalise()=OpamSystem.remove_filepatch_fileinOpamProcess.Job.catch(fune->finalise();raisee)@@fun()->gitrepo_root["add";"."]@@>funr->(* Git diff is to the working dir, but doesn't work properly for
unregistered directories. *)OpamSystem.raise_on_process_errorr;(* We also reset diff.noprefix here to handle already existing repo. *)gitrepo_root~stdout:patch_file["-c";"diff.noprefix=false";"diff";"--text";"--no-ext-diff";"-R";"-p";rref;"--"]@@>funr->ifnot(OpamProcess.check_success_and_cleanupr)then(finalise();OpamSystem.internal_error"Git error: %s not found."rref)elseifOpamSystem.file_is_emptypatch_filethen(finalise();DoneNone)elseDone(Some(OpamFilename.of_stringpatch_file))letis_up_to_daterepo_rootrepo_url=letrref=remote_refrepo_urlingitrepo_root["diff";"--no-ext-diff";"--quiet";rref;"--"]@@>function|{OpamProcess.r_code=0;_}->Donetrue|{OpamProcess.r_code=1;_}asr->OpamProcess.cleanup~force:truer;Donefalse|r->OpamSystem.process_errorrletversioned_filesrepo_root=gitrepo_root~verbose:false["ls-files"]@@>funr->OpamSystem.raise_on_process_errorr;Doner.OpamProcess.r_stdoutletvc_dirrepo_root=OpamFilename.Op.(repo_root/".git")letcurrent_branchdir=gitdir["symbolic-ref";"--quiet";"--short";"HEAD"]@@>function|{OpamProcess.r_code=0;OpamProcess.r_stdout=[s];_}->Done(Somes)|_->Done(Some"HEAD")letis_dirty?subpathdir=letsubpath=matchsubpathwith|None->[]|Somedir->["--";dir]ingitdir(["diff";"--no-ext-diff";"--quiet";"HEAD"]@subpath)@@>function|{OpamProcess.r_code=0;_}->(gitdir["ls-files";"--others";"--exclude-standard"]@@>function|{OpamProcess.r_code=0;OpamProcess.r_stdout=[];_}->Donefalse|{OpamProcess.r_code=0;_}|{OpamProcess.r_code=1;_}asr->OpamProcess.cleanup~force:truer;Donetrue|r->OpamSystem.process_errorr)|{OpamProcess.r_code=1;_}asr->OpamProcess.cleanup~force:truer;Donetrue|r->OpamSystem.process_errorrletmodified_filesrepo_root=gitrepo_root~verbose:false["status";"--short"]@@>funr->OpamSystem.raise_on_process_errorr;letfiles=OpamStd.List.filter_map(funline->matchOpamStd.String.splitline' 'with|("A"|"M"|"AM")::file::[]|("R"|"RM"|"C"|"CM")::_::"->"::file::[]->Somefile|_->None)r.OpamProcess.r_stdoutinDonefilesletorigin="origin"(** check if a hash or branch is present in remote origin and returns *)letcheck_remoterepo_roothash_or_b=letis_hexstr=OpamStd.String.fold_left(funhexch->hex&&matchchwith|'0'..'9'|'A'..'F'|'a'..'f'->true|_->false)truestrin(* get the hash of the branch *)lethash=gitrepo_root["branch"]@@>funr->ifOpamProcess.is_successrthenletis_branch=List.exists(OpamStd.String.contains~sub:hash_or_b)r.r_stdoutinifis_branchthengitrepo_root["rev-list";hash_or_b;"-1"]@@>funr->ifOpamProcess.is_successrthen(matchList.filteris_hexr.r_stdoutwith|[hash]->Done(Somehash)|_->DoneNone)elseDoneNoneelseifis_hexhash_or_bthenDone(Somehash_or_b)elseDoneNoneelseDoneNoneinhash@@+function|Somehash->(* check if hash / branch is present in remote *)(gitrepo_root["branch";"-r";"--contains";hash]@@>function|{OpamProcess.r_code=0;_}asr->ifr.r_stdout<>[]&&(List.exists(OpamStd.String.contains~sub:origin)r.r_stdout)thenDone(Somehash_or_b)elseDoneNone|{OpamProcess.r_code=1;_}->DoneNone|r->OpamSystem.process_errorr)|None->DoneNoneletget_remote_url?hashrepo_root=gitrepo_root["remote";"get-url";origin]@@>function|{OpamProcess.r_code=0;OpamProcess.r_stdout=[url];_}->(letu=OpamUrl.parse~backend:`giturlinifOpamUrl.local_diru<>NonethenDoneNoneelselethash_in_remote=matchhashwith|None->(current_branchrepo_root@@+function|None|Some"HEAD"->DoneNone|Somehash->check_remoterepo_roothash)|Somehash->check_remoterepo_roothashinhash_in_remote@@+function|Some_ashash->Done(Some{uwithOpamUrl.hash=hash})|None->Done(Some{uwithOpamUrl.hash=None}))|{OpamProcess.r_code=0;_}|{OpamProcess.r_code=1;_}->DoneNone|r->OpamSystem.process_errorrendmoduleB=OpamVCS.Make(VCS)