"""Code links (roadmap #1): what code belongs to a card? Pure matching helpers — a branch, pull request or commit is linked to an issue when it references the issue number the way developers actually write it: `#12` in a title/body/message, or the number as a token in a branch name (`feature/12-login`, `issue-12`, `12-fix`). """ import re import unicodedata _REF_RE = re.compile(r"(? set[int]: """All #n references in a text (titles, bodies, messages).""" if not text: return set() return {int(m.group(1)) for m in _REF_RE.finditer(text)} def branch_issue_number(branch_name: str | None) -> int | None: """The issue number encoded in a branch name, if any.""" if not branch_name: return None m = _BRANCH_TOKEN_RE.search(branch_name) return int(m.group(1)) if m else None def slugify_title(title: str, max_len: int = 40) -> str: """ASCII slug for a branch name: 'Créer la branche !' -> 'creer-la-branche'.""" normalized = unicodedata.normalize("NFKD", title or "") ascii_text = normalized.encode("ascii", "ignore").decode("ascii") slug = re.sub(r"[^a-z0-9]+", "-", ascii_text.lower()).strip("-") slug = re.sub(r"-{2,}", "-", slug) if len(slug) > max_len: slug = slug[:max_len].rstrip("-") return slug or "issue" def branch_name_for(number: int, title: str) -> str: """The branch a card would create (issue #3): issue--. The name is recognized back by branch_issue_number, which is what links the future PR to this card (issue #2). """ return f"issue-{number}-{slugify_title(title)}" def find_code_links( branches: list[dict], pulls: list[dict], commits: list[dict], number: int, ) -> dict: """Linked branches/PRs/commits for one issue number.""" linked_branches = [ b.get("name", "") for b in branches if branch_issue_number(b.get("name")) == number ] linked_pulls = [] for pr in pulls: head_ref = ((pr.get("head") or {}).get("ref")) or "" refs = extract_issue_refs(pr.get("title")) | extract_issue_refs( pr.get("body") ) if number in refs or branch_issue_number(head_ref) == number: linked_pulls.append( { "number": pr.get("number"), "title": pr.get("title", ""), "state": pr.get("state", "open"), "merged": bool(pr.get("merged")), "html_url": pr.get("html_url", ""), "branch": head_ref, } ) linked_commits = [] for commit in commits: message = ((commit.get("commit") or {}).get("message")) or "" if number in extract_issue_refs(message): linked_commits.append( { "sha": (commit.get("sha") or "")[:7], "message": message.splitlines()[0] if message else "", "html_url": commit.get("html_url", ""), "date": ( ((commit.get("commit") or {}).get("author") or {}).get( "date", "" ) or "" )[:10], } ) # Best PR to badge: a merged one beats an open one beats closed. pr_badge = None for wanted in ("merged", "open", "closed"): for pr in linked_pulls: state = ( "merged" if pr["merged"] else "open" if pr["state"] == "open" else "closed" ) if state == wanted: pr_badge = { "number": pr["number"], "state": state, "label": { "merged": "fusionnée", "open": "ouverte", "closed": "fermée", }[state], } break if pr_badge: break return { "branches": linked_branches, "pulls": linked_pulls, "commits": linked_commits, "summary": { "branches": len(linked_branches), "commits": len(linked_commits), "pr": pr_badge, }, }