Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

false positive 'c-extension-no-member' when running in environment with setuptools>=60.0 #5704

Closed
jnsnow opened this issue Jan 19, 2022 · 18 comments
Labels
Bug 🪲 Downstream Bug 🪲 The problem happens in a lib depending on pylint, not pylint
Milestone

Comments

@jnsnow
Copy link
Contributor

jnsnow commented Jan 19, 2022

Bug description

For some reason that I definitely don't understand, if I am using Python 3.7 (3.7.12) and I am running my pylint tests from inside of a venv that simply happens to have setuptools >=60.0.0 installed, pylint gives me warnings I do not see in any other circumstance:

qemu/qmp/qmp_shell.py:191:8: I1101: Module 'readline' has no 'set_history_length' member, but source is
unavailable. Consider adding this module to extension-pkg-allow-list if you want to perform analysis based
on run-time introspection of living objects. (c-extension-no-member)

or

iotests.py:384:21: E1101: Module 'struct' has no 'pack' member (no-member)

or

testrunner.py:65:15: I1101: Module 'termios' has no 'tcgetattr' member, but source is unavailable. Consider adding
this module to extension-pkg-allow-list if you want to perform analysis based on run-time introspection of living
objects. (c-extension-no-member)

I see this on pylint versions from as old as 2.8.0 all the way up to 2.12.0.

It does not seem to matter what version of setuptools I had on-hand when I installed other packages, merely having this package in the venv appears to change the behavior of pylint.

In fact, uninstalling setuptools from the test venv fixes the problem:

> pip uninstall setuptools
> python3 -m pylint qemu/

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

> pip install setuptools==59.8.0
Collecting setuptools==59.8.0
  Using cached setuptools-59.8.0-py3-none-any.whl (952 kB)
Installing collected packages: setuptools
Successfully installed setuptools-59.8.0

> python3 -m pylint qemu/

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

> pip install setuptools==60.0.0
Collecting setuptools==60.0.0
  Using cached setuptools-60.0.0-py3-none-any.whl (952 kB)
Installing collected packages: setuptools
  Attempting uninstall: setuptools
    Found existing installation: setuptools 59.8.0
    Uninstalling setuptools-59.8.0:
      Successfully uninstalled setuptools-59.8.0
Successfully installed setuptools-60.0.0

> python3 -m pylint qemu/
************* Module qmp.qmp_shell
qemu/qmp/qmp_shell.py:191:8: I1101: Module 'readline' has no 'set_history_length' member, but source is
unavailable. Consider adding this module to extension-pkg-allow-list if you want to perform analysis based
on run-time introspection of living objects. (c-extension-no-member)

Uhm, can someone please call me an exorcist?

Command used

python3 -m pylint qemu/

Pylint output

(See above.)

Expected behavior

I would expect either these errors to show all of the time ... or none of the time. As it stands, I have no idea why they show up when they do, so I can't control CI behavior to accommodate them. I am pursued by phantasms I can neither see nor perceive.

Pylint version

> python3 -m pylint --version
pylint 2.12.2
astroid 2.9.3
Python 3.7.12 (default, Sep  6 2021, 13:38:52) 
[GCC 11.2.1 20210728 (Red Hat 11.2.1-1)]

(Also appears to be a problem in Python 3.8, but not under Python 3.9 or Python 3.10.)

OS / Environment

cat /etc/fedora-release
Fedora release 34 (Thirty Four)

@jnsnow jnsnow added the Needs triage 📥 Just created, needs acknowledgment, triage, and proper labelling label Jan 19, 2022
@jnsnow
Copy link
Contributor Author

jnsnow commented Jan 19, 2022

Possibly related reading:

pypa/setuptools#2896
pylint-dev/astroid#1316
pylint-dev/astroid#1313

Additionally, setting SETUPTOOLS_USE_DISTUTILS to 'stdlib' does not appear to change behavior again. The only thing that helps is uninstalling the modern setuptools package.

(Edit: originally I wrote that setting the env var to 'local' did not improve behavior, but I meant to write that setting it to 'stdlib' doesn't improve behavior. For laughs, I've tried both and neither change behavior.)

Edit Edit: I was wrong, SETUPTOOLS_USE_DISTUTILS=stdlib works just fine as a workaround.

@Pierre-Sassoulas Pierre-Sassoulas added Bug 🪲 False Positive 🦟 A message is emitted but nothing is wrong with the code and removed Needs triage 📥 Just created, needs acknowledgment, triage, and proper labelling labels Jan 20, 2022
@jnsnow
Copy link
Contributor Author

jnsnow commented Jan 21, 2022

Doing some digging here. I'm using today's origin/main for pylint (gently mangled with debug prints) and astroid==2.9.3.
As a recap, I can find the extra error output only when using setuptools >= 60.0.0 and python 3.7 or python 3.8.

I see some failures for socket.AF_UNIX, socket.SOCK_STREAM, socket.timeout, socket.AF_INET, socket.SOL_SOCKET, socket.SO_REUSEADDR, socket.SCM_RIGHTS, struct.pack, readline.set_history_length, readline.set_completer, readline.parse_and_bind, readline.set_completer_delims, readline.read_history_file, and readline.write_history_file.

I'm looking at this pylint method in pylint/checkers/typecheck.py:

    @check_messages("no-member", "c-extension-no-member")
    def visit_attribute(self, node: nodes.Attribute) -> None:
        ...

the socket errors don't wind up actually getting printed to screen (I don't know why), though they are passed along to add_message. The readline ones actually do make it to the terminal output. Both of these messages making it as far as add_message is new behavior compared to when setuptools v59.8.0 is installed.

Modifying the stanza that calls missingattr.add((owner, name)) to print out the current exception and trace, I see some entries like this:

Exception on node <Attribute.AF_UNIX l.38 at 0x7f637ae12050> in file '/home/jsnow/src/qemu/python/qemu/machine/console_socket.py'
Traceback (most recent call last):
  File "/home/jsnow/src/qemu/python/py37venv/lib64/python3.7/site-packages/pylint/utils/ast_walker.py", line 72, in walk
    callback(astroid)
  File "/home/jsnow/src/qemu/python/py37venv/lib64/python3.7/site-packages/pylint/checkers/typecheck.py", line 1069, in visit_attribute
    for n in owner.getattr(node.attrname)
  File "/home/jsnow/src/qemu/python/py37venv/lib64/python3.7/site-packages/astroid/nodes/scoped_nodes/scoped_nodes.py", line 630, in getattr
    raise AttributeInferenceError(target=self, attribute=name, context=context)
astroid.exceptions.AttributeInferenceError: 'AF_UNIX' not found on <Module.socket l.0 at 0x7f637ae7b490>.
Traceback (most recent call last):
  File "/home/jsnow/src/qemu/python/py37venv/lib64/python3.7/site-packages/pylint/checkers/typecheck.py", line 1070, in visit_attribute
    for n in owner.getattr(node.attrname)
  File "/home/jsnow/src/qemu/python/py37venv/lib64/python3.7/site-packages/astroid/nodes/scoped_nodes/scoped_nodes.py", line 630, in getattr
    raise AttributeInferenceError(target=self, attribute=name, context=context)
astroid.exceptions.AttributeInferenceError: 'set_history_length' not found on <Module.readline l.0 at 0x7fb54a74ae50>.

Hm. Well, nosing around in Astroid, I notice STD_LIB_DIRS does some different things.

STD_LIB_DIRS = {'/home/jsnow/src/qemu/python/py37venv/lib64/python3.7', '/usr/lib64/python3.7'} # 59.8.0, Python 3.7 [OK]
STD_LIB_DIRS = {'/home/jsnow/src/qemu/python/py37venv/lib/python3.7', '/usr/lib/python3.7'}  # 60.0.0, Python 3.7 [FAIL]
STD_LIB_DIRS = {'/home/jsnow/src/qemu/python/py39venv/lib64/python3.9', '/usr/lib64/python3.9'}  # 53.0.0, Python 3.9 [OK]
STD_LIB_DIRS = {'/usr/lib64/python3.9', '/home/jsnow/src/qemu/python/py39venv/lib64/python3.9'}  # 60.0.0, Python 3.9 [OK]

It's certainly looking like the odd one out here is the setuptools 60.0.0 install with Python 3.7, where it wants to use /usr/lib/python3.7 as a stdlib dir.

I do indeed have a /usr/lib/python3.7 dir, but the only thing in it is a __pycache__ folder. All of the real goodies are in /usr/lib64/python3.7. If it helps, this is on a Fedora distribution ... I am not well-versed in what packaging magick goes into configuring Python directories as the distro level, so I'm not sure what info to include here.

@Pierre-Sassoulas - This is the best triage I can do, it's in your hands now. 😔

Edit: Just kidding, I can't leave well enough alone.

CPython 3.9 has a distutils that uses sys.platlibdir to know where the standard python modules are installed. CPython 3.8.12, however, does not offer that constant and instead uses a hardcoded os.path.join(prefix, "lib", "python" + get_python_version()) which ... winds up being wrong on my system.

Looks like https://docs.python.org/3/library/sys.html#sys.platlibdir might be the culprit, so the actual behavior here is... "pylint gives false positives on Fedora and SuSE systems running Python 3.7 or Python 3.8 that are using setuptools >= 60.0.0."

Looking at the setuptools distutils code https://github.com/pypa/setuptools/blob/main/setuptools/_distutils/sysconfig.py#L172 it looks as though we will TRY to use platlibdir, but we'll have a hard-fallback to 'lib'.

Mmmmk. What's not clear to me right now then is why this ever worked under Python 3.7 or Python 3.8, even without the new setuptools arrangement ... Oh, Fedora ships its own patched distutils, naturally. And so with the switch to a setuptools-provided distutils, this function is no longer patched on Fedora distributions, and so it breaks.

Uh. Well then. Not sure where to go from here. 😅

@hroncok
Copy link

hroncok commented Jan 21, 2022

Oh, Fedora ships its own patched distutils, naturally. And so with the switch to a setuptools-provided distutils, this function is no longer patched on Fedora distributions, and so it breaks.

The thing is, we have changed Python 3.9 and 3.10 to work with the setuptools 60+ distutils, but not yet 3.8 or 3.7.
If I give you a build of Python 3.7 that does the same, could you test it? What is your Fedora version?

@hroncok
Copy link

hroncok commented Jan 21, 2022

Oh, I see that it is Fedora 34.

@hroncok
Copy link

hroncok commented Jan 21, 2022

bah. we have figured out how to move our distutils patch to sysconfig for the /local/ addition in pypa/setuptools#2896 but we have not considered the lib64 thing :/ will need to change pypa/distutils even more -- this will take some time.

@hroncok
Copy link

hroncok commented Jan 21, 2022

Additionally, setting SETUPTOOLS_USE_DISTUTILS to 'stdlib' does not appear to change behavior again.

This is however very weird :/ Maybe the environment value isn't propagated deep enough?

@hroncok
Copy link

hroncok commented Jan 21, 2022

I've opened pypa/distutils#110

@jnsnow
Copy link
Contributor Author

jnsnow commented Jan 22, 2022

Additionally, setting SETUPTOOLS_USE_DISTUTILS to 'stdlib' does not appear to change behavior again.

This is however very weird :/ Maybe the environment value isn't propagated deep enough?

Yup, I goofed. I think it was getting dropped in an intermediate process somewhere.

(py37venv) jsnow@scv ~/s/q/python (python)> SETUPTOOLS_USE_DISTUTILS=stdlib python3 -m pylint qemu/

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

(py37venv) jsnow@scv ~/s/q/python (python)> python3 -m pylint qemu/
************* Module aqmp.qmp_shell
qemu/aqmp/qmp_shell.py:196:8: I1101: Module 'readline' has no 'set_history_length' member, but source is unavailable. Consider adding this module to extension-pkg-allow-list if you want to perform analysis based on run-time introspection of living objects. (c-extension-no-member)

So just in case anyone wanders in from your search engine of choice, setting SETUPTOOLS_USE_DISTUTILS=stdlib really ought to help here.

Thanks @hroncok !

bonzini pushed a commit to qemu/qemu that referenced this issue Jan 22, 2022
setuptools is a package that replaces the python stdlib 'distutils'. It
is generally installed by all venv-creating tools "by default". It isn't
actually needed at runtime for the qemu package, so our own setup.cfg
does not mention it as a dependency.

However, tox will create virtual environments that include it, and will
upgrade it to the very latest version. the 'venv' tool will also include
whichever version your host system happens to have.

Unfortunately, setuptools version 60.0.0 and above include a hack to
forcibly overwrite python's built-in distutils. The pylint tool that we
use to run code analysis checks on this package relies on distutils and
suffers regressions when setuptools >= 60.0.0 is present at all, see
pylint-dev/pylint#5704

Instruct tox and the 'check-dev' targets to avoid setuptools packages
that are too new, for now. Pipenv is unaffected, because setuptools 60
does not offer Python 3.6 support, and our pipenv config is pinned
against Python 3.6.

Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Beraldo Leal <bleal@redhat.com>
Reviewed-by: Cleber Rosa <crosa@redhat.com>
Tested-by: Cleber Rosa <crosa@redhat.com>
Message-id: 20220121005221.142236-1-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
@DanielNoord
Copy link
Collaborator

@hroncok Just trying to see if there is anything we can do to solve this. Should we wait for an upstream fix for this or can we do anything in astroid/pylint to help solve this?

@hroncok
Copy link

hroncok commented Jan 24, 2022

You could apply a dirty workaround. If you detect Python version is lower than 3.9 you can monkeypatch https://github.com/pypa/setuptools/blob/v60.0.0/_distutils_hack/__init__.py#L41 to return 'stdlib'. Not sure if you want to do that but it should work.

@DanielNoord
Copy link
Collaborator

Do you have any timeframe for the fix to be live? If it's a matter of weeks I think we might hold off on it: we'll likely forget about it and might up with unnecessary fixes in the codebase.

/CC: @Pierre-Sassoulas

@hroncok
Copy link

hroncok commented Jan 24, 2022

Do you have any timeframe for the fix to be live?

It currently seems that it will be easy, so matter of days. However, there might be unexpected problems, as always.

@hroncok
Copy link

hroncok commented Jan 24, 2022

@jnsnow If I give you this PR https://src.fedoraproject.org/rpms/python3.8/pull-request/56 with this CI build for Fedora 34 https://koji.fedoraproject.org/koji/taskinfo?taskID=81784999 you can upgrade Python 3.8 with:

sudo dnf upgrade https://kojipkgs.fedoraproject.org//work/tasks/5456/81785456/python3.8-3.8.12-6.fc34.x86_64.rpm

My questions for you:

  1. Does that help?
  2. Does it regress in some other way?

@Pierre-Sassoulas Pierre-Sassoulas added this to the 2.13.0 milestone Jan 25, 2022
fpetrot pushed a commit to fpetrot/qemu-riscv128 that referenced this issue Jan 27, 2022
setuptools is a package that replaces the python stdlib 'distutils'. It
is generally installed by all venv-creating tools "by default". It isn't
actually needed at runtime for the qemu package, so our own setup.cfg
does not mention it as a dependency.

However, tox will create virtual environments that include it, and will
upgrade it to the very latest version. the 'venv' tool will also include
whichever version your host system happens to have.

Unfortunately, setuptools version 60.0.0 and above include a hack to
forcibly overwrite python's built-in distutils. The pylint tool that we
use to run code analysis checks on this package relies on distutils and
suffers regressions when setuptools >= 60.0.0 is present at all, see
pylint-dev/pylint#5704

Instruct tox and the 'check-dev' targets to avoid setuptools packages
that are too new, for now. Pipenv is unaffected, because setuptools 60
does not offer Python 3.6 support, and our pipenv config is pinned
against Python 3.6.

Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Beraldo Leal <bleal@redhat.com>
Reviewed-by: Cleber Rosa <crosa@redhat.com>
Tested-by: Cleber Rosa <crosa@redhat.com>
Message-id: 20220121005221.142236-1-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
@hroncok
Copy link

hroncok commented Jan 31, 2022

@jnsnow Any chance you were able to test the provided Python 3.8 build?

@hroncok
Copy link

hroncok commented Feb 3, 2022

There is also this PR https://src.fedoraproject.org/rpms/python3.7/pull-request/41 with this CI build for Fedora 34 https://koji.fedoraproject.org/koji/taskinfo?taskID=82345254 you can upgrade Python 3.7 with:

sudo dnf upgrade https://kojipkgs.fedoraproject.org//work/tasks/5351/82345351/python3.7-3.7.12-8.fc34.x86_64.rpm

@jnsnow
Copy link
Contributor Author

jnsnow commented Feb 4, 2022

@hroncok Sorry about that, didn't catch the github notif. I tried the 3.8 package and left 3.7 alone, and can observe my test suites in QEMU upstream working for 3.8 (and failing for 3.7), so that gives me some good confidence in the fix making a difference here.

I tried the 3.7 package afterwards, and everything looks green as far as I can see. Thanks!

@DanielNoord
Copy link
Collaborator

I'm going to close the issue then! Thanks @hroncok for your work on this and getting back to us!

@DanielNoord DanielNoord added Downstream Bug 🪲 The problem happens in a lib depending on pylint, not pylint and removed False Positive 🦟 A message is emitted but nothing is wrong with the code labels Feb 5, 2022
@hroncok
Copy link

hroncok commented Feb 17, 2022

Fedora updates:

The updates will soon be installable from the updates-testing repo.

Sorry for the delay.

bonzini pushed a commit to qemu/qemu that referenced this issue Feb 24, 2022
Setuptools v60 and later include a bundled version of distutils, a
deprecated standard library scheduled for removal in future versions of
Python. Setuptools v60 is only possible to install for Python 3.7 and later.

Python has a distutils.sysconfig.get_python_lib() function that returns
'/usr/lib/pythonX.Y' on posix systems. RPM-based systems actually use
'/usr/lib64/pythonX.Y' instead, so Fedora patches stdlib distutils for
Python 3.7 and Python 3.8 to return the correct value.

Python 3.9 and later introduce a sys.platlibdir property, which returns
the correct value on RPM-based systems.

The change to a distutils package not provided by Fedora on Python 3.7
and 3.8 causes a regression in distutils.sysconfig.get_python_lib() that
ultimately causes false positives to be emitted by pylint, because it
can no longer find the system source libraries.

Many Python tools are fairly aggressive about updating setuptools
packages, and so even though this package is a fair bit newer than
Python 3.7/3.8, it's not entirely unreasonable for a given user to have
such a modern package with a fairly old Python interpreter.

Updates to Python 3.7 and Python 3.8 are being produced for Fedora which
will fix the problem on up-to-date systems. Until then, we can force the
loading of platform-provided distutils when running the pylint
test. This is the least-invasive yet most comprehensive fix.

References:
 pypa/setuptools#2896
 pylint-dev/pylint#5704
 pypa/distutils#110

Signed-off-by: John Snow <jsnow@redhat.com>
Message-id: 20220204221804.2047468-2-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
liweiwei90 pushed a commit to plctlab/plct-qemu that referenced this issue Feb 27, 2022
Setuptools v60 and later include a bundled version of distutils, a
deprecated standard library scheduled for removal in future versions of
Python. Setuptools v60 is only possible to install for Python 3.7 and later.

Python has a distutils.sysconfig.get_python_lib() function that returns
'/usr/lib/pythonX.Y' on posix systems. RPM-based systems actually use
'/usr/lib64/pythonX.Y' instead, so Fedora patches stdlib distutils for
Python 3.7 and Python 3.8 to return the correct value.

Python 3.9 and later introduce a sys.platlibdir property, which returns
the correct value on RPM-based systems.

The change to a distutils package not provided by Fedora on Python 3.7
and 3.8 causes a regression in distutils.sysconfig.get_python_lib() that
ultimately causes false positives to be emitted by pylint, because it
can no longer find the system source libraries.

Many Python tools are fairly aggressive about updating setuptools
packages, and so even though this package is a fair bit newer than
Python 3.7/3.8, it's not entirely unreasonable for a given user to have
such a modern package with a fairly old Python interpreter.

Updates to Python 3.7 and Python 3.8 are being produced for Fedora which
will fix the problem on up-to-date systems. Until then, we can force the
loading of platform-provided distutils when running the pylint
test. This is the least-invasive yet most comprehensive fix.

References:
 pypa/setuptools#2896
 pylint-dev/pylint#5704
 pypa/distutils#110

Signed-off-by: John Snow <jsnow@redhat.com>
Message-id: 20220204221804.2047468-2-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
newfriday pushed a commit to newfriday/qemu that referenced this issue Feb 28, 2022
Setuptools v60 and later include a bundled version of distutils, a
deprecated standard library scheduled for removal in future versions of
Python. Setuptools v60 is only possible to install for Python 3.7 and later.

Python has a distutils.sysconfig.get_python_lib() function that returns
'/usr/lib/pythonX.Y' on posix systems. RPM-based systems actually use
'/usr/lib64/pythonX.Y' instead, so Fedora patches stdlib distutils for
Python 3.7 and Python 3.8 to return the correct value.

Python 3.9 and later introduce a sys.platlibdir property, which returns
the correct value on RPM-based systems.

The change to a distutils package not provided by Fedora on Python 3.7
and 3.8 causes a regression in distutils.sysconfig.get_python_lib() that
ultimately causes false positives to be emitted by pylint, because it
can no longer find the system source libraries.

Many Python tools are fairly aggressive about updating setuptools
packages, and so even though this package is a fair bit newer than
Python 3.7/3.8, it's not entirely unreasonable for a given user to have
such a modern package with a fairly old Python interpreter.

Updates to Python 3.7 and Python 3.8 are being produced for Fedora which
will fix the problem on up-to-date systems. Until then, we can force the
loading of platform-provided distutils when running the pylint
test. This is the least-invasive yet most comprehensive fix.

References:
 pypa/setuptools#2896
 pylint-dev/pylint#5704
 pypa/distutils#110

Signed-off-by: John Snow <jsnow@redhat.com>
Message-id: 20220204221804.2047468-2-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
vlsunil pushed a commit to ventana-micro-systems/RISC-V-qemu that referenced this issue Mar 4, 2022
Setuptools v60 and later include a bundled version of distutils, a
deprecated standard library scheduled for removal in future versions of
Python. Setuptools v60 is only possible to install for Python 3.7 and later.

Python has a distutils.sysconfig.get_python_lib() function that returns
'/usr/lib/pythonX.Y' on posix systems. RPM-based systems actually use
'/usr/lib64/pythonX.Y' instead, so Fedora patches stdlib distutils for
Python 3.7 and Python 3.8 to return the correct value.

Python 3.9 and later introduce a sys.platlibdir property, which returns
the correct value on RPM-based systems.

The change to a distutils package not provided by Fedora on Python 3.7
and 3.8 causes a regression in distutils.sysconfig.get_python_lib() that
ultimately causes false positives to be emitted by pylint, because it
can no longer find the system source libraries.

Many Python tools are fairly aggressive about updating setuptools
packages, and so even though this package is a fair bit newer than
Python 3.7/3.8, it's not entirely unreasonable for a given user to have
such a modern package with a fairly old Python interpreter.

Updates to Python 3.7 and Python 3.8 are being produced for Fedora which
will fix the problem on up-to-date systems. Until then, we can force the
loading of platform-provided distutils when running the pylint
test. This is the least-invasive yet most comprehensive fix.

References:
 pypa/setuptools#2896
 pylint-dev/pylint#5704
 pypa/distutils#110

Signed-off-by: John Snow <jsnow@redhat.com>
Message-id: 20220204221804.2047468-2-jsnow@redhat.com
Signed-off-by: John Snow <jsnow@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug 🪲 Downstream Bug 🪲 The problem happens in a lib depending on pylint, not pylint
Projects
None yet
Development

No branches or pull requests

4 participants