summaryrefslogtreecommitdiffstats
path: root/aux-files
diff options
context:
space:
mode:
Diffstat (limited to 'aux-files')
-rw-r--r--aux-files/matrix-purple/fix-g_memdup.patch25
-rw-r--r--aux-files/openbox/openbox-add-fix-for-glib2-exposed-segfault.patch50
-rw-r--r--aux-files/openbox/openbox-python3.patch165
-rw-r--r--aux-files/python-bugwarrior/gitea-support.patch1563
-rw-r--r--aux-files/stable-diffusion-cpp/vulkan-support.patch123
5 files changed, 1926 insertions, 0 deletions
diff --git a/aux-files/matrix-purple/fix-g_memdup.patch b/aux-files/matrix-purple/fix-g_memdup.patch
new file mode 100644
index 0000000..d0ed6dc
--- /dev/null
+++ b/aux-files/matrix-purple/fix-g_memdup.patch
@@ -0,0 +1,25 @@
+From 2452ec47427f62a9a521742c4d46bceb073229db Mon Sep 17 00:00:00 2001
+From: msglm <msglm@techchud.xyz>
+Date: Sun, 19 Jan 2025 11:53:20 -0600
+Subject: [PATCH] g_memdup -> g_memdup2
+
+---
+ matrix-room.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/matrix-room.c b/matrix-room.c
+index 34ae48b..0d7bc06 100644
+--- a/matrix-room.c
++++ b/matrix-room.c
+@@ -671,7 +671,7 @@ static void _image_download_complete(MatrixConnectionData *ma,
+ }
+ if (is_known_image_type(content_type)) {
+ /* Excellent - something to work with */
+- int img_id = purple_imgstore_add_with_id(g_memdup(raw_body, raw_body_len),
++ int img_id = purple_imgstore_add_with_id(g_memdup2(raw_body, raw_body_len),
+ raw_body_len, NULL);
+ serv_got_chat_in(rid->conv->account->gc, g_str_hash(rid->room_id), rid->sender_display_name,
+ PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_IMAGES,
+--
+2.46.0
+
diff --git a/aux-files/openbox/openbox-add-fix-for-glib2-exposed-segfault.patch b/aux-files/openbox/openbox-add-fix-for-glib2-exposed-segfault.patch
new file mode 100644
index 0000000..ef68d31
--- /dev/null
+++ b/aux-files/openbox/openbox-add-fix-for-glib2-exposed-segfault.patch
@@ -0,0 +1,50 @@
+From 9ed6fdd71890c5cc43747f105382d5677e5d37e7 Mon Sep 17 00:00:00 2001
+From: pldubouilh <pldubouilh@gmail.com>
+Date: Fri, 17 Mar 2023 18:23:47 +0100
+Subject: [PATCH] Fix list traversal issue in client_calc_layer
+
+The calls to client_calc_layer_internal can modify stacking_list, which
+can cause us to follow dangling ->next pointers (either by the pointer
+itself already being freed, or it pointing to a freed area). Avoid this
+by copying the list first, the goal is to visit every client in the list
+once so this should be fine.
+---
+ openbox/client.c | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/openbox/client.c b/openbox/client.c
+index 7168b2407..b8264587c 100644
+--- a/openbox/client.c
++++ b/openbox/client.c
+@@ -2742,9 +2742,12 @@ static void client_calc_layer_internal(ObClient *self)
+ void client_calc_layer(ObClient *self)
+ {
+ GList *it;
++ /* the client_calc_layer_internal calls below modify stacking_list,
++ so we have to make a copy to iterate over */
++ GList *list = g_list_copy(stacking_list);
+
+ /* skip over stuff above fullscreen layer */
+- for (it = stacking_list; it; it = g_list_next(it))
++ for (it = list; it; it = g_list_next(it))
+ if (window_layer(it->data) <= OB_STACKING_LAYER_FULLSCREEN) break;
+
+ /* find the windows in the fullscreen layer, and mark them not-visited */
+@@ -2757,7 +2760,7 @@ void client_calc_layer(ObClient *self)
+ client_calc_layer_internal(self);
+
+ /* skip over stuff above fullscreen layer */
+- for (it = stacking_list; it; it = g_list_next(it))
++ for (it = list; it; it = g_list_next(it))
+ if (window_layer(it->data) <= OB_STACKING_LAYER_FULLSCREEN) break;
+
+ /* now recalc any windows in the fullscreen layer which have not
+@@ -2768,6 +2771,8 @@ void client_calc_layer(ObClient *self)
+ !WINDOW_AS_CLIENT(it->data)->visited)
+ client_calc_layer_internal(it->data);
+ }
++
++ g_list_free(it);
+ }
+
+ gboolean client_should_show(ObClient *self)
diff --git a/aux-files/openbox/openbox-python3.patch b/aux-files/openbox/openbox-python3.patch
new file mode 100644
index 0000000..782524d
--- /dev/null
+++ b/aux-files/openbox/openbox-python3.patch
@@ -0,0 +1,165 @@
+Retrieved from the openbox Debian package.
+
+From acfbbc4ea40932f183617bb7006700140fe5f61e Mon Sep 17 00:00:00 2001
+From: Troy Curtis Jr <troycurtisjr@gmail.com>
+Date: Wed, 13 Sep 2017 21:59:48 -0500
+Subject: [PATCH] Add python3 support to openbox-xdg-autostart.
+
+Updated syntax in openbox-xdg-autostart to support both python2 and
+python3.
+
+Added a configure substitution to set the chosen python at build time.
+
+https://bugzilla.icculus.org/show_bug.cgi?id=6444
+---
+ .gitignore | 1 +
+ configure.ac | 3 +
+ ...xdg-autostart => openbox-xdg-autostart.in} | 70 +++++++++----------
+ 3 files changed, 38 insertions(+), 36 deletions(-)
+ rename data/autostart/{openbox-xdg-autostart => openbox-xdg-autostart.in} (77%)
+
+diff --git a/configure.ac b/configure.ac
+index ca1602670..9a31e9845 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -103,6 +103,8 @@ AC_CHECK_HEADERS(ctype.h dirent.h errno.h fcntl.h grp.h locale.h pwd.h)
+ AC_CHECK_HEADERS(signal.h string.h stdio.h stdlib.h unistd.h sys/stat.h)
+ AC_CHECK_HEADERS(sys/select.h sys/socket.h sys/time.h sys/types.h sys/wait.h)
+
++AM_PATH_PYTHON([2],,)
++
+ AC_PATH_PROG([SED], [sed], [no])
+ if test "$SED" = "no"; then
+ AC_MSG_ERROR([The program "sed" is not available. This program is required to build Openbox.])
+@@ -259,6 +261,7 @@ AC_CONFIG_FILES([
+ obrender/version.h
+ obt/version.h
+ version.h
++ data/autostart/openbox-xdg-autostart
+ ])
+ AC_CONFIG_COMMANDS([doc],
+ [test -d doc || mkdir doc])
+diff --git a/data/autostart/openbox-xdg-autostart b/data/autostart/openbox-xdg-autostart.in
+similarity index 77%
+rename from data/autostart/openbox-xdg-autostart
+rename to data/autostart/openbox-xdg-autostart.in
+index 04a17a199..3c365b112 100755
+--- a/data/autostart/openbox-xdg-autostart
++++ b/data/autostart/openbox-xdg-autostart.in
+@@ -1,4 +1,4 @@
+-#!/usr/bin/env python
++#!@PYTHON@
+
+ # openbox-xdg-autostart runs things based on the XDG autostart specification
+ # Copyright (C) 2008 Dana Jansens
+@@ -28,9 +28,7 @@ try:
+ from xdg.DesktopEntry import DesktopEntry
+ from xdg.Exceptions import ParsingError
+ except ImportError:
+- print
+- print >>sys.stderr, "ERROR:", ME, "requires PyXDG to be installed"
+- print
++ sys.stderr.write("\nERROR: %s requires PyXDG to be installed\n" % ME)
+ sys.exit(1)
+
+ def main(argv=sys.argv):
+@@ -51,7 +49,7 @@ def main(argv=sys.argv):
+ try:
+ autofile = AutostartFile(path)
+ except ParsingError:
+- print "Invalid .desktop file: " + path
++ print("Invalid .desktop file: " + path)
+ else:
+ if not autofile in files:
+ files.append(autofile)
+@@ -99,9 +97,9 @@ class AutostartFile:
+
+ def _alert(self, str, info=False):
+ if info:
+- print "\t ", str
++ print("\t ", str)
+ else:
+- print "\t*", str
++ print("\t*", str)
+
+ def _showInEnvironment(self, envs, verbose=False):
+ default = not self.de.getOnlyShowIn()
+@@ -146,14 +144,14 @@ class AutostartFile:
+
+ def display(self, envs):
+ if self._shouldRun(envs):
+- print "[*] " + self.de.getName()
++ print("[*] " + self.de.getName())
+ else:
+- print "[ ] " + self.de.getName()
++ print("[ ] " + self.de.getName())
+ self._alert("File: " + self.path, info=True)
+ if self.de.getExec():
+ self._alert("Executes: " + self.de.getExec(), info=True)
+ self._shouldRun(envs, True)
+- print
++ print()
+
+ def run(self, envs):
+ here = os.getcwd()
+@@ -165,34 +163,34 @@ class AutostartFile:
+ os.chdir(here)
+
+ def show_help():
+- print "Usage:", ME, "[OPTION]... [ENVIRONMENT]..."
+- print
+- print "This tool will run xdg autostart .desktop files"
+- print
+- print "OPTIONS"
+- print " --list Show a list of the files which would be run"
+- print " Files which would be run are marked with an asterix"
+- print " symbol [*]. For files which would not be run,"
+- print " information is given for why they are excluded"
+- print " --help Show this help and exit"
+- print " --version Show version and copyright information"
+- print
+- print "ENVIRONMENT specifies a list of environments for which to run autostart"
+- print "applications. If none are specified, only applications which do not "
+- print "limit themselves to certain environments will be run."
+- print
+- print "ENVIRONMENT can be one or more of:"
+- print " GNOME Gnome Desktop"
+- print " KDE KDE Desktop"
+- print " ROX ROX Desktop"
+- print " XFCE XFCE Desktop"
+- print " Old Legacy systems"
+- print
++ print("Usage:", ME, "[OPTION]... [ENVIRONMENT]...")
++ print()
++ print("This tool will run xdg autostart .desktop files")
++ print()
++ print("OPTIONS")
++ print(" --list Show a list of the files which would be run")
++ print(" Files which would be run are marked with an asterix")
++ print(" symbol [*]. For files which would not be run,")
++ print(" information is given for why they are excluded")
++ print(" --help Show this help and exit")
++ print(" --version Show version and copyright information")
++ print()
++ print("ENVIRONMENT specifies a list of environments for which to run autostart")
++ print("applications. If none are specified, only applications which do not ")
++ print("limit themselves to certain environments will be run.")
++ print()
++ print("ENVIRONMENT can be one or more of:")
++ print(" GNOME Gnome Desktop")
++ print(" KDE KDE Desktop")
++ print(" ROX ROX Desktop")
++ print(" XFCE XFCE Desktop")
++ print(" Old Legacy systems")
++ print()
+
+ def show_version():
+- print ME, VERSION
+- print "Copyright (c) 2008 Dana Jansens"
+- print
++ print(ME, VERSION)
++ print("Copyright (c) 2008 Dana Jansens")
++ print()
+
+ if __name__ == "__main__":
+ sys.exit(main())
diff --git a/aux-files/python-bugwarrior/gitea-support.patch b/aux-files/python-bugwarrior/gitea-support.patch
new file mode 100644
index 0000000..80ccb4b
--- /dev/null
+++ b/aux-files/python-bugwarrior/gitea-support.patch
@@ -0,0 +1,1563 @@
+From 6d7e50b5d1f7e79a685b08045cd91ea0b24f2154 Mon Sep 17 00:00:00 2001
+From: wamsachel <wamsachel@gmail.com>
+Date: Wed, 1 Apr 2020 22:27:52 +0000
+Subject: [PATCH 1/8] (WIP) Adding service support for Gitea
+
+Imported from https://github.com/GothenburgBitFactory/bugwarrior/pull/720
+---
+ bugwarrior/services/gitea.py | 536 +++++++++++++++++++++++++++++++++++
+ setup.py | 1 +
+ 2 files changed, 537 insertions(+)
+ create mode 100644 bugwarrior/services/gitea.py
+
+diff --git a/bugwarrior/services/gitea.py b/bugwarrior/services/gitea.py
+new file mode 100644
+index 00000000..a4c174c8
+--- /dev/null
++++ b/bugwarrior/services/gitea.py
+@@ -0,0 +1,536 @@
++# coding: utf-8
++# gitea.py
++"""Bugwarrior service support class for Gitea
++
++Available classes:
++- GiteaClient(ServiceClient): Constructs Gitea API strings
++- GiteaIssue(Issue): TaskWarrior Interface
++- GiteaService(IssueService): Engine for firing off requests
++
++Todo:
++ * Add token support
++ * Flesh out more features offered by gitea api
++"""
++from builtins import filter
++import re
++import six
++from urllib.parse import urlparse
++from urllib.parse import quote_plus
++
++import requests
++from six.moves.urllib.parse import quote_plus
++from jinja2 import Template
++
++from bugwarrior.config import asbool, aslist, die
++from bugwarrior.services import IssueService, Issue, ServiceClient
++
++import logging
++log = logging.getLogger(__name__) # pylint: disable-msg=C0103
++
++
++class GiteaClient(ServiceClient):
++ """Builds Gitea API strings
++ Args:
++ host (str): remote gitea server
++ auth (dict): authentication credentials
++
++ Attributes:
++ host (str): remote gitea server
++ auth (dict): authentication credentials
++ session (requests.Session): requests persist settings
++
++ Publics Functions:
++ - get_repos:
++ - get_query:
++ - get_issues:
++ - get_directly_assigned_issues:
++ - get_comments:
++ - get_pulls:
++ """
++ def __init__(self, host, auth):
++ self.host = host
++ self.auth = auth
++ self.session = requests.Session()
++ if 'token' in self.auth:
++ authorization = 'token ' + self.auth['token']
++ self.session.headers['Authorization'] = authorization
++
++ def _api_url(self, path, **context):
++ """ Build the full url to the API endpoint """
++ # TODO add token support
++ if 'basic' in self.auth:
++ (username, password) = self.auth['basic']
++ baseurl = 'https://{user}:{secret}@{host}/api/v1'.format(
++ host=self.host,
++ user=username,
++ secret=quote_plus(password))
++ if 'token' in self.auth:
++ baseurl = 'https://{host}/api/v1'.format(
++ host=self.host)
++ return baseurl + path.format(**context)
++
++ # TODO Modify these for gitea support
++ def get_repos(self, username):
++ # user_repos = self._getter(self._api_url("/user/repos?per_page=100"))
++ public_repos = self._getter(self._api_url(
++ '/users/{username}/repos', username=username))
++ return public_repos
++
++ def get_query(self, query):
++ """Run a generic issue/PR query"""
++ url = self._api_url(
++ '/search/issues?q={query}&per_page=100', query=query)
++ return self._getter(url, subkey='items')
++
++ def get_issues(self, username, repo):
++ url = self._api_url(
++ '/repos/{username}/{repo}/issues?per_page=100',
++ username=username, repo=repo)
++ return self._getter(url)
++
++ def get_directly_assigned_issues(self, username):
++ """ Returns all issues assigned to authenticated user.
++
++ This will return all issues assigned to the authenticated user
++ regardless of whether the user owns the repositories in which the
++ issues exist.
++ """
++ url = self._api_url('/users/{username}/issues',
++ username=username)
++ return self._getter(url)
++
++ # TODO close to gitea format: /comments/{id}
++ def get_comments(self, username, repo, number):
++ url = self._api_url(
++ '/repos/{username}/{repo}/issues/{number}/comments?per_page=100',
++ username=username, repo=repo, number=number)
++ return self._getter(url)
++
++ def get_pulls(self, username, repo):
++ url = self._api_url(
++ '/repos/{username}/{repo}/pulls?per_page=100',
++ username=username, repo=repo)
++ return self._getter(url)
++
++ def _getter(self, url, subkey=None):
++ """ Pagination utility. Obnoxious. """
++
++ kwargs = {}
++ if 'basic' in self.auth:
++ kwargs['auth'] = self.auth['basic']
++
++ results = []
++ link = dict(next=url)
++
++ while 'next' in link:
++ response = self.session.get(link['next'], **kwargs)
++
++ # Warn about the mis-leading 404 error code. See:
++ # https://gitea.com/ralphbean/bugwarrior/issues/374
++ # TODO this is a copy paste from github.py, see what gitea produces
++ if response.status_code == 404 and 'token' in self.auth:
++ log.warning('A \'404\' from gitea may indicate an auth '
++ 'failure. Make sure both that your token is correct '
++ 'and that it has \'public_repo\' and not \'public '
++ 'access\' rights.')
++
++ json_res = self.json_response(response)
++
++ if subkey is not None:
++ json_res = json_res[subkey]
++
++ results += json_res
++
++ link = self._link_field_to_dict(response.headers.get('link', None))
++
++ return results
++
++ # TODO: just copied from github.py
++ @staticmethod
++ def _link_field_to_dict(field):
++ """ Utility for ripping apart gitea's Link header field.
++ It's kind of ugly.
++ """
++
++ if not field:
++ return dict()
++
++ return dict([
++ (
++ part.split('; ')[1][5:-1],
++ part.split('; ')[0][1:-1],
++ ) for part in field.split(', ')
++ ])
++
++
++class GiteaIssue(Issue):
++ TITLE = 'giteatitle'
++ BODY = 'giteabody'
++ CREATED_AT = 'giteacreatedon'
++ UPDATED_AT = 'giteaupdatedat'
++ CLOSED_AT = 'giteaclosedon'
++ MILESTONE = 'giteamilestone'
++ URL = 'giteaurl'
++ REPO = 'gitearepo'
++ TYPE = 'giteatype'
++ NUMBER = 'giteanumber'
++ USER = 'giteauser'
++ NAMESPACE = 'giteanamespace'
++ STATE = 'giteastate'
++
++ UDAS = {
++ TITLE: {
++ 'type': 'string',
++ 'label': 'Gitea Title',
++ },
++ BODY: {
++ 'type': 'string',
++ 'label': 'Gitea Body',
++ },
++ CREATED_AT: {
++ 'type': 'date',
++ 'label': 'Gitea Created',
++ },
++ UPDATED_AT: {
++ 'type': 'date',
++ 'label': 'Gitea Updated',
++ },
++ CLOSED_AT: {
++ 'type': 'date',
++ 'label': 'Gitea Closed',
++ },
++ MILESTONE: {
++ 'type': 'string',
++ 'label': 'Gitea Milestone',
++ },
++ REPO: {
++ 'type': 'string',
++ 'label': 'Gitea Repo Slug',
++ },
++ URL: {
++ 'type': 'string',
++ 'label': 'Gitea URL',
++ },
++ TYPE: {
++ 'type': 'string',
++ 'label': 'Gitea Type',
++ },
++ NUMBER: {
++ 'type': 'numeric',
++ 'label': 'Gitea Issue/PR #',
++ },
++ USER: {
++ 'type': 'string',
++ 'label': 'Gitea User',
++ },
++ NAMESPACE: {
++ 'type': 'string',
++ 'label': 'Gitea Namespace',
++ },
++ STATE: {
++ 'type': 'string',
++ 'label': 'Gitea State',
++ }
++ }
++ UNIQUE_KEY = (URL, TYPE,)
++
++ @staticmethod
++ def _normalize_label_to_tag(label):
++ return re.sub(r'[^a-zA-Z0-9]', '_', label)
++
++ def to_taskwarrior(self):
++ milestone = self.record['milestone']
++ if milestone:
++ milestone = milestone['title']
++
++ body = self.record['body']
++ if body:
++ body = body.replace('\r\n', '\n')
++
++ created = self.parse_date(self.record.get('created_at'))
++ updated = self.parse_date(self.record.get('updated_at'))
++ closed = self.parse_date(self.record.get('closed_at'))
++
++ return {
++ 'project': self.extra['project'],
++ 'priority': self.origin['default_priority'],
++ 'annotations': self.extra.get('annotations', []),
++ 'tags': self.get_tags(),
++ 'entry': created,
++ 'end': closed,
++
++ self.URL: self.record['url'],
++ self.REPO: self.record['repo'],
++ self.TYPE: self.extra['type'],
++ self.USER: self.record['user']['login'],
++ self.TITLE: self.record['title'],
++ self.BODY: body,
++ self.MILESTONE: milestone,
++ self.NUMBER: self.record['number'],
++ self.CREATED_AT: created,
++ self.UPDATED_AT: updated,
++ self.CLOSED_AT: closed,
++ self.NAMESPACE: self.extra['namespace'],
++ self.STATE: self.record.get('state', '')
++ }
++
++ def get_tags(self):
++ tags = []
++
++ if not self.origin['import_labels_as_tags']:
++ return tags
++
++ context = self.record.copy()
++ label_template = Template(self.origin['label_template'])
++
++ for label_dict in self.record.get('labels', []):
++ context.update({
++ 'label': self._normalize_label_to_tag(label_dict['name'])
++ })
++ tags.append(
++ label_template.render(context)
++ )
++
++ return tags
++
++ def get_default_description(self):
++ log.info('In get_default_description')
++ return self.build_default_description(
++ title=self.record['title'],
++ url=self.get_processed_url(self.record['url']),
++ number=self.record['number'],
++ cls=self.extra['type'],
++ )
++
++
++class GiteaService(IssueService):
++ ISSUE_CLASS = GiteaIssue
++ CONFIG_PREFIX = 'gitea'
++
++ def __init__(self, *args, **kw):
++ super(GiteaService, self).__init__(*args, **kw)
++
++ self.host = self.config.get('host', 'gitea.com')
++ self.login = self.config.get('login')
++
++ auth = {}
++ token = self.config.get('token')
++ if 'token' in self.config:
++ token = self.get_password('token', self.login)
++ auth['token'] = token
++ else:
++ password = self.get_password('password', self.login)
++ auth['basic'] = (self.login, password)
++
++ self.client = GiteaClient(self.host, auth)
++
++ self.exclude_repos = self.config.get('exclude_repos', [], aslist)
++ self.include_repos = self.config.get('include_repos', [], aslist)
++
++ self.username = self.config.get('username')
++ self.filter_pull_requests = self.config.get(
++ 'filter_pull_requests', default=False, to_type=asbool
++ )
++ self.exclude_pull_requests = self.config.get(
++ 'exclude_pull_requests', default=False, to_type=asbool
++ )
++ self.involved_issues = self.config.get(
++ 'involved_issues', default=False, to_type=asbool
++ )
++ self.import_labels_as_tags = self.config.get(
++ 'import_labels_as_tags', default=False, to_type=asbool
++ )
++ self.label_template = self.config.get(
++ 'label_template', default='{{label}}', to_type=six.text_type
++ )
++ self.project_owner_prefix = self.config.get(
++ 'project_owner_prefix', default=False, to_type=asbool
++ )
++
++ self.query = self.config.get(
++ 'query',
++ default='involves:{user} state:open'.format(
++ user=self.username) if self.involved_issues else '',
++ to_type=six.text_type
++ )
++
++ @staticmethod
++ def get_keyring_service(service_config):
++ #TODO grok this
++ login = service_config.get('login')
++ username = service_config.get('username')
++ host = service_config.get('host', default='gitea.com')
++ return 'gitea://{login}@{host}/{username}'.format(
++ login=login, username=username, host=host)
++
++ def get_service_metadata(self):
++ return {
++ 'import_labels_as_tags': self.import_labels_as_tags,
++ 'label_template': self.label_template,
++ }
++
++ def get_owned_repo_issues(self, tag):
++ """ Grab all the issues """
++ issues = {}
++ for issue in self.client.get_issues(*tag.split('/')):
++ issues[issue['url']] = (tag, issue)
++ return issues
++
++ def get_query(self, query):
++ """ Grab all issues matching a gitea query """
++ log.info('In get_query')
++ issues = {}
++ for issue in self.client.get_query(query):
++ url = issue['url']
++ try:
++ repo = self.get_repository_from_issue(issue)
++ except ValueError as e:
++ log.critical(e)
++ else:
++ issues[url] = (repo, issue)
++ return issues
++
++ def get_directly_assigned_issues(self, username):
++ issues = {}
++ for issue in self.client.get_directly_assigned_issues(self.username):
++ repos = self.get_repository_from_issue(issue)
++ issues[issue['url']] = (repos, issue)
++ return issues
++
++ @classmethod
++ def get_repository_from_issue(cls, issue):
++ if 'repo' in issue:
++ return issue['repo']
++ if 'repos_url' in issue:
++ url = issue['repos_url']
++ elif 'repository_url' in issue:
++ url = issue['repository_url']
++ else:
++ raise ValueError('Issue has no repository url' + str(issue))
++ tag = re.match('.*/([^/]*/[^/]*)$', url)
++ if tag is None:
++ raise ValueError('Unrecognized URL: {}.'.format(url))
++ return tag.group(1)
++
++ def _comments(self, tag, number):
++ user, repo = tag.split('/')
++ return self.client.get_comments(user, repo, number)
++
++ def annotations(self, tag, issue, issue_obj):
++ log.info('in Annotations')
++ #log.info(repr(issue))
++ log.info('body: {}'.format(issue['body']))
++ url = issue['url']
++ annotations = []
++ if self.annotation_comments:
++ comments = self._comments(tag, issue['body'])
++ # log.info(" got comments for %s", issue['url'])
++ annotations = ((
++ c['user']['login'],
++ c['body'],
++ ) for c in comments)
++ annotations_result = self.build_annotations(
++ annotations,
++ issue_obj.get_processed_url(url))
++ log.info('annotations: {}'.format(annotations_result))
++ return annotations_result
++
++ def _reqs(self, tag):
++ """ Grab all the pull requests """
++ return [
++ (tag, i) for i in
++ self.client.get_pulls(*tag.split('/'))
++ ]
++
++ def get_owner(self, issue):
++ if issue[1]['assignee']:
++ return issue[1]['assignee']['login']
++
++ def filter_issues(self, issue):
++ repo, _ = issue
++ return self.filter_repo_name(repo.split('/')[-3])
++
++ def filter_repos(self, repo):
++ if repo['owner']['login'] != self.username:
++ return False
++
++ return self.filter_repo_name(repo['name'])
++
++ def filter_repo_name(self, name):
++ if self.exclude_repos:
++ if name in self.exclude_repos:
++ return False
++
++ if self.include_repos:
++ if name in self.include_repos:
++ return True
++ else:
++ return False
++
++ return True
++
++ def include(self, issue):
++ if 'pull_request' in issue[1]:
++ if self.exclude_pull_requests:
++ return False
++ if not self.filter_pull_requests:
++ return True
++ return super(GiteaService, self).include(issue)
++
++ def issues(self):
++ issues = {}
++ if self.query:
++ issues.update(self.get_query(self.query))
++
++ if self.config.get('include_user_repos', True, asbool):
++ # Only query for all repos if an explicit
++ # include_repos list is not specified.
++ if self.include_repos:
++ repos = self.include_repos
++ else:
++ all_repos = self.client.get_repos(self.username)
++ repos = filter(self.filter_repos, all_repos)
++ repos = [repo['name'] for repo in repos]
++
++ for repo in repos:
++ log.info('Found repo: {}'.format(repo))
++ issues.update(
++ self.get_owned_repo_issues(
++ self.username + '/' + repo)
++ )
++ issues.update(
++ filter(self.filter_issues,
++ self.get_directly_assigned_issues(self.username).items())
++ )
++
++ log.info(' Found %i issues.', len(issues)) # these were debug logs
++ issues = list(filter(self.include, issues.values()))
++ log.info(' Pruned down to %i issues.', len(issues)) # these were debug logs
++
++ for tag, issue in issues:
++ # Stuff this value into the upstream dict for:
++ # https://gitea.com/ralphbean/bugwarrior/issues/159
++ issue['repo'] = tag
++
++ issue_obj = self.get_issue_for_record(issue)
++ tagParts = tag.split('/')
++ projectName = tagParts[1]
++ if self.project_owner_prefix:
++ projectName = tagParts[0]+'.'+projectName
++ extra = {
++ 'project': projectName,
++ 'type': 'pull_request' if 'pull_request' in issue else 'issue',
++ 'annotations': [issue['body']],
++ 'namespace': self.username,
++ }
++ issue_obj.update_extra(extra)
++ yield issue_obj
++
++ @classmethod
++ def validate_config(cls, service_config, target):
++ if 'login' not in service_config:
++ die('[%s] has no \'gitea.login\'' % target)
++
++ if 'token' not in service_config and 'password' not in service_config:
++ die('[%s] has no \'gitea.token\' or \'gitea.password\'' % target)
++
+diff --git a/setup.py b/setup.py
+index c761a231..b5306da5 100644
+--- a/setup.py
++++ b/setup.py
+@@ -82,6 +82,7 @@
+ bugwarrior-vault = bugwarrior:vault
+ bugwarrior-uda = bugwarrior:uda
+ [bugwarrior.service]
++ gitea=bugwarrior.services.gitea:GiteaService
+ github=bugwarrior.services.github:GithubService
+ gitlab=bugwarrior.services.gitlab:GitlabService
+ bitbucket=bugwarrior.services.bitbucket:BitbucketService
+
+From 80cd03d1ff85244f8a2c2beb37eab16af11e1adf Mon Sep 17 00:00:00 2001
+From: msglm <msglm@techchud.xyz>
+Date: Tue, 21 May 2024 04:21:35 -0500
+Subject: [PATCH 2/8] Add basic Gitea support
+
+Builds off PR #720 to add gitea integration to bugwarrior.
+---
+ bugwarrior/services/gitea.py | 120 +++++++++++++++++++++--------------
+ 1 file changed, 74 insertions(+), 46 deletions(-)
+
+diff --git a/bugwarrior/services/gitea.py b/bugwarrior/services/gitea.py
+index a4c174c8..445846ff 100644
+--- a/bugwarrior/services/gitea.py
++++ b/bugwarrior/services/gitea.py
+@@ -1,4 +1,4 @@
+-# coding: utf-8
++#/home/joybuke/Documents/ComputerScience/Projects/Personal/bugwarrior/bugwarrior/services coding: utf-8
+ # gitea.py
+ """Bugwarrior service support class for Gitea
+
+@@ -12,21 +12,45 @@
+ * Flesh out more features offered by gitea api
+ """
+ from builtins import filter
++import logging
++import pathlib
+ import re
+ import six
++import sys
+ from urllib.parse import urlparse
+ from urllib.parse import quote_plus
+
+ import requests
+ from six.moves.urllib.parse import quote_plus
++import typing_extensions
+ from jinja2 import Template
+
+-from bugwarrior.config import asbool, aslist, die
++from bugwarrior import config
+ from bugwarrior.services import IssueService, Issue, ServiceClient
+
+-import logging
+ log = logging.getLogger(__name__) # pylint: disable-msg=C0103
+
++class GiteaConfig(config.ServiceConfig):
++ service: typing_extensions.Literal['gitea']
++
++ host = "gitea.com"
++ login: str
++ token: str
++ login: str
++ username: str
++ password: str
++ exclude_repos = []
++ include_repos = []
++
++ def get(self, key, default=None, to_type=None):
++ try:
++ value = self.config_parser.get(self.service_target, self._get_key(key))
++ if to_type:
++ return to_type(value)
++ return value
++ except:
++ return default
++
+
+ class GiteaClient(ServiceClient):
+ """Builds Gitea API strings
+@@ -95,9 +119,9 @@ def get_directly_assigned_issues(self, username):
+ regardless of whether the user owns the repositories in which the
+ issues exist.
+ """
+- url = self._api_url('/users/{username}/issues',
+- username=username)
+- return self._getter(url)
++ url = self._api_url('/repos/issues/search',
++ username=username, assignee=True)
++ return self._getter(url, passedParams={'assigned': True, 'limit': 100}) #TODO: make the limit configurable
+
+ # TODO close to gitea format: /comments/{id}
+ def get_comments(self, username, repo, number):
+@@ -112,7 +136,7 @@ def get_pulls(self, username, repo):
+ username=username, repo=repo)
+ return self._getter(url)
+
+- def _getter(self, url, subkey=None):
++ def _getter(self, url, subkey=None, passedParams={}):
+ """ Pagination utility. Obnoxious. """
+
+ kwargs = {}
+@@ -123,7 +147,7 @@ def _getter(self, url, subkey=None):
+ link = dict(next=url)
+
+ while 'next' in link:
+- response = self.session.get(link['next'], **kwargs)
++ response = self.session.get(link['next'], params=passedParams, **kwargs)
+
+ # Warn about the mis-leading 404 error code. See:
+ # https://gitea.com/ralphbean/bugwarrior/issues/374
+@@ -253,14 +277,14 @@ def to_taskwarrior(self):
+
+ return {
+ 'project': self.extra['project'],
+- 'priority': self.origin['default_priority'],
++ 'priority': self.config.default_priority,
+ 'annotations': self.extra.get('annotations', []),
+ 'tags': self.get_tags(),
+ 'entry': created,
+ 'end': closed,
+
+ self.URL: self.record['url'],
+- self.REPO: self.record['repo'],
++ self.REPO: self.record['repository'],
+ self.TYPE: self.extra['type'],
+ self.USER: self.record['user']['login'],
+ self.TITLE: self.record['title'],
+@@ -277,11 +301,11 @@ def to_taskwarrior(self):
+ def get_tags(self):
+ tags = []
+
+- if not self.origin['import_labels_as_tags']:
++ if not self.config.get('import_labels_as_tags'):
+ return tags
+
+ context = self.record.copy()
+- label_template = Template(self.origin['label_template'])
++ label_template = Template(self.config.get('label_template'))
+
+ for label_dict in self.record.get('labels', []):
+ context.update({
+@@ -305,46 +329,51 @@ def get_default_description(self):
+
+ class GiteaService(IssueService):
+ ISSUE_CLASS = GiteaIssue
++ CONFIG_SCHEMA = GiteaConfig
+ CONFIG_PREFIX = 'gitea'
+
+ def __init__(self, *args, **kw):
+ super(GiteaService, self).__init__(*args, **kw)
+
+- self.host = self.config.get('host', 'gitea.com')
+- self.login = self.config.get('login')
+-
+ auth = {}
+- token = self.config.get('token')
+- if 'token' in self.config:
+- token = self.get_password('token', self.login)
++ token = self.config.token
++ self.login = self.config.login
++ if hasattr(self.config, 'token'):
++ token = self.get_password('token', login=self.login)
+ auth['token'] = token
+- else:
+- password = self.get_password('password', self.login)
++ elif hasattr(self.config.hasattr, 'password'):
++ password = self.get_password('password', login=self.login)
+ auth['basic'] = (self.login, password)
++ else:
++ #Probably should be called by validate_config, but I don't care to fix that.
++ logging.critical("ERROR! Neither token or password was provided in config!")
++ sys.exit(1)
+
+- self.client = GiteaClient(self.host, auth)
++ self.client = GiteaClient(host=self.config.host, auth=auth)
+
+- self.exclude_repos = self.config.get('exclude_repos', [], aslist)
+- self.include_repos = self.config.get('include_repos', [], aslist)
++ self.host = self.config.host
+
+- self.username = self.config.get('username')
++ self.exclude_repos = self.config.exclude_repos
++ self.include_repos = self.config.include_repos
++
++ self.username = self.config.username
+ self.filter_pull_requests = self.config.get(
+- 'filter_pull_requests', default=False, to_type=asbool
++ 'filter_pull_requests', default=False, to_type=bool
+ )
+ self.exclude_pull_requests = self.config.get(
+- 'exclude_pull_requests', default=False, to_type=asbool
++ 'exclude_pull_requests', default=False, to_type=bool
+ )
+ self.involved_issues = self.config.get(
+- 'involved_issues', default=False, to_type=asbool
++ 'involved_issues', default=False, to_type=bool
+ )
+ self.import_labels_as_tags = self.config.get(
+- 'import_labels_as_tags', default=False, to_type=asbool
++ 'import_labels_as_tags', default=False, to_type=bool
+ )
+ self.label_template = self.config.get(
+ 'label_template', default='{{label}}', to_type=six.text_type
+ )
+ self.project_owner_prefix = self.config.get(
+- 'project_owner_prefix', default=False, to_type=asbool
++ 'project_owner_prefix', default=False, to_type=bool
+ )
+
+ self.query = self.config.get(
+@@ -357,9 +386,9 @@ def __init__(self, *args, **kw):
+ @staticmethod
+ def get_keyring_service(service_config):
+ #TODO grok this
+- login = service_config.get('login')
+- username = service_config.get('username')
+- host = service_config.get('host', default='gitea.com')
++ login = service_config.login
++ username = service_config.username
++ host = service_config.host
+ return 'gitea://{login}@{host}/{username}'.format(
+ login=login, username=username, host=host)
+
+@@ -399,18 +428,17 @@ def get_directly_assigned_issues(self, username):
+
+ @classmethod
+ def get_repository_from_issue(cls, issue):
+- if 'repo' in issue:
+- return issue['repo']
+- if 'repos_url' in issue:
+- url = issue['repos_url']
+- elif 'repository_url' in issue:
+- url = issue['repository_url']
++ if 'repository' in issue:
++ url = issueloc=issue["html_url"]
+ else:
+ raise ValueError('Issue has no repository url' + str(issue))
++
++ #Literal cargo-cult crap, idk if this should be kept
+ tag = re.match('.*/([^/]*/[^/]*)$', url)
+ if tag is None:
+ raise ValueError('Unrecognized URL: {}.'.format(url))
+- return tag.group(1)
++
++ return url.rsplit("/",2)[0]
+
+ def _comments(self, tag, number):
+ user, repo = tag.split('/')
+@@ -482,7 +510,7 @@ def issues(self):
+ if self.query:
+ issues.update(self.get_query(self.query))
+
+- if self.config.get('include_user_repos', True, asbool):
++ if self.config.get('include_user_repos', True, bool):
+ # Only query for all repos if an explicit
+ # include_repos list is not specified.
+ if self.include_repos:
+@@ -510,13 +538,11 @@ def issues(self):
+ for tag, issue in issues:
+ # Stuff this value into the upstream dict for:
+ # https://gitea.com/ralphbean/bugwarrior/issues/159
+- issue['repo'] = tag
++ projectName = issue['repository']["name"]
+
+ issue_obj = self.get_issue_for_record(issue)
+- tagParts = tag.split('/')
+- projectName = tagParts[1]
+ if self.project_owner_prefix:
+- projectName = tagParts[0]+'.'+projectName
++ projectName = issue['repository']["owner"] +'.'+projectName
+ extra = {
+ 'project': projectName,
+ 'type': 'pull_request' if 'pull_request' in issue else 'issue',
+@@ -529,8 +555,10 @@ def issues(self):
+ @classmethod
+ def validate_config(cls, service_config, target):
+ if 'login' not in service_config:
+- die('[%s] has no \'gitea.login\'' % target)
++ log.critical('[%s] has no \'gitea.login\'' % target)
++ sys.exit(1)
+
+ if 'token' not in service_config and 'password' not in service_config:
+- die('[%s] has no \'gitea.token\' or \'gitea.password\'' % target)
++ log.critical('[%s] has no \'gitea.token\' or \'gitea.password\'' % target)
++ sys.exit(1)
+
+
+From 81b3fa0b47db93fb83a54c6727f5ee3c408797c5 Mon Sep 17 00:00:00 2001
+From: msglm <msglm@techchud.xyz>
+Date: Wed, 22 May 2024 20:59:54 -0500
+Subject: [PATCH 3/8] Add basic documentation
+
+mish-mash between the github and gitlab documentation with everything I
+don't think is supported removed.
+---
+ bugwarrior/docs/services/gitea.rst | 124 +++++++++++++++++++++++++++++
+ 1 file changed, 124 insertions(+)
+ create mode 100644 bugwarrior/docs/services/gitea.rst
+
+diff --git a/bugwarrior/docs/services/gitea.rst b/bugwarrior/docs/services/gitea.rst
+new file mode 100644
+index 00000000..6ccf2308
+--- /dev/null
++++ b/bugwarrior/docs/services/gitea.rst
+@@ -0,0 +1,124 @@
++Gitea
++======
++
++You can import tasks from your Gitea instance using
++the ``gitea`` service name.
++
++Example Service
++---------------
++
++Here's an example of a Gitea target:
++
++.. config::
++
++ [user_gitea]
++ service = gitea
++ gitea.login = ralphbean
++ gitea.username = ralphbean
++ gitea.host = git.bean.com #Note: the lack of https, the service will assume HTTPS by default.
++ gitea.password = @oracle:eval:pass show 'git.bean.com'
++ gitea.token = 0000000000000000000000000000000
++
++The above example is the minimum required to import issues from
++Gitea. You can also feel free to use any of the
++configuration options described in :ref:`common_configuration_options`
++or described in `Service Features`_ below.
++
++The ``token`` is your private API token.
++
++Service Features
++----------------
++
++Include and Exclude Certain Repositories
++++++++++++++++++++++++++++++++++++++++++
++
++If you happen to be working with a large number of projects, you
++may want to pull issues from only a subset of your repositories. To
++do that, you can use the ``include_repos`` option.
++
++For example, if you would like to only pull-in issues from
++your own ``project_foo`` and team ``bar``'s ``project_fox`` repositories, you
++could add this line to your service configuration (replacing ``me`` by your own
++login):
++
++.. config::
++ :fragment: gitea
++
++ gitea.include_repos = me/project_foo, bar/project_fox
++
++Alternatively, if you have a particularly noisy repository, you can
++instead choose to import all issues excepting it using the
++``exclude_repos`` configuration option.
++
++In this example, ``noisy/repository`` is the repository you would
++*not* like issues created for:
++
++.. config::
++ :fragment: gitea
++
++ gitea.exclude_repos = noisy/repository
++
++.. hint::
++ If you omit the repository's namespace, bugwarrior will automatically add
++ your login as namespace. E.g. the following are equivalent:
++
++.. config::
++ :fragment: gitea
++
++ gitea.login = foo
++ gitea.include_repos = bar
++
++and:
++
++.. config::
++ :fragment: gitea
++
++ gitea.login = foo
++ gitea.include_repos = foo/bar
++
++Alternatively, you can use project IDs instead of names by prefixing the
++project id with `id:`:
++
++.. config::
++ :fragment: gitea
++
++ gitea.include_repos = id:1234,id:3141
++
++Import Labels as Tags
+++++++++++++++++++++++
++
++The gitea issue tracker allows you to attach labels to issues; to
++use those labels as tags, you can use the ``import_labels_as_tags``
++option:
++
++.. config::
++ :fragment: gitea
++
++ gitea.import_labels_as_tags = True
++
++Also, if you would like to control how these labels are created, you can
++specify a template used for converting the gitea label into a Taskwarrior
++tag.
++
++For example, to prefix all incoming labels with the string 'gitea_' (perhaps
++to differentiate them from any existing tags you might have), you could
++add the following configuration option:
++
++.. config::
++ :fragment: gitea
++
++ gitea.label_template = gitea_{{label}}
++
++In addition to the context variable ``{{label}}``, you also have access
++to all fields on the Taskwarrior task if needed:
++
++.. note::
++
++ See :ref:`field_templates` for more details regarding how templates
++ are processed.
++
++
++Provided UDA Fields
++-------------------
++
++.. udas:: bugwarrior.services.gitea.GiteaIssue
+
+From 1626a36c15013fc42e369cdc9ef98c23e10845c2 Mon Sep 17 00:00:00 2001
+From: msglm <msglm@techchud.xyz>
+Date: Wed, 22 May 2024 21:00:20 -0500
+Subject: [PATCH 4/8] Remove Six usage and clean the codebase
+
+Suggestions from here are implemented
+https://github.com/GothenburgBitFactory/bugwarrior/pull/1048#pullrequestreview-2070021239
+---
+ bugwarrior/services/gitea.py | 20 ++++----------------
+ 1 file changed, 4 insertions(+), 16 deletions(-)
+
+diff --git a/bugwarrior/services/gitea.py b/bugwarrior/services/gitea.py
+index 445846ff..341ec617 100644
+--- a/bugwarrior/services/gitea.py
++++ b/bugwarrior/services/gitea.py
+@@ -15,7 +15,6 @@
+ import logging
+ import pathlib
+ import re
+-import six
+ import sys
+ from urllib.parse import urlparse
+ from urllib.parse import quote_plus
+@@ -36,11 +35,10 @@ class GiteaConfig(config.ServiceConfig):
+ host = "gitea.com"
+ login: str
+ token: str
+- login: str
+ username: str
+ password: str
+- exclude_repos = []
+- include_repos = []
++ exclude_repos = config.ConfigList([])
++ include_repos = config.ConfigList([])
+
+ def get(self, key, default=None, to_type=None):
+ try:
+@@ -370,7 +368,7 @@ def __init__(self, *args, **kw):
+ 'import_labels_as_tags', default=False, to_type=bool
+ )
+ self.label_template = self.config.get(
+- 'label_template', default='{{label}}', to_type=six.text_type
++ 'label_template', default='{{label}}', to_type=bool
+ )
+ self.project_owner_prefix = self.config.get(
+ 'project_owner_prefix', default=False, to_type=bool
+@@ -380,7 +378,7 @@ def __init__(self, *args, **kw):
+ 'query',
+ default='involves:{user} state:open'.format(
+ user=self.username) if self.involved_issues else '',
+- to_type=six.text_type
++ to_type=str
+ )
+
+ @staticmethod
+@@ -552,13 +550,3 @@ def issues(self):
+ issue_obj.update_extra(extra)
+ yield issue_obj
+
+- @classmethod
+- def validate_config(cls, service_config, target):
+- if 'login' not in service_config:
+- log.critical('[%s] has no \'gitea.login\'' % target)
+- sys.exit(1)
+-
+- if 'token' not in service_config and 'password' not in service_config:
+- log.critical('[%s] has no \'gitea.token\' or \'gitea.password\'' % target)
+- sys.exit(1)
+-
+
+From 17b725774281e9742b786dbcbcf791f7f3dacf61 Mon Sep 17 00:00:00 2001
+From: msglm <msglm@techchud.xyz>
+Date: Thu, 23 May 2024 05:03:39 -0500
+Subject: [PATCH 5/8] Intake Critique and simplify
+
+Remove user+pass auth, token only now.
+Added issue API Querying for writing custom queries
+Added include_assigned,created,mentioned, and review_requested issues
+config settings
+Added ability to limit the number of issues you will query (Gitea limits
+the API by default to 50, but I host my own instance so I raised it)
+get_tags simplified greatly
+---
+ bugwarrior/services/gitea.py | 178 ++++++++++++++++++++---------------
+ 1 file changed, 102 insertions(+), 76 deletions(-)
+
+diff --git a/bugwarrior/services/gitea.py b/bugwarrior/services/gitea.py
+index 341ec617..28a92e96 100644
+--- a/bugwarrior/services/gitea.py
++++ b/bugwarrior/services/gitea.py
+@@ -29,16 +29,28 @@
+
+ log = logging.getLogger(__name__) # pylint: disable-msg=C0103
+
++#TODO: Document this with docstrings
+ class GiteaConfig(config.ServiceConfig):
+ service: typing_extensions.Literal['gitea']
+-
+ host = "gitea.com"
+- login: str
+ token: str
+ username: str
+- password: str
+- exclude_repos = config.ConfigList([])
+- include_repos = config.ConfigList([])
++ include_assigned_issues: bool = False
++ include_created_issues: bool = False
++ include_mentioned_issues: bool = False
++ include_review_requested_issues: bool = False
++ import_labels_as_tags: bool = True
++ involved_issues: bool = False
++ project_owner_prefix: bool = False
++ include_repos: config.ConfigList = config.ConfigList([])
++ exclude_repos: config.ConfigList = config.ConfigList([])
++ label_template = str = '{{label}}'
++ filter_pull_requests: bool = False
++ exclude_pull_requests: bool = False
++ """
++ The maximum number of issues the API may get from the host
++ """
++ issue_limit: int = 100
+
+ def get(self, key, default=None, to_type=None):
+ try:
+@@ -65,7 +77,7 @@ class GiteaClient(ServiceClient):
+ - get_repos:
+ - get_query:
+ - get_issues:
+- - get_directly_assigned_issues:
++ - get_special_issues:
+ - get_comments:
+ - get_pulls:
+ """
+@@ -79,16 +91,8 @@ def __init__(self, host, auth):
+
+ def _api_url(self, path, **context):
+ """ Build the full url to the API endpoint """
+- # TODO add token support
+- if 'basic' in self.auth:
+- (username, password) = self.auth['basic']
+- baseurl = 'https://{user}:{secret}@{host}/api/v1'.format(
+- host=self.host,
+- user=username,
+- secret=quote_plus(password))
+- if 'token' in self.auth:
+- baseurl = 'https://{host}/api/v1'.format(
+- host=self.host)
++ baseurl = 'https://{host}/api/v1'.format(
++ host=self.host)
+ return baseurl + path.format(**context)
+
+ # TODO Modify these for gitea support
+@@ -109,17 +113,17 @@ def get_issues(self, username, repo):
+ '/repos/{username}/{repo}/issues?per_page=100',
+ username=username, repo=repo)
+ return self._getter(url)
++
++ def get_special_issues(self, username, query: str):
++ """ Returns all issues assigned to authenticated user given a specific query.
+
+- def get_directly_assigned_issues(self, username):
+- """ Returns all issues assigned to authenticated user.
+-
+- This will return all issues assigned to the authenticated user
+- regardless of whether the user owns the repositories in which the
+- issues exist.
++ This will return all issues this authenticated user has access to and then
++ filter the issues with the query that the user supplied.
+ """
+- url = self._api_url('/repos/issues/search',
+- username=username, assignee=True)
+- return self._getter(url, passedParams={'assigned': True, 'limit': 100}) #TODO: make the limit configurable
++ logging.info("Querying /repos/issues/search with query: " + query)
++ url = self._api_url('/repos/issues/search?{query}',
++ username=username, query=query)
++ return self._getter(url)
+
+ # TODO close to gitea format: /comments/{id}
+ def get_comments(self, username, repo, number):
+@@ -134,7 +138,7 @@ def get_pulls(self, username, repo):
+ username=username, repo=repo)
+ return self._getter(url)
+
+- def _getter(self, url, subkey=None, passedParams={}):
++ def _getter(self, url, subkey=None):
+ """ Pagination utility. Obnoxious. """
+
+ kwargs = {}
+@@ -145,7 +149,7 @@ def _getter(self, url, subkey=None, passedParams={}):
+ link = dict(next=url)
+
+ while 'next' in link:
+- response = self.session.get(link['next'], params=passedParams, **kwargs)
++ response = self.session.get(link['next'], **kwargs)
+
+ # Warn about the mis-leading 404 error code. See:
+ # https://gitea.com/ralphbean/bugwarrior/issues/374
+@@ -269,6 +273,9 @@ def to_taskwarrior(self):
+ if body:
+ body = body.replace('\r\n', '\n')
+
++ if len(body) < 1:
++ body = "No annotation was provided."
++
+ created = self.parse_date(self.record.get('created_at'))
+ updated = self.parse_date(self.record.get('updated_at'))
+ closed = self.parse_date(self.record.get('closed_at'))
+@@ -295,25 +302,19 @@ def to_taskwarrior(self):
+ self.NAMESPACE: self.extra['namespace'],
+ self.STATE: self.record.get('state', '')
+ }
+-
+ def get_tags(self):
+- tags = []
+-
+- if not self.config.get('import_labels_as_tags'):
+- return tags
++ labels = [label['name'] for label in self.record.get('labels', [])]
++ return self.get_tags_from_labels(labels)
+
+- context = self.record.copy()
+- label_template = Template(self.config.get('label_template'))
+-
+- for label_dict in self.record.get('labels', []):
+- context.update({
+- 'label': self._normalize_label_to_tag(label_dict['name'])
+- })
+- tags.append(
+- label_template.render(context)
+- )
++ def get_default_description(self):
++ log.info('In get_default_description')
++ return self.build_default_description(
++ title=self.record['title'],
++ url=self.get_processed_url(self.record['url']),
++ number=self.record['number'],
++ cls=self.extra['type'],
++ )
+
+- return tags
+
+ def get_default_description(self):
+ log.info('In get_default_description')
+@@ -335,44 +336,42 @@ def __init__(self, *args, **kw):
+
+ auth = {}
+ token = self.config.token
+- self.login = self.config.login
+ if hasattr(self.config, 'token'):
+- token = self.get_password('token', login=self.login)
++ token = self.get_password('token', login=self.config.username)
+ auth['token'] = token
+- elif hasattr(self.config.hasattr, 'password'):
+- password = self.get_password('password', login=self.login)
+- auth['basic'] = (self.login, password)
+ else:
+ #Probably should be called by validate_config, but I don't care to fix that.
+- logging.critical("ERROR! Neither token or password was provided in config!")
++ logging.critical("ERROR! No token was provided in config!")
+ sys.exit(1)
+
++ #TODO: document these with docstrings
+ self.client = GiteaClient(host=self.config.host, auth=auth)
+
+ self.host = self.config.host
+
+ self.exclude_repos = self.config.exclude_repos
++
+ self.include_repos = self.config.include_repos
+
+ self.username = self.config.username
+- self.filter_pull_requests = self.config.get(
+- 'filter_pull_requests', default=False, to_type=bool
+- )
+- self.exclude_pull_requests = self.config.get(
+- 'exclude_pull_requests', default=False, to_type=bool
+- )
+- self.involved_issues = self.config.get(
+- 'involved_issues', default=False, to_type=bool
+- )
+- self.import_labels_as_tags = self.config.get(
+- 'import_labels_as_tags', default=False, to_type=bool
+- )
+- self.label_template = self.config.get(
+- 'label_template', default='{{label}}', to_type=bool
+- )
+- self.project_owner_prefix = self.config.get(
+- 'project_owner_prefix', default=False, to_type=bool
+- )
++
++ self.filter_pull_requests = self.config.filter_pull_requests
++
++ self.exclude_pull_requests = self.config.exclude_pull_requests
++
++ self.involved_issues = self.config.involved_issues
++
++ self.project_owner_prefix = self.config.project_owner_prefix
++
++ self.include_assigned_issues = self.config.include_assigned_issues
++
++ self.include_created_issues = self.config.include_created_issues
++
++ self.include_review_requested_issues = self.config.include_review_requested_issues
++
++ self.import_labels_as_tags = self.config.import_labels_as_tags
++
++ self.label_template = self.config.label_template
+
+ self.query = self.config.get(
+ 'query',
+@@ -384,11 +383,10 @@ def __init__(self, *args, **kw):
+ @staticmethod
+ def get_keyring_service(service_config):
+ #TODO grok this
+- login = service_config.login
+ username = service_config.username
+ host = service_config.host
+- return 'gitea://{login}@{host}/{username}'.format(
+- login=login, username=username, host=host)
++ return 'gitea://{username}@{host}/{username}'.format(
++ username=username, host=host)
+
+ def get_service_metadata(self):
+ return {
+@@ -417,9 +415,9 @@ def get_query(self, query):
+ issues[url] = (repo, issue)
+ return issues
+
+- def get_directly_assigned_issues(self, username):
++ def get_special_issues(self, username, query):
+ issues = {}
+- for issue in self.client.get_directly_assigned_issues(self.username):
++ for issue in self.client.get_special_issues(self.username, query):
+ repos = self.get_repository_from_issue(issue)
+ issues[issue['url']] = (repos, issue)
+ return issues
+@@ -524,10 +522,38 @@ def issues(self):
+ self.get_owned_repo_issues(
+ self.username + '/' + repo)
+ )
+- issues.update(
+- filter(self.filter_issues,
+- self.get_directly_assigned_issues(self.username).items())
+- )
++
++ '''
++ A variable used to represent the attachable HTTP query that can be attached to the /repos/issues/search API end.
++
++ if httpQuery is set to "review_requested=True?mentioned=True" for example, then the /repos/issues/search API end will be told to search for all issues where a review is requested AND where the user is mentioned.
++ '''
++ httpQuery = "limit=" + str(self.config.issue_limit) + "&"
++
++ if self.config.get('include_assigned_issues', True, bool):
++ log.info("assigned was true")
++ issues.update(
++ filter(self.filter_issues,
++ self.get_special_issues(self.username, httpQuery + "assigned=true&").items())
++ )
++ if self.config.get('include_created_issues', True, bool):
++ log.info("created was true")
++ issues.update(
++ filter(self.filter_issues,
++ self.get_special_issues(self.username, httpQuery + "created=true&").items())
++ )
++ if self.config.get('include_mentioned_issues', True, bool):
++ log.info("mentioned was true")
++ issues.update(
++ filter(self.filter_issues,
++ self.get_special_issues(self.username, httpQuery + "mentioned=true&").items())
++ )
++ if self.config.get('include_review_requested_issues', True, bool):
++ log.info("review request was true")
++ issues.update(
++ filter(self.filter_issues,
++ self.get_special_issues(self.username, httpQuery + "review_requested=true&").items())
++ )
+
+ log.info(' Found %i issues.', len(issues)) # these were debug logs
+ issues = list(filter(self.include, issues.values()))
+
+From 3eb6e743c7ee4c7892525c05d880f5d05d3f8600 Mon Sep 17 00:00:00 2001
+From: msglm <msglm@techchud.xyz>
+Date: Thu, 23 May 2024 05:13:33 -0500
+Subject: [PATCH 6/8] Documentation for previous commit
+
+---
+ bugwarrior/docs/services/gitea.rst | 31 +++++++++++++++++++++++++++---
+ 1 file changed, 28 insertions(+), 3 deletions(-)
+
+diff --git a/bugwarrior/docs/services/gitea.rst b/bugwarrior/docs/services/gitea.rst
+index 6ccf2308..19e0930a 100644
+--- a/bugwarrior/docs/services/gitea.rst
++++ b/bugwarrior/docs/services/gitea.rst
+@@ -13,11 +13,9 @@ Here's an example of a Gitea target:
+
+ [user_gitea]
+ service = gitea
+- gitea.login = ralphbean
+ gitea.username = ralphbean
+ gitea.host = git.bean.com #Note: the lack of https, the service will assume HTTPS by default.
+- gitea.password = @oracle:eval:pass show 'git.bean.com'
+- gitea.token = 0000000000000000000000000000000
++ gitea.token = @oracle:eval:pass show 'git.bean.com token'
+
+ The above example is the minimum required to import issues from
+ Gitea. You can also feel free to use any of the
+@@ -117,6 +115,33 @@ to all fields on the Taskwarrior task if needed:
+ See :ref:`field_templates` for more details regarding how templates
+ are processed.
+
++Limit Issues Imported
+++++++++++++++++++++++
++Gitea lets system administrators configure the amount of objects that any given API request will return.
++You may configure the amount to tell Gitea to give to you using the ``issue_limit`` option:
++
++.. config::
++ :fragment: gitea
++
++ gitea.issue_limit = 200
++
++Do note, this will not overwrite what the gitea instance limits you to, it merely lets you set the amount of issues you will import.
++
++
++Including various types of issues
+++++++++++++++++++++++
++
++Gitea has metadata attached to each issue, primarily: If you are assigned to an issue, if you created an issue, if an issue mentions you, and if an issue has a review reqest for you. You may set if each of these traits is worth importing by using the various ``include_*_issues`` options:
++
++.. config::
++ :fragment: gitea
++
++ gitea.include_assigned_issues = true
++ gitea.include_created_issues = true
++ gitea.include_mentioned_issues = true
++ gitea.include_review_requested_issues = true
++
++Each setting will query the API for that trait alone and then add it to your Taskwarrior task list. For example, if you have created issues and mentioned issues off, but assigned issues and review requested issues on: You will only recieve new tasks for the issues you are assigned to do or requested to review, but not for issues you've created or mentioned. Issues that have been assigned to you and created by you would be included though, as these settings merely mark inclusion, not exclusion.
+
+ Provided UDA Fields
+ -------------------
+
+From 439c3f02338a43dcd0309fe297a706f0232a6138 Mon Sep 17 00:00:00 2001
+From: msglm <msglm@techchud.xyz>
+Date: Wed, 19 Feb 2025 03:32:02 -0600
+Subject: [PATCH 7/8] Update to modern standards
+
+---
+ bugwarrior/services/gitea.py | 37 +++++++++++++++---------------------
+ 1 file changed, 15 insertions(+), 22 deletions(-)
+
+diff --git a/bugwarrior/services/gitea.py b/bugwarrior/services/gitea.py
+index 28a92e96..8814633a 100644
+--- a/bugwarrior/services/gitea.py
++++ b/bugwarrior/services/gitea.py
+@@ -3,9 +3,9 @@
+ """Bugwarrior service support class for Gitea
+
+ Available classes:
+-- GiteaClient(ServiceClient): Constructs Gitea API strings
++- GiteaClient(Service): Constructs Gitea API strings
+ - GiteaIssue(Issue): TaskWarrior Interface
+-- GiteaService(IssueService): Engine for firing off requests
++- GiteaService(Issue): Engine for firing off requests
+
+ Todo:
+ * Add token support
+@@ -25,7 +25,7 @@
+ from jinja2 import Template
+
+ from bugwarrior import config
+-from bugwarrior.services import IssueService, Issue, ServiceClient
++from bugwarrior.services import Issue, Service, Client
+
+ log = logging.getLogger(__name__) # pylint: disable-msg=C0103
+
+@@ -62,7 +62,7 @@ def get(self, key, default=None, to_type=None):
+ return default
+
+
+-class GiteaClient(ServiceClient):
++class GiteaClient(Client):
+ """Builds Gitea API strings
+ Args:
+ host (str): remote gitea server
+@@ -263,8 +263,11 @@ class GiteaIssue(Issue):
+ @staticmethod
+ def _normalize_label_to_tag(label):
+ return re.sub(r'[^a-zA-Z0-9]', '_', label)
++ def get_tags(self):
++ labels = [label['name'] for label in self.record.get('labels', [])]
++ return self.get_tags_from_labels(labels)
+
+- def to_taskwarrior(self):
++ def to_taskwarrior(self) -> dict:
+ milestone = self.record['milestone']
+ if milestone:
+ milestone = milestone['title']
+@@ -302,31 +305,18 @@ def to_taskwarrior(self):
+ self.NAMESPACE: self.extra['namespace'],
+ self.STATE: self.record.get('state', '')
+ }
+- def get_tags(self):
+- labels = [label['name'] for label in self.record.get('labels', [])]
+- return self.get_tags_from_labels(labels)
+-
+- def get_default_description(self):
+- log.info('In get_default_description')
+- return self.build_default_description(
+- title=self.record['title'],
+- url=self.get_processed_url(self.record['url']),
+- number=self.record['number'],
+- cls=self.extra['type'],
+- )
+-
+
+ def get_default_description(self):
+ log.info('In get_default_description')
+ return self.build_default_description(
+ title=self.record['title'],
+- url=self.get_processed_url(self.record['url']),
++ url=self.record['url'],
+ number=self.record['number'],
+ cls=self.extra['type'],
+ )
+
+
+-class GiteaService(IssueService):
++class GiteaService(Service):
+ ISSUE_CLASS = GiteaIssue
+ CONFIG_SCHEMA = GiteaConfig
+ CONFIG_PREFIX = 'gitea'
+@@ -455,7 +445,7 @@ def annotations(self, tag, issue, issue_obj):
+ ) for c in comments)
+ annotations_result = self.build_annotations(
+ annotations,
+- issue_obj.get_processed_url(url))
++ url)
+ log.info('annotations: {}'.format(annotations_result))
+ return annotations_result
+
+@@ -573,6 +563,9 @@ def issues(self):
+ 'annotations': [issue['body']],
+ 'namespace': self.username,
+ }
+- issue_obj.update_extra(extra)
++ issue_obj.extra.update(extra)
+ yield issue_obj
+
++
++
++
+
+From db4c47837d15cb126cb97a97f72db061052db5ab Mon Sep 17 00:00:00 2001
+From: msglm <msglm@techchud.xyz>
+Date: Wed, 19 Feb 2025 03:34:16 -0600
+Subject: [PATCH 8/8] Do not yield on AttributeError
+
+Previously, when there is an AttributeError in a service, the service
+will return its issue object instead of a dictionary representation of
+the issue. This fixes it by raising an error if this happens.
+---
+ bugwarrior/collect.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/bugwarrior/collect.py b/bugwarrior/collect.py
+index 8d435461..34af838a 100644
+--- a/bugwarrior/collect.py
++++ b/bugwarrior/collect.py
+@@ -100,7 +100,7 @@ def aggregate_issues(conf, main_section, debug):
+ log.error(f"Aborted [{target}] due to critical error.")
+ yield ('SERVICE FAILED', target)
+ continue
+- yield issue
++ raise
+
+ log.info("Done aggregating remote issues.")
+
diff --git a/aux-files/stable-diffusion-cpp/vulkan-support.patch b/aux-files/stable-diffusion-cpp/vulkan-support.patch
new file mode 100644
index 0000000..88f5fbf
--- /dev/null
+++ b/aux-files/stable-diffusion-cpp/vulkan-support.patch
@@ -0,0 +1,123 @@
+From 15553084e5d1a7d6abd3aac176bee95b0247c379 Mon Sep 17 00:00:00 2001
+From: sohzm <sohxm7@gmail.com>
+Date: Wed, 19 Jun 2024 04:08:18 +0530
+Subject: [PATCH] wip
+
+---
+ CMakeLists.txt | 7 +++++++
+ ggml | 2 +-
+ ggml_extend.hpp | 6 +++++-
+ model.cpp | 4 ++++
+ stable-diffusion.cpp | 6 +++++-
+ upscaler.cpp | 4 ++++
+ 6 files changed, 26 insertions(+), 3 deletions(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 28a03fb..6b5679f 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -27,6 +27,7 @@ option(SD_BUILD_EXAMPLES "sd: build examples" ${SD_STANDALONE})
+ option(SD_CUBLAS "sd: cuda backend" OFF)
+ option(SD_HIPBLAS "sd: rocm backend" OFF)
+ option(SD_METAL "sd: metal backend" OFF)
++option(SD_VULKAN "sd: vulkan backend" OFF)
+ option(SD_FLASH_ATTN "sd: use flash attention for x4 less memory usage" OFF)
+ option(SD_FAST_SOFTMAX "sd: x1.5 faster softmax, indeterministic (sometimes, same seed don't generate same image), cuda only" OFF)
+ option(SD_BUILD_SHARED_LIBS "sd: build shared libs" OFF)
+@@ -44,6 +45,12 @@ if(SD_METAL)
+ add_definitions(-DSD_USE_METAL)
+ endif()
+
++if (SD_VULKAN)
++ message("Use Vulkan as backend stable-diffusion")
++ set(GGML_VULKAN ON)
++ add_definitions(-DSD_USE_VULKAN)
++endif ()
++
+ if (SD_HIPBLAS)
+ message("Use HIPBLAS as backend stable-diffusion")
+ set(GGML_HIPBLAS ON)
+diff --git a/ggml b/ggml
+index 9d562d7..5653a19 160000
+--- a/ggml
++++ b/ggml
+@@ -1 +1 @@
+-Subproject commit 9d562d712513c77a4de44ad0428be62bc3f2a9cf
++Subproject commit 5653a195935ea3ac54652644c9daf154dbc1571b
+diff --git a/ggml_extend.hpp b/ggml_extend.hpp
+index dbe9303..1236996 100644
+--- a/ggml_extend.hpp
++++ b/ggml_extend.hpp
+@@ -32,6 +32,10 @@
+ #include "ggml-metal.h"
+ #endif
+
++#ifdef SD_USE_VULKAN
++#include "ggml-vulkan.h"
++#endif
++
+ #include "rng.hpp"
+ #include "util.h"
+
+@@ -588,7 +592,7 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_nn_attention(struct ggml_context* ctx
+ struct ggml_tensor* k,
+ struct ggml_tensor* v,
+ bool mask = false) {
+-#if defined(SD_USE_FLASH_ATTENTION) && !defined(SD_USE_CUBLAS) && !defined(SD_USE_METAL)
++#if defined(SD_USE_FLASH_ATTENTION) && !defined(SD_USE_CUBLAS) && !defined(SD_USE_METAL) && !defined(SD_USE_VULKAN)
+ struct ggml_tensor* kqv = ggml_flash_attn(ctx, q, k, v, false); // [N * n_head, n_token, d_head]
+ #else
+ float d_head = (float)q->ne[0];
+diff --git a/model.cpp b/model.cpp
+index c4556a9..db8bae8 100644
+--- a/model.cpp
++++ b/model.cpp
+@@ -21,6 +21,10 @@
+ #include "ggml-metal.h"
+ #endif
+
++#ifdef SD_USE_VULKAN
++#include "ggml-vulkan.h"
++#endif
++
+ #define ST_HEADER_SIZE_LEN 8
+
+ uint64_t read_u64(uint8_t* buffer) {
+diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp
+index 8e439d2..3521e76 100644
+--- a/stable-diffusion.cpp
++++ b/stable-diffusion.cpp
+@@ -154,13 +154,17 @@ class StableDiffusionGGML {
+ ggml_backend_metal_log_set_callback(ggml_log_callback_default, nullptr);
+ backend = ggml_backend_metal_init();
+ #endif
++#ifdef SD_USE_VULKAN
++ LOG_DEBUG("Using Vulkan backend");
++ backend = ggml_backend_vk_init();
++#endif
+
+ if (!backend) {
+ LOG_DEBUG("Using CPU backend");
+ backend = ggml_backend_cpu_init();
+ }
+ #ifdef SD_USE_FLASH_ATTENTION
+-#if defined(SD_USE_CUBLAS) || defined(SD_USE_METAL)
++#if defined(SD_USE_CUBLAS) || defined(SD_USE_METAL) || defined(SD_USE_VULKAN)
+ LOG_WARN("Flash Attention not supported with GPU Backend");
+ #else
+ LOG_INFO("Flash Attention enabled");
+diff --git a/upscaler.cpp b/upscaler.cpp
+index 0e3f95d..7623f9b 100644
+--- a/upscaler.cpp
++++ b/upscaler.cpp
+@@ -24,6 +24,10 @@ struct UpscalerGGML {
+ ggml_backend_metal_log_set_callback(ggml_log_callback_default, nullptr);
+ backend = ggml_backend_metal_init();
+ #endif
++#ifdef SD_USE_VULKAN
++ LOG_DEBUG("Using Vulkan backend");
++ backend = ggml_backend_vk_init(0);
++#endif
+
+ if (!backend) {
+ LOG_DEBUG("Using CPU backend");