~sircmpwn/sr.ht-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
8 2

[PATCH] webhooks: update todo tickets with git commits

Details
Message ID
<20211120084722.34640-1-robin@jarry.cc>
DKIM signature
missing
Download raw message
Patch: +86 -1
Allow referencing tracker tickets in git commit messages via specific
trailer words:

  Fixes: <ticket url>
  Implements: <ticket url>
  References: <ticket url>

These must follow standard git trailer syntax. The ticket url must point
to a valid ticket. A comment  will be inserted in the ticket with a back
reference to the git commit. The comment will be made by the user who
pushed the commit. E.g.:

  ~rjarry REPORTED -> FIXED                            9 seconds ago

  Referenced this ticket in commit 8741881.

Open tickets referenced by a Fixes trailer will be resolved with the
FIXED resolution.

Open tickets referenced by a Fixes trailer will be resolved with the
IMPLEMENTED resolution.

Caveats:

* If the user pushing commits does not have triage/comment permissions
  on the bug tracker, nothing will happen.

* Invalid/non-existent ticket urls are ignored.

* When a git repository is part of more than one project, the webhook
  will run once per project and update the same ticket(s) once per
  project as well.

* If an already resolved ticket is referenced by a Fixes or Implements
  trailer, only a comment will be added.

Link: https://git-scm.com/docs/git-interpret-trailers
Implements: https://todo.sr.ht/~sircmpwn/hub.sr.ht/55
Signed-off-by: Robin Jarry <robin@jarry.cc>
---
 hubsrht/blueprints/webhooks.py | 51 +++++++++++++++++++++++++++++++++-
 hubsrht/services.py            | 36 ++++++++++++++++++++++++
 2 files changed, 86 insertions(+), 1 deletion(-)

diff --git a/hubsrht/blueprints/webhooks.py b/hubsrht/blueprints/webhooks.py
index 2b655e2c9d09..5b6ff615d77d 100644
--- a/hubsrht/blueprints/webhooks.py
+++ b/hubsrht/blueprints/webhooks.py
@@ -1,10 +1,11 @@
import email
import html
import json
import re
from datetime import datetime
from flask import Blueprint, request, current_app
from hubsrht.builds import submit_patchset
from hubsrht.services import todo, lists
from hubsrht.services import git, todo, lists
from hubsrht.types import Event, EventType, MailingList, SourceRepo, RepoType
from hubsrht.types import Tracker, User, Visibility
from srht.config import get_origin
@@ -99,10 +100,58 @@ def git_repo(repo_id):
        repo.project.updated = datetime.utcnow()
        db.session.add(event)
        db.session.commit()

        for ref in payload["refs"]:
            old = (ref["old"] or {}).get("id")
            new = (ref["new"] or {}).get("id")
            for commit in reversed(git.log(pusher, repo, old, new)):
                for match in _ticket_trailer_re.finditer(commit["message"]):
                    _update_ticket(pusher, repo, commit, match)

        return "Thanks!"
    else:
        raise NotImplementedError()

_ticket_trailer_re = re.compile(
    rf"""
    ^
    (?P<action>Fixes|Implements|References):\s+
    {re.escape(_todosrht)}
    /(?P<owner>~[a-z_][a-z0-9_-]+)
    /(?P<tracker>[\w.-]+)
    /(?P<ticket>\d+)
    $
    """,
    re.MULTILINE | re.VERBOSE,
)

def _update_ticket(pusher, repo, commit, match):
    if match["action"] == "Fixes":
        resolution = "fixed"
    elif match["action"] == "Implements":
        resolution = "implemented"
    else:
        resolution = None

    commit_message = html.escape(commit["message"].split("\n")[0])
    commit_sha = commit["id"][:7]
    commit_url = repo.url() + f"/commit/{commit_sha}"
    comment = (
        f"<i>Referenced this ticket in commit <a href='{commit_url}' " +
        f"title='{commit_message}'>{commit_sha}</a>.</i>")
    try:
        todo.update_ticket(
            user=pusher,
            owner=match["owner"],
            tracker=match["tracker"],
            ticket=int(match["ticket"]),
            comment=" ".join(comment.split()).strip(),
            resolution=resolution,
        )
    except Exception:
        # invalid ticket or pusher does not have triage access, ignore
        pass

@csrf_bypass
@webhooks.route("/webhooks/hg-user/<int:user_id>", methods=["POST"])
def hg_user(user_id):
diff --git a/hubsrht/services.py b/hubsrht/services.py
index 74042afab509..d85331b0ce0a 100644
--- a/hubsrht/services.py
+++ b/hubsrht/services.py
@@ -179,6 +179,34 @@ class GitService(SrhtService):
            return None
        return manifests

    def log(self, user, repo, old, new):
        query = """
        query Log($owner: String!, $repo: String!, $from: String!) {
            repositoryByOwner(owner: $owner, repo: $repo) {
                log(from: $from) {
                    results {
                        id
                        message
                    }
                }
            }
        }
        """
        r = self.post(user, None, f"{_gitsrht}/query", {
            "query": query,
            "variables": {
                "owner": repo.owner.canonical_name,
                "repo": repo.name,
                "from": new,
            }
        })
        commits = []
        for c in r["data"]["repositoryByOwner"]["log"]["results"]:
            if c["id"] == old:
                break
            commits.append(c)
        return commits

    def create_repo(self, user, valid, visibility):
        query = """
        mutation CreateRepo(
@@ -452,6 +480,14 @@ class TodoService(SrhtService):
        except:
            pass # nbd, upstream was presumably deleted

    def update_ticket(self, user, owner, tracker, ticket, comment, resolution=None):
        url = f"{_todosrht}/api/user/{owner}/trackers/{tracker}/tickets/{ticket}"
        payload = {"comment": comment}
        if resolution is not None:
            payload["resolution"] = resolution
            payload["status"] = "resolved"
        self.put(user, None, url, payload)

class BuildService(SrhtService):
    def submit_build(self, user, manifest, note, tags, execute=True):
        return self.post(user, None, f"{_buildsrht}/api/jobs", {
-- 
2.34.0.rc2.3.g21d73fb69d7a
Details
Message ID
<318C9600-3ED5-48E8-A23A-7B158E566888@jarry.cc>
In-Reply-To
<20211120084722.34640-1-robin@jarry.cc> (view parent)
DKIM signature
missing
Download raw message
Damn, I forgot to add the hub.sr.ht suffix in the email subject.

I have some additional info to include into the message and I want to group this patch with two related others for sr.ht-docs and git.sr.ht.

Please ignore.

[PATCH hub.sr.ht v2] webhooks: update todo tickets with git commits

Details
Message ID
<20211120131330.38102-1-robin@jarry.cc>
In-Reply-To
<20211120084722.34640-1-robin@jarry.cc> (view parent)
DKIM signature
missing
Download raw message
Patch: +86 -1
Allow referencing tracker tickets in git commit messages via specific
trailer words:

  Fixes: <ticket url>
  Implements: <ticket url>
  References: <ticket url>

These must follow standard git trailer syntax. The ticket url must point
to a valid ticket. A comment  will be inserted in the ticket with a back
reference to the git commit. The comment will be made by the user who
pushed the commit. E.g.:

  ~rjarry REPORTED -> FIXED                            9 seconds ago

  Referenced this ticket in commit 8741881.

Open tickets referenced by a Fixes trailer will be resolved with the
FIXED resolution.

Open tickets referenced by a Fixes trailer will be resolved with the
IMPLEMENTED resolution.

Caveats:

* Only the 25 most recent commit messages will be considered when
  pushing long series. This should be a fairly sane limitation.

* If the user pushing commits does not have triage/comment permissions
  on the bug tracker, nothing will happen.

* Invalid/non-existent ticket urls are ignored.

* When a git repository is part of more than one project, the webhook
  will run once per project and update the same ticket(s) once per
  project as well.

* If an already resolved ticket is referenced by a Fixes or Implements
  trailer, only a comment will be added.

Link: https://git-scm.com/docs/git-interpret-trailers
Implements: https://todo.sr.ht/~sircmpwn/hub.sr.ht/55
Signed-off-by: Robin Jarry <robin@jarry.cc>
---
 hubsrht/blueprints/webhooks.py | 51 +++++++++++++++++++++++++++++++++-
 hubsrht/services.py            | 36 ++++++++++++++++++++++++
 2 files changed, 86 insertions(+), 1 deletion(-)

diff --git a/hubsrht/blueprints/webhooks.py b/hubsrht/blueprints/webhooks.py
index 2b655e2c9d09..5b6ff615d77d 100644
--- a/hubsrht/blueprints/webhooks.py
+++ b/hubsrht/blueprints/webhooks.py
@@ -1,10 +1,11 @@
import email
import html
import json
import re
from datetime import datetime
from flask import Blueprint, request, current_app
from hubsrht.builds import submit_patchset
from hubsrht.services import todo, lists
from hubsrht.services import git, todo, lists
from hubsrht.types import Event, EventType, MailingList, SourceRepo, RepoType
from hubsrht.types import Tracker, User, Visibility
from srht.config import get_origin
@@ -99,10 +100,58 @@ def git_repo(repo_id):
        repo.project.updated = datetime.utcnow()
        db.session.add(event)
        db.session.commit()

        for ref in payload["refs"]:
            old = (ref["old"] or {}).get("id")
            new = (ref["new"] or {}).get("id")
            for commit in reversed(git.log(pusher, repo, old, new)):
                for match in _ticket_trailer_re.finditer(commit["message"]):
                    _update_ticket(pusher, repo, commit, match)

        return "Thanks!"
    else:
        raise NotImplementedError()

_ticket_trailer_re = re.compile(
    rf"""
    ^
    (?P<action>Fixes|Implements|References):\s+
    {re.escape(_todosrht)}
    /(?P<owner>~[a-z_][a-z0-9_-]+)
    /(?P<tracker>[\w.-]+)
    /(?P<ticket>\d+)
    $
    """,
    re.MULTILINE | re.VERBOSE,
)

def _update_ticket(pusher, repo, commit, match):
    if match["action"] == "Fixes":
        resolution = "fixed"
    elif match["action"] == "Implements":
        resolution = "implemented"
    else:
        resolution = None

    commit_message = html.escape(commit["message"].split("\n")[0])
    commit_sha = commit["id"][:7]
    commit_url = repo.url() + f"/commit/{commit_sha}"
    comment = (
        f"<i>Referenced this ticket in commit <a href='{commit_url}' " +
        f"title='{commit_message}'>{commit_sha}</a>.</i>")
    try:
        todo.update_ticket(
            user=pusher,
            owner=match["owner"],
            tracker=match["tracker"],
            ticket=int(match["ticket"]),
            comment=" ".join(comment.split()).strip(),
            resolution=resolution,
        )
    except Exception:
        # invalid ticket or pusher does not have triage access, ignore
        pass

@csrf_bypass
@webhooks.route("/webhooks/hg-user/<int:user_id>", methods=["POST"])
def hg_user(user_id):
diff --git a/hubsrht/services.py b/hubsrht/services.py
index 74042afab509..d85331b0ce0a 100644
--- a/hubsrht/services.py
+++ b/hubsrht/services.py
@@ -179,6 +179,34 @@ class GitService(SrhtService):
            return None
        return manifests

    def log(self, user, repo, old, new):
        query = """
        query Log($owner: String!, $repo: String!, $from: String!) {
            repositoryByOwner(owner: $owner, repo: $repo) {
                log(from: $from) {
                    results {
                        id
                        message
                    }
                }
            }
        }
        """
        r = self.post(user, None, f"{_gitsrht}/query", {
            "query": query,
            "variables": {
                "owner": repo.owner.canonical_name,
                "repo": repo.name,
                "from": new,
            }
        })
        commits = []
        for c in r["data"]["repositoryByOwner"]["log"]["results"]:
            if c["id"] == old:
                break
            commits.append(c)
        return commits

    def create_repo(self, user, valid, visibility):
        query = """
        mutation CreateRepo(
@@ -452,6 +480,14 @@ class TodoService(SrhtService):
        except:
            pass # nbd, upstream was presumably deleted

    def update_ticket(self, user, owner, tracker, ticket, comment, resolution=None):
        url = f"{_todosrht}/api/user/{owner}/trackers/{tracker}/tickets/{ticket}"
        payload = {"comment": comment}
        if resolution is not None:
            payload["resolution"] = resolution
            payload["status"] = "resolved"
        self.put(user, None, url, payload)

class BuildService(SrhtService):
    def submit_build(self, user, manifest, note, tags, execute=True):
        return self.post(user, None, f"{_buildsrht}/api/jobs", {
-- 
2.34.0.rc2.3.g21d73fb69d7a

Re: [PATCH hub.sr.ht v2] webhooks: update todo tickets with git commits

Details
Message ID
<CFX09KIIYAS0.2RULGSSZWL6L6@taiga>
In-Reply-To
<20211120131330.38102-1-robin@jarry.cc> (view parent)
DKIM signature
fail
Download raw message
DKIM signature: fail
Looks pretty good, nice work. Did you test this with a push >25 commits
to make sure it behaves as expected?

On Sat Nov 20, 2021 at 2:13 PM CET, Robin Jarry wrote:
> +_ticket_trailer_re = re.compile(
> + rf"""
> + ^
> + (?P<action>Fixes|Implements|References):\s+
> + {re.escape(_todosrht)}
> + /(?P<owner>~[a-z_][a-z0-9_-]+)
> + /(?P<tracker>[\w.-]+)
> + /(?P<ticket>\d+)
> + $
> + """,
> + re.MULTILINE | re.VERBOSE,
> +)

This should be a bit more general. Check out git-interpret-trailers(1)
for the canonical reference on how commit trailers are formatted, then
make a utility function which extracts all trailers from a commit into a
dict of string => [string], e.g. { "Fixes": "url" }, then interpret this
to get the specific trailers we want.

> +def _update_ticket(pusher, repo, commit, match):
> + if match["action"] == "Fixes":
> + resolution = "fixed"
> + elif match["action"] == "Implements":
> + resolution = "implemented"
> + else:
> + resolution = None
> +
> + commit_message = html.escape(commit["message"].split("\n")[0])
> + commit_sha = commit["id"][:7]
> + commit_url = repo.url() + f"/commit/{commit_sha}"
> + comment = (
> + f"<i>Referenced this ticket in commit <a href='{commit_url}' " +
> + f"title='{commit_message}'>{commit_sha}</a>.</i>")

I think this text should change depending on the action. The format
should be:

{pusher.canonicalName} referenced this ticket via {commit URL}.
{pusher.canonicalName} fixed this ticket via {commit URL}.
{pusher.canonicalName} implemented this ticket via {commit URL}.

Re: [PATCH hub.sr.ht v2] webhooks: update todo tickets with git commits

Details
Message ID
<CFX107E3NUZQ.NAFSRFJHABOS@diabtop>
In-Reply-To
<CFX09KIIYAS0.2RULGSSZWL6L6@taiga> (view parent)
DKIM signature
missing
Download raw message
Drew DeVault, Nov 23, 2021 at 09:10:
> Looks pretty good, nice work. Did you test this with a push >25 commits
> to make sure it behaves as expected?

I did not. I'll test this before sending a v3.

> This should be a bit more general. Check out git-interpret-trailers(1)
> for the canonical reference on how commit trailers are formatted, then
> make a utility function which extracts all trailers from a commit into a
> dict of string => [string], e.g. { "Fixes": "url" }, then interpret this
> to get the specific trailers we want.

Too bad we cannot use the git api for this.

https://www.pygit2.org/objects.html#pygit2.Commit.message_trailers

OTOH, I know for a fact that git is very strict with trailer parsing. It
only considers the last group of trailers if some of them are separated
by empty lines.

https://github.com/git/git/blob/v2.34.0/trailer.c#L847-L850

Should we be as strict as git is?

> I think this text should change depending on the action. The format
> should be:
>
> {pusher.canonicalName} referenced this ticket via {commit URL}.
> {pusher.canonicalName} fixed this ticket via {commit URL}.
> {pusher.canonicalName} implemented this ticket via {commit URL}.

I wanted to avoid the duplicate username in the comment. Since the
comment itself is made from {pusher.canonicalName}, I believe it does
not need to be repeated. Same for the verb. The change in the ticket
looks like this:

> ~user   UNRESOLVED -> FIXED                      9 seconds ago
>
> Referenced this ticket from commit 342987df3.

Also, if the ticket is already resolved/fixed and there is an Implements
trailer that references it, the ticket status does not change, only
a comment is inserted. This would make a confusing "implemented this
ticket" comment whereas it is only a "reference".

What do you think?

Re: [PATCH hub.sr.ht v2] webhooks: update todo tickets with git commits

Details
Message ID
<CFX12VH9KF1U.2KTL6I7VJL9MM@taiga>
In-Reply-To
<CFX107E3NUZQ.NAFSRFJHABOS@diabtop> (view parent)
DKIM signature
fail
Download raw message
DKIM signature: fail
On Tue Nov 23, 2021 at 9:45 AM CET, Robin Jarry wrote:
> Should we be as strict as git is?

Yes, git is the definitive source on how trailers are defined.

> I wanted to avoid the duplicate username in the comment. Since the
> comment itself is made from {pusher.canonicalName}, I believe it does
> not need to be repeated. Same for the verb. The change in the ticket
> looks like this:
>
> > ~user   UNRESOLVED -> FIXED                      9 seconds ago
> >
> > Referenced this ticket from commit 342987df3.

The problem is just that the prose does not flow. We need the prose, to
show the commit link, and since we have it, we should make it flow
right.

> Also, if the ticket is already resolved/fixed and there is an Implements
> trailer that references it, the ticket status does not change, only
> a comment is inserted. This would make a confusing "implemented this
> ticket" comment whereas it is only a "reference".

Yeah, in this case let's use "reference" as the verb.

[PATCH hub.sr.ht v3 1/2] git: add function to parse message trailers

Details
Message ID
<20211128200736.158664-1-robin@jarry.cc>
In-Reply-To
<20211120084722.34640-1-robin@jarry.cc> (view parent)
DKIM signature
missing
Download raw message
Patch: +83 -0
This is a pure python implementation of the message trailer parsing
algorithm in git (and libgit2). It is intended for use on finalized
commit messages only. Lines starting with comments are not ignored.

The function returns a list of pairs (name, value) where name is the
name of the trailer. Trailer values may span over multiple lines.

Link: https://git-scm.com/docs/git-interpret-trailers
Link: https://github.com/git/git/blob/master/trailer.c
Link: https://github.com/libgit2/libgit2/blob/main/src/trailer.c
Signed-off-by: Robin Jarry <robin@jarry.cc>
---
v2 -> v3:
    * Added a separate function to extract trailers from commit
      messages.

I tested this manually on a few commit messages. It should work OK for
most cases. Maybe this would benefit from fuzz testing. I don't know if
there is such a framework in sr.ht sources.

 hubsrht/trailers.py | 83 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)
 create mode 100644 hubsrht/trailers.py

diff --git a/hubsrht/trailers.py b/hubsrht/trailers.py
new file mode 100644
index 000000000000..0f923ad98b76
--- /dev/null
+++ b/hubsrht/trailers.py
@@ -0,0 +1,83 @@
import re
from typing import List, Tuple

_git_generated_prefixes = (
    "Signed-off-by: ",
    "(cherry picked from commit ",
)

def commit_trailers(message: str) -> List[Tuple[str, str]]:
    """
    Extract the trailers from a commit message. Return a list of pairs of
    (name, value).

    This borrows a large amount of logic from git core (trailer.c).
    """
    lines = message.strip().splitlines()

    # The first paragraph is the title and cannot be trailers
    while lines and lines[0] != '':
        del lines[0]

    recognized_prefix = False
    only_spaces = True
    trailer_lines = non_trailer_lines = 0
    possible_continuation_lines = 0

    # Get the start of the trailers by looking starting from the end for a
    # blank line before a set of non-blank lines that (i) are all trailers, or
    # (ii) contains at least one Git-generated trailer and consists of at least
    # 25% trailers.
    i = len(lines) - 1
    while i >= 0:
        line = lines[i]

        if not line.strip():
            # blank line
            if only_spaces:
                i -= 1
                continue
            if recognized_prefix and trailer_lines * 3 >= non_trailer_lines:
                i += 1
                break
            if trailer_lines > 0 and non_trailer_lines == 0:
                i += 1
                break
            return []

        only_spaces = False

        if any(line.startswith(p) for p in _git_generated_prefixes):
            trailer_lines += 1
            possible_continuation_lines = 0
            recognized_prefix = True
        elif re.search(r"^[A-Za-z\d][A-Za-z\d-]*\s*:", line):
            trailer_lines += 1
            possible_continuation_lines = 0
        elif line[0] in (" ", "\t"):
            possible_continuation_lines += 1
        else:
            non_trailer_lines += 1 + possible_continuation_lines
            possible_continuation_lines = 0
        i -= 1

    # Iterate over all remaining lines and collect trailer names and values.
    # If a line does not match a trailer and starts with a space or tab, its
    # contents are appended to the current trailer value.
    trailers = []
    name = value = None

    for line in lines[i:]:
        match = re.match(r"^([A-Za-z\d][A-Za-z\d-]*)\s*:\s*(.*)$", line)
        if match:
            if name is not None and value is not None:
                trailers.append((name, value))
            name = match[1]
            value = match[2]
        elif name is not None and value is not None and line[0] in (" ", "\t"):
            # continuation line
            value += "\n" + line
    if name is not None and value is not None:
        trailers.append((name, value))

    return trailers
-- 
2.30.2

[PATCH hub.sr.ht v3 2/2] webhooks: update todo tickets with git commits

Details
Message ID
<20211128200736.158664-2-robin@jarry.cc>
In-Reply-To
<20211128200736.158664-1-robin@jarry.cc> (view parent)
DKIM signature
missing
Download raw message
Patch: +96 -1
Allow referencing tracker tickets in git commit messages via specific
trailer words:

  Fixes: <ticket url>
  Implements: <ticket url>
  References: <ticket url>

These must follow standard git trailer syntax. The trailers are
extracted from commit messages with the function added in previous
commit. The ticket url must point to a valid ticket.

A comment will be inserted in the ticket with a back reference to the
git commit and its original author. The comment will be made by the user
who pushed the commit. E.g.:

  ~arkanoid REPORTED -> FIXED                          9 seconds ago

  John Doe referenced this ticket in commit b4dc4c40.

Open tickets referenced by a Fixes trailer will be resolved with the
FIXED resolution.

Open tickets referenced by an Implements trailer will be resolved with
the IMPLEMENTED resolution.

Caveats:

* Only the 25 most recent commit messages will be considered when
  pushing long series. This should be a fairly sane limitation.

* If the user pushing commits does not have triage/comment permissions
  on the bug tracker, nothing will happen.

* Invalid/non-existent ticket urls are ignored.

* When a git repository is part of more than one project, the webhook
  will run once per project and update the same ticket(s) once per
  project as well.

* If an already resolved ticket is referenced by a Fixes or Implements
  trailer, only a comment will be added.

Link: https://git-scm.com/docs/git-interpret-trailers
Implements: https://todo.sr.ht/~sircmpwn/hub.sr.ht/55
Signed-off-by: Robin Jarry <robin@jarry.cc>
---
v2 -> v3:
    * Use a generic function to extract trailers from commit messages.
    * Tested that the last 25 commits limitation is enforced.
    * Changed the inserted comments in todo tickets to include the
      commit author name.

 hubsrht/blueprints/webhooks.py | 58 +++++++++++++++++++++++++++++++++-
 hubsrht/services.py            | 39 +++++++++++++++++++++++
 2 files changed, 96 insertions(+), 1 deletion(-)

diff --git a/hubsrht/blueprints/webhooks.py b/hubsrht/blueprints/webhooks.py
index 2b655e2c9d09..179c5309b92d 100644
--- a/hubsrht/blueprints/webhooks.py
+++ b/hubsrht/blueprints/webhooks.py
@@ -1,10 +1,12 @@
import email
import html
import json
import re
from datetime import datetime
from flask import Blueprint, request, current_app
from hubsrht.builds import submit_patchset
from hubsrht.services import todo, lists
from hubsrht.services import git, todo, lists
from hubsrht.trailers import commit_trailers
from hubsrht.types import Event, EventType, MailingList, SourceRepo, RepoType
from hubsrht.types import Tracker, User, Visibility
from srht.config import get_origin
@@ -99,10 +101,64 @@ def git_repo(repo_id):
        repo.project.updated = datetime.utcnow()
        db.session.add(event)
        db.session.commit()

        for ref in payload["refs"]:
            old = (ref["old"] or {}).get("id")
            new = (ref["new"] or {}).get("id")
            for commit in reversed(git.log(pusher, repo, old, new)):
                for trailer, value in commit_trailers(commit["message"]):
                    _handle_commit_trailer(trailer, value, pusher, repo, commit)

        return "Thanks!"
    else:
        raise NotImplementedError()

_ticket_url_re = re.compile(
    rf"""
    ^
    {re.escape(_todosrht)}
    /(?P<owner>~[a-z_][a-z0-9_-]+)
    /(?P<tracker>[\w.-]+)
    /(?P<ticket>\d+)
    $
    """,
    re.VERBOSE,
)

def _handle_commit_trailer(trailer, value, pusher, repo, commit):
    if trailer == "Fixes":
        resolution = "fixed"
    elif trailer == "Implements":
        resolution = "implemented"
    elif trailer == "References":
        resolution = None
    else:
        return

    match = _ticket_url_re.match(value.strip())
    if not match:
        return

    commit_message = html.escape(commit["message"].split("\n")[0])
    commit_author = html.escape(commit["author"]["name"].strip())
    commit_sha = commit["id"][:7]
    commit_url = repo.url() + f"/commit/{commit_sha}"
    comment = (
        f"<i>{commit_author} referenced this ticket in commit " +
        f"<a href='{commit_url}' title='{commit_message}'>{commit_sha}</a>.</i>")
    try:
        todo.update_ticket(
            user=pusher,
            owner=match["owner"],
            tracker=match["tracker"],
            ticket=int(match["ticket"]),
            comment=" ".join(comment.split()).strip(),
            resolution=resolution,
        )
    except Exception:
        # invalid ticket or pusher does not have triage access, ignore
        pass

@csrf_bypass
@webhooks.route("/webhooks/hg-user/<int:user_id>", methods=["POST"])
def hg_user(user_id):
diff --git a/hubsrht/services.py b/hubsrht/services.py
index 74042afab509..19178db9fff3 100644
--- a/hubsrht/services.py
+++ b/hubsrht/services.py
@@ -179,6 +179,37 @@ class GitService(SrhtService):
            return None
        return manifests

    def log(self, user, repo, old, new):
        query = """
        query Log($owner: String!, $repo: String!, $from: String!) {
            repositoryByOwner(owner: $owner, repo: $repo) {
                log(from: $from) {
                    results {
                        id
                        message
                        author {
                            name
                        }
                    }
                }
            }
        }
        """
        r = self.post(user, None, f"{_gitsrht}/query", {
            "query": query,
            "variables": {
                "owner": repo.owner.canonical_name,
                "repo": repo.name,
                "from": new,
            }
        })
        commits = []
        for c in r["data"]["repositoryByOwner"]["log"]["results"]:
            if c["id"] == old:
                break
            commits.append(c)
        return commits

    def create_repo(self, user, valid, visibility):
        query = """
        mutation CreateRepo(
@@ -452,6 +483,14 @@ class TodoService(SrhtService):
        except:
            pass # nbd, upstream was presumably deleted

    def update_ticket(self, user, owner, tracker, ticket, comment, resolution=None):
        url = f"{_todosrht}/api/user/{owner}/trackers/{tracker}/tickets/{ticket}"
        payload = {"comment": comment}
        if resolution is not None:
            payload["resolution"] = resolution
            payload["status"] = "resolved"
        self.put(user, None, url, payload)

class BuildService(SrhtService):
    def submit_build(self, user, manifest, note, tags, execute=True):
        return self.post(user, None, f"{_buildsrht}/api/jobs", {
-- 
2.30.2

Re: [PATCH hub.sr.ht v3 2/2] webhooks: update todo tickets with git commits

Details
Message ID
<CG26AGTDS3T1.GPBNW885ADW2@taiga>
In-Reply-To
<20211128200736.158664-2-robin@jarry.cc> (view parent)
DKIM signature
fail
Download raw message
DKIM signature: fail
Great work! Thanks!

To git@git.sr.ht:~sircmpwn/hub.sr.ht
   060b912..be5d50d  master -> master
Reply to thread Export thread (mbox)