from__future__importannotationsimportloggingfromdataclassesimportdataclassfromfunctoolsimportcached_propertyfromtypingimportcastimportrequestsfromgitimportInvalidGitRepositoryErrorfromgit.cmdimportGitfromgit.refsimportHead,RemoteReferencefromgit.repoimportRepofrom.exceptionsimport*from.utilimportprint_stderr__all__=["GitRepoState"]log=logging.getLogger(__name__)def_parse_git_remote_url(url:str)->tuple[str,str]:""" Parse a git remote URL into a GitHub (account, repo) pair. :raises InvalidRemoteError: If the URL can't be parsed correctly. """try:account,repo=(url.split("https://github.com/")[-1].split("git@github.com:")[-1].split(".git")[0].split("/"))exceptValueError:raiseInvalidRemoteError(f"Failed to parse GitHub repo path from remote '{url}'")returnaccount,repodef_resolve_repo()->Repo:try:returnRepo(".")exceptInvalidGitRepositoryErrorase:raiseGitError("gantry must be run from the ROOT of a valid git repository!")frome
[docs]@dataclassclassGitRepoState:""" Represents the state of a local git repository. .. tip:: Use :meth:`from_env()` to instantiate this class. """repo:str""" The repository name, e.g. ``"allenai/beaker-gantry"``. """repo_url:str""" The repository URL for cloning, e.g. ``"https://github.com/allenai/beaker-gantry"``. """ref:str""" The current ref. """branch:str|None=None""" The current active branch, if any. """@propertydefis_dirty(self)->bool:""" If the local repository state is dirty (uncommitted changes). """repo=_resolve_repo()returnrepo.is_dirty()@cached_propertydefis_public(self)->bool:""" If the repository is public. """response=requests.get(self.repo_url)ifresponse.status_codenotin{200,404}:response.raise_for_status()returnresponse.status_code==200@propertydefref_url(self)->str:""" The URL to the current :data:`ref`. """returnf"{self.repo_url}/commit/{self.ref}"@propertydefbranch_url(self)->str|None:""" The URL to the current active :data:`branch`. """ifself.branchisNone:returnNoneelse:returnf"{self.repo_url}/tree/{self.branch}"
[docs]@classmethoddeffrom_env(cls,ref:str|None=None,branch:str|None=None)->GitRepoState:""" Instantiate this class from the root of a git repository. :raises ~gantry.exceptions.GitError: If this method isn't called from the root of a valid git repository. :raises ~gantry.exceptions.UnpushedChangesError: If there are unpushed commits. :raises ~gantry.exceptions.RemoteBranchNotFoundError: If the local branch is not tracking a remote branch. """repo=_resolve_repo()git=Git(".")git_ref=reforstr(repo.commit())remote=repo.remote()active_branch:Head|None=NoneifbranchisnotNone:active_branch=Head(repo,f"refs/heads/{branch}")else:try:active_branch=repo.active_branchexceptTypeError:print_stderr("[yellow]Repo is in 'detached HEAD' state which will result in cloning the entire repo at runtime.\n""It's recommended to run gantry from a branch instead.[/]")remote_branch:RemoteReference|None=Noneifactive_branchisnotNone:remote_branch=active_branch.tracking_branch()ifremote_branchisNone:raiseRemoteBranchNotFoundError(f"Failed to resolve remote tracking branch for local branch '{active_branch.name}'.\n"f"Please make sure your branch exists on the remote, e.g. 'git push --set-upstream {remote.name}'.")remote_branches_containing_ref={remote_branch_name.strip()forremote_branch_nameincast(str,git.execute(["git","branch","-r","--contains",git_ref],stdout_as_string=True),).strip().split("\n")}branch_name:str|None=Noneifremote_branchisnotNone:assertremote_branch.name.startswith(remote_branch.remote_name+"/")remote=repo.remote(remote_branch.remote_name)branch_name=remote_branch.name.replace(remote_branch.remote_name+"/","",1)ifremote_branch.namenotinremote_branches_containing_ref:raiseUnpushedChangesError(f"Current git ref '{git_ref}' does not appear to exist on the remote tracking branch '{remote_branch.name}'!\n""Please push your changes and try again.")else:ifnotremote_branches_containing_ref:raiseUnpushedChangesError(f"Current git ref '{git_ref}' does not appear to exist on the remote!\n""Please push your changes and try again.")account,repo_name=_parse_git_remote_url(remote.url)returncls(repo=f"{account}/{repo_name}",repo_url=f"https://github.com/{account}/{repo_name}",ref=git_ref,branch=branch_name,)