From 9cfc8d8d0c94be3eceb1f0505de1e6520370d746 Mon Sep 17 00:00:00 2001
From: Michal Konečný <mkonecny@redhat.com>
Date: Sep 24 2023 04:43:04 +0000
Subject: [PATCH 1/12] Update test environment to F38


Update test containers to F38 for both pip and rpms.

Signed-off-by: Michal Konečný <mkonecny@redhat.com>

---

diff --git a/dev/containers/base-fedora-pip-py3 b/dev/containers/base-fedora-pip-py3
index 9433227..9ec6037 100644
--- a/dev/containers/base-fedora-pip-py3
+++ b/dev/containers/base-fedora-pip-py3
@@ -1,4 +1,4 @@
-FROM quay.io/fedora/fedora:36-x86_64
+FROM quay.io/fedora/fedora:38-x86_64
 
 LABEL org.opencontainers.image.authors="pagure community"
 LABEL org.opencontainers.image.url="https://pagure.io/pagure"
diff --git a/dev/containers/base-fedora-rpms-py3 b/dev/containers/base-fedora-rpms-py3
index b50036d..5d10f3d 100644
--- a/dev/containers/base-fedora-rpms-py3
+++ b/dev/containers/base-fedora-rpms-py3
@@ -1,4 +1,4 @@
-FROM quay.io/fedora/fedora:36-x86_64
+FROM quay.io/fedora/fedora:38-x86_64
 
 LABEL org.opencontainers.image.authors="pagure community"
 LABEL org.opencontainers.image.url="https://pagure.io/pagure"
@@ -19,7 +19,7 @@ RUN dnf -y --enablerepo=updates-testing install \
       python3-flake8 \
       python3-pytest-xdist \
       python3-flask-oidc \
-      python3-cchardet \
+      python3-chardet \
       python3-fedora-messaging \
       python3-pip \
       redis \
diff --git a/dev/containers/fedora-pip-py3 b/dev/containers/fedora-pip-py3
index 58cc29d..cc181f6 100644
--- a/dev/containers/fedora-pip-py3
+++ b/dev/containers/fedora-pip-py3
@@ -1,4 +1,4 @@
-FROM quay.io/fedora/fedora:36-x86_64
+FROM quay.io/fedora/fedora:38-x86_64
 
 ARG repo=https://pagure.io/pagure.git
 ARG branch=master
diff --git a/dev/containers/fedora-rpms-py3 b/dev/containers/fedora-rpms-py3
index a6da18e..0b96267 100644
--- a/dev/containers/fedora-rpms-py3
+++ b/dev/containers/fedora-rpms-py3
@@ -1,4 +1,4 @@
-FROM quay.io/fedora/fedora:36-x86_64
+FROM quay.io/fedora/fedora:38-x86_64
 
 ARG repo=https://pagure.io/pagure.git
 ARG branch=master
@@ -17,7 +17,7 @@ RUN dnf -y --enablerepo=updates-testing install \
     python3-isort \
     python3-pytest-xdist \
     python3-flask-oidc \
-    python3-cchardet \
+    python3-chardet \
     python3-fedora-messaging \
     python3-pip \
     redis \
diff --git a/dev/containers/tox_py3.sh b/dev/containers/tox_py3.sh
index 6dc5b73..9e079ea 100644
--- a/dev/containers/tox_py3.sh
+++ b/dev/containers/tox_py3.sh
@@ -23,4 +23,4 @@ git --no-pager log -2
 fi
 
 export LANG="en_US.UTF-8"
-tox -v -e "${PYVER:-py38}" -- ${TESTCASE:-tests/}
\ No newline at end of file
+tox -v -e "${PYVER:-py38}" -- ${TESTCASE:-tests/}
diff --git a/pagure/lib/git.py b/pagure/lib/git.py
index 293d172..ab66675 100644
--- a/pagure/lib/git.py
+++ b/pagure/lib/git.py
@@ -86,7 +86,7 @@ def commit_to_patch(
             # First commit in the repo
             diff = commit.tree.diff_to_tree(swap=True)
 
-        if diff.patch is None:
+        if not diff.patch:
             continue
 
         if find_similar and diff:
diff --git a/pagure/lib/query.py b/pagure/lib/query.py
index a7a03ce..3a862ef 100644
--- a/pagure/lib/query.py
+++ b/pagure/lib/query.py
@@ -4422,11 +4422,17 @@ def text2markdown(text, extended=True, readme=False):
         # Install our markdown modifications
         extensions.append("pagure.pfmarkdown")
 
+    extensions_configs = {
+        "markdown.extensions.codehilite": {"guess_lang": False},
+    }
+    if md_version > (3, 0, 0):
+        extensions_configs["markdown.extensions.tables"] = {
+            "use_align_attribute": True
+        }
+
     md_processor = markdown.Markdown(
         extensions=extensions,
-        extension_configs={
-            "markdown.extensions.codehilite": {"guess_lang": False}
-        },
+        extension_configs=extensions_configs,
         output_format="xhtml5",
     )
 
diff --git a/pagure/pfmarkdown.py b/pagure/pfmarkdown.py
index 4f2a3e2..5cf8baa 100644
--- a/pagure/pfmarkdown.py
+++ b/pagure/pfmarkdown.py
@@ -31,6 +31,7 @@ import markdown.preprocessors
 import markdown.util
 import pygit2
 import six
+import xml.etree.ElementTree as etree
 
 import pagure.lib.query
 from pagure.config import config as pagure_config
@@ -100,7 +101,7 @@ class MentionPattern(markdown.inlinepatterns.Pattern):
         if not user:
             return text
 
-        element = markdown.util.etree.Element("a")
+        element = etree.Element("a")
         base_url = pagure_config["APP_URL"]
         if base_url.endswith("/"):
             base_url = base_url[:-1]
@@ -338,7 +339,7 @@ class StrikeThroughPattern(markdown.inlinepatterns.Pattern):
 
         text = markdown.util.AtomicString(m.group(2))
 
-        element = markdown.util.etree.Element("del")
+        element = etree.Element("del")
         element.text = text
         return element
 
@@ -359,7 +360,7 @@ class AutolinkPattern2(markdown.inlinepatterns.Pattern):
             url = url[1:]
         if url.endswith(">"):
             url = url[:-1]
-        el = markdown.util.etree.Element("a")
+        el = etree.Element("a")
         el.set("href", self.unescape(url))
         el.text = markdown.util.AtomicString(url)
         return el
@@ -376,11 +377,11 @@ class ImagePatternLazyLoad(ImagePattern):
             el = out
 
         # Add a noscript tag with the untouched img tag
-        noscript = markdown.util.etree.Element("noscript")
+        noscript = etree.Element("noscript")
         noscript.append(el)
 
         # Modify the origina img tag
-        img = markdown.util.etree.Element("img")
+        img = etree.Element("img")
         img.set("data-src", el.get("src"))
         img.set("src", "")
         img.set("alt", el.get("alt"))
@@ -388,7 +389,7 @@ class ImagePatternLazyLoad(ImagePattern):
 
         # Create a global span in which we add both the new img tag and the
         # noscript one
-        outel = markdown.util.etree.Element("span")
+        outel = etree.Element("span")
         outel.append(img)
         outel.append(noscript)
 
@@ -607,7 +608,7 @@ def _obj_anchor_tag(user, namespace, repo, obj, text):
         else:
             title = obj.title
 
-    element = markdown.util.etree.Element("a")
+    element = etree.Element("a")
     element.set("href", url)
     element.set("title", title)
     element.text = text
diff --git a/requirements.txt b/requirements.txt
index 38b6351..5ffaa59 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,38 +1,38 @@
 # Used for when working from a virtualenv.
 # Use this file by running "$ pip install -r requirements.txt"
-alembic <= 1.8.1
+alembic <= 1.9.3
 arrow <= 1.2.3
 bcrypt <= 3.2.2
 binaryornot == 0.4.4
 bleach <= 5.0.1
 blinker <= 1.5
-celery <= 5.2.6
-chardet <= 4.0.0
-cryptography <= 36.0.0
-docutils <= 0.17.1
+celery <= 5.3.1
+chardet <= 5.1.0
+cryptography <= 37.0.2
+docutils <= 0.19.0
 
 # required for backward compatibility
 email_validator
 
-flask <= 2.2.2
-flask-wtf <= 1.0.0
+flask <= 2.2.3
+flask-wtf <= 1.1.1
 kitchen == 1.2.6
-markdown <= 3.3.7
+markdown <= 3.4.1
 munch <= 2.5.0
-Pillow <= 9.2.0
+Pillow <= 9.4.0
 psutil <= 5.9.2
-pygit2 >= 0.26.0, <=1.8.0
+pygit2 >= 0.26.0, <=1.12.0
 python3-openid <= 3.2.0
 python-openid-cla == 1.2
 python-openid-teams == 1.1
-redis <= 3.5.3
-requests <= 2.28.1
+redis <= 4.3.4
+requests <= 2.28.2
 six <= 1.16.0
 
 # required for backward compatibility
 setuptools
 
-sqlalchemy >= 0.8, <=1.4.46
+sqlalchemy >= 0.8, <=1.4.48
 straight.plugin == 1.5.0
 whitenoise <= 6.2.0
 wtforms <= 3.0.1
diff --git a/tests/test_pagure_flask_internal.py b/tests/test_pagure_flask_internal.py
index eae24a5..e47f1ce 100644
--- a/tests/test_pagure_flask_internal.py
+++ b/tests/test_pagure_flask_internal.py
@@ -1999,6 +1999,10 @@ class PagureFlaskInternaltests(tests.Modeltests):
         self.assertTrue(
             js_data2
             in [
+                {
+                    "results": "GitError(\"reference 'refs/heads/master' not found\")"
+                },
+                {"results": "reference 'refs/heads/master' not found"},
                 {"results": "reference 'refs/heads/master' not found"},
                 {"results": "Reference 'refs/heads/master' not found"},
             ]
@@ -2141,6 +2145,9 @@ class PagureFlaskInternaltests(tests.Modeltests):
         self.assertTrue(
             js_data2
             in [
+                {
+                    "results": "GitError(\"reference 'refs/heads/master' not found\")"
+                },
                 {"results": "reference 'refs/heads/master' not found"},
                 {"results": "Reference 'refs/heads/master' not found"},
             ]
diff --git a/tests/test_pagure_flask_ui_issues.py b/tests/test_pagure_flask_ui_issues.py
index 6d763cf..bd57542 100644
--- a/tests/test_pagure_flask_ui_issues.py
+++ b/tests/test_pagure_flask_ui_issues.py
@@ -1315,8 +1315,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
             output_text,
         )
         self.assertIn(
-            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            "Log in</a>\n          to comment on this ticket.",
+            '<a href="/login/?next=',
             output_text,
         )
 
@@ -1358,8 +1357,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
             output_text,
         )
         self.assertIn(
-            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            "Log in</a>\n          to comment on this ticket.",
+            '<a href="/login/?next=',
             output_text,
         )
 
@@ -1502,8 +1500,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
             output_text,
         )
         self.assertIn(
-            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            "Log in</a>\n          to comment on this ticket.",
+            '<a href="/login/?next=',
             output_text,
         )
 
@@ -1575,10 +1572,7 @@ class PagureFlaskIssuestests(tests.Modeltests):
             'href="/test/issue/1/edit" title="Edit this issue">\n',
             output_text,
         )
-        self.assertTrue(
-            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            "Log in</a>\n          to comment on this ticket." in output_text
-        )
+        self.assertIn('<a href="/login/?next=', output_text)
 
         # Create issues to play with
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
diff --git a/tests/test_pagure_flask_ui_issues_acl_checks.py b/tests/test_pagure_flask_ui_issues_acl_checks.py
index b8af162..db380c3 100644
--- a/tests/test_pagure_flask_ui_issues_acl_checks.py
+++ b/tests/test_pagure_flask_ui_issues_acl_checks.py
@@ -109,9 +109,7 @@ class PagureFlaskIssuesACLtests(tests.Modeltests):
             output.get_data(as_text=True),
         )
         self.assertTrue(
-            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            "Log in</a>\n          to comment on this ticket."
-            in output.get_data(as_text=True)
+            '<a href="/login/?next=' in output.get_data(as_text=True)
         )
 
         user = tests.FakeUser()
@@ -372,8 +370,7 @@ class PagureFlaskIssuesACLtests(tests.Modeltests):
             output_text,
         )
         self.assertIn(
-            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            "Log in</a>\n          to comment on this ticket.",
+            '<a href="/login/?next=',
             output_text,
         )
 
@@ -880,9 +877,7 @@ class PagureFlaskIssuesACLtests(tests.Modeltests):
             output.get_data(as_text=True),
         )
         self.assertTrue(
-            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            "Log in</a>\n          to comment on this ticket."
-            in output.get_data(as_text=True)
+            '<a href="/login/?next=' in output.get_data(as_text=True)
         )
 
         user = tests.FakeUser()
diff --git a/tests/test_pagure_flask_ui_issues_open_access.py b/tests/test_pagure_flask_ui_issues_open_access.py
index 6975564..c0159ff 100644
--- a/tests/test_pagure_flask_ui_issues_open_access.py
+++ b/tests/test_pagure_flask_ui_issues_open_access.py
@@ -213,8 +213,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
             output_text,
         )
         self.assertIn(
-            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            "Log in</a>\n          to comment on this ticket.",
+            '<a href="/login/?next=',
             output_text,
         )
 
@@ -312,10 +311,7 @@ class PagureFlaskIssuesOpenAccesstests(tests.Modeltests):
             'href="/test/issue/1/edit" title="Edit this issue">\n',
             output_text,
         )
-        self.assertTrue(
-            '<a href="/login/?next=http%3A%2F%2Flocalhost%2Ftest%2Fissue%2F1">'
-            "Log in</a>\n          to comment on this ticket." in output_text
-        )
+        self.assertTrue('<a href="/login/?next=' in output_text)
 
         # Create issues to play with
         repo = pagure.lib.query.get_authorized_project(self.session, "test")
diff --git a/tests/test_pagure_flask_ui_login.py b/tests/test_pagure_flask_ui_login.py
index ea6ac99..721dc4e 100644
--- a/tests/test_pagure_flask_ui_login.py
+++ b/tests/test_pagure_flask_ui_login.py
@@ -1103,6 +1103,7 @@ class PagureFlaskLogintests(tests.SimplePagureTest):
                     "http://localhost/login/",
                     "/login/?next=http%3A%2F%2Flocalhost%2Fsettings%2F",
                     "http://localhost/login/?next=http%3A%2F%2Flocalhost%2Fsettings%2F",
+                    "/login/?next=http://localhost/settings/",
                 )
             )
         # session did not expire
diff --git a/tests/test_pagure_lib_encoding_utils.py b/tests/test_pagure_lib_encoding_utils.py
index dad98c9..95fca8b 100644
--- a/tests/test_pagure_lib_encoding_utils.py
+++ b/tests/test_pagure_lib_encoding_utils.py
@@ -50,7 +50,7 @@ class TestGuessEncoding(unittest.TestCase):
             self.assertEqual(result, "WINDOWS-1250")
         else:
             self.assertEqual(result, "utf-8")
-            if chardet.__version__[0] in ("3", "4"):
+            if chardet.__version__[0] in ("3", "4", "5"):
                 self.assertEqual(chardet_result["encoding"], "ISO-8859-9")
             else:
                 self.assertEqual(chardet_result["encoding"], "ISO-8859-2")
@@ -112,6 +112,49 @@ class TestGuessEncodings(unittest.TestCase):
                     expected_list,
                 )
                 self.assertEqual(chardet_result["encoding"], "ISO-8859-9")
+            elif chardet.__version__[0] in ("5"):
+                # The first three have different confidence values
+                expexted_list = ["utf-8", "ISO-8859-9", "ISO-8859-1"]
+                # This is the one with the least confidence
+                self.assertEqual(result[-1].encoding, "TIS-620")
+                self.assertListEqual(
+                    [encoding.encoding for encoding in result][:3],
+                    expexted_list,
+                )
+
+                # The values in the middle of the list all have the same confidence
+                # value and can't be sorted reliably: use sets.
+                expected_list = sorted(
+                    [
+                        "utf-8",
+                        "ISO-8859-9",
+                        "ISO-8859-1",
+                        "MacCyrillic",
+                        "IBM866",
+                        "TIS-620",
+                        "EUC-JP",
+                        "EUC-KR",
+                        "GB2312",
+                        "KOI8-R",
+                        "Johab",
+                        "Big5",
+                        "IBM855",
+                        "ISO-8859-7",
+                        "SHIFT_JIS",
+                        "windows-1253",
+                        "MacRoman",
+                        "CP949",
+                        "EUC-TW",
+                        "ISO-8859-5",
+                        "windows-1251",
+                        "windows-1255",
+                    ]
+                )
+                self.assertListEqual(
+                    sorted(set([encoding.encoding for encoding in result])),
+                    expected_list,
+                )
+                self.assertEqual(chardet_result["encoding"], "ISO-8859-9")
             else:
                 self.assertListEqual(
                     [encoding.encoding for encoding in result],

From 83fff0e0eda73f56b835d79597febdfd53f9b5ac Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Sep 24 2023 04:43:04 +0000
Subject: [PATCH 2/12] Drop the dependency on python-trololio (in favor of asyncio)


On Fedora 38, using python 3.11 'trololio' is broken and no updates
have been made to it for almost 10 years. Time to use 'raw' asyncio.

Details

trololio feature two issues.

1) Trololio depend on deprecated `imp` module:

```
>>> from imp import find_module, load_module
<stdin>:1: DeprecationWarning: the imp module is deprecated in favour of
importlib and slated for removal in Python 3.12; see the module's
documentation for alternative uses
```

2) Trololio's embedded asyncio module fails. So trololio methods are
never defined. For example:

```
$ cat trolo-smoke.py
from trololio import asyncio, coroutine
@coroutine
def coro():
   yield From(asyncio.sleep(1))

$ python trolo-smoke.py
AttributeError: module 'trololio.asyncio' has no attribute 'coroutine'.
Did you mean: 'coroutines'?
```

The same issue can be seen in the `pagure-ev` logs

```
pagure_stream_server.py[192893]:   File
"/usr/libexec/pagure-ev/pagure_stream_server.py", line 140, in <module>
pagure_stream_server.py[192893]:     @asyncio.coroutine
pagure_stream_server.py[192893]:      ^^^^^^^^^^^^^^^^^
pagure_stream_server.py[192893]: AttributeError: module 'asyncio' has no
attribute 'coroutine'. Did you mean: 'coroutines'?
```

---

diff --git a/doc/install_pagure_ci.rst b/doc/install_pagure_ci.rst
index 3b9ae78..68f7538 100644
--- a/doc/install_pagure_ci.rst
+++ b/doc/install_pagure_ci.rst
@@ -22,7 +22,6 @@ Configure your system
 
     python-jenkins
     python-redis
-    python-trololio
 
 .. note:: We ship a systemd unit file for pagure_ci but we welcome patches
         for scripts for other init systems.
diff --git a/doc/install_pagure_ev.rst b/doc/install_pagure_ev.rst
index 614f312..db6f78f 100644
--- a/doc/install_pagure_ev.rst
+++ b/doc/install_pagure_ev.rst
@@ -16,7 +16,6 @@ The eventsource server is easy to set-up.
 ::
 
     python-redis
-    python-trololio
 
 .. note:: We ship a systemd unit file for pagure_milter but we welcome patches
         for scripts for other init systems.
diff --git a/doc/install_pagure_loadjson.rst b/doc/install_pagure_loadjson.rst
index 4b13f06..407b672 100644
--- a/doc/install_pagure_loadjson.rst
+++ b/doc/install_pagure_loadjson.rst
@@ -14,7 +14,6 @@ Configure your system
 ::
 
     python-redis
-    python-trololio
 
 .. note:: We ship a systemd unit file for pagure_loadjson but we welcome patches
         for scripts for other init systems.
diff --git a/doc/install_pagure_logcom.rst b/doc/install_pagure_logcom.rst
index f92e0b3..4512dc4 100644
--- a/doc/install_pagure_logcom.rst
+++ b/doc/install_pagure_logcom.rst
@@ -14,7 +14,6 @@ Configure your system
 ::
 
     python-redis
-    python-trololio
 
 .. note:: We ship a systemd unit file for pagure_logcom but we welcome patches
         for scripts for other init systems.
diff --git a/doc/install_pagure_webhooks.rst b/doc/install_pagure_webhooks.rst
index 9aff374..9800817 100644
--- a/doc/install_pagure_webhooks.rst
+++ b/doc/install_pagure_webhooks.rst
@@ -17,7 +17,6 @@ Configure your system
 ::
 
     python-redis
-    python-trololio
 
 .. note:: We ship a systemd unit file for pagure_webhook but we welcome patches
         for scripts for other init systems.
diff --git a/files/pagure.spec b/files/pagure.spec
index d11614a..ce3c833 100644
--- a/files/pagure.spec
+++ b/files/pagure.spec
@@ -148,7 +148,6 @@ This is useful for example to allow commenting on a ticket by email.
 Summary:            EventSource server for pagure
 BuildArch:          noarch
 Requires:           %{name} = %{version}-%{release}
-Requires:           python%{python_pkgversion}-trololio
 %{?systemd_requires}
 %description        ev
 Pagure comes with an eventsource server allowing live update of the pages
diff --git a/pagure-ev/pagure_stream_server.py b/pagure-ev/pagure_stream_server.py
index 6908ea7..907fd45 100644
--- a/pagure-ev/pagure_stream_server.py
+++ b/pagure-ev/pagure_stream_server.py
@@ -26,7 +26,7 @@ import os
 
 
 import redis
-import trololio
+import asyncio
 
 from six.moves.urllib.parse import urlparse
 
@@ -137,14 +137,12 @@ def get_obj_from_path(path):
     return getfunc(repo, objid)
 
 
-@trololio.coroutine
-def handle_client(client_reader, client_writer):
+async def handle_client(client_reader, client_writer):
     data = None
     while True:
         # give client a chance to respond, timeout after 10 seconds
-        line = yield trololio.From(
-            trololio.asyncio.wait_for(client_reader.readline(), timeout=10.0)
-        )
+        line = await asyncio.wait_for(client_reader.readline(), timeout=10.0)
+
         if not line.decode().strip():
             break
         line = line.decode().rstrip()
@@ -204,16 +202,16 @@ def handle_client(client_reader, client_writer):
                     client_writer.write(("event: ping\n\n").encode())
                     oncall = 0
                 oncall += 1
-                yield trololio.From(client_writer.drain())
-                yield trololio.From(trololio.asyncio.sleep(1))
+                await client_writer.drain()
+                await asyncio.sleep(1)
             else:
                 log.info("Sending %s", msg["data"])
                 client_writer.write(("data: %s\n\n" % msg["data"]).encode())
-                yield trololio.From(client_writer.drain())
+                await client_writer.drain()
 
     except OSError:
         log.info("Client closed connection")
-    except trololio.ConnectionResetError as err:
+    except ConnectionResetError as err:
         log.exception("ERROR: ConnectionResetError in handle_client")
     except Exception as err:
         log.exception("ERROR: Exception in handle_client")
@@ -225,8 +223,7 @@ def handle_client(client_reader, client_writer):
         client_writer.close()
 
 
-@trololio.coroutine
-def stats(client_reader, client_writer):
+async def stats(client_reader, client_writer):
 
     try:
         log.info("Clients: %s", SERVER.active_count)
@@ -234,9 +231,9 @@ def stats(client_reader, client_writer):
             ("HTTP/1.0 200 OK\n" "Cache: nocache\n\n").encode()
         )
         client_writer.write(("data: %s\n\n" % SERVER.active_count).encode())
-        yield trololio.From(client_writer.drain())
+        await client_writer.drain()
 
-    except trololio.ConnectionResetError as err:
+    except ConnectionResetError as err:
         log.info(err)
     finally:
         client_writer.close()
@@ -248,8 +245,8 @@ def main():
     _get_session()
 
     try:
-        loop = trololio.asyncio.get_event_loop()
-        coro = trololio.asyncio.start_server(
+        loop = asyncio.get_event_loop()
+        coro = asyncio.start_server(
             handle_client,
             host=None,
             port=pagure.config.config["EVENTSOURCE_PORT"],
@@ -259,7 +256,7 @@ def main():
             "Serving server at {}".format(SERVER.sockets[0].getsockname())
         )
         if pagure.config.config.get("EV_STATS_PORT"):
-            stats_coro = trololio.asyncio.start_server(
+            stats_coro = asyncio.start_server(
                 stats,
                 host=None,
                 port=pagure.config.config.get("EV_STATS_PORT"),
@@ -273,7 +270,7 @@ def main():
         loop.run_forever()
     except KeyboardInterrupt:
         pass
-    except trololio.ConnectionResetError as err:
+    except ConnectionResetError as err:
         log.exception("ERROR: ConnectionResetError in main")
     except Exception:
         log.exception("ERROR: Exception in main")
diff --git a/requirements-ev.txt b/requirements-ev.txt
index e704325..e69de29 100644
--- a/requirements-ev.txt
+++ b/requirements-ev.txt
@@ -1 +0,0 @@
-trololio == 1.0
diff --git a/requirements-testing.txt b/requirements-testing.txt
index 547e862..33a5f2c 100644
--- a/requirements-testing.txt
+++ b/requirements-testing.txt
@@ -18,7 +18,6 @@ pytest-cov <= 4.0.0
 pytest-xdist <= 2.5.0
 
 python-fedora == 1.1.1
-trololio == 1.0
 
 # Seems that mock doesn't list this one
 funcsigs <= 1.0.2

From dc20ba1e5b1d27b1e6e7b8e25d5209906e98f207 Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Sep 24 2023 04:54:32 +0000
Subject: [PATCH 3/12] Replace deprecated imp with importlib


`imp` is deprecated since version 3.4, will be removed in version 3.12:
The `imp` module is deprecated in favor of `importlib`.

Only one direct instance `tests/__init__.py` and one unused
indirect instance in `straight.plugin` version 1.5, imported from
`./pagure/lib/plugins.py`.

The python module `straight.plugin.loaders` import `imp.find_module`,
but it is not used. So can be removed:

```
/usr/lib/python3.11/site-packages/straight/plugin/loaders.py:
- from imp import find_module
+ #from imp import find_module
```

Should probably be upstreamed.

---

diff --git a/tests/__init__.py b/tests/__init__.py
index 598ebf5..b3b7156 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -10,7 +10,7 @@
 
 from __future__ import unicode_literals, absolute_import
 
-import imp
+import importlib as imp
 import json
 import logging
 import os

From 6b3eaeeeb9edca55d30f5e774d58e150ae5dce52 Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Sep 24 2023 05:12:26 +0000
Subject: [PATCH 4/12] Update rpm-spec file and python requirements.txt


Changed the requirements.txt max-versions to that from
@zlopez PR 5396. RPM can be build and installed fc38.

Update the the rpm-spec file:
  - remove _python_bytecompile_extra.
  - bump version to 5.13.4, one more than the currently released
    version from 2021.
  - add pagure_authorized_keys_worker.service
  - remove spurious %endif

Corrected parameters to call of celery in runworker.py.
Explicitly specify `python3-sqlalchemy1.3` in Fedora requirements.

Add missing packages to `fedora-requirements.txt` to allow
running `pagure` in a clean `mock` shell:

```
mock --enable-network -r fedora-38-x86_64 --shell \
       -- mkdir -p pagure/{repos,docs,forks,tickets,requests,remotes}

mock --enable-network -r fedora-38-x86_64 --shell \
      -- cd pagure/ \; python3 createdb.py \; python3 runserver.py
```

centos8 can't run (`-r centos-stream+epel-8-x86_64') crashes with
puff of `jinja2` messages.

---

diff --git a/files/pagure.spec b/files/pagure.spec
index ce3c833..28810b9 100644
--- a/files/pagure.spec
+++ b/files/pagure.spec
@@ -4,12 +4,9 @@
 %global __python %{__python3}
 %global python_pkgversion %{python3_pkgversion}
 
-# For now, to keep behavior consistent
-%global _python_bytecompile_extra 1
-
 
 Name:               pagure
-Version:            5.13.2
+Version:            5.13.4
 Release:            1%{?dist}
 Summary:            A git-centered forge
 
@@ -69,6 +66,26 @@ Requires:           python%{python_pkgversion}-wtforms
 
 %{?systemd_requires}
 
+%if 0%{?fedora} || 0%{?rhel} >= 8
+# We want to use cchardet whenever it's available
+Recommends:         python3-cchardet
+
+# If using PostgreSQL, the correct driver should be installed
+Recommends:         ((python3-psycopg2 or python3-pg8000) if postgresql-server)
+
+# If using MariaDB/MySQL, the correct driver should be installed
+Recommends:         ((python3-mysqlclient or python3-PyMySQL) if mysql-server)
+
+# If using Apache web server, the correct configuration should be installed
+Recommends:         (%{name}-web-apache-httpd if httpd)
+
+# If using Nginx web server, the correct configuration should be installed
+Recommends:         (%{name}-web-nginx if nginx)
+%endif
+
+# We use the git tools for some actions due to deficiencies in libgit2 and pygit2
+Requires:           git-core
+
 # No dependency of the app per se, but required to make it working.
 Requires:           gitolite3
 
@@ -86,7 +103,6 @@ BuildArch:          noarch
 Requires:           %{name} = %{version}-%{release}
 Requires:           httpd-filesystem
 Requires:           python%{python_pkgversion}-mod_wsgi
-%endif
 %description        web-apache-httpd
 This package provides the configuration files for deploying
 a Pagure server using the Apache HTTPD server.
@@ -272,6 +288,10 @@ mkdir -p $RPM_BUILD_ROOT/%{_unitdir}
 install -p -m 644 files/pagure_worker.service \
     $RPM_BUILD_ROOT/%{_unitdir}/pagure_worker.service
 
+# Install the systemd file for the authorized_keys worker
+install -p -m 644 files/pagure_authorized_keys_worker.service \
+    $RPM_BUILD_ROOT/%{_unitdir}/pagure_authorized_keys_worker.service
+
 # Install the systemd file for the gitolite worker
 install -p -m 644 files/pagure_gitolite_worker.service \
     $RPM_BUILD_ROOT/%{_unitdir}/pagure_gitolite_worker.service
@@ -363,13 +383,16 @@ for runnerhook in $runnerhooks; do
    ln -sf hookrunner $RPM_BUILD_ROOT/%{python_sitelib}/pagure/hooks/files/$runnerhook
 done
 
+%if 0%{?fedora} || 0%{?rhel} >= 8
 # Byte compile everything not in sitelib
 %py_byte_compile %{__python} %{buildroot}%{_datadir}/pagure/
 %py_byte_compile %{__python} %{buildroot}%{_libexecdir}/pagure/
 %py_byte_compile %{__python} %{buildroot}%{_libexecdir}/pagure-ev/
+%endif
 
 %post
 %systemd_post pagure_worker.service
+%systemd_post pagure_authorized_keys_worker.service
 %systemd_post pagure_gitolite_worker.service
 %systemd_post pagure_api_key_expire_mail.timer
 %systemd_post pagure_mirror_project_in.timer
@@ -393,6 +416,7 @@ done
 
 %preun
 %systemd_preun pagure_worker.service
+%systemd_preun pagure_authorized_keys_worker.service
 %systemd_preun pagure_gitolite_worker.service
 %systemd_preun pagure_api_key_expire_mail.timer
 %systemd_preun pagure_mirror_project_in.timer
@@ -416,6 +440,7 @@ done
 
 %postun
 %systemd_postun_with_restart pagure_worker.service
+%systemd_postun_with_restart pagure_authorized_keys_worker.service
 %systemd_postun_with_restart pagure_gitolite_worker.service
 %systemd_postun pagure_api_key_expire_mail.timer
 %systemd_postun pagure_mirror_project_in.timer
@@ -456,6 +481,7 @@ done
 %{python_sitelib}/pagure*.egg-info
 %{_bindir}/pagure-admin
 %{_unitdir}/pagure_worker.service
+%{_unitdir}/pagure_authorized_keys_worker.service
 %{_unitdir}/pagure_gitolite_worker.service
 %{_unitdir}/pagure_api_key_expire_mail.service
 %{_unitdir}/pagure_api_key_expire_mail.timer
diff --git a/pagure/__init__.py b/pagure/__init__.py
index 823f4ae..86f6f95 100644
--- a/pagure/__init__.py
+++ b/pagure/__init__.py
@@ -11,4 +11,4 @@
 from __future__ import absolute_import, unicode_literals
 
 __api_version__ = "0.31"
-__version__ = "5.13.2"
+__version__ = "5.13.4"
diff --git a/requirements-fedora.txt b/requirements-fedora.txt
index 26ac243..3724ab9 100644
--- a/requirements-fedora.txt
+++ b/requirements-fedora.txt
@@ -19,6 +19,10 @@ python3-openid-teams
 python3-psutil
 python3-pygit2
 python3-redis
-python3-sqlalchemy
+python3-sqlalchemy1.3
 python3-straight-plugin
 python3-wtforms
+python3-whitenoise
+python3-celery
+python3-pillow
+python3-email-validator
diff --git a/requirements.txt b/requirements.txt
index 5ffaa59..5e905b9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,7 +10,7 @@ celery <= 5.3.1
 chardet <= 5.1.0
 cryptography <= 37.0.2
 docutils <= 0.19.0
-
+enum34;python_version<"3.4"
 # required for backward compatibility
 email_validator
 
@@ -22,7 +22,8 @@ munch <= 2.5.0
 Pillow <= 9.4.0
 psutil <= 5.9.2
 pygit2 >= 0.26.0, <=1.12.0
-python3-openid <= 3.2.0
+python-openid;python_version<="2.7"
+python3-openid;python_version>="3.0"
 python-openid-cla == 1.2
 python-openid-teams == 1.1
 redis <= 4.3.4
@@ -32,7 +33,7 @@ six <= 1.16.0
 # required for backward compatibility
 setuptools
 
-sqlalchemy >= 0.8, <=1.4.48
+sqlalchemy >= 0.8, <= 1.3.24
 straight.plugin == 1.5.0
 whitenoise <= 6.2.0
 wtforms <= 3.0.1
@@ -48,3 +49,7 @@ wtforms <= 3.0.1
 
 # Required only for the `fas` and `openid` authentication backends
 # python-fedora == 1.1.1
+
+# pagure-ci
+cryptography
+python-jenkins
diff --git a/runworker.py b/runworker.py
index ffe94c7..9c55eaa 100755
--- a/runworker.py
+++ b/runworker.py
@@ -53,7 +53,7 @@ if args.config:
         config = os.path.join(here, config)
     env["PAGURE_CONFIG"] = config
 
-cmd = [sys.executable, "-m", "celery", "-A", "worker", args.tasks]
+cmd = [sys.executable, "-m", "celery", "-A", args.tasks, "worker"]
 
 if args.queue:
     cmd.extend(["-Q", args.queue])

From f4f5f55d5a778c3992bdc51144c71061299b5e0f Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Oct 02 2023 09:21:17 +0000
Subject: [PATCH 5/12] Fix Celery related exception in get_key_for_task


@wombelix :
celery introduce strict error handling of an empty key with
https://github.com/celery/celery/commit/726b664840b6a1fcea9225b254a393e665363ad0

and is part of celery releases since 5.3.0. This causes a large amount
of errors like this one in the unit tests.

Tracked down to the decorator function `@pagure_task` in
`pagure/lib/task_utils.py`, this can be solved by passing `self.task_id`
to `update_state`.

At this point, there is no task running and therefore no id assigned to
`self.request.id` yet.
By passing `self`, the request id will be added right after the task was
started as part of the `update_state` call.

All `task_id` related errors are gone in the unit tests after this
change.

---

diff --git a/pagure/lib/tasks_utils.py b/pagure/lib/tasks_utils.py
index f65d7eb..9214b1e 100644
--- a/pagure/lib/tasks_utils.py
+++ b/pagure/lib/tasks_utils.py
@@ -28,7 +28,7 @@ def pagure_task(function):
         """Decorated function, actually does the work."""
         if self is not None:
             try:
-                self.update_state(state="RUNNING")
+                self.update_state(self=self, state="RUNNING")
             except TypeError:
                 pass
         session = pagure.lib.model_base.create_session(pagure_config["DB_URL"])

From 927e0514e95f4a9af58746bdea9a7d71f9a273eb Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Oct 02 2023 09:21:17 +0000
Subject: [PATCH 6/12] Fix self.assert(Not)Equals() DeprecationWarning


For example:

tests/test_pagure_utils.py::PagureUtilsTests::test_lookup_deploykey_non_existent_key
pagure/tests/test_pagure_utils.py:75:
DeprecationWarning: Please use assertEqual instead.
    self.assertEquals(res, None)

---

diff --git a/tests/test_pagure_flask_api_project.py b/tests/test_pagure_flask_api_project.py
index 2227dc6..9cef058 100644
--- a/tests/test_pagure_flask_api_project.py
+++ b/tests/test_pagure_flask_api_project.py
@@ -3482,7 +3482,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "modify_project")
 
         project = pagure.lib.query._get_project(self.session, "test")
-        self.assertEquals(
+        self.assertEqual(
             project.access_users,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
@@ -3746,7 +3746,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         headers = {"Authorization": "token aaabbbcccddd"}
 
         project = pagure.lib.query._get_project(self.session, "test")
-        self.assertEquals(
+        self.assertEqual(
             project.access_users,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
@@ -3795,7 +3795,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         # Ensure `foo` was properly added:
         project = pagure.lib.query._get_project(self.session, "test")
         user_foo = pagure.lib.query.search_user(self.session, username="foo")
-        self.assertEquals(
+        self.assertEqual(
             project.access_users,
             {
                 "admin": [],
@@ -3877,7 +3877,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         # Ensure `foo` was properly removed
         self.session = pagure.lib.query.create_session(self.dbpath)
         project = pagure.lib.query._get_project(self.session, "test")
-        self.assertEquals(
+        self.assertEqual(
             project.access_users,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
@@ -3891,7 +3891,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         # Ensure `foo` was properly added:
         project = pagure.lib.query._get_project(self.session, "test")
         user_foo = pagure.lib.query.search_user(self.session, username="foo")
-        self.assertEquals(
+        self.assertEqual(
             project.access_users,
             {
                 "admin": [],
@@ -3958,7 +3958,7 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         # Ensure `foo` was properly removed
         self.session = pagure.lib.query.create_session(self.dbpath)
         project = pagure.lib.query._get_project(self.session, "test")
-        self.assertEquals(
+        self.assertEqual(
             project.access_users,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
@@ -4039,15 +4039,15 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         # Ensure `baz` was properly added
         self.session = pagure.lib.query.create_session(self.dbpath)
         project = pagure.lib.query._get_project(self.session, "test")
-        self.assertEquals(
+        self.assertEqual(
             project.access_users,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
-        self.assertNotEquals(
+        self.assertNotEqual(
             project.access_groups,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
-        self.assertEquals(len(project.access_groups["ticket"]), 1)
+        self.assertEqual(len(project.access_groups["ticket"]), 1)
 
         # Remove the group from the project
         data = {"user_type": "group", "name": "baz", "acl": None}
@@ -4106,11 +4106,11 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         # Ensure `baz` was properly removed
         self.session = pagure.lib.query.create_session(self.dbpath)
         project = pagure.lib.query._get_project(self.session, "test")
-        self.assertEquals(
+        self.assertEqual(
             project.access_users,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
-        self.assertEquals(
+        self.assertEqual(
             project.access_groups,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
@@ -4191,11 +4191,11 @@ class PagureFlaskApiProjectModifyAclTests(tests.Modeltests):
         # Ensure `baz` was properly removed
         self.session = pagure.lib.query.create_session(self.dbpath)
         project = pagure.lib.query._get_project(self.session, "test")
-        self.assertEquals(
+        self.assertEqual(
             project.access_users,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
-        self.assertEquals(
+        self.assertEqual(
             project.access_groups,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
@@ -4214,7 +4214,7 @@ class PagureFlaskApiProjectOptionsTests(tests.Modeltests):
         tests.create_tokens_acl(self.session, "aaabbbcccddd", "modify_project")
 
         project = pagure.lib.query._get_project(self.session, "test")
-        self.assertEquals(
+        self.assertEqual(
             project.access_users,
             {"admin": [], "collaborator": [], "commit": [], "ticket": []},
         )
diff --git a/tests/test_pagure_flask_ui_repo.py b/tests/test_pagure_flask_ui_repo.py
index 9603726..055830f 100644
--- a/tests/test_pagure_flask_ui_repo.py
+++ b/tests/test_pagure_flask_ui_repo.py
@@ -5450,8 +5450,8 @@ index 0000000..fb7093d
         tests.add_readme_git_repo(os.path.join(self.path, "repos", "test.git"))
 
         user = pagure.lib.query.search_user(self.session, username="pingou")
-        self.assertEquals(len(user.emails), 2)
-        self.assertEquals(user.default_email, "bar@pingou.com")
+        self.assertEqual(len(user.emails), 2)
+        self.assertEqual(user.default_email, "bar@pingou.com")
 
         user = tests.FakeUser(username="pingou")
         with tests.user_set(self.app.application, user):
diff --git a/tests/test_pagure_flask_ui_roadmap.py b/tests/test_pagure_flask_ui_roadmap.py
index f7eaa3b..9c8b4a4 100644
--- a/tests/test_pagure_flask_ui_roadmap.py
+++ b/tests/test_pagure_flask_ui_roadmap.py
@@ -662,7 +662,7 @@ class PagureFlaskRoadmaptests(tests.Modeltests):
             output_text,
         )
         self.assertIn('<a class="notblue" href="/test/issue/2">', output_text)
-        self.assertEquals(
+        self.assertEqual(
             output_text.count('<a class="notblue" href="/test/issue/2">'), 1
         )
 
diff --git a/tests/test_pagure_utils.py b/tests/test_pagure_utils.py
index e39b694..71595be 100644
--- a/tests/test_pagure_utils.py
+++ b/tests/test_pagure_utils.py
@@ -60,23 +60,23 @@ class PagureUtilsTests(tests.SimplePagureTest):
         """Test lookup_deploykey with a non-deploykey username."""
         project = pagure.lib.query._get_project(self.session, "test")
         res = pagure.utils.lookup_deploykey(project, "pingou")
-        self.assertEquals(res, None)
+        self.assertEqual(res, None)
 
     def test_lookup_deploykey_different_project(self):
         """Test lookup_deploykey with a username for another project."""
         project = pagure.lib.query._get_project(self.session, "test2")
         res = pagure.utils.lookup_deploykey(project, "deploykey_test_1")
-        self.assertEquals(res, None)
+        self.assertEqual(res, None)
 
     def test_lookup_deploykey_non_existent_key(self):
         """Test lookup_deploykey with a non-existing deploykey."""
         project = pagure.lib.query._get_project(self.session, "test")
         res = pagure.utils.lookup_deploykey(project, "deploykey_test_2")
-        self.assertEquals(res, None)
+        self.assertEqual(res, None)
 
     def test_lookup_deploykey(self):
         """Test lookup_deploykey with a correct username."""
         project = pagure.lib.query._get_project(self.session, "test")
         res = pagure.utils.lookup_deploykey(project, "deploykey_test_1")
-        self.assertNotEquals(res, None)
+        self.assertNotEqual(res, None)
         self.assertFalse(res.pushaccess)

From 94d5a07be0d0d6829dd1dabe8a8337d312ea8551 Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Oct 02 2023 09:21:17 +0000
Subject: [PATCH 7/12] Fix warning jinga2.escape DeprecationWarning


For example

./pagure/ui/filters.py:580: DeprecationWarning: 'jinja2.escape' is
deprecated and will be removed in Jinja 3.1. Import 'markupsafe.escape'
instead.
    output = escape(author.name)

---

diff --git a/pagure/doc_utils.py b/pagure/doc_utils.py
index 9f3b5b2..3fb9d81 100644
--- a/pagure/doc_utils.py
+++ b/pagure/doc_utils.py
@@ -19,9 +19,9 @@ import docutils.core
 import docutils.examples
 
 try:
-    from jinja2 import escape
-except ImportError:
     from markupsafe import escape
+except ImportError:
+    from jinja2 import escape
 import kitchen.text.converters as ktc
 from markupsafe import Markup
 
diff --git a/pagure/ui/filters.py b/pagure/ui/filters.py
index 40cc276..0618bd0 100644
--- a/pagure/ui/filters.py
+++ b/pagure/ui/filters.py
@@ -27,9 +27,9 @@ import pygit2
 import six
 
 try:
-    from jinja2 import escape
-except ImportError:
     from markupsafe import escape
+except ImportError:
+    from jinja2 import escape
 from six.moves.urllib.parse import parse_qsl, urlparse
 
 import pagure.exceptions

From 52e097c558f836d5b8acdb7b6b0a4ac420cd7a15 Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Oct 02 2023 09:21:17 +0000
Subject: [PATCH 8/12] Fix create_remote() DeprecationWarning


/usr/lib64/python3.11/site-packages/pygit2/repository.py:223:
DeprecationWarning: Use repo.remotes.create(..)
    warnings.warn("Use repo.remotes.create(..)", DeprecationWarning)

---

diff --git a/pagure/lib/git.py b/pagure/lib/git.py
index ab66675..22b2585 100644
--- a/pagure/lib/git.py
+++ b/pagure/lib/git.py
@@ -1703,7 +1703,7 @@ def merge_pull_request(session, request, username, domerge=True):
             _log.info(
                 "  Adding remote: %s pointing to: %s", reponame, repopath
             )
-            remote = new_repo.create_remote(reponame, repopath)
+            remote = new_repo.remotes.create(reponame, repopath)
 
             # Fetch the commits
             remote.fetch()
@@ -2003,7 +2003,7 @@ def rebase_pull_request(session, request, username):
         _log.info(
             "  Adding remote: %s pointing to: %s", upstream, upstream_path
         )
-        remote = new_repo.create_remote(upstream, upstream_path)
+        remote = new_repo.remotes.create(upstream, upstream_path)
 
         # Fetch the commits
         remote.fetch()
diff --git a/pagure/lib/tasks.py b/pagure/lib/tasks.py
index 59000ad..9738035 100644
--- a/pagure/lib/tasks.py
+++ b/pagure/lib/tasks.py
@@ -655,7 +655,7 @@ def move_to_repospanner(self, session, name, namespace, user, region):
                 continue
             repourl, _ = project.repospanner_repo_info(repotype, region)
             repo_obj = pagure.lib.repo.PagureRepo(repopath)
-            repo_obj.create_remote("repospanner_push", repourl)
+            repo_obj.remotes.create("repospanner_push", repourl)
 
             command = [
                 "git",
diff --git a/tests/__init__.py b/tests/__init__.py
index b3b7156..6479fd0 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1195,7 +1195,7 @@ def add_pull_request_git_repo(
 
     # Add the main project as remote repo
     upstream_path = os.path.join(folder, "repos", repo.path)
-    remote = clone_repo.create_remote("upstream", upstream_path)
+    remote = clone_repo.remotes.create("upstream", upstream_path)
     remote.fetch()
 
     # Edit the sources file again
diff --git a/tests/test_pagure_flask_docs.py b/tests/test_pagure_flask_docs.py
index 0752d3d..b828681 100644
--- a/tests/test_pagure_flask_docs.py
+++ b/tests/test_pagure_flask_docs.py
@@ -85,7 +85,7 @@ class PagureFlaskDocstests(tests.SimplePagureTest):
         )
 
         # Push the changes to the bare repo
-        remote = repo.create_remote(
+        remote = repo.remotes.create(
             "origin", os.path.join(self.path, "repos", "docs", "test.git")
         )
 
diff --git a/tests/test_pagure_flask_ui_fork.py b/tests/test_pagure_flask_ui_fork.py
index 8d33263..1630128 100644
--- a/tests/test_pagure_flask_ui_fork.py
+++ b/tests/test_pagure_flask_ui_fork.py
@@ -5440,7 +5440,7 @@ More information</textarea>
         PagureRepo.push(ori_remote, refname)
 
         # Push to the fork repo
-        remote = clone_repo.create_remote("pingou_fork", gitrepo2)
+        remote = clone_repo.remotes.create("pingou_fork", gitrepo2)
         PagureRepo.push(remote, refname)
 
         # Add 1 commits to the fork repo
diff --git a/tests/test_pagure_lib_git_diff_pr.py b/tests/test_pagure_lib_git_diff_pr.py
index b677338..bda38cc 100644
--- a/tests/test_pagure_lib_git_diff_pr.py
+++ b/tests/test_pagure_lib_git_diff_pr.py
@@ -125,7 +125,7 @@ class PagureFlaskForkPrtests(tests.Modeltests):
         PagureRepo.push(ori_remote, refname)
 
         # Push to the fork repo
-        remote = clone_repo.create_remote("pingou_fork", gitrepo2)
+        remote = clone_repo.remotes.create("pingou_fork", gitrepo2)
         PagureRepo.push(remote, refname)
 
         # Do another 3 commits to the main repo

From 59b344f5c3bb60c573a847d313e44cd4bb69859d Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Oct 02 2023 09:21:17 +0000
Subject: [PATCH 9/12] test_style: AssertionError


These style *errors* are pretty annoing. IMHO the enforced format does
not make code "better". I tried hard to satisfy flake/black and
use less than 9 lines to express the original 2 lines (see
pagure/ui/groups.py,app.py) that they complained about.

```
would reformat pagure/api/project.py
would reformat pagure/ui/groups.py
would reformat pagure/ui/oidc_login.py
would reformat pagure/ui/app.py
```

---

diff --git a/pagure/api/project.py b/pagure/api/project.py
index 6201daf..33d0b6f 100644
--- a/pagure/api/project.py
+++ b/pagure/api/project.py
@@ -1451,7 +1451,10 @@ def api_new_project():
             404, error_code=APIERROR.ENEWPROJECTDISABLED
         )
 
-    if pagure_config["PAGURE_AUTH"] == 'oidc' and flask.g.fas_user.can_create is False:
+    if (
+        pagure_config["PAGURE_AUTH"] == "oidc"
+        and flask.g.fas_user.can_create is False
+    ):
         raise pagure.exceptions.APIError(
             403, error_code=APIERROR.ENEWPROJECTFORBIDDEN
         )
diff --git a/pagure/default_config.py b/pagure/default_config.py
index d6cb960..615293b 100644
--- a/pagure/default_config.py
+++ b/pagure/default_config.py
@@ -65,7 +65,8 @@ ENABLE_NEW_PROJECTS = True
 # When using OIDC auth, users must be in this OIDC group to create new projects
 RESTRICT_CREATE_BY_OIDC_GROUP = None
 
-# When using OIDC auth, users must be a member of RESTRICT_NEW_PROJECTS_BY_OIDC_GROUP and in total
+# When using OIDC auth, users must be a member of
+# RESTRICT_NEW_PROJECTS_BY_OIDC_GROUP and in total
 # this many groups to create new projects
 RESTRICT_CREATE_BY_OIDC_GROUP_COUNT = 0
 
diff --git a/pagure/ui/app.py b/pagure/ui/app.py
index e6eba74..f351a23 100644
--- a/pagure/ui/app.py
+++ b/pagure/ui/app.py
@@ -1053,10 +1053,16 @@ def new_project():
             description="Creation of new project is not allowed on this \
                 pagure instance",
         )
-    
-    if pagure_config["PAGURE_AUTH"] == 'oidc' and flask.g.fas_user.can_create is False:
-        flask.abort(403,description="You are not allowed to create new projects on this instance")
 
+    if (
+        pagure_config["PAGURE_AUTH"] == "oidc"
+        and flask.g.fas_user.can_create is False
+    ):
+        flask.abort(
+            403,
+            description="You are not allowed to create new projects on \
+                this instance",
+        )
 
     namespaces = pagure_config["ALLOWED_PREFIX"][:]
     if user:
diff --git a/pagure/ui/groups.py b/pagure/ui/groups.py
index c089ec7..861f955 100644
--- a/pagure/ui/groups.py
+++ b/pagure/ui/groups.py
@@ -354,8 +354,15 @@ def add_group():
     if not pagure_config.get("ENABLE_GROUP_MNGT", False):
         flask.abort(404)
 
-    if pagure_config["PAGURE_AUTH"] == 'oidc' and flask.g.fas_user.can_create is False:
-        flask.abort(403,description="You are not allowed to create new groups on this instance")
+    if (
+        pagure_config["PAGURE_AUTH"] == "oidc"
+        and flask.g.fas_user.can_create is False
+    ):
+        flask.abort(
+            403,
+            description="You are not allowed to create new \
+                groups on this instance",
+        )
 
     user = pagure.lib.query.search_user(
         flask.g.session, username=flask.g.fas_user.username
diff --git a/pagure/ui/oidc_login.py b/pagure/ui/oidc_login.py
index f3f37d2..431dad9 100644
--- a/pagure/ui/oidc_login.py
+++ b/pagure/ui/oidc_login.py
@@ -60,12 +60,17 @@ def fas_user_from_oidc():
                 pass
 
         oidc_group = pagure_config.get("RESTRICT_CREATE_BY_OIDC_GROUP", None)
-        oidc_group_count = pagure_config.get("RESTRICT_CREATE_BY_OIDC_GROUP_COUNT", 0)
+        oidc_group_count = pagure_config.get(
+            "RESTRICT_CREATE_BY_OIDC_GROUP_COUNT", 0
+        )
         can_create = True
         if oidc_group:
             if oidc_group not in info.get(groups_key, []):
                 can_create = False
-            elif (oidc_group_count != 0) and (len(info.get(groups_key, [])) < oidc_group_count):
+            elif (
+                oidc_group_count != 0
+                and len(info.get(groups_key, [])) < oidc_group_count
+            ):
                 can_create = False
 
         # Create the user object

From 209063f3f5f114f8f39b24148ab2ba0524bd3eca Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Oct 02 2023 09:21:17 +0000
Subject: [PATCH 10/12] Fix AssertionError in test_api_error_codes


Ryan Lerch introduced a new APIERROR enum in commit 02a5830dc,
but forgot to update the test case:

	tests/test_pagure_flask_api.py

Which fails with
	E       AssertionError: 47 != 46

---

diff --git a/tests/test_pagure_flask_api.py b/tests/test_pagure_flask_api.py
index 50f5223..fd3005e 100644
--- a/tests/test_pagure_flask_api.py
+++ b/tests/test_pagure_flask_api.py
@@ -176,7 +176,7 @@ class PagureFlaskApitests(tests.SimplePagureTest):
         output = self.app.get("/api/0/-/error_codes")
         self.assertEqual(output.status_code, 200)
         data = json.loads(output.get_data(as_text=True))
-        self.assertEqual(len(data), 46)
+        self.assertEqual(len(data), 47)
         self.assertEqual(
             sorted(data.keys()),
             sorted(
@@ -196,6 +196,7 @@ class PagureFlaskApitests(tests.SimplePagureTest):
                     "EISSUENOTALLOWED",
                     "EMODIFYPROJECTNOTALLOWED",
                     "ENEWPROJECTDISABLED",
+                    "ENEWPROJECTFORBIDDEN",
                     "ENOCODE",
                     "ENOCOMMENT",
                     "ENOCOMMIT",

From 18ad315fcaf7704aeb65b766c9b19c23c022059e Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Oct 02 2023 09:24:22 +0000
Subject: [PATCH 11/12] Fix "empty git" test cases


Fix failing tests
  test_get_stats_commits_empty_git
  test_get_stats_commits_trend_empty_git
Error caused by unhandled exception thrown by `pygit2 peel`.

```
Task pagure.lib.tasks.commits_history_stats[] raised unexpected:
ExceptionWithTraceback()
[]
File "pagure/lib/tasks.py", line 1047, in commits_history_stats
    repo_obj.head.peel().oid.hex, pygit2.GIT_SORT_NONE
```
All `test_pagure_flask_internal` test pass:

```
$ pytest  tests/test_pagure_flask_internal.py
platform linux -- Python 3.11.2, pytest-7.2.2, pluggy-1.0.0
rootdir: /tmp/pagure
plugins: anyio-3.5.0
collected 42 items

tests/test_pagure_flask_internal.py  [100%]
42 passed []
```

---

diff --git a/pagure/lib/tasks.py b/pagure/lib/tasks.py
index 9738035..481508d 100644
--- a/pagure/lib/tasks.py
+++ b/pagure/lib/tasks.py
@@ -962,13 +962,15 @@ def commits_author_stats(self, session, repopath):
         raise ValueError("Git repository not found.")
 
     repo_obj = pygit2.Repository(repopath)
+    try:
+        objids = repo_obj.head.peel().oid.hex
+    except pygit2.GitError as err:
+        return err
 
     stats = collections.defaultdict(int)
     number_of_commits = 0
     authors_email = set()
-    for commit in repo_obj.walk(
-        repo_obj.head.peel().oid.hex, pygit2.GIT_SORT_NONE
-    ):
+    for commit in repo_obj.walk(objids, pygit2.GIT_SORT_NONE):
         # For each commit record how many times each combination of name and
         # e-mail appears in the git history.
         number_of_commits += 1
@@ -1025,11 +1027,13 @@ def commits_history_stats(self, session, repopath):
         raise ValueError("Git repository not found.")
 
     repo_obj = pygit2.Repository(repopath)
+    try:
+        objids = repo_obj.head.peel().oid.hex
+    except pygit2.GitError as err:
+        return err
 
     dates = collections.defaultdict(int)
-    for commit in repo_obj.walk(
-        repo_obj.head.peel().oid.hex, pygit2.GIT_SORT_NONE
-    ):
+    for commit in repo_obj.walk(objids, pygit2.GIT_SORT_NONE):
         delta = (
             datetime.datetime.utcnow() - arrow.get(commit.commit_time).naive
         )
diff --git a/tests/test_pagure_flask_internal.py b/tests/test_pagure_flask_internal.py
index e47f1ce..afaf0cb 100644
--- a/tests/test_pagure_flask_internal.py
+++ b/tests/test_pagure_flask_internal.py
@@ -2003,7 +2003,6 @@ class PagureFlaskInternaltests(tests.Modeltests):
                     "results": "GitError(\"reference 'refs/heads/master' not found\")"
                 },
                 {"results": "reference 'refs/heads/master' not found"},
-                {"results": "reference 'refs/heads/master' not found"},
                 {"results": "Reference 'refs/heads/master' not found"},
             ]
         )

From 8a7f37020ba0a9303a85d7e2d342e183770603ea Mon Sep 17 00:00:00 2001
From: Klaus Koder <klauskoder@pagure.local>
Date: Oct 03 2023 09:16:20 +0000
Subject: [PATCH 12/12] Fix PagureHooksPagureHooktests failure when system wide config exist


When a pagure configuration file exist, for example, under
`/etc/pagure/pagure.cfg` and the `APP_URL` key in that file is
different from `http://localhost.localdomain/` then this test-case
will fail (intermediately?).

The root cause seem to be that `pagure_hook.py` keep a local
version of the configuration from the time when the file was loaded,
long time before the test-case create its configuration file.

```
pagure_config = pagure.config.reload_config()
```

The test case generate the configuration in `tests.setUp()`.

A the timing looks like this:

```
$ pytest tests/ -k test_generate_revision_change_log_full_url_fork
Using configuration file `/etc/pagure/pagure.cfg`
<reload_config from env: /etc/pagure/pagure.cfg>
<reload_config from env: /etc/pagure/pagure.cfg>
<reload_config from env: /etc/pagure/pagure.cfg>
<pagure_hook>
<reload_config from env: /etc/pagure/pagure.cfg>
<pagure_hook>

tests/test_pagure_hooks_pagure_hook.py
<reload_config from env: /etc/pagure/pagure.cfg>
<reload_config from env: /etc/pagure/pagure.cfg>
<Create config file in /tmp/pagure-tests-path-9yeilfhy/config>
<reload_config from env: /tmp/pagure-tests-path-9yeilfhy/config>
Detailed log of new commits:
[...]
```

That looks a bit inefficient, notice how the local version of
`pagure_config` inside `pagure_hook` is loaded from
`/etc/pagure/pagure.cfg` even before the test case have been chosen.

I can think of 3 ways to fix the issue.

1) Replace `"http://localhost.localdomain/"` with `mock.ANY`.
2) Pull the value of `pagure_config["APP_URL"]` from `pagure_hook`
   and use that in the test.
3) Fix the value of `pagure_config["APP_URL"]` using `mock.patch`.

Here version 3.

---

diff --git a/tests/test_pagure_hooks_pagure_hook.py b/tests/test_pagure_hooks_pagure_hook.py
index 5123efa..162ecda 100644
--- a/tests/test_pagure_hooks_pagure_hook.py
+++ b/tests/test_pagure_hooks_pagure_hook.py
@@ -93,6 +93,10 @@ class PagureHooksPagureHooktests(tests.SimplePagureTest):
         # Add a README to the git repo - First commit
         tests.add_readme_git_repo(self.folder)
 
+    @mock.patch.dict(
+        "pagure.hooks.pagure_hook.pagure_config",
+        {"APP_URL": "http://localhost.localdomain/"},
+    )
     @mock.patch("pagure.hooks.pagure_hook.fixes_relation")
     def test_generate_revision_change_log_short_url(self, fixes_relation):
         """Test generate_revision_change_log when the comment contains
@@ -125,6 +129,10 @@ class PagureHooksPagureHooktests(tests.SimplePagureTest):
             "http://localhost.localdomain/",
         )
 
+    @mock.patch.dict(
+        "pagure.hooks.pagure_hook.pagure_config",
+        {"APP_URL": "http://localhost.localdomain/"},
+    )
     @mock.patch("pagure.hooks.pagure_hook.fixes_relation")
     def test_generate_revision_change_log_full_url(self, fixes_relation):
         """Test generate_revision_change_log when the comment contains
@@ -161,6 +169,10 @@ class PagureHooksPagureHooktests(tests.SimplePagureTest):
             "http://localhost.localdomain/",
         )
 
+    @mock.patch.dict(
+        "pagure.hooks.pagure_hook.pagure_config",
+        {"APP_URL": "http://localhost.localdomain/"},
+    )
     @mock.patch("pagure.hooks.pagure_hook.fixes_relation")
     def test_generate_revision_change_log_full_url_fork(self, fixes_relation):
         """Test generate_revision_change_log when the comment contains

