Skip to content

Commit

Permalink
scan: add --git-remote option to select remote
Browse files Browse the repository at this point in the history
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: pkgcore#600
Signed-off-by: Arthur Zamarin <arthurzam@gentoo.org>
  • Loading branch information
arthurzam committed Jul 9, 2023
1 parent 4e9dbc1 commit 123abbc
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 30 deletions.
67 changes: 42 additions & 25 deletions src/pkgcheck/addons/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -441,14 +441,17 @@ 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
cache = caches.CacheData(type="git", file="git.pickle", version=5)

@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",
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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)
13 changes: 8 additions & 5 deletions tests/addons/test_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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))
Expand Down

0 comments on commit 123abbc

Please sign in to comment.