123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206(**************************************************************************)(* *)(* Copyright 2012-2015 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")letgitrepo_root=letdir=OpamFilename.Dir.to_stringrepo_rootinfun?verbose?env?stdoutargs->OpamSystem.make_command~dir?verbose?env?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"];(* 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_dirrepo_rootrepo_url=(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->letorigin=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"))elseOpamSystem.process_errorrletrevisionrepo_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."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";"--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_dirtydir=gitdir["diff";"--no-ext-diff";"--quiet"]@@>function|{OpamProcess.r_code=0;_}->Donefalse|{OpamProcess.r_code=1;_}asr->OpamProcess.cleanup~force:truer;Donetrue|r->OpamSystem.process_errorrendmoduleB=OpamVCS.Make(VCS)