2323from .github import GithubClient , GitHubPullRequest
2424from .nix import Attr , nix_build , nix_eval , nix_shell
2525from .report import Report
26- from .utils import System , current_system , die , info , sh , system_order_key , warn
26+ from .utils import System , current_system , die , info , system_order_key , warn
2727
2828if TYPE_CHECKING :
2929 import argparse
@@ -232,8 +232,8 @@ def _process_aliases_for_systems(self, system: str) -> set[str]:
232232 case _:
233233 return {system }
234234
235- def worktree_dir (self ) -> str :
236- return str (self .builddir .worktree_dir )
235+ def clone_dir (self ) -> str :
236+ return str (self .builddir .clone_dir )
237237
238238 def _render_markdown (self , content : str , max_length : int = 1000 ) -> None :
239239 """Render markdown content using glow if available, otherwise plain text."""
@@ -329,17 +329,15 @@ def _display_pr_info(self, pr: GitHubPullRequest, pr_number: int) -> None:
329329 print (f"{ '=' * 80 } \n " )
330330
331331 def git_merge (self , commit : str ) -> None :
332- res = git .run (
333- ["merge" , "--no-commit" , "--no-ff" , commit ], cwd = self .worktree_dir ()
334- )
332+ res = git .run (["merge" , "--no-commit" , "--no-ff" , commit ], cwd = self .clone_dir ())
335333 if res .returncode != 0 :
336- msg = f"Failed to merge { commit } into { self .worktree_dir ()} . git merge failed with exit code { res .returncode } "
334+ msg = f"Failed to merge { commit } into { self .clone_dir ()} . git merge failed with exit code { res .returncode } "
337335 raise NixpkgsReviewError (msg )
338336
339337 def git_checkout (self , commit : str ) -> None :
340- res = git .run (["checkout" , commit ], cwd = self .worktree_dir ())
338+ res = git .run (["checkout" , commit ], cwd = self .clone_dir ())
341339 if res .returncode != 0 :
342- msg = f"Failed to checkout { commit } in { self .worktree_dir ()} . git checkout failed with exit code { res .returncode } "
340+ msg = f"Failed to checkout { commit } in { self .clone_dir ()} . git checkout failed with exit code { res .returncode } "
343341 raise NixpkgsReviewError (msg )
344342
345343 def apply_uncommitted (self , changes : UncommittedChanges ) -> None :
@@ -362,10 +360,10 @@ def apply_uncommitted(self, changes: UncommittedChanges) -> None:
362360 sys .exit (0 )
363361
364362 info ("Applying `nixpkgs` diff..." )
365- result = git .run (["apply" ], cwd = self .worktree_dir (), stdin = diff )
363+ result = git .run (["apply" ], cwd = self .clone_dir (), stdin = diff )
366364
367365 if result .returncode != 0 :
368- die (f"Failed to apply diff in { self .worktree_dir ()} " )
366+ die (f"Failed to apply diff in { self .clone_dir ()} " )
369367
370368 def build_changes (
371369 self ,
@@ -380,8 +378,11 @@ def build_changes(
380378 `changes` can be a string naming a commit, or it can be one of the
381379 `UncommittedChanges` variants. `merge_commit`, if given, is used
382380 instead of repeating the merge of `changes` into `base_commit`.
381+
382+ The sandbox repository is expected to be set up and able to see both
383+ commits.
383384 """
384- self .git_worktree (base_commit )
385+ self .git_checkout (base_commit )
385386 changed_attrs : dict [System , set [str ]] = {}
386387
387388 if self .only_packages :
@@ -448,10 +449,42 @@ def build_changes(
448449
449450 return self .build (changed_attrs , self .build_args )
450451
451- def git_worktree (self , commit : str ) -> None :
452- res = git .run (["worktree" , "add" , self .worktree_dir (), commit ])
452+ def git_clone (
453+ self , repo : str , ref : str , * , shallow_depth : int | None = None
454+ ) -> None :
455+ # moral equivalent of git clone --revision=<new_head> --reference=. [--depth=...] <repo> <dir>
456+ # but it will work even with a shallow repository as reference
457+ # we need the isolation of a separate repository because --depth "irreversibly" mutates .git/shallow
458+ res = git .run (["init" ], self .clone_dir ())
459+ if res .returncode != 0 :
460+ msg = f"Failed to create sandbox repository for { ref } in { self .clone_dir ()} . git init failed with exit code { res .returncode } "
461+ raise NixpkgsReviewError (msg )
462+
463+ # really, we should be locking this file until the clone is disposed of
464+ shallow_original = resolve_git_dir () / "shallow"
465+ if shallow_original .exists ():
466+ shallow_target = Path (self .clone_dir ()) / ".git" / "shallow"
467+ shutil .copyfile (shallow_original , shallow_target )
468+
469+ alternates_path = (
470+ Path (self .clone_dir ()) / ".git" / "objects" / "info" / "alternates"
471+ )
472+ object_store_path = resolve_git_dir ().absolute () / "objects"
473+ with alternates_path .open ("a" ) as alternates :
474+ alternates .write (f"{ object_store_path } \n " )
475+
476+ fetch_args = ["fetch" , repo , f"+{ ref } :refs/heads/nixpkgs-review" ]
477+ if shallow_depth :
478+ fetch_args .append (f"--depth={ shallow_depth } " )
479+ # we should warn if shallow_depth is not set and the original repository is shallow (and there are commits to fetch)
480+ # in this case git fetch (really, upload-pack) acts stupidly and has a very high chance of both unshallowing the repository
481+ # AND taking forever to compute how to do it
482+ # (but it WILL work and we WILL have enough history to do any merges we need later, which is better than if we set shallow_depth incorrectly)
483+ # if the user wants something more efficient, they can kill us and do it themselves
484+
485+ res = git .run (fetch_args , self .clone_dir ())
453486 if res .returncode != 0 :
454- msg = f"Failed to add worktree for { commit } in { self .worktree_dir ()} . git worktree failed with exit code { res .returncode } "
487+ msg = f"Failed to fetch { ref } into sandbox repository { self .clone_dir ()} . git fetch failed with exit code { res .returncode } "
455488 raise NixpkgsReviewError (msg )
456489
457490 def build (
@@ -526,24 +559,26 @@ def build_pr(self, pr_number: int) -> dict[System, list[Attr]]:
526559 packages_per_system = (
527560 self ._fetch_packages_from_github_eval (pr ) if self ._use_github_eval else None
528561 )
529-
530- [merge_rev ] = fetch_refs (self .remote , pr ["merge_commit_sha" ], shallow_depth = 2 )
531- base_rev = git .verify_commit_hash (f"{ merge_rev } ^1" )
532- head_rev = git .verify_commit_hash (f"{ merge_rev } ^2" )
533-
534562 if self .only_packages :
535563 packages_per_system = {
536564 system : set (self .only_packages ) for system in self .systems
537565 }
538566
567+ merge_rev = pr ["merge_commit_sha" ]
568+ base_rev = pr ["base" ]["sha" ]
569+ head_rev = pr ["head" ]["sha" ]
570+
539571 if packages_per_system is None :
572+ self .git_clone (self .remote , merge_rev , shallow_depth = 2 )
540573 return self .build_changes (base_rev , head_rev , merge_rev )
541574
542575 match self .checkout :
543576 case CheckoutOption .MERGE :
544- self . git_worktree ( merge_rev )
577+ checkout_rev = merge_rev
545578 case CheckoutOption .COMMIT :
546- self .git_worktree (head_rev )
579+ checkout_rev = head_rev
580+ self .git_clone (self .remote , checkout_rev , shallow_depth = 1 )
581+ self .git_checkout (checkout_rev )
547582
548583 for system in list (packages_per_system .keys ()):
549584 if system not in self .systems :
@@ -622,10 +657,10 @@ def review_local(
622657 print_result : bool = False ,
623658 approve_pr : bool = False ,
624659 ) -> None :
625- branch_rev = fetch_refs (self .remote , branch )[ 0 ]
660+ self . git_clone (self .remote , branch )
626661 self .start_review (
627662 changes if isinstance (changes , str ) else None ,
628- self .build_changes (branch_rev , changes ),
663+ self .build_changes ("nixpkgs-review" , changes ),
629664 path ,
630665 print_result = print_result ,
631666 approve_pr = approve_pr ,
@@ -904,49 +939,6 @@ def resolve_git_dir() -> Path:
904939 raise NixpkgsReviewError (msg )
905940
906941
907- def fetch_refs (repo : str , * refs : str , shallow_depth : int = 1 ) -> list [str ]:
908- shallow = subprocess .run (
909- ["git" , "rev-parse" , "--is-shallow-repository" ],
910- text = True ,
911- stdout = subprocess .PIPE ,
912- check = False ,
913- )
914- if shallow .returncode != 0 :
915- msg = f"Failed to detect if { repo } is shallow repository"
916- raise NixpkgsReviewError (msg )
917-
918- fetch_cmd = [
919- "git" ,
920- "-c" ,
921- "fetch.prune=false" ,
922- "fetch" ,
923- "--no-tags" ,
924- "--force" ,
925- repo ,
926- ]
927- if shallow .stdout .strip () == "true" :
928- fetch_cmd .append (f"--depth={ shallow_depth } " )
929- for i , ref in enumerate (refs ):
930- fetch_cmd .append (f"{ ref } :refs/nixpkgs-review/{ i } " )
931- dotgit = resolve_git_dir ()
932- with locked_open (dotgit / "nixpkgs-review" , "w" ):
933- res = sh (fetch_cmd )
934- if res .returncode != 0 :
935- msg = f"Failed to fetch { refs } from { repo } . git fetch failed with exit code { res .returncode } "
936- raise NixpkgsReviewError (msg )
937- shas = []
938- for i , ref in enumerate (refs ):
939- rev_parse_cmd = ["git" , "rev-parse" , "--verify" , f"refs/nixpkgs-review/{ i } " ]
940- out = subprocess .run (
941- rev_parse_cmd , text = True , stdout = subprocess .PIPE , check = False
942- )
943- if out .returncode != 0 :
944- msg = f"Failed to fetch { ref } from { repo } with command: { '' .join (rev_parse_cmd )} "
945- raise NixpkgsReviewError (msg )
946- shas .append (out .stdout .strip ())
947- return shas
948-
949-
950942def differences (
951943 old : list [Package ], new : list [Package ]
952944) -> tuple [list [Package ], list [Package ]]:
0 commit comments