@@ -550,6 +550,7 @@ def git_fetch(
550550 ref : str ,
551551 remote : str = "origin" ,
552552 tags : bool = False ,
553+ shallow : bool = False ,
553554 env : Optional [Dict [str , str ]] = None ,
554555):
555556 args = ["git" , "fetch" ]
@@ -558,6 +559,35 @@ def git_fetch(
558559 args .extend (["--tags" , "--force" ])
559560
560561 args .extend ([remote , ref ])
562+
563+ if shallow :
564+ # If we have a full sha, we can fetch it directly
565+ if re .match (r"^[a-f0-9]{40}$" , ref ):
566+ fetch_args = args [:2 ] + ["--depth=1" ] + args [2 :]
567+ ret = run_command (b"vcs" , fetch_args , cwd = destination_path , extra_env = env )
568+ if ret == 0 :
569+ return
570+
571+ # Otherwise we need to incrementally deepen the repo until we detect
572+ # the ref.
573+ for deepen in range (10 , 100 , 10 ):
574+ fetch_args = args [:2 ] + [f"--deepen={ deepen } " ] + args [2 :]
575+ run_command (b"vcs" , fetch_args , cwd = destination_path , extra_env = env )
576+
577+ # Check if the target ref exists, if not deepen further.
578+ ret = run_command (
579+ b"vcs" ,
580+ ["git" , "cat-file" , "-e" , "FETCH_HEAD" ],
581+ cwd = destination_path ,
582+ extra_env = env ,
583+ )
584+ if ret == 0 :
585+ return
586+
587+ print (f"unable to fetch { ref } from { remote } in shallow clone" )
588+ sys .exit (1 )
589+
590+ # Non-shallow repo
561591 retry_required_command (b"vcs" , args , cwd = destination_path , extra_env = env )
562592
563593
@@ -600,6 +630,19 @@ def _clean_git_checkout(destination_path):
600630 print_line (b"vcs" , b"successfully cleaned git checkout!\n " )
601631
602632
633+ def shortref (ref : str ) -> str :
634+ """Normalize a git ref to its short form.
635+
636+ Returns the ref unchanged if it's already in short form.
637+ """
638+ # Strip common ref prefixes
639+ for prefix in ("refs/heads/" , "refs/tags/" ):
640+ if ref .startswith (prefix ):
641+ return ref [len (prefix ) :]
642+
643+ return ref
644+
645+
603646def git_checkout (
604647 destination_path : str ,
605648 head_repo : str ,
@@ -609,6 +652,7 @@ def git_checkout(
609652 commit : Optional [str ],
610653 ssh_key_file : Optional [Path ],
611654 ssh_known_hosts_file : Optional [Path ],
655+ shallow : bool = False ,
612656):
613657 env = {
614658 # abort if transfer speed is lower than 1kB/s for 1 minute
@@ -652,16 +696,24 @@ def git_checkout(
652696 args = [
653697 "git" ,
654698 "clone" ,
655- base_repo if base_repo else head_repo ,
656- destination_path ,
657699 ]
658700
701+ if shallow :
702+ args .extend (["--depth=1" , "--no-checkout" ])
703+
704+ args .extend (
705+ [
706+ base_repo if base_repo else head_repo ,
707+ destination_path ,
708+ ]
709+ )
710+
659711 retry_required_command (b"vcs" , args , extra_env = env )
660712
661713 # First fetch the base_rev. This allows Taskgraph to compute the files
662714 # changed by the push.
663715 if base_rev and base_rev != NULL_REVISION :
664- git_fetch (destination_path , base_rev , env = env )
716+ git_fetch (destination_path , base_rev , shallow = shallow , env = env )
665717
666718 # Next fetch the head ref.
667719
@@ -676,16 +728,27 @@ def git_checkout(
676728
677729 # If a ref isn't provided, we fetch all refs from head_repo, which may be slow.
678730 target = ref if ref else "+refs/heads/*:refs/remotes/work/*"
679- git_fetch (destination_path , target , remote = head_repo , tags = tags , env = env )
731+ git_fetch (
732+ destination_path ,
733+ target ,
734+ remote = head_repo ,
735+ tags = tags ,
736+ shallow = shallow ,
737+ env = env ,
738+ )
739+
740+ # If we have a shallow clone and specific commit, we need to fetch it too.
741+ if shallow and commit and commit != ref :
742+ git_fetch (destination_path , commit , remote = head_repo , shallow = shallow , env = env )
680743
681744 args = [
682745 "git" ,
683746 "checkout" ,
684747 "-f" ,
685748 ]
686749
687- if ref :
688- args .extend (["-B" , ref ])
750+ if ref and ref != commit :
751+ args .extend (["-B" , shortref ( ref ) ])
689752
690753 # `git fetch` set `FETCH_HEAD` reference to the last commit of the desired branch
691754 args .append (commit if commit else "FETCH_HEAD" )
@@ -862,11 +925,17 @@ def add_vcs_arguments(parser, project, name):
862925 f"--{ project } -sparse-profile" ,
863926 help = f"Path to sparse profile for { name } checkout" ,
864927 )
928+ parser .add_argument (
929+ f"--{ project } -shallow-clone" ,
930+ action = "store_true" ,
931+ help = f"Use shallow clone for { name } " ,
932+ )
865933
866934
867935def collect_vcs_options (args , project , name ):
868936 checkout = getattr (args , f"{ project } _checkout" )
869937 sparse_profile = getattr (args , f"{ project } _sparse_profile" )
938+ shallow_clone = getattr (args , f"{ project } _shallow_clone" )
870939
871940 env_prefix = project .upper ()
872941
@@ -911,6 +980,7 @@ def collect_vcs_options(args, project, name):
911980 "repo-type" : repo_type ,
912981 "ssh-secret-name" : private_key_secret ,
913982 "pip-requirements" : pip_requirements ,
983+ "shallow-clone" : shallow_clone ,
914984 }
915985
916986
@@ -958,6 +1028,7 @@ def vcs_checkout_from_args(options):
9581028 revision ,
9591029 ssh_key_file ,
9601030 ssh_known_hosts_file ,
1031+ shallow = options .get ("shallow-clone" , False ),
9611032 )
9621033 elif options ["repo-type" ] == "hg" :
9631034 if not revision and not ref :
0 commit comments