From 123abbc101b4fafc25e3c288f9893dda7625c1de Mon Sep 17 00:00:00 2001 From: Arthur Zamarin Date: Sun, 9 Jul 2023 22:13:33 +0300 Subject: [PATCH] scan: add `--git-remote` option to select remote For repos with multiple remotes, it might be useful to select a specific remote to use (and not the default origin). This can be set using the `--git-remote` option for cmd call, or by adding `git-remote=value` to the config file. Resolves: https://github.com/pkgcore/pkgcheck/issues/600 Signed-off-by: Arthur Zamarin --- src/pkgcheck/addons/git.py | 67 ++++++++++++++++++++++++-------------- tests/addons/test_git.py | 13 +++++--- 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/src/pkgcheck/addons/git.py b/src/pkgcheck/addons/git.py index 2547bcc59..669ac262b 100644 --- a/src/pkgcheck/addons/git.py +++ b/src/pkgcheck/addons/git.py @@ -342,16 +342,16 @@ class _ScanGit(argparse.Action): def __init__(self, *args, staged=False, **kwargs): super().__init__(*args, **kwargs) if staged: - default_ref = "HEAD" diff_cmd = ["git", "diff-index", "--name-only", "--cached", "-z"] else: - default_ref = "origin..HEAD" diff_cmd = ["git", "diff-tree", "-r", "--name-only", "-z"] self.staged = staged - self.default_ref = default_ref self.diff_cmd = diff_cmd + def default_ref(self, remote): + return "HEAD" if self.staged else f"{remote}..HEAD" + def generate_restrictions(self, parser, namespace, ref): """Generate restrictions for a given diff command.""" try: @@ -363,10 +363,10 @@ def generate_restrictions(self, parser, namespace, ref): check=True, encoding="utf8", ) - except FileNotFoundError as e: - parser.error(str(e)) - except subprocess.CalledProcessError as e: - error = e.stderr.splitlines()[0] + except FileNotFoundError as exc: + parser.error(str(exc)) + except subprocess.CalledProcessError as exc: + error = exc.stderr.splitlines()[0] parser.error(f"failed running git: {error}") if not p.stdout: @@ -417,7 +417,7 @@ def __call__(self, parser, namespace, value, option_string=None): namespace.enabled_checks.update(objects.CHECKS.select(GitCommitsCheck).values()) # determine target ref - ref = value if value is not None else self.default_ref + ref = value if value is not None else self.default_ref(namespace.git_remote) setattr(namespace, self.dest, ref) # generate scanning restrictions @@ -441,6 +441,9 @@ class GitAddon(caches.CachedAddon): Additionally, the origin/HEAD ref must exist. If it doesn't, running ``git remote set-head origin master`` or similar for other branches will create it. + + You can override the default git remote used for all git comparison using + ``--git-remote``. """ # cache registry @@ -448,7 +451,7 @@ class GitAddon(caches.CachedAddon): @classmethod def mangle_argparser(cls, parser): - group = parser.add_argument_group("git", docs=cls.__doc__) + group: argparse.ArgumentParser = parser.add_argument_group("git", docs=cls.__doc__) git_opts = group.add_mutually_exclusive_group() git_opts.add_argument( "--commits", @@ -484,6 +487,17 @@ def mangle_argparser(cls, parser): temporarily stashing them during the scanning process. """, ) + group.add_argument( + "--git-remote", + default="origin", + metavar="REMOTE", + help="git remote used for all git comparison and operations", + docs=""" + The git remote to be used for all operations by pkgcheck. The + default value, and the recommended value is ``origin``, but + you can use any valid git remote name. + """, + ) def __init__(self, *args): super().__init__(*args) @@ -551,11 +565,11 @@ def _get_current_branch(path, commit="HEAD"): return p.stdout.strip() @staticmethod - def _get_default_branch(path): + def _get_default_branch(path, remote): """Retrieve a git repo's default branch used with origin remote.""" try: p = subprocess.run( - ["git", "symbolic-ref", "refs/remotes/origin/HEAD"], + ["git", "symbolic-ref", f"refs/remotes/{remote}/HEAD"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, cwd=path, @@ -591,10 +605,11 @@ def pkg_history(repo, commit_range, data=None, local=False, verbosity=-1): def update_cache(self, force=False): """Update related cache and push updates to disk.""" + remote = self.options.git_remote for repo in self.options.target_repo.trees: try: branch = self._get_current_branch(repo.location) - default_branch = self._get_default_branch(repo.location) + default_branch = self._get_default_branch(repo.location, remote) # skip cache usage when not running on the default branch if branch != default_branch: logger.debug( @@ -603,7 +618,7 @@ def update_cache(self, force=False): branch, ) continue - commit = self._get_commit_hash(repo.location, "origin/HEAD") + commit = self._get_commit_hash(repo.location, f"{remote}/HEAD") except GitError: continue @@ -619,17 +634,17 @@ def update_cache(self, force=False): logger.debug("updating %s git repo cache to %s", repo, commit[:13]) if git_cache is None: data = {} - commit_range = "origin/HEAD" + commit_range = f"{remote}/HEAD" else: data = git_cache.data - commit_range = f"{git_cache.commit}..origin/HEAD" + commit_range = f"{git_cache.commit}..{remote}/HEAD" try: self.pkg_history( repo, commit_range, data=data, verbosity=self.options.verbosity ) - except GitError as e: - raise PkgcheckUserException(str(e)) + except GitError as exc: + raise PkgcheckUserException(str(exc)) git_cache = GitCache(data, self.cache, commit=commit) else: cache_repo = False @@ -652,29 +667,31 @@ def cached_repo(self, repo_cls): def commits_repo(self, repo_cls): target_repo = self.options.target_repo + remote = self.options.git_remote data = {} try: - origin = self._get_commit_hash(target_repo.location, "origin/HEAD") + origin = self._get_commit_hash(target_repo.location, f"{remote}/HEAD") head = self._get_commit_hash(target_repo.location, "HEAD") if origin != head: - data = self.pkg_history(target_repo, "origin/HEAD..HEAD", local=True) - except GitError as e: - raise PkgcheckUserException(str(e)) + data = self.pkg_history(target_repo, f"{remote}/HEAD..HEAD", local=True) + except GitError as exc: + raise PkgcheckUserException(str(exc)) repo_id = f"{target_repo.repo_id}-commits" return repo_cls(data, repo_id=repo_id) def commits(self): target_repo = self.options.target_repo + remote = self.options.git_remote commits = () try: - origin = self._get_commit_hash(target_repo.location, "origin/HEAD") + origin = self._get_commit_hash(target_repo.location, f"{remote}/HEAD") head = self._get_commit_hash(target_repo.location, "HEAD") if origin != head: - commits = GitRepoCommits(target_repo.location, "origin/HEAD..HEAD") - except GitError as e: - raise PkgcheckUserException(str(e)) + commits = GitRepoCommits(target_repo.location, f"{remote}/HEAD..HEAD") + except GitError as exc: + raise PkgcheckUserException(str(exc)) return iter(commits) diff --git a/tests/addons/test_git.py b/tests/addons/test_git.py index da88d5013..b896304d9 100644 --- a/tests/addons/test_git.py +++ b/tests/addons/test_git.py @@ -71,7 +71,8 @@ def test_commits_nonexistent(self, make_repo, make_git_repo, tmp_path): options, _func = self.tool.parse_args(self.args + ["-r", local.path, "--commits"]) assert excinfo.value.code == 0 - def test_commits_existing(self, make_repo, make_git_repo, tmp_path): + @pytest.mark.parametrize("remote", ("origin", "pkgcheck")) + def test_commits_existing(self, remote, make_repo, make_git_repo, tmp_path): # create parent repo parent = make_repo() origin = make_git_repo(parent.location, commit=True) @@ -80,9 +81,9 @@ def test_commits_existing(self, make_repo, make_git_repo, tmp_path): # create child repo and pull from parent local = make_git_repo(str(tmp_path), commit=False) - local.run(["git", "remote", "add", "origin", origin.path]) - local.run(["git", "pull", "origin", "main"]) - local.run(["git", "remote", "set-head", "origin", "main"]) + local.run(["git", "remote", "add", remote, origin.path]) + local.run(["git", "pull", remote, "main"]) + local.run(["git", "remote", "set-head", remote, "main"]) child = make_repo(local.path) # create local commits on child repo @@ -91,7 +92,9 @@ def test_commits_existing(self, make_repo, make_git_repo, tmp_path): child.create_ebuild("cat/pkg-2") local.add_all("cat/pkg-2") - options, _func = self.tool.parse_args(self.args + ["-r", local.path, "--commits"]) + options, _func = self.tool.parse_args( + self.args + ["-r", local.path, "--commits", "--git-remote", remote] + ) atom_restricts = [atom_cls("cat/pkg")] assert list(options.restrictions) == [ (base.package_scope, packages.OrRestriction(*atom_restricts))